wuliangbo
4 years ago
commit
9f80d5a2ac
372 changed files with 30028 additions and 0 deletions
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
# Default ignored files |
||||
/shelf/ |
||||
/workspace.xml |
||||
# Datasource local storage ignored files |
||||
/dataSources/ |
||||
/dataSources.local.xml |
||||
# Editor-based HTTP Client requests |
||||
/httpRequests/ |
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<module type="WEB_MODULE" version="4"> |
||||
<component name="NewModuleRootManager"> |
||||
<content url="file://$MODULE_DIR$" /> |
||||
<orderEntry type="inheritedJdk" /> |
||||
<orderEntry type="sourceFolder" forTests="false" /> |
||||
</component> |
||||
</module> |
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<project version="4"> |
||||
<component name="JavaScriptSettings"> |
||||
<option name="languageLevel" value="ES6" /> |
||||
</component> |
||||
</project> |
@ -0,0 +1,8 @@
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<project version="4"> |
||||
<component name="ProjectModuleManager"> |
||||
<modules> |
||||
<module fileurl="file://$PROJECT_DIR$/.idea/base-wechat.iml" filepath="$PROJECT_DIR$/.idea/base-wechat.iml" /> |
||||
</modules> |
||||
</component> |
||||
</project> |
@ -0,0 +1,14 @@
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?> |
||||
<project version="4"> |
||||
<component name="PhpCSFixer"> |
||||
<phpcsfixer_settings> |
||||
<PhpCSFixerConfiguration tool_path="$PROJECT_DIR$/vendor/bin/php-cs-fixer" /> |
||||
</phpcsfixer_settings> |
||||
</component> |
||||
<component name="PhpProjectSharedConfiguration" php_language_level="7.2" /> |
||||
<component name="PhpUnit"> |
||||
<phpunit_settings> |
||||
<PhpUnitSettings custom_loader_path="$PROJECT_DIR$/vendor/autoload.php" /> |
||||
</phpunit_settings> |
||||
</component> |
||||
</project> |
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT) |
||||
|
||||
Copyright (c) overtrue <i@overtrue.me> |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to deal |
||||
in the Software without restriction, including without limitation the rights |
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
copies of the Software, and to permit persons to whom the Software is |
||||
furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in all |
||||
copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
SOFTWARE. |
||||
|
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
基于 [overtrue/wechat](https://github.com/overtrue/wechat) 二次修改版 自用 |
@ -0,0 +1,60 @@
@@ -0,0 +1,60 @@
|
||||
{ |
||||
"name": "wuearl/base-wechat", |
||||
"description": "微信SDK", |
||||
"keywords": [ |
||||
"easywechat", |
||||
"wechat", |
||||
"weixin", |
||||
"weixin-sdk", |
||||
"sdk" |
||||
], |
||||
"license": "MIT", |
||||
"authors": [ |
||||
{ |
||||
"name": "overtrue", |
||||
"email": "anzhengchao@gmail.com" |
||||
} |
||||
], |
||||
"require": { |
||||
"php": ">=7.2", |
||||
"ext-fileinfo": "*", |
||||
"ext-openssl": "*", |
||||
"ext-simplexml": "*", |
||||
"easywechat-composer/easywechat-composer": "^1.1", |
||||
"guzzlehttp/guzzle": "^6.2", |
||||
"monolog/monolog": "^1.22 || ^2.0", |
||||
"overtrue/socialite": "~2.0", |
||||
"pimple/pimple": "^3.0", |
||||
"psr/simple-cache": "^1.0", |
||||
"symfony/cache": "^3.3 || ^4.3 || ^5.0", |
||||
"symfony/event-dispatcher": "^4.3 || ^5.0", |
||||
"symfony/http-foundation": "^2.7 || ^3.0 || ^4.0 || ^5.0", |
||||
"symfony/psr-http-message-bridge": "^0.3 || ^1.0 || ^2.0" |
||||
}, |
||||
"require-dev": { |
||||
"friendsofphp/php-cs-fixer": "^2.15", |
||||
"mikey179/vfsstream": "^1.6", |
||||
"mockery/mockery": "^1.2.3", |
||||
"phpstan/phpstan": "^0.12.0", |
||||
"phpunit/phpunit": "^7.5" |
||||
}, |
||||
"autoload": { |
||||
"psr-4": { |
||||
"EasyWeChat\\": "src/" |
||||
}, |
||||
"files": [ |
||||
"src/Kernel/Support/Helpers.php", |
||||
"src/Kernel/Helpers.php" |
||||
] |
||||
}, |
||||
"autoload-dev": { |
||||
"psr-4": { |
||||
"EasyWeChat\\Tests\\": "tests/" |
||||
} |
||||
}, |
||||
"scripts": { |
||||
"phpcs": "vendor/bin/php-cs-fixer fix", |
||||
"phpstan": "vendor/bin/phpstan analyse", |
||||
"test": "vendor/bin/phpunit" |
||||
} |
||||
} |
@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\BasicService; |
||||
|
||||
use EasyWeChat\Kernel\ServiceContainer; |
||||
|
||||
/** |
||||
* Class Application. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
* |
||||
* @property \EasyWeChat\BasicService\Jssdk\Client $jssdk |
||||
* @property \EasyWeChat\BasicService\Media\Client $media |
||||
* @property \EasyWeChat\BasicService\QrCode\Client $qrcode |
||||
* @property \EasyWeChat\BasicService\Url\Client $url |
||||
* @property \EasyWeChat\BasicService\ContentSecurity\Client $content_security |
||||
*/ |
||||
class Application extends ServiceContainer |
||||
{ |
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $providers = [ |
||||
Jssdk\ServiceProvider::class, |
||||
QrCode\ServiceProvider::class, |
||||
Media\ServiceProvider::class, |
||||
Url\ServiceProvider::class, |
||||
ContentSecurity\ServiceProvider::class, |
||||
]; |
||||
} |
@ -0,0 +1,123 @@
@@ -0,0 +1,123 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\BasicService\ContentSecurity; |
||||
|
||||
use EasyWeChat\Kernel\BaseClient; |
||||
use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; |
||||
|
||||
/** |
||||
* Class Client. |
||||
* |
||||
* @author tianyong90 <412039588@qq.com> |
||||
*/ |
||||
class Client extends BaseClient |
||||
{ |
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $baseUri = 'https://api.weixin.qq.com/wxa/'; |
||||
|
||||
/** |
||||
* Text content security check. |
||||
* |
||||
* @param string $text |
||||
* |
||||
* @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function checkText(string $text) |
||||
{ |
||||
$params = [ |
||||
'content' => $text, |
||||
]; |
||||
|
||||
return $this->httpPostJson('msg_sec_check', $params); |
||||
} |
||||
|
||||
/** |
||||
* Image security check. |
||||
* |
||||
* @param string $path |
||||
* |
||||
* @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function checkImage(string $path) |
||||
{ |
||||
return $this->httpUpload('img_sec_check', ['media' => $path]); |
||||
} |
||||
|
||||
/** |
||||
* Media security check. |
||||
* |
||||
* @param string $mediaUrl |
||||
* @param int $mediaType |
||||
* |
||||
* @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
*/ |
||||
public function checkMediaAsync(string $mediaUrl, int $mediaType) |
||||
{ |
||||
/* |
||||
* 1:音频;2:图片 |
||||
*/ |
||||
$mediaTypes = [1, 2]; |
||||
|
||||
if (!in_array($mediaType, $mediaTypes, true)) { |
||||
throw new InvalidArgumentException('media type must be 1 or 2'); |
||||
} |
||||
|
||||
$params = [ |
||||
'media_url' => $mediaUrl, |
||||
'media_type' => $mediaType, |
||||
]; |
||||
|
||||
return $this->httpPostJson('media_check_async', $params); |
||||
} |
||||
|
||||
/** |
||||
* Image security check async. |
||||
* |
||||
* @param string $mediaUrl |
||||
* |
||||
* @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function checkImageAsync(string $mediaUrl) |
||||
{ |
||||
return $this->checkMediaAsync($mediaUrl, 2); |
||||
} |
||||
|
||||
/** |
||||
* Audio security check async. |
||||
* |
||||
* @param string $mediaUrl |
||||
* |
||||
* @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function checkAudioAsync(string $mediaUrl) |
||||
{ |
||||
return $this->checkMediaAsync($mediaUrl, 1); |
||||
} |
||||
} |
@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\BasicService\ContentSecurity; |
||||
|
||||
use Pimple\Container; |
||||
use Pimple\ServiceProviderInterface; |
||||
|
||||
/** |
||||
* Class ServiceProvider. |
||||
*/ |
||||
class ServiceProvider implements ServiceProviderInterface |
||||
{ |
||||
/** |
||||
* {@inheritdoc}. |
||||
*/ |
||||
public function register(Container $app) |
||||
{ |
||||
$app['content_security'] = function ($app) { |
||||
return new Client($app); |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,207 @@
@@ -0,0 +1,207 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\BasicService\Jssdk; |
||||
|
||||
use EasyWeChat\Kernel\BaseClient; |
||||
use EasyWeChat\Kernel\Exceptions\RuntimeException; |
||||
use EasyWeChat\Kernel\Support; |
||||
use EasyWeChat\Kernel\Traits\InteractsWithCache; |
||||
|
||||
/** |
||||
* Class Client. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class Client extends BaseClient |
||||
{ |
||||
use InteractsWithCache; |
||||
|
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $ticketEndpoint = 'https://api.weixin.qq.com/cgi-bin/ticket/getticket'; |
||||
|
||||
/** |
||||
* Current URI. |
||||
* |
||||
* @var string |
||||
*/ |
||||
protected $url; |
||||
|
||||
/** |
||||
* Get config json for jsapi. |
||||
* |
||||
* @param array $jsApiList |
||||
* @param bool $debug |
||||
* @param bool $beta |
||||
* @param bool $json |
||||
* |
||||
* @return array|string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \Psr\SimpleCache\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\RuntimeException |
||||
*/ |
||||
public function buildConfig(array $jsApiList, bool $debug = false, bool $beta = false, bool $json = true) |
||||
{ |
||||
$config = array_merge(compact('debug', 'beta', 'jsApiList'), $this->configSignature()); |
||||
|
||||
return $json ? json_encode($config) : $config; |
||||
} |
||||
|
||||
/** |
||||
* Return jsapi config as a PHP array. |
||||
* |
||||
* @param array $apis |
||||
* @param bool $debug |
||||
* @param bool $beta |
||||
* |
||||
* @return array |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \Psr\SimpleCache\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\RuntimeException |
||||
*/ |
||||
public function getConfigArray(array $apis, bool $debug = false, bool $beta = false) |
||||
{ |
||||
return $this->buildConfig($apis, $debug, $beta, false); |
||||
} |
||||
|
||||
/** |
||||
* Get js ticket. |
||||
* |
||||
* @param bool $refresh |
||||
* @param string $type |
||||
* |
||||
* @return array |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\RuntimeException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
* @throws \Psr\SimpleCache\InvalidArgumentException |
||||
*/ |
||||
public function getTicket(bool $refresh = false, string $type = 'jsapi'): array |
||||
{ |
||||
$cacheKey = sprintf('easywechat.basic_service.jssdk.ticket.%s.%s', $type, $this->getAppId()); |
||||
|
||||
if (!$refresh && $this->getCache()->has($cacheKey)) { |
||||
return $this->getCache()->get($cacheKey); |
||||
} |
||||
|
||||
/** @var array<string, mixed> $result */ |
||||
$result = $this->castResponseToType( |
||||
$this->requestRaw($this->ticketEndpoint, 'GET', ['query' => ['type' => $type]]), |
||||
'array' |
||||
); |
||||
|
||||
$this->getCache()->set($cacheKey, $result, $result['expires_in'] - 500); |
||||
|
||||
if (!$this->getCache()->has($cacheKey)) { |
||||
throw new RuntimeException('Failed to cache jssdk ticket.'); |
||||
} |
||||
|
||||
return $result; |
||||
} |
||||
|
||||
/** |
||||
* Build signature. |
||||
* |
||||
* @param string|null $url |
||||
* @param string|null $nonce |
||||
* @param int|null $timestamp |
||||
* |
||||
* @return array |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\RuntimeException |
||||
* @throws \Psr\SimpleCache\InvalidArgumentException |
||||
*/ |
||||
protected function configSignature(string $url = null, string $nonce = null, $timestamp = null): array |
||||
{ |
||||
$url = $url ?: $this->getUrl(); |
||||
$nonce = $nonce ?: Support\Str::quickRandom(10); |
||||
$timestamp = $timestamp ?: time(); |
||||
|
||||
return [ |
||||
'appId' => $this->getAppId(), |
||||
'nonceStr' => $nonce, |
||||
'timestamp' => $timestamp, |
||||
'url' => $url, |
||||
'signature' => $this->getTicketSignature($this->getTicket()['ticket'], $nonce, $timestamp, $url), |
||||
]; |
||||
} |
||||
|
||||
/** |
||||
* Sign the params. |
||||
* |
||||
* @param string $ticket |
||||
* @param string $nonce |
||||
* @param int $timestamp |
||||
* @param string $url |
||||
* |
||||
* @return string |
||||
*/ |
||||
public function getTicketSignature($ticket, $nonce, $timestamp, $url): string |
||||
{ |
||||
return sha1(sprintf('jsapi_ticket=%s&noncestr=%s×tamp=%s&url=%s', $ticket, $nonce, $timestamp, $url)); |
||||
} |
||||
|
||||
/** |
||||
* @return string |
||||
*/ |
||||
public function dictionaryOrderSignature() |
||||
{ |
||||
$params = func_get_args(); |
||||
|
||||
sort($params, SORT_STRING); |
||||
|
||||
return sha1(implode('', $params)); |
||||
} |
||||
|
||||
/** |
||||
* Set current url. |
||||
* |
||||
* @param string $url |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function setUrl(string $url) |
||||
{ |
||||
$this->url = $url; |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Get current url. |
||||
* |
||||
* @return string |
||||
*/ |
||||
public function getUrl(): string |
||||
{ |
||||
if ($this->url) { |
||||
return $this->url; |
||||
} |
||||
|
||||
return Support\current_url(); |
||||
} |
||||
|
||||
/** |
||||
* @return string |
||||
*/ |
||||
protected function getAppId() |
||||
{ |
||||
return $this->app['config']->get('app_id'); |
||||
} |
||||
} |
@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\BasicService\Jssdk; |
||||
|
||||
use Pimple\Container; |
||||
use Pimple\ServiceProviderInterface; |
||||
|
||||
/** |
||||
* Class ServiceProvider. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class ServiceProvider implements ServiceProviderInterface |
||||
{ |
||||
/** |
||||
* {@inheritdoc}. |
||||
*/ |
||||
public function register(Container $app) |
||||
{ |
||||
$app['jssdk'] = function ($app) { |
||||
return new Client($app); |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,212 @@
@@ -0,0 +1,212 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\BasicService\Media; |
||||
|
||||
use EasyWeChat\Kernel\BaseClient; |
||||
use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; |
||||
use EasyWeChat\Kernel\Http\StreamResponse; |
||||
|
||||
/** |
||||
* Class Client. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class Client extends BaseClient |
||||
{ |
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $baseUri = 'https://api.weixin.qq.com/cgi-bin/'; |
||||
|
||||
/** |
||||
* Allow media type. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected $allowTypes = ['image', 'voice', 'video', 'thumb']; |
||||
|
||||
/** |
||||
* Upload image. |
||||
* |
||||
* @param string $path |
||||
* |
||||
* @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function uploadImage($path) |
||||
{ |
||||
return $this->upload('image', $path); |
||||
} |
||||
|
||||
/** |
||||
* Upload video. |
||||
* |
||||
* @param string $path |
||||
* |
||||
* @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function uploadVideo($path) |
||||
{ |
||||
return $this->upload('video', $path); |
||||
} |
||||
|
||||
/** |
||||
* @param string $path |
||||
* |
||||
* @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function uploadVoice($path) |
||||
{ |
||||
return $this->upload('voice', $path); |
||||
} |
||||
|
||||
/** |
||||
* @param string $path |
||||
* |
||||
* @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function uploadThumb($path) |
||||
{ |
||||
return $this->upload('thumb', $path); |
||||
} |
||||
|
||||
/** |
||||
* Upload temporary material. |
||||
* |
||||
* @param string $type |
||||
* @param string $path |
||||
* |
||||
* @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function upload(string $type, string $path) |
||||
{ |
||||
if (!file_exists($path) || !is_readable($path)) { |
||||
throw new InvalidArgumentException(sprintf("File does not exist, or the file is unreadable: '%s'", $path)); |
||||
} |
||||
|
||||
if (!in_array($type, $this->allowTypes, true)) { |
||||
throw new InvalidArgumentException(sprintf("Unsupported media type: '%s'", $type)); |
||||
} |
||||
|
||||
return $this->httpUpload('media/upload', ['media' => $path], ['type' => $type]); |
||||
} |
||||
|
||||
/** |
||||
* @param string $path |
||||
* @param string $title |
||||
* @param string $description |
||||
* |
||||
* @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function uploadVideoForBroadcasting(string $path, string $title, string $description) |
||||
{ |
||||
$response = $this->uploadVideo($path); |
||||
/** @var array $arrayResponse */ |
||||
$arrayResponse = $this->detectAndCastResponseToType($response, 'array'); |
||||
|
||||
if (!empty($arrayResponse['media_id'])) { |
||||
return $this->createVideoForBroadcasting($arrayResponse['media_id'], $title, $description); |
||||
} |
||||
|
||||
return $response; |
||||
} |
||||
|
||||
/** |
||||
* @param string $mediaId |
||||
* @param string $title |
||||
* @param string $description |
||||
* |
||||
* @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function createVideoForBroadcasting(string $mediaId, string $title, string $description) |
||||
{ |
||||
return $this->httpPostJson('media/uploadvideo', [ |
||||
'media_id' => $mediaId, |
||||
'title' => $title, |
||||
'description' => $description, |
||||
]); |
||||
} |
||||
|
||||
/** |
||||
* Fetch item from WeChat server. |
||||
* |
||||
* @param string $mediaId |
||||
* |
||||
* @return \EasyWeChat\Kernel\Http\StreamResponse|\Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function get(string $mediaId) |
||||
{ |
||||
$response = $this->requestRaw('media/get', 'GET', [ |
||||
'query' => [ |
||||
'media_id' => $mediaId, |
||||
], |
||||
]); |
||||
|
||||
if (false !== stripos($response->getHeaderLine('Content-disposition'), 'attachment')) { |
||||
return StreamResponse::buildFromPsrResponse($response); |
||||
} |
||||
|
||||
return $this->castResponseToType($response, $this->app['config']->get('response_type')); |
||||
} |
||||
|
||||
/** |
||||
* @param string $mediaId |
||||
* |
||||
* @return array|\EasyWeChat\Kernel\Http\Response|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function getJssdkMedia(string $mediaId) |
||||
{ |
||||
$response = $this->requestRaw('media/get/jssdk', 'GET', [ |
||||
'query' => [ |
||||
'media_id' => $mediaId, |
||||
], |
||||
]); |
||||
|
||||
if (false !== stripos($response->getHeaderLine('Content-disposition'), 'attachment')) { |
||||
return StreamResponse::buildFromPsrResponse($response); |
||||
} |
||||
|
||||
return $this->castResponseToType($response, $this->app['config']->get('response_type')); |
||||
} |
||||
} |
@ -0,0 +1,44 @@
@@ -0,0 +1,44 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
/** |
||||
* ServiceProvider.php. |
||||
* |
||||
* This file is part of the wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\BasicService\Media; |
||||
|
||||
use Pimple\Container; |
||||
use Pimple\ServiceProviderInterface; |
||||
|
||||
/** |
||||
* Class ServiceProvider. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class ServiceProvider implements ServiceProviderInterface |
||||
{ |
||||
/** |
||||
* {@inheritdoc}. |
||||
*/ |
||||
public function register(Container $app) |
||||
{ |
||||
$app['media'] = function ($app) { |
||||
return new Client($app); |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,120 @@
@@ -0,0 +1,120 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\BasicService\QrCode; |
||||
|
||||
use EasyWeChat\Kernel\BaseClient; |
||||
|
||||
/** |
||||
* Class Client. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class Client extends BaseClient |
||||
{ |
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $baseUri = 'https://api.weixin.qq.com/cgi-bin/'; |
||||
|
||||
const DAY = 86400; |
||||
const SCENE_MAX_VALUE = 100000; |
||||
const SCENE_QR_CARD = 'QR_CARD'; |
||||
const SCENE_QR_TEMPORARY = 'QR_SCENE'; |
||||
const SCENE_QR_TEMPORARY_STR = 'QR_STR_SCENE'; |
||||
const SCENE_QR_FOREVER = 'QR_LIMIT_SCENE'; |
||||
const SCENE_QR_FOREVER_STR = 'QR_LIMIT_STR_SCENE'; |
||||
|
||||
/** |
||||
* Create forever QR code. |
||||
* |
||||
* @param string|int $sceneValue |
||||
* |
||||
* @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string |
||||
*/ |
||||
public function forever($sceneValue) |
||||
{ |
||||
if (is_int($sceneValue) && $sceneValue > 0 && $sceneValue < self::SCENE_MAX_VALUE) { |
||||
$type = self::SCENE_QR_FOREVER; |
||||
$sceneKey = 'scene_id'; |
||||
} else { |
||||
$type = self::SCENE_QR_FOREVER_STR; |
||||
$sceneKey = 'scene_str'; |
||||
} |
||||
$scene = [$sceneKey => $sceneValue]; |
||||
|
||||
return $this->create($type, $scene, false); |
||||
} |
||||
|
||||
/** |
||||
* Create temporary QR code. |
||||
* |
||||
* @param string|int $sceneValue |
||||
* @param int|null $expireSeconds |
||||
* |
||||
* @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string |
||||
*/ |
||||
public function temporary($sceneValue, $expireSeconds = null) |
||||
{ |
||||
if (is_int($sceneValue) && $sceneValue > 0) { |
||||
$type = self::SCENE_QR_TEMPORARY; |
||||
$sceneKey = 'scene_id'; |
||||
} else { |
||||
$type = self::SCENE_QR_TEMPORARY_STR; |
||||
$sceneKey = 'scene_str'; |
||||
} |
||||
$scene = [$sceneKey => $sceneValue]; |
||||
|
||||
return $this->create($type, $scene, true, $expireSeconds); |
||||
} |
||||
|
||||
/** |
||||
* Return url for ticket. |
||||
* Detail: https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1443433542 . |
||||
* |
||||
* @param string $ticket |
||||
* |
||||
* @return string |
||||
*/ |
||||
public function url($ticket) |
||||
{ |
||||
return sprintf('https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=%s', urlencode($ticket)); |
||||
} |
||||
|
||||
/** |
||||
* Create a QrCode. |
||||
* |
||||
* @param string $actionName |
||||
* @param array $actionInfo |
||||
* @param bool $temporary |
||||
* @param int $expireSeconds |
||||
* |
||||
* @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
protected function create($actionName, $actionInfo, $temporary = true, $expireSeconds = null) |
||||
{ |
||||
null !== $expireSeconds || $expireSeconds = 7 * self::DAY; |
||||
|
||||
$params = [ |
||||
'action_name' => $actionName, |
||||
'action_info' => ['scene' => $actionInfo], |
||||
]; |
||||
|
||||
if ($temporary) { |
||||
$params['expire_seconds'] = min($expireSeconds, 30 * self::DAY); |
||||
} |
||||
|
||||
return $this->httpPostJson('qrcode/create', $params); |
||||
} |
||||
} |
@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\BasicService\QrCode; |
||||
|
||||
use Pimple\Container; |
||||
use Pimple\ServiceProviderInterface; |
||||
|
||||
/** |
||||
* Class ServiceProvider. |
||||
*/ |
||||
class ServiceProvider implements ServiceProviderInterface |
||||
{ |
||||
/** |
||||
* {@inheritdoc}. |
||||
*/ |
||||
public function register(Container $app) |
||||
{ |
||||
$app['qrcode'] = function ($app) { |
||||
return new Client($app); |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,47 @@
@@ -0,0 +1,47 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\BasicService\Url; |
||||
|
||||
use EasyWeChat\Kernel\BaseClient; |
||||
|
||||
/** |
||||
* Class Client. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class Client extends BaseClient |
||||
{ |
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $baseUri = 'https://api.weixin.qq.com/'; |
||||
|
||||
/** |
||||
* Shorten the url. |
||||
* |
||||
* @param string $url |
||||
* |
||||
* @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function shorten(string $url) |
||||
{ |
||||
$params = [ |
||||
'action' => 'long2short', |
||||
'long_url' => $url, |
||||
]; |
||||
|
||||
return $this->httpPostJson('cgi-bin/shorturl', $params); |
||||
} |
||||
} |
@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\BasicService\Url; |
||||
|
||||
use Pimple\Container; |
||||
use Pimple\ServiceProviderInterface; |
||||
|
||||
/** |
||||
* Class ServiceProvider. |
||||
*/ |
||||
class ServiceProvider implements ServiceProviderInterface |
||||
{ |
||||
/** |
||||
* {@inheritdoc}. |
||||
*/ |
||||
public function register(Container $app) |
||||
{ |
||||
$app['url'] = function ($app) { |
||||
return new Client($app); |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,54 @@
@@ -0,0 +1,54 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat; |
||||
|
||||
/** |
||||
* Class Factory. |
||||
* |
||||
* @method static \EasyWeChat\Payment\Application payment(array $config) |
||||
* @method static \EasyWeChat\MiniProgram\Application miniProgram(array $config) |
||||
* @method static \EasyWeChat\OpenPlatform\Application openPlatform(array $config) |
||||
* @method static \EasyWeChat\OfficialAccount\Application officialAccount(array $config) |
||||
* @method static \EasyWeChat\BasicService\Application basicService(array $config) |
||||
* @method static \EasyWeChat\Work\Application work(array $config) |
||||
* @method static \EasyWeChat\OpenWork\Application openWork(array $config) |
||||
* @method static \EasyWeChat\MicroMerchant\Application microMerchant(array $config) |
||||
*/ |
||||
class Factory |
||||
{ |
||||
/** |
||||
* @param string $name |
||||
* @param array $config |
||||
* |
||||
* @return \EasyWeChat\Kernel\ServiceContainer |
||||
*/ |
||||
public static function make($name, array $config) |
||||
{ |
||||
$namespace = Kernel\Support\Str::studly($name); |
||||
$application = "\\EasyWeChat\\{$namespace}\\Application"; |
||||
|
||||
return new $application($config); |
||||
} |
||||
|
||||
/** |
||||
* Dynamically pass methods to the application. |
||||
* |
||||
* @param string $name |
||||
* @param array $arguments |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public static function __callStatic($name, $arguments) |
||||
{ |
||||
return self::make($name, ...$arguments); |
||||
} |
||||
} |
@ -0,0 +1,277 @@
@@ -0,0 +1,277 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel; |
||||
|
||||
use EasyWeChat\Kernel\Contracts\AccessTokenInterface; |
||||
use EasyWeChat\Kernel\Exceptions\HttpException; |
||||
use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; |
||||
use EasyWeChat\Kernel\Exceptions\RuntimeException; |
||||
use EasyWeChat\Kernel\Traits\HasHttpRequests; |
||||
use EasyWeChat\Kernel\Traits\InteractsWithCache; |
||||
use Psr\Http\Message\RequestInterface; |
||||
use Psr\Http\Message\ResponseInterface; |
||||
|
||||
/** |
||||
* Class AccessToken. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
abstract class AccessToken implements AccessTokenInterface |
||||
{ |
||||
use HasHttpRequests; |
||||
use InteractsWithCache; |
||||
|
||||
/** |
||||
* @var \EasyWeChat\Kernel\ServiceContainer |
||||
*/ |
||||
protected $app; |
||||
|
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $requestMethod = 'GET'; |
||||
|
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $endpointToGetToken; |
||||
|
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $queryName; |
||||
|
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $token; |
||||
|
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $tokenKey = 'access_token'; |
||||
|
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $cachePrefix = 'easywechat.kernel.access_token.'; |
||||
|
||||
/** |
||||
* AccessToken constructor. |
||||
* |
||||
* @param \EasyWeChat\Kernel\ServiceContainer $app |
||||
*/ |
||||
public function __construct(ServiceContainer $app) |
||||
{ |
||||
$this->app = $app; |
||||
} |
||||
|
||||
/** |
||||
* @return array |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\HttpException |
||||
* @throws \Psr\SimpleCache\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\RuntimeException |
||||
*/ |
||||
public function getRefreshedToken(): array |
||||
{ |
||||
return $this->getToken(true); |
||||
} |
||||
|
||||
/** |
||||
* @param bool $refresh |
||||
* |
||||
* @return array |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\HttpException |
||||
* @throws \Psr\SimpleCache\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\RuntimeException |
||||
*/ |
||||
public function getToken(bool $refresh = false): array |
||||
{ |
||||
$cacheKey = $this->getCacheKey(); |
||||
$cache = $this->getCache(); |
||||
|
||||
if (!$refresh && $cache->has($cacheKey)) { |
||||
return $cache->get($cacheKey); |
||||
} |
||||
|
||||
/** @var array $token */ |
||||
$token = $this->requestToken($this->getCredentials(), true); |
||||
|
||||
$this->setToken($token[$this->tokenKey], $token['expires_in'] ?? 7200); |
||||
|
||||
$this->app->events->dispatch(new Events\AccessTokenRefreshed($this)); |
||||
|
||||
return $token; |
||||
} |
||||
|
||||
/** |
||||
* @param string $token |
||||
* @param int $lifetime |
||||
* |
||||
* @return \EasyWeChat\Kernel\Contracts\AccessTokenInterface |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\RuntimeException |
||||
* @throws \Psr\SimpleCache\InvalidArgumentException |
||||
*/ |
||||
public function setToken(string $token, int $lifetime = 7200): AccessTokenInterface |
||||
{ |
||||
$this->getCache()->set($this->getCacheKey(), [ |
||||
$this->tokenKey => $token, |
||||
'expires_in' => $lifetime, |
||||
], $lifetime); |
||||
|
||||
if (!$this->getCache()->has($this->getCacheKey())) { |
||||
throw new RuntimeException('Failed to cache access token.'); |
||||
} |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* @return \EasyWeChat\Kernel\Contracts\AccessTokenInterface |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\HttpException |
||||
* @throws \Psr\SimpleCache\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\RuntimeException |
||||
*/ |
||||
public function refresh(): AccessTokenInterface |
||||
{ |
||||
$this->getToken(true); |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* @param array $credentials |
||||
* @param bool $toArray |
||||
* |
||||
* @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\HttpException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
*/ |
||||
public function requestToken(array $credentials, $toArray = false) |
||||
{ |
||||
$response = $this->sendRequest($credentials); |
||||
$result = json_decode($response->getBody()->getContents(), true); |
||||
$formatted = $this->castResponseToType($response, $this->app['config']->get('response_type')); |
||||
|
||||
if (empty($result[$this->tokenKey])) { |
||||
throw new HttpException('Request access_token fail: '.json_encode($result, JSON_UNESCAPED_UNICODE), $response, $formatted); |
||||
} |
||||
|
||||
return $toArray ? $result : $formatted; |
||||
} |
||||
|
||||
/** |
||||
* @param \Psr\Http\Message\RequestInterface $request |
||||
* @param array $requestOptions |
||||
* |
||||
* @return \Psr\Http\Message\RequestInterface |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\HttpException |
||||
* @throws \Psr\SimpleCache\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\RuntimeException |
||||
*/ |
||||
public function applyToRequest(RequestInterface $request, array $requestOptions = []): RequestInterface |
||||
{ |
||||
parse_str($request->getUri()->getQuery(), $query); |
||||
|
||||
$query = http_build_query(array_merge($this->getQuery(), $query)); |
||||
|
||||
return $request->withUri($request->getUri()->withQuery($query)); |
||||
} |
||||
|
||||
/** |
||||
* Send http request. |
||||
* |
||||
* @param array $credentials |
||||
* |
||||
* @return ResponseInterface |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
protected function sendRequest(array $credentials): ResponseInterface |
||||
{ |
||||
$options = [ |
||||
('GET' === $this->requestMethod) ? 'query' : 'json' => $credentials, |
||||
]; |
||||
|
||||
return $this->setHttpClient($this->app['http_client'])->request($this->getEndpoint(), $this->requestMethod, $options); |
||||
} |
||||
|
||||
/** |
||||
* @return string |
||||
*/ |
||||
protected function getCacheKey() |
||||
{ |
||||
return $this->cachePrefix.md5(json_encode($this->getCredentials())); |
||||
} |
||||
|
||||
/** |
||||
* The request query will be used to add to the request. |
||||
* |
||||
* @return array |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\HttpException |
||||
* @throws \Psr\SimpleCache\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\RuntimeException |
||||
*/ |
||||
protected function getQuery(): array |
||||
{ |
||||
return [$this->queryName ?? $this->tokenKey => $this->getToken()[$this->tokenKey]]; |
||||
} |
||||
|
||||
/** |
||||
* @return string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
*/ |
||||
public function getEndpoint(): string |
||||
{ |
||||
if (empty($this->endpointToGetToken)) { |
||||
throw new InvalidArgumentException('No endpoint for access token request.'); |
||||
} |
||||
|
||||
return $this->endpointToGetToken; |
||||
} |
||||
|
||||
/** |
||||
* @return string |
||||
*/ |
||||
public function getTokenKey() |
||||
{ |
||||
return $this->tokenKey; |
||||
} |
||||
|
||||
/** |
||||
* Credential for get token. |
||||
* |
||||
* @return array |
||||
*/ |
||||
abstract protected function getCredentials(): array; |
||||
} |
@ -0,0 +1,271 @@
@@ -0,0 +1,271 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel; |
||||
|
||||
use EasyWeChat\Kernel\Contracts\AccessTokenInterface; |
||||
use EasyWeChat\Kernel\Http\Response; |
||||
use EasyWeChat\Kernel\Traits\HasHttpRequests; |
||||
use GuzzleHttp\MessageFormatter; |
||||
use GuzzleHttp\Middleware; |
||||
use Psr\Http\Message\RequestInterface; |
||||
use Psr\Http\Message\ResponseInterface; |
||||
use Psr\Log\LogLevel; |
||||
|
||||
/** |
||||
* Class BaseClient. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class BaseClient |
||||
{ |
||||
use HasHttpRequests { request as performRequest; } |
||||
|
||||
/** |
||||
* @var \EasyWeChat\Kernel\ServiceContainer |
||||
*/ |
||||
protected $app; |
||||
|
||||
/** |
||||
* @var \EasyWeChat\Kernel\Contracts\AccessTokenInterface |
||||
*/ |
||||
protected $accessToken; |
||||
|
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $baseUri; |
||||
|
||||
/** |
||||
* BaseClient constructor. |
||||
* |
||||
* @param \EasyWeChat\Kernel\ServiceContainer $app |
||||
* @param \EasyWeChat\Kernel\Contracts\AccessTokenInterface|null $accessToken |
||||
*/ |
||||
public function __construct(ServiceContainer $app, AccessTokenInterface $accessToken = null) |
||||
{ |
||||
$this->app = $app; |
||||
$this->accessToken = $accessToken ?? $this->app['access_token']; |
||||
} |
||||
|
||||
/** |
||||
* GET request. |
||||
* |
||||
* @param string $url |
||||
* @param array $query |
||||
* |
||||
* @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function httpGet(string $url, array $query = []) |
||||
{ |
||||
return $this->request($url, 'GET', ['query' => $query]); |
||||
} |
||||
|
||||
/** |
||||
* POST request. |
||||
* |
||||
* @param string $url |
||||
* @param array $data |
||||
* |
||||
* @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function httpPost(string $url, array $data = []) |
||||
{ |
||||
return $this->request($url, 'POST', ['form_params' => $data]); |
||||
} |
||||
|
||||
/** |
||||
* JSON request. |
||||
* |
||||
* @param string $url |
||||
* @param array $data |
||||
* @param array $query |
||||
* |
||||
* @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function httpPostJson(string $url, array $data = [], array $query = []) |
||||
{ |
||||
return $this->request($url, 'POST', ['query' => $query, 'json' => $data]); |
||||
} |
||||
|
||||
/** |
||||
* Upload file. |
||||
* |
||||
* @param string $url |
||||
* @param array $files |
||||
* @param array $form |
||||
* @param array $query |
||||
* |
||||
* @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function httpUpload(string $url, array $files = [], array $form = [], array $query = []) |
||||
{ |
||||
$multipart = []; |
||||
|
||||
foreach ($files as $name => $path) { |
||||
$multipart[] = [ |
||||
'name' => $name, |
||||
'contents' => fopen($path, 'r'), |
||||
]; |
||||
} |
||||
|
||||
foreach ($form as $name => $contents) { |
||||
$multipart[] = compact('name', 'contents'); |
||||
} |
||||
|
||||
return $this->request($url, 'POST', ['query' => $query, 'multipart' => $multipart, 'connect_timeout' => 30, 'timeout' => 30, 'read_timeout' => 30]); |
||||
} |
||||
|
||||
/** |
||||
* @return AccessTokenInterface |
||||
*/ |
||||
public function getAccessToken(): AccessTokenInterface |
||||
{ |
||||
return $this->accessToken; |
||||
} |
||||
|
||||
/** |
||||
* @param \EasyWeChat\Kernel\Contracts\AccessTokenInterface $accessToken |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function setAccessToken(AccessTokenInterface $accessToken) |
||||
{ |
||||
$this->accessToken = $accessToken; |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* @param string $url |
||||
* @param string $method |
||||
* @param array $options |
||||
* @param bool $returnRaw |
||||
* |
||||
* @return \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function request(string $url, string $method = 'GET', array $options = [], $returnRaw = false) |
||||
{ |
||||
if (empty($this->middlewares)) { |
||||
$this->registerHttpMiddlewares(); |
||||
} |
||||
|
||||
$response = $this->performRequest($url, $method, $options); |
||||
|
||||
$this->app->events->dispatch(new Events\HttpResponseCreated($response)); |
||||
|
||||
return $returnRaw ? $response : $this->castResponseToType($response, $this->app->config->get('response_type')); |
||||
} |
||||
|
||||
/** |
||||
* @param string $url |
||||
* @param string $method |
||||
* @param array $options |
||||
* |
||||
* @return \EasyWeChat\Kernel\Http\Response |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function requestRaw(string $url, string $method = 'GET', array $options = []) |
||||
{ |
||||
return Response::buildFromPsrResponse($this->request($url, $method, $options, true)); |
||||
} |
||||
|
||||
/** |
||||
* Register Guzzle middlewares. |
||||
*/ |
||||
protected function registerHttpMiddlewares() |
||||
{ |
||||
// retry |
||||
$this->pushMiddleware($this->retryMiddleware(), 'retry'); |
||||
// access token |
||||
$this->pushMiddleware($this->accessTokenMiddleware(), 'access_token'); |
||||
// log |
||||
$this->pushMiddleware($this->logMiddleware(), 'log'); |
||||
} |
||||
|
||||
/** |
||||
* Attache access token to request query. |
||||
* |
||||
* @return \Closure |
||||
*/ |
||||
protected function accessTokenMiddleware() |
||||
{ |
||||
return function (callable $handler) { |
||||
return function (RequestInterface $request, array $options) use ($handler) { |
||||
if ($this->accessToken) { |
||||
$request = $this->accessToken->applyToRequest($request, $options); |
||||
} |
||||
|
||||
return $handler($request, $options); |
||||
}; |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* Log the request. |
||||
* |
||||
* @return \Closure |
||||
*/ |
||||
protected function logMiddleware() |
||||
{ |
||||
$formatter = new MessageFormatter($this->app['config']['http.log_template'] ?? MessageFormatter::DEBUG); |
||||
|
||||
return Middleware::log($this->app['logger'], $formatter, LogLevel::DEBUG); |
||||
} |
||||
|
||||
/** |
||||
* Return retry middleware. |
||||
* |
||||
* @return \Closure |
||||
*/ |
||||
protected function retryMiddleware() |
||||
{ |
||||
return Middleware::retry(function ( |
||||
$retries, |
||||
RequestInterface $request, |
||||
ResponseInterface $response = null |
||||
) { |
||||
// Limit the number of retries to 2 |
||||
if ($retries < $this->app->config->get('http.max_retries', 1) && $response && $body = $response->getBody()) { |
||||
// Retry on server errors |
||||
$response = json_decode($body, true); |
||||
|
||||
if (!empty($response['errcode']) && in_array(abs($response['errcode']), [40001, 40014, 42001], true)) { |
||||
$this->accessToken->refresh(); |
||||
$this->app['logger']->debug('Retrying with refreshed access token.'); |
||||
|
||||
return true; |
||||
} |
||||
} |
||||
|
||||
return false; |
||||
}, function () { |
||||
return abs($this->app->config->get('http.retry_delay', 500)); |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,64 @@
@@ -0,0 +1,64 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Clauses; |
||||
|
||||
/** |
||||
* Class Clause. |
||||
* |
||||
* @author mingyoung <mingyoungcheung@gmail.com> |
||||
*/ |
||||
class Clause |
||||
{ |
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $clauses = [ |
||||
'where' => [], |
||||
]; |
||||
|
||||
/** |
||||
* @param mixed ...$args |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function where(...$args) |
||||
{ |
||||
array_push($this->clauses['where'], $args); |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* @param mixed $payload |
||||
* |
||||
* @return bool |
||||
*/ |
||||
public function intercepted($payload) |
||||
{ |
||||
return (bool) $this->interceptWhereClause($payload); |
||||
} |
||||
|
||||
/** |
||||
* @param mixed $payload |
||||
* |
||||
* @return bool |
||||
*/ |
||||
protected function interceptWhereClause($payload) |
||||
{ |
||||
foreach ($this->clauses['where'] as $item) { |
||||
list($key, $value) = $item; |
||||
if (isset($payload[$key]) && $payload[$key] !== $value) { |
||||
return true; |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel; |
||||
|
||||
use EasyWeChat\Kernel\Support\Collection; |
||||
|
||||
/** |
||||
* Class Config. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class Config extends Collection |
||||
{ |
||||
} |
@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Contracts; |
||||
|
||||
use Psr\Http\Message\RequestInterface; |
||||
|
||||
/** |
||||
* Interface AuthorizerAccessToken. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
interface AccessTokenInterface |
||||
{ |
||||
/** |
||||
* @return array |
||||
*/ |
||||
public function getToken(): array; |
||||
|
||||
/** |
||||
* @return \EasyWeChat\Kernel\Contracts\AccessTokenInterface |
||||
*/ |
||||
public function refresh(): self; |
||||
|
||||
/** |
||||
* @param \Psr\Http\Message\RequestInterface $request |
||||
* @param array $requestOptions |
||||
* |
||||
* @return \Psr\Http\Message\RequestInterface |
||||
*/ |
||||
public function applyToRequest(RequestInterface $request, array $requestOptions = []): RequestInterface; |
||||
} |
@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Contracts; |
||||
|
||||
use ArrayAccess; |
||||
|
||||
/** |
||||
* Interface Arrayable. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
interface Arrayable extends ArrayAccess |
||||
{ |
||||
/** |
||||
* Get the instance as an array. |
||||
* |
||||
* @return array |
||||
*/ |
||||
public function toArray(); |
||||
} |
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Contracts; |
||||
|
||||
/** |
||||
* Interface EventHandlerInterface. |
||||
* |
||||
* @author mingyoung <mingyoungcheung@gmail.com> |
||||
*/ |
||||
interface EventHandlerInterface |
||||
{ |
||||
/** |
||||
* @param mixed $payload |
||||
*/ |
||||
public function handle($payload = null); |
||||
} |
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Contracts; |
||||
|
||||
/** |
||||
* Interface MediaInterface. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
interface MediaInterface extends MessageInterface |
||||
{ |
||||
/** |
||||
* @return string |
||||
*/ |
||||
public function getMediaId(): string; |
||||
} |
@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Contracts; |
||||
|
||||
/** |
||||
* Interface MessageInterface. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
interface MessageInterface |
||||
{ |
||||
/** |
||||
* @return string |
||||
*/ |
||||
public function getType(): string; |
||||
|
||||
/** |
||||
* @return array |
||||
*/ |
||||
public function transformForJsonRequest(): array; |
||||
|
||||
/** |
||||
* @return string |
||||
*/ |
||||
public function transformToXml(): string; |
||||
} |
@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Decorators; |
||||
|
||||
/** |
||||
* Class FinallyResult. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class FinallyResult |
||||
{ |
||||
/** |
||||
* @var mixed |
||||
*/ |
||||
public $content; |
||||
|
||||
/** |
||||
* FinallyResult constructor. |
||||
* |
||||
* @param mixed $content |
||||
*/ |
||||
public function __construct($content) |
||||
{ |
||||
$this->content = $content; |
||||
} |
||||
} |
@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Decorators; |
||||
|
||||
/** |
||||
* Class TerminateResult. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class TerminateResult |
||||
{ |
||||
/** |
||||
* @var mixed |
||||
*/ |
||||
public $content; |
||||
|
||||
/** |
||||
* FinallyResult constructor. |
||||
* |
||||
* @param mixed $content |
||||
*/ |
||||
public function __construct($content) |
||||
{ |
||||
$this->content = $content; |
||||
} |
||||
} |
@ -0,0 +1,219 @@
@@ -0,0 +1,219 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel; |
||||
|
||||
use EasyWeChat\Kernel\Exceptions\RuntimeException; |
||||
use EasyWeChat\Kernel\Support\AES; |
||||
use function EasyWeChat\Kernel\Support\str_random; |
||||
use EasyWeChat\Kernel\Support\XML; |
||||
use Throwable; |
||||
|
||||
/** |
||||
* Class Encryptor. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class Encryptor |
||||
{ |
||||
const ERROR_INVALID_SIGNATURE = -40001; // Signature verification failed |
||||
const ERROR_PARSE_XML = -40002; // Parse XML failed |
||||
const ERROR_CALC_SIGNATURE = -40003; // Calculating the signature failed |
||||
const ERROR_INVALID_AES_KEY = -40004; // Invalid AESKey |
||||
const ERROR_INVALID_APP_ID = -40005; // Check AppID failed |
||||
const ERROR_ENCRYPT_AES = -40006; // AES EncryptionInterface failed |
||||
const ERROR_DECRYPT_AES = -40007; // AES decryption failed |
||||
const ERROR_INVALID_XML = -40008; // Invalid XML |
||||
const ERROR_BASE64_ENCODE = -40009; // Base64 encoding failed |
||||
const ERROR_BASE64_DECODE = -40010; // Base64 decoding failed |
||||
const ERROR_XML_BUILD = -40011; // XML build failed |
||||
const ILLEGAL_BUFFER = -41003; // Illegal buffer |
||||
|
||||
/** |
||||
* App id. |
||||
* |
||||
* @var string |
||||
*/ |
||||
protected $appId; |
||||
|
||||
/** |
||||
* App token. |
||||
* |
||||
* @var string |
||||
*/ |
||||
protected $token; |
||||
|
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $aesKey; |
||||
|
||||
/** |
||||
* Block size. |
||||
* |
||||
* @var int |
||||
*/ |
||||
protected $blockSize = 32; |
||||
|
||||
/** |
||||
* Constructor. |
||||
* |
||||
* @param string $appId |
||||
* @param string|null $token |
||||
* @param string|null $aesKey |
||||
*/ |
||||
public function __construct(string $appId, string $token = null, string $aesKey = null) |
||||
{ |
||||
$this->appId = $appId; |
||||
$this->token = $token; |
||||
$this->aesKey = base64_decode($aesKey.'=', true); |
||||
} |
||||
|
||||
/** |
||||
* Get the app token. |
||||
* |
||||
* @return string |
||||
*/ |
||||
public function getToken(): string |
||||
{ |
||||
return $this->token; |
||||
} |
||||
|
||||
/** |
||||
* Encrypt the message and return XML. |
||||
* |
||||
* @param string $xml |
||||
* @param string $nonce |
||||
* @param int $timestamp |
||||
* |
||||
* @return string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\RuntimeException |
||||
*/ |
||||
public function encrypt($xml, $nonce = null, $timestamp = null): string |
||||
{ |
||||
try { |
||||
$xml = $this->pkcs7Pad(str_random(16).pack('N', strlen($xml)).$xml.$this->appId, $this->blockSize); |
||||
|
||||
$encrypted = base64_encode(AES::encrypt( |
||||
$xml, |
||||
$this->aesKey, |
||||
substr($this->aesKey, 0, 16), |
||||
OPENSSL_NO_PADDING |
||||
)); |
||||
// @codeCoverageIgnoreStart |
||||
} catch (Throwable $e) { |
||||
throw new RuntimeException($e->getMessage(), self::ERROR_ENCRYPT_AES); |
||||
} |
||||
// @codeCoverageIgnoreEnd |
||||
|
||||
!is_null($nonce) || $nonce = substr($this->appId, 0, 10); |
||||
!is_null($timestamp) || $timestamp = time(); |
||||
|
||||
$response = [ |
||||
'Encrypt' => $encrypted, |
||||
'MsgSignature' => $this->signature($this->token, $timestamp, $nonce, $encrypted), |
||||
'TimeStamp' => $timestamp, |
||||
'Nonce' => $nonce, |
||||
]; |
||||
|
||||
//生成响应xml |
||||
return XML::build($response); |
||||
} |
||||
|
||||
/** |
||||
* Decrypt message. |
||||
* |
||||
* @param string $content |
||||
* @param string $msgSignature |
||||
* @param string $nonce |
||||
* @param string $timestamp |
||||
* |
||||
* @return string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\RuntimeException |
||||
*/ |
||||
public function decrypt($content, $msgSignature, $nonce, $timestamp): string |
||||
{ |
||||
$signature = $this->signature($this->token, $timestamp, $nonce, $content); |
||||
|
||||
if ($signature !== $msgSignature) { |
||||
throw new RuntimeException('Invalid Signature.', self::ERROR_INVALID_SIGNATURE); |
||||
} |
||||
|
||||
$decrypted = AES::decrypt( |
||||
base64_decode($content, true), |
||||
$this->aesKey, |
||||
substr($this->aesKey, 0, 16), |
||||
OPENSSL_NO_PADDING |
||||
); |
||||
$result = $this->pkcs7Unpad($decrypted); |
||||
$content = substr($result, 16, strlen($result)); |
||||
$contentLen = unpack('N', substr($content, 0, 4))[1]; |
||||
|
||||
if (trim(substr($content, $contentLen + 4)) !== $this->appId) { |
||||
throw new RuntimeException('Invalid appId.', self::ERROR_INVALID_APP_ID); |
||||
} |
||||
|
||||
return substr($content, 4, $contentLen); |
||||
} |
||||
|
||||
/** |
||||
* Get SHA1. |
||||
* |
||||
* @return string |
||||
*/ |
||||
public function signature(): string |
||||
{ |
||||
$array = func_get_args(); |
||||
sort($array, SORT_STRING); |
||||
|
||||
return sha1(implode($array)); |
||||
} |
||||
|
||||
/** |
||||
* PKCS#7 pad. |
||||
* |
||||
* @param string $text |
||||
* @param int $blockSize |
||||
* |
||||
* @return string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\RuntimeException |
||||
*/ |
||||
public function pkcs7Pad(string $text, int $blockSize): string |
||||
{ |
||||
if ($blockSize > 256) { |
||||
throw new RuntimeException('$blockSize may not be more than 256'); |
||||
} |
||||
$padding = $blockSize - (strlen($text) % $blockSize); |
||||
$pattern = chr($padding); |
||||
|
||||
return $text.str_repeat($pattern, $padding); |
||||
} |
||||
|
||||
/** |
||||
* PKCS#7 unpad. |
||||
* |
||||
* @param string $text |
||||
* |
||||
* @return string |
||||
*/ |
||||
public function pkcs7Unpad(string $text): string |
||||
{ |
||||
$pad = ord(substr($text, -1)); |
||||
if ($pad < 1 || $pad > $this->blockSize) { |
||||
$pad = 0; |
||||
} |
||||
|
||||
return substr($text, 0, (strlen($text) - $pad)); |
||||
} |
||||
} |
@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Events; |
||||
|
||||
use EasyWeChat\Kernel\AccessToken; |
||||
|
||||
/** |
||||
* Class AccessTokenRefreshed. |
||||
* |
||||
* @author mingyoung <mingyoungcheung@gmail.com> |
||||
*/ |
||||
class AccessTokenRefreshed |
||||
{ |
||||
/** |
||||
* @var \EasyWeChat\Kernel\AccessToken |
||||
*/ |
||||
public $accessToken; |
||||
|
||||
/** |
||||
* @param \EasyWeChat\Kernel\AccessToken $accessToken |
||||
*/ |
||||
public function __construct(AccessToken $accessToken) |
||||
{ |
||||
$this->accessToken = $accessToken; |
||||
} |
||||
} |
@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Events; |
||||
|
||||
use EasyWeChat\Kernel\ServiceContainer; |
||||
|
||||
/** |
||||
* Class ApplicationInitialized. |
||||
* |
||||
* @author mingyoung <mingyoungcheung@gmail.com> |
||||
*/ |
||||
class ApplicationInitialized |
||||
{ |
||||
/** |
||||
* @var \EasyWeChat\Kernel\ServiceContainer |
||||
*/ |
||||
public $app; |
||||
|
||||
/** |
||||
* @param \EasyWeChat\Kernel\ServiceContainer $app |
||||
*/ |
||||
public function __construct(ServiceContainer $app) |
||||
{ |
||||
$this->app = $app; |
||||
} |
||||
} |
@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Events; |
||||
|
||||
use Psr\Http\Message\ResponseInterface; |
||||
|
||||
/** |
||||
* Class HttpResponseCreated. |
||||
* |
||||
* @author mingyoung <mingyoungcheung@gmail.com> |
||||
*/ |
||||
class HttpResponseCreated |
||||
{ |
||||
/** |
||||
* @var \Psr\Http\Message\ResponseInterface |
||||
*/ |
||||
public $response; |
||||
|
||||
/** |
||||
* @param \Psr\Http\Message\ResponseInterface $response |
||||
*/ |
||||
public function __construct(ResponseInterface $response) |
||||
{ |
||||
$this->response = $response; |
||||
} |
||||
} |
@ -0,0 +1,35 @@
@@ -0,0 +1,35 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Events; |
||||
|
||||
use Symfony\Component\HttpFoundation\Response; |
||||
|
||||
/** |
||||
* Class ServerGuardResponseCreated. |
||||
* |
||||
* @author mingyoung <mingyoungcheung@gmail.com> |
||||
*/ |
||||
class ServerGuardResponseCreated |
||||
{ |
||||
/** |
||||
* @var \Symfony\Component\HttpFoundation\Response |
||||
*/ |
||||
public $response; |
||||
|
||||
/** |
||||
* @param \Symfony\Component\HttpFoundation\Response $response |
||||
*/ |
||||
public function __construct(Response $response) |
||||
{ |
||||
$this->response = $response; |
||||
} |
||||
} |
@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Exceptions; |
||||
|
||||
/** |
||||
* Class BadRequestException. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class BadRequestException extends Exception |
||||
{ |
||||
} |
@ -0,0 +1,16 @@
@@ -0,0 +1,16 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Exceptions; |
||||
|
||||
class DecryptException extends Exception |
||||
{ |
||||
} |
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Exceptions; |
||||
|
||||
use Exception as BaseException; |
||||
|
||||
/** |
||||
* Class Exception. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class Exception extends BaseException |
||||
{ |
||||
} |
@ -0,0 +1,52 @@
@@ -0,0 +1,52 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Exceptions; |
||||
|
||||
use Psr\Http\Message\ResponseInterface; |
||||
|
||||
/** |
||||
* Class HttpException. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class HttpException extends Exception |
||||
{ |
||||
/** |
||||
* @var \Psr\Http\Message\ResponseInterface|null |
||||
*/ |
||||
public $response; |
||||
|
||||
/** |
||||
* @var \Psr\Http\Message\ResponseInterface|\EasyWeChat\Kernel\Support\Collection|array|object|string|null |
||||
*/ |
||||
public $formattedResponse; |
||||
|
||||
/** |
||||
* HttpException constructor. |
||||
* |
||||
* @param string $message |
||||
* @param \Psr\Http\Message\ResponseInterface|null $response |
||||
* @param null $formattedResponse |
||||
* @param int|null $code |
||||
*/ |
||||
public function __construct($message, ResponseInterface $response = null, $formattedResponse = null, $code = null) |
||||
{ |
||||
parent::__construct($message, $code); |
||||
|
||||
$this->response = $response; |
||||
$this->formattedResponse = $formattedResponse; |
||||
|
||||
if ($response) { |
||||
$response->getBody()->rewind(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Exceptions; |
||||
|
||||
/** |
||||
* Class InvalidArgumentException. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class InvalidArgumentException extends Exception |
||||
{ |
||||
} |
@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Exceptions; |
||||
|
||||
/** |
||||
* Class InvalidConfigException. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class InvalidConfigException extends Exception |
||||
{ |
||||
} |
@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Exceptions; |
||||
|
||||
/** |
||||
* Class RuntimeException. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class RuntimeException extends Exception |
||||
{ |
||||
} |
@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Exceptions; |
||||
|
||||
/** |
||||
* Class InvalidConfigException. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class UnboundServiceException extends Exception |
||||
{ |
||||
} |
@ -0,0 +1,57 @@
@@ -0,0 +1,57 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel; |
||||
|
||||
use EasyWeChat\Kernel\Contracts\Arrayable; |
||||
use EasyWeChat\Kernel\Exceptions\RuntimeException; |
||||
use EasyWeChat\Kernel\Support\Arr; |
||||
use EasyWeChat\Kernel\Support\Collection; |
||||
|
||||
function data_get($data, $key, $default = null) |
||||
{ |
||||
switch (true) { |
||||
case is_array($data): |
||||
return Arr::get($data, $key, $default); |
||||
case $data instanceof Collection: |
||||
return $data->get($key, $default); |
||||
case $data instanceof Arrayable: |
||||
return Arr::get($data->toArray(), $key, $default); |
||||
case $data instanceof \ArrayIterator: |
||||
return $data->getArrayCopy()[$key] ?? $default; |
||||
case $data instanceof \ArrayAccess: |
||||
return $data[$key] ?? $default; |
||||
case $data instanceof \IteratorAggregate && $data->getIterator() instanceof \ArrayIterator: |
||||
return $data->getIterator()->getArrayCopy()[$key] ?? $default; |
||||
case is_object($data): |
||||
return $data->{$key} ?? $default; |
||||
default: |
||||
throw new RuntimeException(sprintf('Can\'t access data with key "%s"', $key)); |
||||
} |
||||
} |
||||
|
||||
function data_to_array($data) |
||||
{ |
||||
switch (true) { |
||||
case is_array($data): |
||||
return $data; |
||||
case $data instanceof Collection: |
||||
return $data->all(); |
||||
case $data instanceof Arrayable: |
||||
return $data->toArray(); |
||||
case $data instanceof \IteratorAggregate && $data->getIterator() instanceof \ArrayIterator: |
||||
return $data->getIterator()->getArrayCopy(); |
||||
case $data instanceof \ArrayIterator: |
||||
return $data->getArrayCopy(); |
||||
default: |
||||
throw new RuntimeException(sprintf('Can\'t transform data to array')); |
||||
} |
||||
} |
@ -0,0 +1,121 @@
@@ -0,0 +1,121 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Http; |
||||
|
||||
use EasyWeChat\Kernel\Support\Collection; |
||||
use EasyWeChat\Kernel\Support\XML; |
||||
use GuzzleHttp\Psr7\Response as GuzzleResponse; |
||||
use Psr\Http\Message\ResponseInterface; |
||||
|
||||
/** |
||||
* Class Response. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class Response extends GuzzleResponse |
||||
{ |
||||
/** |
||||
* @return string |
||||
*/ |
||||
public function getBodyContents() |
||||
{ |
||||
$this->getBody()->rewind(); |
||||
$contents = $this->getBody()->getContents(); |
||||
$this->getBody()->rewind(); |
||||
|
||||
return $contents; |
||||
} |
||||
|
||||
/** |
||||
* @param \Psr\Http\Message\ResponseInterface $response |
||||
* |
||||
* @return \EasyWeChat\Kernel\Http\Response |
||||
*/ |
||||
public static function buildFromPsrResponse(ResponseInterface $response) |
||||
{ |
||||
return new static( |
||||
$response->getStatusCode(), |
||||
$response->getHeaders(), |
||||
$response->getBody(), |
||||
$response->getProtocolVersion(), |
||||
$response->getReasonPhrase() |
||||
); |
||||
} |
||||
|
||||
/** |
||||
* Build to json. |
||||
* |
||||
* @return string |
||||
*/ |
||||
public function toJson() |
||||
{ |
||||
return json_encode($this->toArray()); |
||||
} |
||||
|
||||
/** |
||||
* Build to array. |
||||
* |
||||
* @return array |
||||
*/ |
||||
public function toArray() |
||||
{ |
||||
$content = $this->removeControlCharacters($this->getBodyContents()); |
||||
|
||||
if (false !== stripos($this->getHeaderLine('Content-Type'), 'xml') || 0 === stripos($content, '<xml')) { |
||||
return XML::parse($content); |
||||
} |
||||
|
||||
$array = json_decode($content, true, 512, JSON_BIGINT_AS_STRING); |
||||
|
||||
if (JSON_ERROR_NONE === json_last_error()) { |
||||
return (array) $array; |
||||
} |
||||
|
||||
return []; |
||||
} |
||||
|
||||
/** |
||||
* Get collection data. |
||||
* |
||||
* @return \EasyWeChat\Kernel\Support\Collection |
||||
*/ |
||||
public function toCollection() |
||||
{ |
||||
return new Collection($this->toArray()); |
||||
} |
||||
|
||||
/** |
||||
* @return object |
||||
*/ |
||||
public function toObject() |
||||
{ |
||||
return json_decode($this->toJson()); |
||||
} |
||||
|
||||
/** |
||||
* @return bool|string |
||||
*/ |
||||
public function __toString() |
||||
{ |
||||
return $this->getBodyContents(); |
||||
} |
||||
|
||||
/** |
||||
* @param string $content |
||||
* |
||||
* @return string |
||||
*/ |
||||
protected function removeControlCharacters(string $content) |
||||
{ |
||||
return \preg_replace('/[\x00-\x1F\x80-\x9F]/u', '', $content); |
||||
} |
||||
} |
@ -0,0 +1,86 @@
@@ -0,0 +1,86 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Http; |
||||
|
||||
use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; |
||||
use EasyWeChat\Kernel\Exceptions\RuntimeException; |
||||
use EasyWeChat\Kernel\Support\File; |
||||
|
||||
/** |
||||
* Class StreamResponse. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class StreamResponse extends Response |
||||
{ |
||||
/** |
||||
* @param string $directory |
||||
* @param string $filename |
||||
* @param bool $appendSuffix |
||||
* |
||||
* @return bool|int |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\RuntimeException |
||||
*/ |
||||
public function save(string $directory, string $filename = '', bool $appendSuffix = true) |
||||
{ |
||||
$this->getBody()->rewind(); |
||||
|
||||
$directory = rtrim($directory, '/'); |
||||
|
||||
if (!is_dir($directory)) { |
||||
mkdir($directory, 0755, true); // @codeCoverageIgnore |
||||
} |
||||
|
||||
if (!is_writable($directory)) { |
||||
throw new InvalidArgumentException(sprintf("'%s' is not writable.", $directory)); |
||||
} |
||||
|
||||
$contents = $this->getBody()->getContents(); |
||||
|
||||
if (empty($contents) || '{' === $contents[0]) { |
||||
throw new RuntimeException('Invalid media response content.'); |
||||
} |
||||
|
||||
if (empty($filename)) { |
||||
if (preg_match('/filename="(?<filename>.*?)"/', $this->getHeaderLine('Content-Disposition'), $match)) { |
||||
$filename = $match['filename']; |
||||
} else { |
||||
$filename = md5($contents); |
||||
} |
||||
} |
||||
|
||||
if ($appendSuffix && empty(pathinfo($filename, PATHINFO_EXTENSION))) { |
||||
$filename .= File::getStreamExt($contents); |
||||
} |
||||
|
||||
file_put_contents($directory.'/'.$filename, $contents); |
||||
|
||||
return $filename; |
||||
} |
||||
|
||||
/** |
||||
* @param string $directory |
||||
* @param string $filename |
||||
* @param bool $appendSuffix |
||||
* |
||||
* @return bool|int |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\RuntimeException |
||||
*/ |
||||
public function saveAs(string $directory, string $filename, bool $appendSuffix = true) |
||||
{ |
||||
return $this->save($directory, $filename, $appendSuffix); |
||||
} |
||||
} |
@ -0,0 +1,608 @@
@@ -0,0 +1,608 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Log; |
||||
|
||||
use EasyWeChat\Kernel\ServiceContainer; |
||||
use InvalidArgumentException; |
||||
use Monolog\Formatter\LineFormatter; |
||||
use Monolog\Handler\ErrorLogHandler; |
||||
use Monolog\Handler\FormattableHandlerInterface; |
||||
use Monolog\Handler\HandlerInterface; |
||||
use Monolog\Handler\RotatingFileHandler; |
||||
use Monolog\Handler\SlackWebhookHandler; |
||||
use Monolog\Handler\StreamHandler; |
||||
use Monolog\Handler\SyslogHandler; |
||||
use Monolog\Handler\WhatFailureGroupHandler; |
||||
use Monolog\Logger as Monolog; |
||||
use Psr\Log\LoggerInterface; |
||||
|
||||
/** |
||||
* Class LogManager. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class LogManager implements LoggerInterface |
||||
{ |
||||
/** |
||||
* @var \EasyWeChat\Kernel\ServiceContainer |
||||
*/ |
||||
protected $app; |
||||
|
||||
/** |
||||
* The array of resolved channels. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected $channels = []; |
||||
|
||||
/** |
||||
* The registered custom driver creators. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected $customCreators = []; |
||||
|
||||
/** |
||||
* The Log levels. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected $levels = [ |
||||
'debug' => Monolog::DEBUG, |
||||
'info' => Monolog::INFO, |
||||
'notice' => Monolog::NOTICE, |
||||
'warning' => Monolog::WARNING, |
||||
'error' => Monolog::ERROR, |
||||
'critical' => Monolog::CRITICAL, |
||||
'alert' => Monolog::ALERT, |
||||
'emergency' => Monolog::EMERGENCY, |
||||
]; |
||||
|
||||
/** |
||||
* LogManager constructor. |
||||
* |
||||
* @param \EasyWeChat\Kernel\ServiceContainer $app |
||||
*/ |
||||
public function __construct(ServiceContainer $app) |
||||
{ |
||||
$this->app = $app; |
||||
} |
||||
|
||||
/** |
||||
* Create a new, on-demand aggregate logger instance. |
||||
* |
||||
* @param array $channels |
||||
* @param string|null $channel |
||||
* |
||||
* @return \Psr\Log\LoggerInterface |
||||
* |
||||
* @throws \Exception |
||||
*/ |
||||
public function stack(array $channels, $channel = null) |
||||
{ |
||||
return $this->createStackDriver(compact('channels', 'channel')); |
||||
} |
||||
|
||||
/** |
||||
* Get a log channel instance. |
||||
* |
||||
* @param string|null $channel |
||||
* |
||||
* @return mixed |
||||
* |
||||
* @throws \Exception |
||||
*/ |
||||
public function channel($channel = null) |
||||
{ |
||||
return $this->driver($channel); |
||||
} |
||||
|
||||
/** |
||||
* Get a log driver instance. |
||||
* |
||||
* @param string|null $driver |
||||
* |
||||
* @return mixed |
||||
* |
||||
* @throws \Exception |
||||
*/ |
||||
public function driver($driver = null) |
||||
{ |
||||
return $this->get($driver ?? $this->getDefaultDriver()); |
||||
} |
||||
|
||||
/** |
||||
* Attempt to get the log from the local cache. |
||||
* |
||||
* @param string $name |
||||
* |
||||
* @return \Psr\Log\LoggerInterface |
||||
* |
||||
* @throws \Exception |
||||
*/ |
||||
protected function get($name) |
||||
{ |
||||
try { |
||||
return $this->channels[$name] ?? ($this->channels[$name] = $this->resolve($name)); |
||||
} catch (\Throwable $e) { |
||||
$logger = $this->createEmergencyLogger(); |
||||
|
||||
$logger->emergency('Unable to create configured logger. Using emergency logger.', [ |
||||
'exception' => $e, |
||||
]); |
||||
|
||||
return $logger; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Resolve the given log instance by name. |
||||
* |
||||
* @param string $name |
||||
* |
||||
* @return \Psr\Log\LoggerInterface |
||||
* |
||||
* @throws InvalidArgumentException |
||||
*/ |
||||
protected function resolve($name) |
||||
{ |
||||
$config = $this->app['config']->get(\sprintf('log.channels.%s', $name)); |
||||
|
||||
if (is_null($config)) { |
||||
throw new InvalidArgumentException(\sprintf('Log [%s] is not defined.', $name)); |
||||
} |
||||
|
||||
if (isset($this->customCreators[$config['driver']])) { |
||||
return $this->callCustomCreator($config); |
||||
} |
||||
|
||||
$driverMethod = 'create'.ucfirst($config['driver']).'Driver'; |
||||
|
||||
if (method_exists($this, $driverMethod)) { |
||||
return $this->{$driverMethod}($config); |
||||
} |
||||
|
||||
throw new InvalidArgumentException(\sprintf('Driver [%s] is not supported.', $config['driver'])); |
||||
} |
||||
|
||||
/** |
||||
* Create an emergency log handler to avoid white screens of death. |
||||
* |
||||
* @return \Monolog\Logger |
||||
* |
||||
* @throws \Exception |
||||
*/ |
||||
protected function createEmergencyLogger() |
||||
{ |
||||
return new Monolog('EasyWeChat', $this->prepareHandlers([new StreamHandler( |
||||
\sys_get_temp_dir().'/easywechat/easywechat.log', |
||||
$this->level(['level' => 'debug']) |
||||
)])); |
||||
} |
||||
|
||||
/** |
||||
* Call a custom driver creator. |
||||
* |
||||
* @param array $config |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
protected function callCustomCreator(array $config) |
||||
{ |
||||
return $this->customCreators[$config['driver']]($this->app, $config); |
||||
} |
||||
|
||||
/** |
||||
* Create an aggregate log driver instance. |
||||
* |
||||
* @param array $config |
||||
* |
||||
* @return \Monolog\Logger |
||||
* |
||||
* @throws \Exception |
||||
*/ |
||||
protected function createStackDriver(array $config) |
||||
{ |
||||
$handlers = []; |
||||
|
||||
foreach ($config['channels'] ?? [] as $channel) { |
||||
$handlers = \array_merge($handlers, $this->channel($channel)->getHandlers()); |
||||
} |
||||
|
||||
if ($config['ignore_exceptions'] ?? false) { |
||||
$handlers = [new WhatFailureGroupHandler($handlers)]; |
||||
} |
||||
|
||||
return new Monolog($this->parseChannel($config), $handlers); |
||||
} |
||||
|
||||
/** |
||||
* Create an instance of the single file log driver. |
||||
* |
||||
* @param array $config |
||||
* |
||||
* @return \Psr\Log\LoggerInterface |
||||
* |
||||
* @throws \Exception |
||||
*/ |
||||
protected function createSingleDriver(array $config) |
||||
{ |
||||
return new Monolog($this->parseChannel($config), [ |
||||
$this->prepareHandler(new StreamHandler( |
||||
$config['path'], |
||||
$this->level($config), |
||||
$config['bubble'] ?? true, |
||||
$config['permission'] ?? null, |
||||
$config['locking'] ?? false |
||||
), $config), |
||||
]); |
||||
} |
||||
|
||||
/** |
||||
* Create an instance of the daily file log driver. |
||||
* |
||||
* @param array $config |
||||
* |
||||
* @return \Psr\Log\LoggerInterface |
||||
*/ |
||||
protected function createDailyDriver(array $config) |
||||
{ |
||||
return new Monolog($this->parseChannel($config), [ |
||||
$this->prepareHandler(new RotatingFileHandler( |
||||
$config['path'], |
||||
$config['days'] ?? 7, |
||||
$this->level($config), |
||||
$config['bubble'] ?? true, |
||||
$config['permission'] ?? null, |
||||
$config['locking'] ?? false |
||||
), $config), |
||||
]); |
||||
} |
||||
|
||||
/** |
||||
* Create an instance of the Slack log driver. |
||||
* |
||||
* @param array $config |
||||
* |
||||
* @return \Psr\Log\LoggerInterface |
||||
*/ |
||||
protected function createSlackDriver(array $config) |
||||
{ |
||||
return new Monolog($this->parseChannel($config), [ |
||||
$this->prepareHandler(new SlackWebhookHandler( |
||||
$config['url'], |
||||
$config['channel'] ?? null, |
||||
$config['username'] ?? 'EasyWeChat', |
||||
$config['attachment'] ?? true, |
||||
$config['emoji'] ?? ':boom:', |
||||
$config['short'] ?? false, |
||||
$config['context'] ?? true, |
||||
$this->level($config), |
||||
$config['bubble'] ?? true, |
||||
$config['exclude_fields'] ?? [] |
||||
), $config), |
||||
]); |
||||
} |
||||
|
||||
/** |
||||
* Create an instance of the syslog log driver. |
||||
* |
||||
* @param array $config |
||||
* |
||||
* @return \Psr\Log\LoggerInterface |
||||
*/ |
||||
protected function createSyslogDriver(array $config) |
||||
{ |
||||
return new Monolog($this->parseChannel($config), [ |
||||
$this->prepareHandler(new SyslogHandler( |
||||
'EasyWeChat', |
||||
$config['facility'] ?? LOG_USER, |
||||
$this->level($config) |
||||
), $config), |
||||
]); |
||||
} |
||||
|
||||
/** |
||||
* Create an instance of the "error log" log driver. |
||||
* |
||||
* @param array $config |
||||
* |
||||
* @return \Psr\Log\LoggerInterface |
||||
*/ |
||||
protected function createErrorlogDriver(array $config) |
||||
{ |
||||
return new Monolog($this->parseChannel($config), [ |
||||
$this->prepareHandler( |
||||
new ErrorLogHandler( |
||||
$config['type'] ?? ErrorLogHandler::OPERATING_SYSTEM, |
||||
$this->level($config) |
||||
) |
||||
), |
||||
]); |
||||
} |
||||
|
||||
/** |
||||
* Prepare the handlers for usage by Monolog. |
||||
* |
||||
* @param array $handlers |
||||
* |
||||
* @return array |
||||
*/ |
||||
protected function prepareHandlers(array $handlers) |
||||
{ |
||||
foreach ($handlers as $key => $handler) { |
||||
$handlers[$key] = $this->prepareHandler($handler); |
||||
} |
||||
|
||||
return $handlers; |
||||
} |
||||
|
||||
/** |
||||
* Prepare the handler for usage by Monolog. |
||||
* |
||||
* @param \Monolog\Handler\HandlerInterface $handler |
||||
* |
||||
* @return \Monolog\Handler\HandlerInterface |
||||
*/ |
||||
protected function prepareHandler(HandlerInterface $handler, array $config = []) |
||||
{ |
||||
if (!isset($config['formatter'])) { |
||||
if ($handler instanceof FormattableHandlerInterface) { |
||||
$handler->setFormatter($this->formatter()); |
||||
} |
||||
} |
||||
|
||||
return $handler; |
||||
} |
||||
|
||||
/** |
||||
* Get a Monolog formatter instance. |
||||
* |
||||
* @return \Monolog\Formatter\FormatterInterface |
||||
*/ |
||||
protected function formatter() |
||||
{ |
||||
$formatter = new LineFormatter(null, null, true, true); |
||||
$formatter->includeStacktraces(); |
||||
|
||||
return $formatter; |
||||
} |
||||
|
||||
/** |
||||
* Extract the log channel from the given configuration. |
||||
* |
||||
* @param array $config |
||||
* |
||||
* @return string |
||||
*/ |
||||
protected function parseChannel(array $config) |
||||
{ |
||||
return $config['name'] ?? 'EasyWeChat'; |
||||
} |
||||
|
||||
/** |
||||
* Parse the string level into a Monolog constant. |
||||
* |
||||
* @param array $config |
||||
* |
||||
* @return int |
||||
* |
||||
* @throws InvalidArgumentException |
||||
*/ |
||||
protected function level(array $config) |
||||
{ |
||||
$level = $config['level'] ?? 'debug'; |
||||
|
||||
if (isset($this->levels[$level])) { |
||||
return $this->levels[$level]; |
||||
} |
||||
|
||||
throw new InvalidArgumentException('Invalid log level.'); |
||||
} |
||||
|
||||
/** |
||||
* Get the default log driver name. |
||||
* |
||||
* @return string |
||||
*/ |
||||
public function getDefaultDriver() |
||||
{ |
||||
return $this->app['config']['log.default']; |
||||
} |
||||
|
||||
/** |
||||
* Set the default log driver name. |
||||
* |
||||
* @param string $name |
||||
*/ |
||||
public function setDefaultDriver($name) |
||||
{ |
||||
$this->app['config']['log.default'] = $name; |
||||
} |
||||
|
||||
/** |
||||
* Register a custom driver creator Closure. |
||||
* |
||||
* @param string $driver |
||||
* @param \Closure $callback |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function extend($driver, \Closure $callback) |
||||
{ |
||||
$this->customCreators[$driver] = $callback->bindTo($this, $this); |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* System is unusable. |
||||
* |
||||
* @param string $message |
||||
* @param array $context |
||||
* |
||||
* @return mixed |
||||
* |
||||
* @throws \Exception |
||||
*/ |
||||
public function emergency($message, array $context = []) |
||||
{ |
||||
return $this->driver()->emergency($message, $context); |
||||
} |
||||
|
||||
/** |
||||
* Action must be taken immediately. |
||||
* |
||||
* Example: Entire website down, database unavailable, etc. This should |
||||
* trigger the SMS alerts and wake you up. |
||||
* |
||||
* @param string $message |
||||
* @param array $context |
||||
* |
||||
* @return mixed |
||||
* |
||||
* @throws \Exception |
||||
*/ |
||||
public function alert($message, array $context = []) |
||||
{ |
||||
return $this->driver()->alert($message, $context); |
||||
} |
||||
|
||||
/** |
||||
* Critical conditions. |
||||
* |
||||
* Example: Application component unavailable, unexpected exception. |
||||
* |
||||
* @param string $message |
||||
* @param array $context |
||||
* |
||||
* @return mixed |
||||
* |
||||
* @throws \Exception |
||||
*/ |
||||
public function critical($message, array $context = []) |
||||
{ |
||||
return $this->driver()->critical($message, $context); |
||||
} |
||||
|
||||
/** |
||||
* Runtime errors that do not require immediate action but should typically |
||||
* be logged and monitored. |
||||
* |
||||
* @param string $message |
||||
* @param array $context |
||||
* |
||||
* @return mixed |
||||
* |
||||
* @throws \Exception |
||||
*/ |
||||
public function error($message, array $context = []) |
||||
{ |
||||
return $this->driver()->error($message, $context); |
||||
} |
||||
|
||||
/** |
||||
* Exceptional occurrences that are not errors. |
||||
* |
||||
* Example: Use of deprecated APIs, poor use of an API, undesirable things |
||||
* that are not necessarily wrong. |
||||
* |
||||
* @param string $message |
||||
* @param array $context |
||||
* |
||||
* @return mixed |
||||
* |
||||
* @throws \Exception |
||||
*/ |
||||
public function warning($message, array $context = []) |
||||
{ |
||||
return $this->driver()->warning($message, $context); |
||||
} |
||||
|
||||
/** |
||||
* Normal but significant events. |
||||
* |
||||
* @param string $message |
||||
* @param array $context |
||||
* |
||||
* @return mixed |
||||
* |
||||
* @throws \Exception |
||||
*/ |
||||
public function notice($message, array $context = []) |
||||
{ |
||||
return $this->driver()->notice($message, $context); |
||||
} |
||||
|
||||
/** |
||||
* Interesting events. |
||||
* |
||||
* Example: User logs in, SQL logs. |
||||
* |
||||
* @param string $message |
||||
* @param array $context |
||||
* |
||||
* @return mixed |
||||
* |
||||
* @throws \Exception |
||||
*/ |
||||
public function info($message, array $context = []) |
||||
{ |
||||
return $this->driver()->info($message, $context); |
||||
} |
||||
|
||||
/** |
||||
* Detailed debug information. |
||||
* |
||||
* @param string $message |
||||
* @param array $context |
||||
* |
||||
* @return mixed |
||||
* |
||||
* @throws \Exception |
||||
*/ |
||||
public function debug($message, array $context = []) |
||||
{ |
||||
return $this->driver()->debug($message, $context); |
||||
} |
||||
|
||||
/** |
||||
* Logs with an arbitrary level. |
||||
* |
||||
* @param mixed $level |
||||
* @param string $message |
||||
* @param array $context |
||||
* |
||||
* @return mixed |
||||
* |
||||
* @throws \Exception |
||||
*/ |
||||
public function log($level, $message, array $context = []) |
||||
{ |
||||
return $this->driver()->log($level, $message, $context); |
||||
} |
||||
|
||||
/** |
||||
* Dynamically call the default driver instance. |
||||
* |
||||
* @param string $method |
||||
* @param array $parameters |
||||
* |
||||
* @return mixed |
||||
* |
||||
* @throws \Exception |
||||
*/ |
||||
public function __call($method, $parameters) |
||||
{ |
||||
return $this->driver()->$method(...$parameters); |
||||
} |
||||
} |
@ -0,0 +1,58 @@
@@ -0,0 +1,58 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Messages; |
||||
|
||||
/** |
||||
* Class Article. |
||||
*/ |
||||
class Article extends Message |
||||
{ |
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $type = 'mpnews'; |
||||
|
||||
/** |
||||
* Properties. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected $properties = [ |
||||
'thumb_media_id', |
||||
'author', |
||||
'title', |
||||
'content', |
||||
'digest', |
||||
'source_url', |
||||
'show_cover', |
||||
]; |
||||
|
||||
/** |
||||
* Aliases of attribute. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected $jsonAliases = [ |
||||
'content_source_url' => 'source_url', |
||||
'show_cover_pic' => 'show_cover', |
||||
]; |
||||
|
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $required = [ |
||||
'thumb_media_id', |
||||
'title', |
||||
'content', |
||||
'show_cover', |
||||
]; |
||||
} |
@ -0,0 +1,52 @@
@@ -0,0 +1,52 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
/** |
||||
* Card.php. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
* @copyright 2015 overtrue <i@overtrue.me> |
||||
* |
||||
* @see https://github.com/overtrue |
||||
* @see http://overtrue.me |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Messages; |
||||
|
||||
/** |
||||
* Class Card. |
||||
*/ |
||||
class Card extends Message |
||||
{ |
||||
/** |
||||
* Message type. |
||||
* |
||||
* @var string |
||||
*/ |
||||
protected $type = 'wxcard'; |
||||
|
||||
/** |
||||
* Properties. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected $properties = ['card_id']; |
||||
|
||||
/** |
||||
* Media constructor. |
||||
* |
||||
* @param string $cardId |
||||
*/ |
||||
public function __construct(string $cardId) |
||||
{ |
||||
parent::__construct(['card_id' => $cardId]); |
||||
} |
||||
} |
@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Messages; |
||||
|
||||
/** |
||||
* Class DeviceEvent. |
||||
* |
||||
* @property string $media_id |
||||
*/ |
||||
class DeviceEvent extends Message |
||||
{ |
||||
/** |
||||
* Messages type. |
||||
* |
||||
* @var string |
||||
*/ |
||||
protected $type = 'device_event'; |
||||
|
||||
/** |
||||
* Properties. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected $properties = [ |
||||
'device_type', |
||||
'device_id', |
||||
'content', |
||||
'session_id', |
||||
'open_id', |
||||
]; |
||||
} |
@ -0,0 +1,50 @@
@@ -0,0 +1,50 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Messages; |
||||
|
||||
/** |
||||
* Class DeviceText. |
||||
* |
||||
* @property string $content |
||||
*/ |
||||
class DeviceText extends Message |
||||
{ |
||||
/** |
||||
* Messages type. |
||||
* |
||||
* @var string |
||||
*/ |
||||
protected $type = 'device_text'; |
||||
|
||||
/** |
||||
* Properties. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected $properties = [ |
||||
'device_type', |
||||
'device_id', |
||||
'content', |
||||
'session_id', |
||||
'open_id', |
||||
]; |
||||
|
||||
public function toXmlArray() |
||||
{ |
||||
return [ |
||||
'DeviceType' => $this->get('device_type'), |
||||
'DeviceID' => $this->get('device_id'), |
||||
'SessionID' => $this->get('session_id'), |
||||
'Content' => base64_encode($this->get('content')), |
||||
]; |
||||
} |
||||
} |
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Messages; |
||||
|
||||
/** |
||||
* Class Image. |
||||
* |
||||
* @property string $media_id |
||||
*/ |
||||
class File extends Media |
||||
{ |
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $type = 'file'; |
||||
} |
@ -0,0 +1,27 @@
@@ -0,0 +1,27 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Messages; |
||||
|
||||
/** |
||||
* Class Image. |
||||
* |
||||
* @property string $media_id |
||||
*/ |
||||
class Image extends Media |
||||
{ |
||||
/** |
||||
* Messages type. |
||||
* |
||||
* @var string |
||||
*/ |
||||
protected $type = 'image'; |
||||
} |
@ -0,0 +1,36 @@
@@ -0,0 +1,36 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Messages; |
||||
|
||||
/** |
||||
* Class Link. |
||||
*/ |
||||
class Link extends Message |
||||
{ |
||||
/** |
||||
* Messages type. |
||||
* |
||||
* @var string |
||||
*/ |
||||
protected $type = 'link'; |
||||
|
||||
/** |
||||
* Properties. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected $properties = [ |
||||
'title', |
||||
'description', |
||||
'url', |
||||
]; |
||||
} |
@ -0,0 +1,38 @@
@@ -0,0 +1,38 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Messages; |
||||
|
||||
/** |
||||
* Class Location. |
||||
*/ |
||||
class Location extends Message |
||||
{ |
||||
/** |
||||
* Messages type. |
||||
* |
||||
* @var string |
||||
*/ |
||||
protected $type = 'location'; |
||||
|
||||
/** |
||||
* Properties. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected $properties = [ |
||||
'latitude', |
||||
'longitude', |
||||
'scale', |
||||
'label', |
||||
'precision', |
||||
]; |
||||
} |
@ -0,0 +1,70 @@
@@ -0,0 +1,70 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Messages; |
||||
|
||||
use EasyWeChat\Kernel\Contracts\MediaInterface; |
||||
use EasyWeChat\Kernel\Support\Str; |
||||
|
||||
/** |
||||
* Class Media. |
||||
*/ |
||||
class Media extends Message implements MediaInterface |
||||
{ |
||||
/** |
||||
* Properties. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected $properties = ['media_id']; |
||||
|
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $required = [ |
||||
'media_id', |
||||
]; |
||||
|
||||
/** |
||||
* MaterialClient constructor. |
||||
* |
||||
* @param string $mediaId |
||||
* @param string $type |
||||
* @param array $attributes |
||||
*/ |
||||
public function __construct(string $mediaId, $type = null, array $attributes = []) |
||||
{ |
||||
parent::__construct(array_merge(['media_id' => $mediaId], $attributes)); |
||||
|
||||
!empty($type) && $this->setType($type); |
||||
} |
||||
|
||||
/** |
||||
* @return string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
*/ |
||||
public function getMediaId(): string |
||||
{ |
||||
$this->checkRequiredAttributes(); |
||||
|
||||
return $this->get('media_id'); |
||||
} |
||||
|
||||
public function toXmlArray() |
||||
{ |
||||
return [ |
||||
Str::studly($this->getType()) => [ |
||||
'MediaId' => $this->get('media_id'), |
||||
], |
||||
]; |
||||
} |
||||
} |
@ -0,0 +1,208 @@
@@ -0,0 +1,208 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Messages; |
||||
|
||||
use EasyWeChat\Kernel\Contracts\MessageInterface; |
||||
use EasyWeChat\Kernel\Exceptions\RuntimeException; |
||||
use EasyWeChat\Kernel\Support\XML; |
||||
use EasyWeChat\Kernel\Traits\HasAttributes; |
||||
|
||||
/** |
||||
* Class Messages. |
||||
*/ |
||||
abstract class Message implements MessageInterface |
||||
{ |
||||
use HasAttributes; |
||||
|
||||
const TEXT = 2; |
||||
const IMAGE = 4; |
||||
const VOICE = 8; |
||||
const VIDEO = 16; |
||||
const SHORT_VIDEO = 32; |
||||
const LOCATION = 64; |
||||
const LINK = 128; |
||||
const DEVICE_EVENT = 256; |
||||
const DEVICE_TEXT = 512; |
||||
const FILE = 1024; |
||||
const TEXT_CARD = 2048; |
||||
const TRANSFER = 4096; |
||||
const EVENT = 1048576; |
||||
const MINIPROGRAM_PAGE = 2097152; |
||||
const ALL = self::TEXT | self::IMAGE | self::VOICE | self::VIDEO | self::SHORT_VIDEO | self::LOCATION | self::LINK |
||||
| self::DEVICE_EVENT | self::DEVICE_TEXT | self::FILE | self::TEXT_CARD | self::TRANSFER | self::EVENT | self::MINIPROGRAM_PAGE; |
||||
|
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $type; |
||||
|
||||
/** |
||||
* @var int |
||||
*/ |
||||
protected $id; |
||||
|
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $to; |
||||
|
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $from; |
||||
|
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $properties = []; |
||||
|
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $jsonAliases = []; |
||||
|
||||
/** |
||||
* Message constructor. |
||||
* |
||||
* @param array $attributes |
||||
*/ |
||||
public function __construct(array $attributes = []) |
||||
{ |
||||
$this->setAttributes($attributes); |
||||
} |
||||
|
||||
/** |
||||
* Return type name message. |
||||
* |
||||
* @return string |
||||
*/ |
||||
public function getType(): string |
||||
{ |
||||
return $this->type; |
||||
} |
||||
|
||||
/** |
||||
* @param string $type |
||||
*/ |
||||
public function setType(string $type) |
||||
{ |
||||
$this->type = $type; |
||||
} |
||||
|
||||
/** |
||||
* Magic getter. |
||||
* |
||||
* @param string $property |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public function __get($property) |
||||
{ |
||||
if (property_exists($this, $property)) { |
||||
return $this->$property; |
||||
} |
||||
|
||||
return $this->getAttribute($property); |
||||
} |
||||
|
||||
/** |
||||
* Magic setter. |
||||
* |
||||
* @param string $property |
||||
* @param mixed $value |
||||
* |
||||
* @return Message |
||||
*/ |
||||
public function __set($property, $value) |
||||
{ |
||||
if (property_exists($this, $property)) { |
||||
$this->$property = $value; |
||||
} else { |
||||
$this->setAttribute($property, $value); |
||||
} |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* @param array $appends |
||||
* |
||||
* @return array |
||||
*/ |
||||
public function transformForJsonRequestWithoutType(array $appends = []) |
||||
{ |
||||
return $this->transformForJsonRequest($appends, false); |
||||
} |
||||
|
||||
/** |
||||
* @param array $appends |
||||
* @param bool $withType |
||||
* |
||||
* @return array |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
*/ |
||||
public function transformForJsonRequest(array $appends = [], $withType = true): array |
||||
{ |
||||
if (!$withType) { |
||||
return $this->propertiesToArray([], $this->jsonAliases); |
||||
} |
||||
$messageType = $this->getType(); |
||||
$data = array_merge(['msgtype' => $messageType], $appends); |
||||
|
||||
$data[$messageType] = array_merge($data[$messageType] ?? [], $this->propertiesToArray([], $this->jsonAliases)); |
||||
|
||||
return $data; |
||||
} |
||||
|
||||
/** |
||||
* @param array $appends |
||||
* @param bool $returnAsArray |
||||
* |
||||
* @return string |
||||
*/ |
||||
public function transformToXml(array $appends = [], bool $returnAsArray = false): string |
||||
{ |
||||
$data = array_merge(['MsgType' => $this->getType()], $this->toXmlArray(), $appends); |
||||
|
||||
return $returnAsArray ? $data : XML::build($data); |
||||
} |
||||
|
||||
/** |
||||
* @param array $data |
||||
* @param array $aliases |
||||
* |
||||
* @return array |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
*/ |
||||
protected function propertiesToArray(array $data, array $aliases = []): array |
||||
{ |
||||
$this->checkRequiredAttributes(); |
||||
|
||||
foreach ($this->attributes as $property => $value) { |
||||
if (is_null($value) && !$this->isRequired($property)) { |
||||
continue; |
||||
} |
||||
$alias = array_search($property, $aliases, true); |
||||
|
||||
$data[$alias ?: $property] = $this->get($property); |
||||
} |
||||
|
||||
return $data; |
||||
} |
||||
|
||||
public function toXmlArray() |
||||
{ |
||||
throw new RuntimeException(sprintf('Class "%s" cannot support transform to XML message.', __CLASS__)); |
||||
} |
||||
} |
@ -0,0 +1,31 @@
@@ -0,0 +1,31 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Messages; |
||||
|
||||
/** |
||||
* Class MiniProgramPage. |
||||
*/ |
||||
class MiniProgramPage extends Message |
||||
{ |
||||
protected $type = 'miniprogrampage'; |
||||
|
||||
protected $properties = [ |
||||
'title', |
||||
'appid', |
||||
'pagepath', |
||||
'thumb_media_id', |
||||
]; |
||||
|
||||
protected $required = [ |
||||
'thumb_media_id', 'appid', 'pagepath', |
||||
]; |
||||
} |
@ -0,0 +1,73 @@
@@ -0,0 +1,73 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Messages; |
||||
|
||||
/** |
||||
* Class Music. |
||||
* |
||||
* @property string $url |
||||
* @property string $hq_url |
||||
* @property string $title |
||||
* @property string $description |
||||
* @property string $thumb_media_id |
||||
* @property string $format |
||||
*/ |
||||
class Music extends Message |
||||
{ |
||||
/** |
||||
* Messages type. |
||||
* |
||||
* @var string |
||||
*/ |
||||
protected $type = 'music'; |
||||
|
||||
/** |
||||
* Properties. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected $properties = [ |
||||
'title', |
||||
'description', |
||||
'url', |
||||
'hq_url', |
||||
'thumb_media_id', |
||||
'format', |
||||
]; |
||||
|
||||
/** |
||||
* Aliases of attribute. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected $jsonAliases = [ |
||||
'musicurl' => 'url', |
||||
'hqmusicurl' => 'hq_url', |
||||
]; |
||||
|
||||
public function toXmlArray() |
||||
{ |
||||
$music = [ |
||||
'Music' => [ |
||||
'Title' => $this->get('title'), |
||||
'Description' => $this->get('description'), |
||||
'MusicUrl' => $this->get('url'), |
||||
'HQMusicUrl' => $this->get('hq_url'), |
||||
], |
||||
]; |
||||
if ($thumbMediaId = $this->get('thumb_media_id')) { |
||||
$music['Music']['ThumbMediaId'] = $thumbMediaId; |
||||
} |
||||
|
||||
return $music; |
||||
} |
||||
} |
@ -0,0 +1,73 @@
@@ -0,0 +1,73 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Messages; |
||||
|
||||
/** |
||||
* Class News. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class News extends Message |
||||
{ |
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $type = 'news'; |
||||
|
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $properties = [ |
||||
'items', |
||||
]; |
||||
|
||||
/** |
||||
* News constructor. |
||||
* |
||||
* @param array $items |
||||
*/ |
||||
public function __construct(array $items = []) |
||||
{ |
||||
parent::__construct(compact('items')); |
||||
} |
||||
|
||||
/** |
||||
* @param array $data |
||||
* @param array $aliases |
||||
* |
||||
* @return array |
||||
*/ |
||||
public function propertiesToArray(array $data, array $aliases = []): array |
||||
{ |
||||
return ['articles' => array_map(function ($item) { |
||||
if ($item instanceof NewsItem) { |
||||
return $item->toJsonArray(); |
||||
} |
||||
}, $this->get('items'))]; |
||||
} |
||||
|
||||
public function toXmlArray() |
||||
{ |
||||
$items = []; |
||||
|
||||
foreach ($this->get('items') as $item) { |
||||
if ($item instanceof NewsItem) { |
||||
$items[] = $item->toXmlArray(); |
||||
} |
||||
} |
||||
|
||||
return [ |
||||
'ArticleCount' => count($items), |
||||
'Articles' => $items, |
||||
]; |
||||
} |
||||
} |
@ -0,0 +1,57 @@
@@ -0,0 +1,57 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Messages; |
||||
|
||||
/** |
||||
* Class NewsItem. |
||||
*/ |
||||
class NewsItem extends Message |
||||
{ |
||||
/** |
||||
* Messages type. |
||||
* |
||||
* @var string |
||||
*/ |
||||
protected $type = 'news'; |
||||
|
||||
/** |
||||
* Properties. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected $properties = [ |
||||
'title', |
||||
'description', |
||||
'url', |
||||
'image', |
||||
]; |
||||
|
||||
public function toJsonArray() |
||||
{ |
||||
return [ |
||||
'title' => $this->get('title'), |
||||
'description' => $this->get('description'), |
||||
'url' => $this->get('url'), |
||||
'picurl' => $this->get('image'), |
||||
]; |
||||
} |
||||
|
||||
public function toXmlArray() |
||||
{ |
||||
return [ |
||||
'Title' => $this->get('title'), |
||||
'Description' => $this->get('description'), |
||||
'Url' => $this->get('url'), |
||||
'PicUrl' => $this->get('image'), |
||||
]; |
||||
} |
||||
} |
@ -0,0 +1,56 @@
@@ -0,0 +1,56 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Messages; |
||||
|
||||
/** |
||||
* Class Raw. |
||||
*/ |
||||
class Raw extends Message |
||||
{ |
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $type = 'raw'; |
||||
|
||||
/** |
||||
* Properties. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected $properties = ['content']; |
||||
|
||||
/** |
||||
* Constructor. |
||||
* |
||||
* @param string $content |
||||
*/ |
||||
public function __construct(string $content) |
||||
{ |
||||
parent::__construct(['content' => strval($content)]); |
||||
} |
||||
|
||||
/** |
||||
* @param array $appends |
||||
* @param bool $withType |
||||
* |
||||
* @return array |
||||
*/ |
||||
public function transformForJsonRequest(array $appends = [], $withType = true): array |
||||
{ |
||||
return json_decode($this->content, true) ?? []; |
||||
} |
||||
|
||||
public function __toString() |
||||
{ |
||||
return $this->get('content') ?? ''; |
||||
} |
||||
} |
@ -0,0 +1,30 @@
@@ -0,0 +1,30 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Messages; |
||||
|
||||
/** |
||||
* Class ShortVideo. |
||||
* |
||||
* @property string $title |
||||
* @property string $media_id |
||||
* @property string $description |
||||
* @property string $thumb_media_id |
||||
*/ |
||||
class ShortVideo extends Video |
||||
{ |
||||
/** |
||||
* Messages type. |
||||
* |
||||
* @var string |
||||
*/ |
||||
protected $type = 'shortvideo'; |
||||
} |
@ -0,0 +1,44 @@
@@ -0,0 +1,44 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Messages; |
||||
|
||||
/** |
||||
* Class TaskCard. |
||||
* |
||||
* @property string $title |
||||
* @property string $description |
||||
* @property string $url |
||||
* @property string $task_id |
||||
* @property array $btn |
||||
*/ |
||||
class TaskCard extends Message |
||||
{ |
||||
/** |
||||
* Messages type. |
||||
* |
||||
* @var string |
||||
*/ |
||||
protected $type = 'taskcard'; |
||||
|
||||
/** |
||||
* Properties. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected $properties = [ |
||||
'title', |
||||
'description', |
||||
'url', |
||||
'task_id', |
||||
'btn', |
||||
]; |
||||
} |
@ -0,0 +1,54 @@
@@ -0,0 +1,54 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Messages; |
||||
|
||||
/** |
||||
* Class Text. |
||||
* |
||||
* @property string $content |
||||
*/ |
||||
class Text extends Message |
||||
{ |
||||
/** |
||||
* Message type. |
||||
* |
||||
* @var string |
||||
*/ |
||||
protected $type = 'text'; |
||||
|
||||
/** |
||||
* Properties. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected $properties = ['content']; |
||||
|
||||
/** |
||||
* Text constructor. |
||||
* |
||||
* @param string $content |
||||
*/ |
||||
public function __construct(string $content) |
||||
{ |
||||
parent::__construct(compact('content')); |
||||
} |
||||
|
||||
/** |
||||
* @return array |
||||
*/ |
||||
public function toXmlArray() |
||||
{ |
||||
return [ |
||||
'Content' => $this->get('content'), |
||||
]; |
||||
} |
||||
} |
@ -0,0 +1,40 @@
@@ -0,0 +1,40 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Messages; |
||||
|
||||
/** |
||||
* Class Text. |
||||
* |
||||
* @property string $title |
||||
* @property string $description |
||||
* @property string $url |
||||
*/ |
||||
class TextCard extends Message |
||||
{ |
||||
/** |
||||
* Messages type. |
||||
* |
||||
* @var string |
||||
*/ |
||||
protected $type = 'textcard'; |
||||
|
||||
/** |
||||
* Properties. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected $properties = [ |
||||
'title', |
||||
'description', |
||||
'url', |
||||
]; |
||||
} |
@ -0,0 +1,56 @@
@@ -0,0 +1,56 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Messages; |
||||
|
||||
/** |
||||
* Class Transfer. |
||||
* |
||||
* @property string $to |
||||
* @property string $account |
||||
*/ |
||||
class Transfer extends Message |
||||
{ |
||||
/** |
||||
* Messages type. |
||||
* |
||||
* @var string |
||||
*/ |
||||
protected $type = 'transfer_customer_service'; |
||||
|
||||
/** |
||||
* Properties. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected $properties = [ |
||||
'account', |
||||
]; |
||||
|
||||
/** |
||||
* Transfer constructor. |
||||
* |
||||
* @param string|null $account |
||||
*/ |
||||
public function __construct(string $account = null) |
||||
{ |
||||
parent::__construct(compact('account')); |
||||
} |
||||
|
||||
public function toXmlArray() |
||||
{ |
||||
return empty($this->get('account')) ? [] : [ |
||||
'TransInfo' => [ |
||||
'KfAccount' => $this->get('account'), |
||||
], |
||||
]; |
||||
} |
||||
} |
@ -0,0 +1,65 @@
@@ -0,0 +1,65 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Messages; |
||||
|
||||
/** |
||||
* Class Video. |
||||
* |
||||
* @property string $video |
||||
* @property string $title |
||||
* @property string $media_id |
||||
* @property string $description |
||||
* @property string $thumb_media_id |
||||
*/ |
||||
class Video extends Media |
||||
{ |
||||
/** |
||||
* Messages type. |
||||
* |
||||
* @var string |
||||
*/ |
||||
protected $type = 'video'; |
||||
|
||||
/** |
||||
* Properties. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected $properties = [ |
||||
'title', |
||||
'description', |
||||
'media_id', |
||||
'thumb_media_id', |
||||
]; |
||||
|
||||
/** |
||||
* Video constructor. |
||||
* |
||||
* @param string $mediaId |
||||
* @param array $attributes |
||||
*/ |
||||
public function __construct(string $mediaId, array $attributes = []) |
||||
{ |
||||
parent::__construct($mediaId, 'video', $attributes); |
||||
} |
||||
|
||||
public function toXmlArray() |
||||
{ |
||||
return [ |
||||
'Video' => [ |
||||
'MediaId' => $this->get('media_id'), |
||||
'Title' => $this->get('title'), |
||||
'Description' => $this->get('description'), |
||||
], |
||||
]; |
||||
} |
||||
} |
@ -0,0 +1,37 @@
@@ -0,0 +1,37 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Messages; |
||||
|
||||
/** |
||||
* Class Voice. |
||||
* |
||||
* @property string $media_id |
||||
*/ |
||||
class Voice extends Media |
||||
{ |
||||
/** |
||||
* Messages type. |
||||
* |
||||
* @var string |
||||
*/ |
||||
protected $type = 'voice'; |
||||
|
||||
/** |
||||
* Properties. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected $properties = [ |
||||
'media_id', |
||||
'recognition', |
||||
]; |
||||
} |
@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Providers; |
||||
|
||||
use EasyWeChat\Kernel\Config; |
||||
use Pimple\Container; |
||||
use Pimple\ServiceProviderInterface; |
||||
|
||||
/** |
||||
* Class ConfigServiceProvider. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class ConfigServiceProvider implements ServiceProviderInterface |
||||
{ |
||||
/** |
||||
* Registers services on the given container. |
||||
* |
||||
* This method should only be used to configure services and parameters. |
||||
* It should not get services. |
||||
* |
||||
* @param Container $pimple A container instance |
||||
*/ |
||||
public function register(Container $pimple) |
||||
{ |
||||
$pimple['config'] = function ($app) { |
||||
return new Config($app->getConfig()); |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,47 @@
@@ -0,0 +1,47 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Providers; |
||||
|
||||
use Pimple\Container; |
||||
use Pimple\ServiceProviderInterface; |
||||
use Symfony\Component\EventDispatcher\EventDispatcher; |
||||
|
||||
/** |
||||
* Class EventDispatcherServiceProvider. |
||||
* |
||||
* @author mingyoung <mingyoungcheung@gmail.com> |
||||
*/ |
||||
class EventDispatcherServiceProvider implements ServiceProviderInterface |
||||
{ |
||||
/** |
||||
* Registers services on the given container. |
||||
* |
||||
* This method should only be used to configure services and parameters. |
||||
* It should not get services. |
||||
* |
||||
* @param Container $pimple A container instance |
||||
*/ |
||||
public function register(Container $pimple) |
||||
{ |
||||
$pimple['events'] = function ($app) { |
||||
$dispatcher = new EventDispatcher(); |
||||
|
||||
foreach ($app->config->get('events.listen', []) as $event => $listeners) { |
||||
foreach ($listeners as $listener) { |
||||
$dispatcher->addListener($event, $listener); |
||||
} |
||||
} |
||||
|
||||
return $dispatcher; |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Providers; |
||||
|
||||
use EasyWeChatComposer\Extension; |
||||
use Pimple\Container; |
||||
use Pimple\ServiceProviderInterface; |
||||
|
||||
/** |
||||
* Class ExtensionServiceProvider. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class ExtensionServiceProvider implements ServiceProviderInterface |
||||
{ |
||||
/** |
||||
* Registers services on the given container. |
||||
* |
||||
* This method should only be used to configure services and parameters. |
||||
* It should not get services. |
||||
* |
||||
* @param Container $pimple A container instance |
||||
*/ |
||||
public function register(Container $pimple) |
||||
{ |
||||
$pimple['extension'] = function ($app) { |
||||
return new Extension($app); |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Providers; |
||||
|
||||
use GuzzleHttp\Client; |
||||
use Pimple\Container; |
||||
use Pimple\ServiceProviderInterface; |
||||
|
||||
/** |
||||
* Class HttpClientServiceProvider. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class HttpClientServiceProvider implements ServiceProviderInterface |
||||
{ |
||||
/** |
||||
* Registers services on the given container. |
||||
* |
||||
* This method should only be used to configure services and parameters. |
||||
* It should not get services. |
||||
* |
||||
* @param Container $pimple A container instance |
||||
*/ |
||||
public function register(Container $pimple) |
||||
{ |
||||
$pimple['http_client'] = function ($app) { |
||||
return new Client($app['config']->get('http', [])); |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,79 @@
@@ -0,0 +1,79 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Providers; |
||||
|
||||
use EasyWeChat\Kernel\Log\LogManager; |
||||
use Pimple\Container; |
||||
use Pimple\ServiceProviderInterface; |
||||
|
||||
/** |
||||
* Class LoggingServiceProvider. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class LogServiceProvider implements ServiceProviderInterface |
||||
{ |
||||
/** |
||||
* Registers services on the given container. |
||||
* |
||||
* This method should only be used to configure services and parameters. |
||||
* It should not get services. |
||||
* |
||||
* @param Container $pimple A container instance |
||||
*/ |
||||
public function register(Container $pimple) |
||||
{ |
||||
$pimple['logger'] = $pimple['log'] = function ($app) { |
||||
$config = $this->formatLogConfig($app); |
||||
|
||||
if (!empty($config)) { |
||||
$app->rebind('config', $app['config']->merge($config)); |
||||
} |
||||
|
||||
return new LogManager($app); |
||||
}; |
||||
} |
||||
|
||||
public function formatLogConfig($app) |
||||
{ |
||||
if (!empty($app['config']->get('log.channels'))) { |
||||
return $app['config']->get('log'); |
||||
} |
||||
|
||||
if (empty($app['config']->get('log'))) { |
||||
return [ |
||||
'log' => [ |
||||
'default' => 'errorlog', |
||||
'channels' => [ |
||||
'errorlog' => [ |
||||
'driver' => 'errorlog', |
||||
'level' => 'debug', |
||||
], |
||||
], |
||||
], |
||||
]; |
||||
} |
||||
|
||||
return [ |
||||
'log' => [ |
||||
'default' => 'single', |
||||
'channels' => [ |
||||
'single' => [ |
||||
'driver' => 'single', |
||||
'path' => $app['config']->get('log.file') ?: \sys_get_temp_dir().'/logs/easywechat.log', |
||||
'level' => $app['config']->get('log.level', 'debug'), |
||||
], |
||||
], |
||||
], |
||||
]; |
||||
} |
||||
} |
@ -0,0 +1,39 @@
@@ -0,0 +1,39 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Providers; |
||||
|
||||
use Pimple\Container; |
||||
use Pimple\ServiceProviderInterface; |
||||
use Symfony\Component\HttpFoundation\Request; |
||||
|
||||
/** |
||||
* Class RequestServiceProvider. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class RequestServiceProvider implements ServiceProviderInterface |
||||
{ |
||||
/** |
||||
* Registers services on the given container. |
||||
* |
||||
* This method should only be used to configure services and parameters. |
||||
* It should not get services. |
||||
* |
||||
* @param Container $pimple A container instance |
||||
*/ |
||||
public function register(Container $pimple) |
||||
{ |
||||
$pimple['request'] = function () { |
||||
return Request::createFromGlobals(); |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,375 @@
@@ -0,0 +1,375 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel; |
||||
|
||||
use EasyWeChat\Kernel\Contracts\MessageInterface; |
||||
use EasyWeChat\Kernel\Exceptions\BadRequestException; |
||||
use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; |
||||
use EasyWeChat\Kernel\Messages\Message; |
||||
use EasyWeChat\Kernel\Messages\News; |
||||
use EasyWeChat\Kernel\Messages\NewsItem; |
||||
use EasyWeChat\Kernel\Messages\Raw as RawMessage; |
||||
use EasyWeChat\Kernel\Messages\Text; |
||||
use EasyWeChat\Kernel\Support\XML; |
||||
use EasyWeChat\Kernel\Traits\Observable; |
||||
use EasyWeChat\Kernel\Traits\ResponseCastable; |
||||
use Symfony\Component\HttpFoundation\Response; |
||||
|
||||
/** |
||||
* Class ServerGuard. |
||||
* |
||||
* 1. url 里的 signature 只是将 token+nonce+timestamp 得到的签名,只是用于验证当前请求的,在公众号环境下一直有 |
||||
* 2. 企业号消息发送时是没有的,因为固定为完全模式,所以 url 里不会存在 signature, 只有 msg_signature 用于解密消息的 |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class ServerGuard |
||||
{ |
||||
use Observable; |
||||
use ResponseCastable; |
||||
|
||||
/** |
||||
* @var bool |
||||
*/ |
||||
protected $alwaysValidate = false; |
||||
|
||||
/** |
||||
* Empty string. |
||||
*/ |
||||
const SUCCESS_EMPTY_RESPONSE = 'success'; |
||||
|
||||
/** |
||||
* @var array |
||||
*/ |
||||
const MESSAGE_TYPE_MAPPING = [ |
||||
'text' => Message::TEXT, |
||||
'image' => Message::IMAGE, |
||||
'voice' => Message::VOICE, |
||||
'video' => Message::VIDEO, |
||||
'shortvideo' => Message::SHORT_VIDEO, |
||||
'location' => Message::LOCATION, |
||||
'link' => Message::LINK, |
||||
'device_event' => Message::DEVICE_EVENT, |
||||
'device_text' => Message::DEVICE_TEXT, |
||||
'event' => Message::EVENT, |
||||
'file' => Message::FILE, |
||||
'miniprogrampage' => Message::MINIPROGRAM_PAGE, |
||||
]; |
||||
|
||||
/** |
||||
* @var \EasyWeChat\Kernel\ServiceContainer |
||||
*/ |
||||
protected $app; |
||||
|
||||
/** |
||||
* Constructor. |
||||
* |
||||
* @codeCoverageIgnore |
||||
* |
||||
* @param \EasyWeChat\Kernel\ServiceContainer $app |
||||
*/ |
||||
public function __construct(ServiceContainer $app) |
||||
{ |
||||
$this->app = $app; |
||||
|
||||
foreach ($this->app->extension->observers() as $observer) { |
||||
call_user_func_array([$this, 'push'], $observer); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Handle and return response. |
||||
* |
||||
* @return Response |
||||
* |
||||
* @throws BadRequestException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
*/ |
||||
public function serve(): Response |
||||
{ |
||||
$this->app['logger']->debug('Request received:', [ |
||||
'method' => $this->app['request']->getMethod(), |
||||
'uri' => $this->app['request']->getUri(), |
||||
'content-type' => $this->app['request']->getContentType(), |
||||
'content' => $this->app['request']->getContent(), |
||||
]); |
||||
|
||||
$response = $this->validate()->resolve(); |
||||
|
||||
$this->app['logger']->debug('Server response created:', ['content' => $response->getContent()]); |
||||
|
||||
return $response; |
||||
} |
||||
|
||||
/** |
||||
* @return $this |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\BadRequestException |
||||
*/ |
||||
public function validate() |
||||
{ |
||||
if (!$this->alwaysValidate && !$this->isSafeMode()) { |
||||
return $this; |
||||
} |
||||
|
||||
if ($this->app['request']->get('signature') !== $this->signature([ |
||||
$this->getToken(), |
||||
$this->app['request']->get('timestamp'), |
||||
$this->app['request']->get('nonce'), |
||||
])) { |
||||
throw new BadRequestException('Invalid request signature.', 400); |
||||
} |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Force validate request. |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function forceValidate() |
||||
{ |
||||
$this->alwaysValidate = true; |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Get request message. |
||||
* |
||||
* @return array|\EasyWeChat\Kernel\Support\Collection|object|string |
||||
* |
||||
* @throws BadRequestException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
*/ |
||||
public function getMessage() |
||||
{ |
||||
$message = $this->parseMessage($this->app['request']->getContent(false)); |
||||
|
||||
if (!is_array($message) || empty($message)) { |
||||
throw new BadRequestException('No message received.'); |
||||
} |
||||
|
||||
if ($this->isSafeMode() && !empty($message['Encrypt'])) { |
||||
$message = $this->decryptMessage($message); |
||||
|
||||
// Handle JSON format. |
||||
$dataSet = json_decode($message, true); |
||||
|
||||
if ($dataSet && (JSON_ERROR_NONE === json_last_error())) { |
||||
return $dataSet; |
||||
} |
||||
|
||||
$message = XML::parse($message); |
||||
} |
||||
|
||||
return $this->detectAndCastResponseToType($message, $this->app->config->get('response_type')); |
||||
} |
||||
|
||||
/** |
||||
* Resolve server request and return the response. |
||||
* |
||||
* @return \Symfony\Component\HttpFoundation\Response |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\BadRequestException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
*/ |
||||
protected function resolve(): Response |
||||
{ |
||||
$result = $this->handleRequest(); |
||||
|
||||
if ($this->shouldReturnRawResponse()) { |
||||
$response = new Response($result['response']); |
||||
} else { |
||||
$response = new Response( |
||||
$this->buildResponse($result['to'], $result['from'], $result['response']), |
||||
200, |
||||
['Content-Type' => 'application/xml'] |
||||
); |
||||
} |
||||
|
||||
$this->app->events->dispatch(new Events\ServerGuardResponseCreated($response)); |
||||
|
||||
return $response; |
||||
} |
||||
|
||||
/** |
||||
* @return string|null |
||||
*/ |
||||
protected function getToken() |
||||
{ |
||||
return $this->app['config']['token']; |
||||
} |
||||
|
||||
/** |
||||
* @param string $to |
||||
* @param string $from |
||||
* @param \EasyWeChat\Kernel\Contracts\MessageInterface|string|int $message |
||||
* |
||||
* @return string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
*/ |
||||
public function buildResponse(string $to, string $from, $message) |
||||
{ |
||||
if (empty($message) || self::SUCCESS_EMPTY_RESPONSE === $message) { |
||||
return self::SUCCESS_EMPTY_RESPONSE; |
||||
} |
||||
|
||||
if ($message instanceof RawMessage) { |
||||
return $message->get('content', self::SUCCESS_EMPTY_RESPONSE); |
||||
} |
||||
|
||||
if (is_string($message) || is_numeric($message)) { |
||||
$message = new Text((string) $message); |
||||
} |
||||
|
||||
if (is_array($message) && reset($message) instanceof NewsItem) { |
||||
$message = new News($message); |
||||
} |
||||
|
||||
if (!($message instanceof Message)) { |
||||
throw new InvalidArgumentException(sprintf('Invalid Messages type "%s".', gettype($message))); |
||||
} |
||||
|
||||
return $this->buildReply($to, $from, $message); |
||||
} |
||||
|
||||
/** |
||||
* Handle request. |
||||
* |
||||
* @return array |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\BadRequestException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
*/ |
||||
protected function handleRequest(): array |
||||
{ |
||||
$castedMessage = $this->getMessage(); |
||||
|
||||
$messageArray = $this->detectAndCastResponseToType($castedMessage, 'array'); |
||||
|
||||
$response = $this->dispatch(self::MESSAGE_TYPE_MAPPING[$messageArray['MsgType'] ?? $messageArray['msg_type'] ?? 'text'], $castedMessage); |
||||
|
||||
return [ |
||||
'to' => $messageArray['FromUserName'] ?? '', |
||||
'from' => $messageArray['ToUserName'] ?? '', |
||||
'response' => $response, |
||||
]; |
||||
} |
||||
|
||||
/** |
||||
* Build reply XML. |
||||
* |
||||
* @param string $to |
||||
* @param string $from |
||||
* @param \EasyWeChat\Kernel\Contracts\MessageInterface $message |
||||
* |
||||
* @return string |
||||
*/ |
||||
protected function buildReply(string $to, string $from, MessageInterface $message): string |
||||
{ |
||||
$prepends = [ |
||||
'ToUserName' => $to, |
||||
'FromUserName' => $from, |
||||
'CreateTime' => time(), |
||||
'MsgType' => $message->getType(), |
||||
]; |
||||
|
||||
$response = $message->transformToXml($prepends); |
||||
|
||||
if ($this->isSafeMode()) { |
||||
$this->app['logger']->debug('Messages safe mode is enabled.'); |
||||
$response = $this->app['encryptor']->encrypt($response); |
||||
} |
||||
|
||||
return $response; |
||||
} |
||||
|
||||
/** |
||||
* @param array $params |
||||
* |
||||
* @return string |
||||
*/ |
||||
protected function signature(array $params) |
||||
{ |
||||
sort($params, SORT_STRING); |
||||
|
||||
return sha1(implode($params)); |
||||
} |
||||
|
||||
/** |
||||
* Parse message array from raw php input. |
||||
* |
||||
* @param string $content |
||||
* |
||||
* @return array |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\BadRequestException |
||||
*/ |
||||
protected function parseMessage($content) |
||||
{ |
||||
try { |
||||
if (0 === stripos($content, '<')) { |
||||
$content = XML::parse($content); |
||||
} else { |
||||
// Handle JSON format. |
||||
$dataSet = json_decode($content, true); |
||||
if ($dataSet && (JSON_ERROR_NONE === json_last_error())) { |
||||
$content = $dataSet; |
||||
} |
||||
} |
||||
|
||||
return (array) $content; |
||||
} catch (\Exception $e) { |
||||
throw new BadRequestException(sprintf('Invalid message content:(%s) %s', $e->getCode(), $e->getMessage()), $e->getCode()); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Check the request message safe mode. |
||||
* |
||||
* @return bool |
||||
*/ |
||||
protected function isSafeMode(): bool |
||||
{ |
||||
return $this->app['request']->get('signature') && 'aes' === $this->app['request']->get('encrypt_type'); |
||||
} |
||||
|
||||
/** |
||||
* @return bool |
||||
*/ |
||||
protected function shouldReturnRawResponse(): bool |
||||
{ |
||||
return false; |
||||
} |
||||
|
||||
/** |
||||
* @param array $message |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
protected function decryptMessage(array $message) |
||||
{ |
||||
return $message = $this->app['encryptor']->decrypt( |
||||
$message['Encrypt'], |
||||
$this->app['request']->get('msg_signature'), |
||||
$this->app['request']->get('nonce'), |
||||
$this->app['request']->get('timestamp') |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,167 @@
@@ -0,0 +1,167 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel; |
||||
|
||||
use EasyWeChat\Kernel\Providers\ConfigServiceProvider; |
||||
use EasyWeChat\Kernel\Providers\EventDispatcherServiceProvider; |
||||
use EasyWeChat\Kernel\Providers\ExtensionServiceProvider; |
||||
use EasyWeChat\Kernel\Providers\HttpClientServiceProvider; |
||||
use EasyWeChat\Kernel\Providers\LogServiceProvider; |
||||
use EasyWeChat\Kernel\Providers\RequestServiceProvider; |
||||
use EasyWeChatComposer\Traits\WithAggregator; |
||||
use Pimple\Container; |
||||
|
||||
/** |
||||
* Class ServiceContainer. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
* |
||||
* @property \EasyWeChat\Kernel\Config $config |
||||
* @property \Symfony\Component\HttpFoundation\Request $request |
||||
* @property \GuzzleHttp\Client $http_client |
||||
* @property \Monolog\Logger $logger |
||||
* @property \Symfony\Component\EventDispatcher\EventDispatcher $events |
||||
*/ |
||||
class ServiceContainer extends Container |
||||
{ |
||||
use WithAggregator; |
||||
|
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $id; |
||||
|
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $providers = []; |
||||
|
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $defaultConfig = []; |
||||
|
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $userConfig = []; |
||||
|
||||
/** |
||||
* Constructor. |
||||
* |
||||
* @param array $config |
||||
* @param array $prepends |
||||
* @param string|null $id |
||||
*/ |
||||
public function __construct(array $config = [], array $prepends = [], string $id = null) |
||||
{ |
||||
$this->registerProviders($this->getProviders()); |
||||
|
||||
parent::__construct($prepends); |
||||
|
||||
$this->userConfig = $config; |
||||
|
||||
$this->id = $id; |
||||
|
||||
$this->aggregate(); |
||||
|
||||
$this->events->dispatch(new Events\ApplicationInitialized($this)); |
||||
} |
||||
|
||||
/** |
||||
* @return string |
||||
*/ |
||||
public function getId() |
||||
{ |
||||
return $this->id ?? $this->id = md5(json_encode($this->userConfig)); |
||||
} |
||||
|
||||
/** |
||||
* @return array |
||||
*/ |
||||
public function getConfig() |
||||
{ |
||||
$base = [ |
||||
// http://docs.guzzlephp.org/en/stable/request-options.html |
||||
'http' => [ |
||||
'timeout' => 30.0, |
||||
'base_uri' => 'https://api.weixin.qq.com/', |
||||
], |
||||
]; |
||||
|
||||
return array_replace_recursive($base, $this->defaultConfig, $this->userConfig); |
||||
} |
||||
|
||||
/** |
||||
* Return all providers. |
||||
* |
||||
* @return array |
||||
*/ |
||||
public function getProviders() |
||||
{ |
||||
return array_merge([ |
||||
ConfigServiceProvider::class, |
||||
LogServiceProvider::class, |
||||
RequestServiceProvider::class, |
||||
HttpClientServiceProvider::class, |
||||
ExtensionServiceProvider::class, |
||||
EventDispatcherServiceProvider::class, |
||||
], $this->providers); |
||||
} |
||||
|
||||
/** |
||||
* @param string $id |
||||
* @param mixed $value |
||||
*/ |
||||
public function rebind($id, $value) |
||||
{ |
||||
$this->offsetUnset($id); |
||||
$this->offsetSet($id, $value); |
||||
} |
||||
|
||||
/** |
||||
* Magic get access. |
||||
* |
||||
* @param string $id |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public function __get($id) |
||||
{ |
||||
if ($this->shouldDelegate($id)) { |
||||
return $this->delegateTo($id); |
||||
} |
||||
|
||||
return $this->offsetGet($id); |
||||
} |
||||
|
||||
/** |
||||
* Magic set access. |
||||
* |
||||
* @param string $id |
||||
* @param mixed $value |
||||
*/ |
||||
public function __set($id, $value) |
||||
{ |
||||
$this->offsetSet($id, $value); |
||||
} |
||||
|
||||
/** |
||||
* @param array $providers |
||||
*/ |
||||
public function registerProviders(array $providers) |
||||
{ |
||||
foreach ($providers as $provider) { |
||||
parent::register(new $provider()); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,85 @@
@@ -0,0 +1,85 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Support; |
||||
|
||||
/** |
||||
* Class AES. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class AES |
||||
{ |
||||
/** |
||||
* @param string $text |
||||
* @param string $key |
||||
* @param string $iv |
||||
* @param int $option |
||||
* |
||||
* @return string |
||||
*/ |
||||
public static function encrypt(string $text, string $key, string $iv, int $option = OPENSSL_RAW_DATA): string |
||||
{ |
||||
self::validateKey($key); |
||||
self::validateIv($iv); |
||||
|
||||
return openssl_encrypt($text, self::getMode($key), $key, $option, $iv); |
||||
} |
||||
|
||||
/** |
||||
* @param string $cipherText |
||||
* @param string $key |
||||
* @param string $iv |
||||
* @param int $option |
||||
* @param string|null $method |
||||
* |
||||
* @return string |
||||
*/ |
||||
public static function decrypt(string $cipherText, string $key, string $iv, int $option = OPENSSL_RAW_DATA, $method = null): string |
||||
{ |
||||
self::validateKey($key); |
||||
self::validateIv($iv); |
||||
|
||||
return openssl_decrypt($cipherText, $method ?: self::getMode($key), $key, $option, $iv); |
||||
} |
||||
|
||||
/** |
||||
* @param string $key |
||||
* |
||||
* @return string |
||||
*/ |
||||
public static function getMode($key) |
||||
{ |
||||
return 'aes-'.(8 * strlen($key)).'-cbc'; |
||||
} |
||||
|
||||
/** |
||||
* @param string $key |
||||
*/ |
||||
public static function validateKey(string $key) |
||||
{ |
||||
if (!in_array(strlen($key), [16, 24, 32], true)) { |
||||
throw new \InvalidArgumentException(sprintf('Key length must be 16, 24, or 32 bytes; got key len (%s).', strlen($key))); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @param string $iv |
||||
* |
||||
* @throws \InvalidArgumentException |
||||
*/ |
||||
public static function validateIv(string $iv) |
||||
{ |
||||
if (!empty($iv) && 16 !== strlen($iv)) { |
||||
throw new \InvalidArgumentException('IV length must be 16 bytes.'); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,466 @@
@@ -0,0 +1,466 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Support; |
||||
|
||||
/** |
||||
* Array helper from Illuminate\Support\Arr. |
||||
*/ |
||||
class Arr |
||||
{ |
||||
/** |
||||
* Add an element to an array using "dot" notation if it doesn't exist. |
||||
* |
||||
* @param array $array |
||||
* @param string $key |
||||
* @param mixed $value |
||||
* |
||||
* @return array |
||||
*/ |
||||
public static function add(array $array, $key, $value) |
||||
{ |
||||
if (is_null(static::get($array, $key))) { |
||||
static::set($array, $key, $value); |
||||
} |
||||
|
||||
return $array; |
||||
} |
||||
|
||||
/** |
||||
* Cross join the given arrays, returning all possible permutations. |
||||
* |
||||
* @param array ...$arrays |
||||
* |
||||
* @return array |
||||
*/ |
||||
public static function crossJoin(...$arrays) |
||||
{ |
||||
$results = [[]]; |
||||
|
||||
foreach ($arrays as $index => $array) { |
||||
$append = []; |
||||
|
||||
foreach ($results as $product) { |
||||
foreach ($array as $item) { |
||||
$product[$index] = $item; |
||||
|
||||
$append[] = $product; |
||||
} |
||||
} |
||||
|
||||
$results = $append; |
||||
} |
||||
|
||||
return $results; |
||||
} |
||||
|
||||
/** |
||||
* Divide an array into two arrays. One with keys and the other with values. |
||||
* |
||||
* @param array $array |
||||
* |
||||
* @return array |
||||
*/ |
||||
public static function divide(array $array) |
||||
{ |
||||
return [array_keys($array), array_values($array)]; |
||||
} |
||||
|
||||
/** |
||||
* Flatten a multi-dimensional associative array with dots. |
||||
* |
||||
* @param array $array |
||||
* @param string $prepend |
||||
* |
||||
* @return array |
||||
*/ |
||||
public static function dot(array $array, $prepend = '') |
||||
{ |
||||
$results = []; |
||||
|
||||
foreach ($array as $key => $value) { |
||||
if (is_array($value) && !empty($value)) { |
||||
$results = array_merge($results, static::dot($value, $prepend.$key.'.')); |
||||
} else { |
||||
$results[$prepend.$key] = $value; |
||||
} |
||||
} |
||||
|
||||
return $results; |
||||
} |
||||
|
||||
/** |
||||
* Get all of the given array except for a specified array of items. |
||||
* |
||||
* @param array $array |
||||
* @param array|string $keys |
||||
* |
||||
* @return array |
||||
*/ |
||||
public static function except(array $array, $keys) |
||||
{ |
||||
static::forget($array, $keys); |
||||
|
||||
return $array; |
||||
} |
||||
|
||||
/** |
||||
* Determine if the given key exists in the provided array. |
||||
* |
||||
* @param array $array |
||||
* @param string|int $key |
||||
* |
||||
* @return bool |
||||
*/ |
||||
public static function exists(array $array, $key) |
||||
{ |
||||
return array_key_exists($key, $array); |
||||
} |
||||
|
||||
/** |
||||
* Return the first element in an array passing a given truth test. |
||||
* |
||||
* @param array $array |
||||
* @param callable|null $callback |
||||
* @param mixed $default |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public static function first(array $array, callable $callback = null, $default = null) |
||||
{ |
||||
if (is_null($callback)) { |
||||
if (empty($array)) { |
||||
return $default; |
||||
} |
||||
|
||||
foreach ($array as $item) { |
||||
return $item; |
||||
} |
||||
} |
||||
|
||||
foreach ($array as $key => $value) { |
||||
if (call_user_func($callback, $value, $key)) { |
||||
return $value; |
||||
} |
||||
} |
||||
|
||||
return $default; |
||||
} |
||||
|
||||
/** |
||||
* Return the last element in an array passing a given truth test. |
||||
* |
||||
* @param array $array |
||||
* @param callable|null $callback |
||||
* @param mixed $default |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public static function last(array $array, callable $callback = null, $default = null) |
||||
{ |
||||
if (is_null($callback)) { |
||||
return empty($array) ? $default : end($array); |
||||
} |
||||
|
||||
return static::first(array_reverse($array, true), $callback, $default); |
||||
} |
||||
|
||||
/** |
||||
* Flatten a multi-dimensional array into a single level. |
||||
* |
||||
* @param array $array |
||||
* @param int $depth |
||||
* |
||||
* @return array |
||||
*/ |
||||
public static function flatten(array $array, $depth = INF) |
||||
{ |
||||
return array_reduce($array, function ($result, $item) use ($depth) { |
||||
$item = $item instanceof Collection ? $item->all() : $item; |
||||
|
||||
if (!is_array($item)) { |
||||
return array_merge($result, [$item]); |
||||
} elseif (1 === $depth) { |
||||
return array_merge($result, array_values($item)); |
||||
} |
||||
|
||||
return array_merge($result, static::flatten($item, $depth - 1)); |
||||
}, []); |
||||
} |
||||
|
||||
/** |
||||
* Remove one or many array items from a given array using "dot" notation. |
||||
* |
||||
* @param array $array |
||||
* @param array|string $keys |
||||
*/ |
||||
public static function forget(array &$array, $keys) |
||||
{ |
||||
$original = &$array; |
||||
|
||||
$keys = (array) $keys; |
||||
|
||||
if (0 === count($keys)) { |
||||
return; |
||||
} |
||||
|
||||
foreach ($keys as $key) { |
||||
// if the exact key exists in the top-level, remove it |
||||
if (static::exists($array, $key)) { |
||||
unset($array[$key]); |
||||
|
||||
continue; |
||||
} |
||||
|
||||
$parts = explode('.', $key); |
||||
|
||||
// clean up before each pass |
||||
$array = &$original; |
||||
|
||||
while (count($parts) > 1) { |
||||
$part = array_shift($parts); |
||||
|
||||
if (isset($array[$part]) && is_array($array[$part])) { |
||||
$array = &$array[$part]; |
||||
} else { |
||||
continue 2; |
||||
} |
||||
} |
||||
|
||||
unset($array[array_shift($parts)]); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Get an item from an array using "dot" notation. |
||||
* |
||||
* @param array $array |
||||
* @param string $key |
||||
* @param mixed $default |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public static function get(array $array, $key, $default = null) |
||||
{ |
||||
if (is_null($key)) { |
||||
return $array; |
||||
} |
||||
|
||||
if (static::exists($array, $key)) { |
||||
return $array[$key]; |
||||
} |
||||
|
||||
foreach (explode('.', $key) as $segment) { |
||||
if (static::exists($array, $segment)) { |
||||
$array = $array[$segment]; |
||||
} else { |
||||
return $default; |
||||
} |
||||
} |
||||
|
||||
return $array; |
||||
} |
||||
|
||||
/** |
||||
* Check if an item or items exist in an array using "dot" notation. |
||||
* |
||||
* @param array $array |
||||
* @param string|array $keys |
||||
* |
||||
* @return bool |
||||
*/ |
||||
public static function has(array $array, $keys) |
||||
{ |
||||
if (is_null($keys)) { |
||||
return false; |
||||
} |
||||
|
||||
$keys = (array) $keys; |
||||
|
||||
if (empty($array)) { |
||||
return false; |
||||
} |
||||
|
||||
if ($keys === []) { |
||||
return false; |
||||
} |
||||
|
||||
foreach ($keys as $key) { |
||||
$subKeyArray = $array; |
||||
|
||||
if (static::exists($array, $key)) { |
||||
continue; |
||||
} |
||||
|
||||
foreach (explode('.', $key) as $segment) { |
||||
if (static::exists($subKeyArray, $segment)) { |
||||
$subKeyArray = $subKeyArray[$segment]; |
||||
} else { |
||||
return false; |
||||
} |
||||
} |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
/** |
||||
* Determines if an array is associative. |
||||
* |
||||
* An array is "associative" if it doesn't have sequential numerical keys beginning with zero. |
||||
* |
||||
* @param array $array |
||||
* |
||||
* @return bool |
||||
*/ |
||||
public static function isAssoc(array $array) |
||||
{ |
||||
$keys = array_keys($array); |
||||
|
||||
return array_keys($keys) !== $keys; |
||||
} |
||||
|
||||
/** |
||||
* Get a subset of the items from the given array. |
||||
* |
||||
* @param array $array |
||||
* @param array|string $keys |
||||
* |
||||
* @return array |
||||
*/ |
||||
public static function only(array $array, $keys) |
||||
{ |
||||
return array_intersect_key($array, array_flip((array) $keys)); |
||||
} |
||||
|
||||
/** |
||||
* Push an item onto the beginning of an array. |
||||
* |
||||
* @param array $array |
||||
* @param mixed $value |
||||
* @param mixed $key |
||||
* |
||||
* @return array |
||||
*/ |
||||
public static function prepend(array $array, $value, $key = null) |
||||
{ |
||||
if (is_null($key)) { |
||||
array_unshift($array, $value); |
||||
} else { |
||||
$array = [$key => $value] + $array; |
||||
} |
||||
|
||||
return $array; |
||||
} |
||||
|
||||
/** |
||||
* Get a value from the array, and remove it. |
||||
* |
||||
* @param array $array |
||||
* @param string $key |
||||
* @param mixed $default |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public static function pull(array &$array, $key, $default = null) |
||||
{ |
||||
$value = static::get($array, $key, $default); |
||||
|
||||
static::forget($array, $key); |
||||
|
||||
return $value; |
||||
} |
||||
|
||||
/** |
||||
* Get a 1 value from an array. |
||||
* |
||||
* @param array $array |
||||
* @param int|null $amount |
||||
* |
||||
* @return mixed |
||||
* |
||||
* @throws \InvalidArgumentException |
||||
*/ |
||||
public static function random(array $array, int $amount = null) |
||||
{ |
||||
if (is_null($amount)) { |
||||
return $array[array_rand($array)]; |
||||
} |
||||
|
||||
$keys = array_rand($array, $amount); |
||||
|
||||
$results = []; |
||||
|
||||
foreach ((array) $keys as $key) { |
||||
$results[] = $array[$key]; |
||||
} |
||||
|
||||
return $results; |
||||
} |
||||
|
||||
/** |
||||
* Set an array item to a given value using "dot" notation. |
||||
* |
||||
* If no key is given to the method, the entire array will be replaced. |
||||
* |
||||
* @param array $array |
||||
* @param string $key |
||||
* @param mixed $value |
||||
* |
||||
* @return array |
||||
*/ |
||||
public static function set(array &$array, string $key, $value) |
||||
{ |
||||
$keys = explode('.', $key); |
||||
|
||||
while (count($keys) > 1) { |
||||
$key = array_shift($keys); |
||||
|
||||
// If the key doesn't exist at this depth, we will just create an empty array |
||||
// to hold the next value, allowing us to create the arrays to hold final |
||||
// values at the correct depth. Then we'll keep digging into the array. |
||||
if (!isset($array[$key]) || !is_array($array[$key])) { |
||||
$array[$key] = []; |
||||
} |
||||
|
||||
$array = &$array[$key]; |
||||
} |
||||
|
||||
$array[array_shift($keys)] = $value; |
||||
|
||||
return $array; |
||||
} |
||||
|
||||
/** |
||||
* Filter the array using the given callback. |
||||
* |
||||
* @param array $array |
||||
* @param callable $callback |
||||
* |
||||
* @return array |
||||
*/ |
||||
public static function where(array $array, callable $callback) |
||||
{ |
||||
return array_filter($array, $callback, ARRAY_FILTER_USE_BOTH); |
||||
} |
||||
|
||||
/** |
||||
* If the given value is not an array, wrap it in one. |
||||
* |
||||
* @param mixed $value |
||||
* |
||||
* @return array |
||||
*/ |
||||
public static function wrap($value) |
||||
{ |
||||
return !is_array($value) ? [$value] : $value; |
||||
} |
||||
} |
@ -0,0 +1,66 @@
@@ -0,0 +1,66 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Support; |
||||
|
||||
use ArrayAccess; |
||||
use ArrayIterator; |
||||
use EasyWeChat\Kernel\Contracts\Arrayable; |
||||
use IteratorAggregate; |
||||
|
||||
/** |
||||
* Class ArrayAccessible. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class ArrayAccessible implements ArrayAccess, IteratorAggregate, Arrayable |
||||
{ |
||||
private $array; |
||||
|
||||
public function __construct(array $array = []) |
||||
{ |
||||
$this->array = $array; |
||||
} |
||||
|
||||
public function offsetExists($offset) |
||||
{ |
||||
return array_key_exists($offset, $this->array); |
||||
} |
||||
|
||||
public function offsetGet($offset) |
||||
{ |
||||
return $this->array[$offset]; |
||||
} |
||||
|
||||
public function offsetSet($offset, $value) |
||||
{ |
||||
if (null === $offset) { |
||||
$this->array[] = $value; |
||||
} else { |
||||
$this->array[$offset] = $value; |
||||
} |
||||
} |
||||
|
||||
public function offsetUnset($offset) |
||||
{ |
||||
unset($this->array[$offset]); |
||||
} |
||||
|
||||
public function getIterator() |
||||
{ |
||||
return new ArrayIterator($this->array); |
||||
} |
||||
|
||||
public function toArray() |
||||
{ |
||||
return $this->array; |
||||
} |
||||
} |
@ -0,0 +1,421 @@
@@ -0,0 +1,421 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Support; |
||||
|
||||
use ArrayAccess; |
||||
use ArrayIterator; |
||||
use Countable; |
||||
use EasyWeChat\Kernel\Contracts\Arrayable; |
||||
use IteratorAggregate; |
||||
use JsonSerializable; |
||||
use Serializable; |
||||
|
||||
/** |
||||
* Class Collection. |
||||
*/ |
||||
class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable, Serializable, Arrayable |
||||
{ |
||||
/** |
||||
* The collection data. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected $items = []; |
||||
|
||||
/** |
||||
* set data. |
||||
* |
||||
* @param array $items |
||||
*/ |
||||
public function __construct(array $items = []) |
||||
{ |
||||
foreach ($items as $key => $value) { |
||||
$this->set($key, $value); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Return all items. |
||||
* |
||||
* @return array |
||||
*/ |
||||
public function all() |
||||
{ |
||||
return $this->items; |
||||
} |
||||
|
||||
/** |
||||
* Return specific items. |
||||
* |
||||
* @param array $keys |
||||
* |
||||
* @return \EasyWeChat\Kernel\Support\Collection |
||||
*/ |
||||
public function only(array $keys) |
||||
{ |
||||
$return = []; |
||||
|
||||
foreach ($keys as $key) { |
||||
$value = $this->get($key); |
||||
|
||||
if (!is_null($value)) { |
||||
$return[$key] = $value; |
||||
} |
||||
} |
||||
|
||||
return new static($return); |
||||
} |
||||
|
||||
/** |
||||
* Get all items except for those with the specified keys. |
||||
* |
||||
* @param mixed $keys |
||||
* |
||||
* @return static |
||||
*/ |
||||
public function except($keys) |
||||
{ |
||||
$keys = is_array($keys) ? $keys : func_get_args(); |
||||
|
||||
return new static(Arr::except($this->items, $keys)); |
||||
} |
||||
|
||||
/** |
||||
* Merge data. |
||||
* |
||||
* @param Collection|array $items |
||||
* |
||||
* @return \EasyWeChat\Kernel\Support\Collection |
||||
*/ |
||||
public function merge($items) |
||||
{ |
||||
$clone = new static($this->all()); |
||||
|
||||
foreach ($items as $key => $value) { |
||||
$clone->set($key, $value); |
||||
} |
||||
|
||||
return $clone; |
||||
} |
||||
|
||||
/** |
||||
* To determine Whether the specified element exists. |
||||
* |
||||
* @param string $key |
||||
* |
||||
* @return bool |
||||
*/ |
||||
public function has($key) |
||||
{ |
||||
return !is_null(Arr::get($this->items, $key)); |
||||
} |
||||
|
||||
/** |
||||
* Retrieve the first item. |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public function first() |
||||
{ |
||||
return reset($this->items); |
||||
} |
||||
|
||||
/** |
||||
* Retrieve the last item. |
||||
* |
||||
* @return bool |
||||
*/ |
||||
public function last() |
||||
{ |
||||
$end = end($this->items); |
||||
|
||||
reset($this->items); |
||||
|
||||
return $end; |
||||
} |
||||
|
||||
/** |
||||
* add the item value. |
||||
* |
||||
* @param string $key |
||||
* @param mixed $value |
||||
*/ |
||||
public function add($key, $value) |
||||
{ |
||||
Arr::set($this->items, $key, $value); |
||||
} |
||||
|
||||
/** |
||||
* Set the item value. |
||||
* |
||||
* @param string $key |
||||
* @param mixed $value |
||||
*/ |
||||
public function set($key, $value) |
||||
{ |
||||
Arr::set($this->items, $key, $value); |
||||
} |
||||
|
||||
/** |
||||
* Retrieve item from Collection. |
||||
* |
||||
* @param string $key |
||||
* @param mixed $default |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public function get($key, $default = null) |
||||
{ |
||||
return Arr::get($this->items, $key, $default); |
||||
} |
||||
|
||||
/** |
||||
* Remove item form Collection. |
||||
* |
||||
* @param string $key |
||||
*/ |
||||
public function forget($key) |
||||
{ |
||||
Arr::forget($this->items, $key); |
||||
} |
||||
|
||||
/** |
||||
* Build to array. |
||||
* |
||||
* @return array |
||||
*/ |
||||
public function toArray() |
||||
{ |
||||
return $this->all(); |
||||
} |
||||
|
||||
/** |
||||
* Build to json. |
||||
* |
||||
* @param int $option |
||||
* |
||||
* @return string |
||||
*/ |
||||
public function toJson($option = JSON_UNESCAPED_UNICODE) |
||||
{ |
||||
return json_encode($this->all(), $option); |
||||
} |
||||
|
||||
/** |
||||
* To string. |
||||
* |
||||
* @return string |
||||
*/ |
||||
public function __toString() |
||||
{ |
||||
return $this->toJson(); |
||||
} |
||||
|
||||
/** |
||||
* (PHP 5 >= 5.4.0)<br/> |
||||
* Specify data which should be serialized to JSON. |
||||
* |
||||
* @see http://php.net/manual/en/jsonserializable.jsonserialize.php |
||||
* |
||||
* @return mixed data which can be serialized by <b>json_encode</b>, |
||||
* which is a value of any type other than a resource |
||||
*/ |
||||
public function jsonSerialize() |
||||
{ |
||||
return $this->items; |
||||
} |
||||
|
||||
/** |
||||
* (PHP 5 >= 5.1.0)<br/> |
||||
* String representation of object. |
||||
* |
||||
* @see http://php.net/manual/en/serializable.serialize.php |
||||
* |
||||
* @return string the string representation of the object or null |
||||
*/ |
||||
public function serialize() |
||||
{ |
||||
return serialize($this->items); |
||||
} |
||||
|
||||
/** |
||||
* (PHP 5 >= 5.0.0)<br/> |
||||
* Retrieve an external iterator. |
||||
* |
||||
* @see http://php.net/manual/en/iteratoraggregate.getiterator.php |
||||
* |
||||
* @return \ArrayIterator An instance of an object implementing <b>Iterator</b> or |
||||
* <b>Traversable</b> |
||||
*/ |
||||
public function getIterator() |
||||
{ |
||||
return new ArrayIterator($this->items); |
||||
} |
||||
|
||||
/** |
||||
* (PHP 5 >= 5.1.0)<br/> |
||||
* Count elements of an object. |
||||
* |
||||
* @see http://php.net/manual/en/countable.count.php |
||||
* |
||||
* @return int the custom count as an integer. |
||||
* </p> |
||||
* <p> |
||||
* The return value is cast to an integer |
||||
*/ |
||||
public function count() |
||||
{ |
||||
return count($this->items); |
||||
} |
||||
|
||||
/** |
||||
* (PHP 5 >= 5.1.0)<br/> |
||||
* Constructs the object. |
||||
* |
||||
* @see http://php.net/manual/en/serializable.unserialize.php |
||||
* |
||||
* @param string $serialized <p> |
||||
* The string representation of the object. |
||||
* </p> |
||||
* |
||||
* @return mixed|void |
||||
*/ |
||||
public function unserialize($serialized) |
||||
{ |
||||
return $this->items = unserialize($serialized); |
||||
} |
||||
|
||||
/** |
||||
* Get a data by key. |
||||
* |
||||
* @param string $key |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public function __get($key) |
||||
{ |
||||
return $this->get($key); |
||||
} |
||||
|
||||
/** |
||||
* Assigns a value to the specified data. |
||||
* |
||||
* @param string $key |
||||
* @param mixed $value |
||||
*/ |
||||
public function __set($key, $value) |
||||
{ |
||||
$this->set($key, $value); |
||||
} |
||||
|
||||
/** |
||||
* Whether or not an data exists by key. |
||||
* |
||||
* @param string $key |
||||
* |
||||
* @return bool |
||||
*/ |
||||
public function __isset($key) |
||||
{ |
||||
return $this->has($key); |
||||
} |
||||
|
||||
/** |
||||
* Unset an data by key. |
||||
* |
||||
* @param string $key |
||||
*/ |
||||
public function __unset($key) |
||||
{ |
||||
$this->forget($key); |
||||
} |
||||
|
||||
/** |
||||
* var_export. |
||||
* |
||||
* @return array |
||||
*/ |
||||
public function __set_state() |
||||
{ |
||||
return $this->all(); |
||||
} |
||||
|
||||
/** |
||||
* (PHP 5 >= 5.0.0)<br/> |
||||
* Whether a offset exists. |
||||
* |
||||
* @see http://php.net/manual/en/arrayaccess.offsetexists.php |
||||
* |
||||
* @param mixed $offset <p> |
||||
* An offset to check for. |
||||
* </p> |
||||
* |
||||
* @return bool true on success or false on failure. |
||||
* The return value will be casted to boolean if non-boolean was returned |
||||
*/ |
||||
public function offsetExists($offset) |
||||
{ |
||||
return $this->has($offset); |
||||
} |
||||
|
||||
/** |
||||
* (PHP 5 >= 5.0.0)<br/> |
||||
* Offset to unset. |
||||
* |
||||
* @see http://php.net/manual/en/arrayaccess.offsetunset.php |
||||
* |
||||
* @param mixed $offset <p> |
||||
* The offset to unset. |
||||
* </p> |
||||
*/ |
||||
public function offsetUnset($offset) |
||||
{ |
||||
if ($this->offsetExists($offset)) { |
||||
$this->forget($offset); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* (PHP 5 >= 5.0.0)<br/> |
||||
* Offset to retrieve. |
||||
* |
||||
* @see http://php.net/manual/en/arrayaccess.offsetget.php |
||||
* |
||||
* @param mixed $offset <p> |
||||
* The offset to retrieve. |
||||
* </p> |
||||
* |
||||
* @return mixed Can return all value types |
||||
*/ |
||||
public function offsetGet($offset) |
||||
{ |
||||
return $this->offsetExists($offset) ? $this->get($offset) : null; |
||||
} |
||||
|
||||
/** |
||||
* (PHP 5 >= 5.0.0)<br/> |
||||
* Offset to set. |
||||
* |
||||
* @see http://php.net/manual/en/arrayaccess.offsetset.php |
||||
* |
||||
* @param mixed $offset <p> |
||||
* The offset to assign the value to. |
||||
* </p> |
||||
* @param mixed $value <p> |
||||
* The value to set. |
||||
* </p> |
||||
*/ |
||||
public function offsetSet($offset, $value) |
||||
{ |
||||
$this->set($offset, $value); |
||||
} |
||||
} |
@ -0,0 +1,135 @@
@@ -0,0 +1,135 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Support; |
||||
|
||||
use finfo; |
||||
|
||||
/** |
||||
* Class File. |
||||
*/ |
||||
class File |
||||
{ |
||||
/** |
||||
* MIME mapping. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected static $extensionMap = [ |
||||
'audio/wav' => '.wav', |
||||
'audio/x-ms-wma' => '.wma', |
||||
'video/x-ms-wmv' => '.wmv', |
||||
'video/mp4' => '.mp4', |
||||
'audio/mpeg' => '.mp3', |
||||
'audio/amr' => '.amr', |
||||
'application/vnd.rn-realmedia' => '.rm', |
||||
'audio/mid' => '.mid', |
||||
'image/bmp' => '.bmp', |
||||
'image/gif' => '.gif', |
||||
'image/png' => '.png', |
||||
'image/tiff' => '.tiff', |
||||
'image/jpeg' => '.jpg', |
||||
'application/pdf' => '.pdf', |
||||
|
||||
// 列举更多的文件 mime, 企业号是支持的,公众平台这边之后万一也更新了呢 |
||||
'application/msword' => '.doc', |
||||
|
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => '.docx', |
||||
'application/vnd.openxmlformats-officedocument.wordprocessingml.template' => '.dotx', |
||||
'application/vnd.ms-word.document.macroEnabled.12' => '.docm', |
||||
'application/vnd.ms-word.template.macroEnabled.12' => '.dotm', |
||||
|
||||
'application/vnd.ms-excel' => '.xls', |
||||
|
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => '.xlsx', |
||||
'application/vnd.openxmlformats-officedocument.spreadsheetml.template' => '.xltx', |
||||
'application/vnd.ms-excel.sheet.macroEnabled.12' => '.xlsm', |
||||
'application/vnd.ms-excel.template.macroEnabled.12' => '.xltm', |
||||
'application/vnd.ms-excel.addin.macroEnabled.12' => '.xlam', |
||||
'application/vnd.ms-excel.sheet.binary.macroEnabled.12' => '.xlsb', |
||||
|
||||
'application/vnd.ms-powerpoint' => '.ppt', |
||||
|
||||
'application/vnd.openxmlformats-officedocument.presentationml.presentation' => '.pptx', |
||||
'application/vnd.openxmlformats-officedocument.presentationml.template' => '.potx', |
||||
'application/vnd.openxmlformats-officedocument.presentationml.slideshow' => '.ppsx', |
||||
'application/vnd.ms-powerpoint.addin.macroEnabled.12' => '.ppam', |
||||
]; |
||||
|
||||
/** |
||||
* File header signatures. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected static $signatures = [ |
||||
'ffd8ff' => '.jpg', |
||||
'424d' => '.bmp', |
||||
'47494638' => '.gif', |
||||
'2f55736572732f6f7665' => '.png', |
||||
'89504e47' => '.png', |
||||
'494433' => '.mp3', |
||||
'fffb' => '.mp3', |
||||
'fff3' => '.mp3', |
||||
'3026b2758e66cf11' => '.wma', |
||||
'52494646' => '.wav', |
||||
'57415645' => '.wav', |
||||
'41564920' => '.avi', |
||||
'000001ba' => '.mpg', |
||||
'000001b3' => '.mpg', |
||||
'2321414d52' => '.amr', |
||||
'25504446' => '.pdf', |
||||
]; |
||||
|
||||
/** |
||||
* Return steam extension. |
||||
* |
||||
* @param string $stream |
||||
* |
||||
* @return string|false |
||||
*/ |
||||
public static function getStreamExt($stream) |
||||
{ |
||||
$ext = self::getExtBySignature($stream); |
||||
|
||||
try { |
||||
if (empty($ext) && is_readable($stream)) { |
||||
$stream = file_get_contents($stream); |
||||
} |
||||
} catch (\Exception $e) { |
||||
} |
||||
|
||||
$fileInfo = new finfo(FILEINFO_MIME); |
||||
|
||||
$mime = strstr($fileInfo->buffer($stream), ';', true); |
||||
|
||||
return isset(self::$extensionMap[$mime]) ? self::$extensionMap[$mime] : $ext; |
||||
} |
||||
|
||||
/** |
||||
* Get file extension by file header signature. |
||||
* |
||||
* @param string $stream |
||||
* |
||||
* @return string |
||||
*/ |
||||
public static function getExtBySignature($stream) |
||||
{ |
||||
$prefix = strval(bin2hex(mb_strcut($stream, 0, 10))); |
||||
|
||||
foreach (self::$signatures as $signature => $extension) { |
||||
if (0 === strpos($prefix, strval($signature))) { |
||||
return $extension; |
||||
} |
||||
} |
||||
|
||||
return ''; |
||||
} |
||||
} |
@ -0,0 +1,131 @@
@@ -0,0 +1,131 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Support; |
||||
|
||||
/* |
||||
* helpers. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
|
||||
/** |
||||
* Generate a signature. |
||||
* |
||||
* @param array $attributes |
||||
* @param string $key |
||||
* @param string $encryptMethod |
||||
* |
||||
* @return string |
||||
*/ |
||||
function generate_sign(array $attributes, $key, $encryptMethod = 'md5') |
||||
{ |
||||
ksort($attributes); |
||||
|
||||
$attributes['key'] = $key; |
||||
|
||||
return strtoupper(call_user_func_array($encryptMethod, [urldecode(http_build_query($attributes))])); |
||||
} |
||||
|
||||
/** |
||||
* @param string $signType |
||||
* @param string $secretKey |
||||
* |
||||
* @return \Closure|string |
||||
*/ |
||||
function get_encrypt_method(string $signType, string $secretKey = '') |
||||
{ |
||||
if ('HMAC-SHA256' === $signType) { |
||||
return function ($str) use ($secretKey) { |
||||
return hash_hmac('sha256', $str, $secretKey); |
||||
}; |
||||
} |
||||
|
||||
return 'md5'; |
||||
} |
||||
|
||||
/** |
||||
* Get client ip. |
||||
* |
||||
* @return string |
||||
*/ |
||||
function get_client_ip() |
||||
{ |
||||
if (!empty($_SERVER['REMOTE_ADDR'])) { |
||||
$ip = $_SERVER['REMOTE_ADDR']; |
||||
} else { |
||||
// for php-cli(phpunit etc.) |
||||
$ip = defined('PHPUNIT_RUNNING') ? '127.0.0.1' : gethostbyname(gethostname()); |
||||
} |
||||
|
||||
return filter_var($ip, FILTER_VALIDATE_IP) ?: '127.0.0.1'; |
||||
} |
||||
|
||||
/** |
||||
* Get current server ip. |
||||
* |
||||
* @return string |
||||
*/ |
||||
function get_server_ip() |
||||
{ |
||||
if (!empty($_SERVER['SERVER_ADDR'])) { |
||||
$ip = $_SERVER['SERVER_ADDR']; |
||||
} elseif (!empty($_SERVER['SERVER_NAME'])) { |
||||
$ip = gethostbyname($_SERVER['SERVER_NAME']); |
||||
} else { |
||||
// for php-cli(phpunit etc.) |
||||
$ip = defined('PHPUNIT_RUNNING') ? '127.0.0.1' : gethostbyname(gethostname()); |
||||
} |
||||
|
||||
return filter_var($ip, FILTER_VALIDATE_IP) ?: '127.0.0.1'; |
||||
} |
||||
|
||||
/** |
||||
* Return current url. |
||||
* |
||||
* @return string |
||||
*/ |
||||
function current_url() |
||||
{ |
||||
$protocol = 'http://'; |
||||
|
||||
if ((!empty($_SERVER['HTTPS']) && 'off' !== $_SERVER['HTTPS']) || ($_SERVER['HTTP_X_FORWARDED_PROTO'] ?? 'http') === 'https') { |
||||
$protocol = 'https://'; |
||||
} |
||||
|
||||
return $protocol.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']; |
||||
} |
||||
|
||||
/** |
||||
* Return random string. |
||||
* |
||||
* @param string $length |
||||
* |
||||
* @return string |
||||
*/ |
||||
function str_random($length) |
||||
{ |
||||
return Str::random($length); |
||||
} |
||||
|
||||
/** |
||||
* @param string $content |
||||
* @param string $publicKey |
||||
* |
||||
* @return string |
||||
*/ |
||||
function rsa_public_encrypt($content, $publicKey) |
||||
{ |
||||
$encrypted = ''; |
||||
openssl_public_encrypt($content, $encrypted, openssl_pkey_get_public($publicKey), OPENSSL_PKCS1_OAEP_PADDING); |
||||
|
||||
return base64_encode($encrypted); |
||||
} |
@ -0,0 +1,193 @@
@@ -0,0 +1,193 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Support; |
||||
|
||||
use EasyWeChat\Kernel\Exceptions\RuntimeException; |
||||
|
||||
/** |
||||
* Class Str. |
||||
*/ |
||||
class Str |
||||
{ |
||||
/** |
||||
* The cache of snake-cased words. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected static $snakeCache = []; |
||||
|
||||
/** |
||||
* The cache of camel-cased words. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected static $camelCache = []; |
||||
|
||||
/** |
||||
* The cache of studly-cased words. |
||||
* |
||||
* @var array |
||||
*/ |
||||
protected static $studlyCache = []; |
||||
|
||||
/** |
||||
* Convert a value to camel case. |
||||
* |
||||
* @param string $value |
||||
* |
||||
* @return string |
||||
*/ |
||||
public static function camel($value) |
||||
{ |
||||
if (isset(static::$camelCache[$value])) { |
||||
return static::$camelCache[$value]; |
||||
} |
||||
|
||||
return static::$camelCache[$value] = lcfirst(static::studly($value)); |
||||
} |
||||
|
||||
/** |
||||
* Generate a more truly "random" alpha-numeric string. |
||||
* |
||||
* @param int $length |
||||
* |
||||
* @return string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\RuntimeException |
||||
*/ |
||||
public static function random($length = 16) |
||||
{ |
||||
$string = ''; |
||||
|
||||
while (($len = strlen($string)) < $length) { |
||||
$size = $length - $len; |
||||
|
||||
$bytes = static::randomBytes($size); |
||||
|
||||
$string .= substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $size); |
||||
} |
||||
|
||||
return $string; |
||||
} |
||||
|
||||
/** |
||||
* Generate a more truly "random" bytes. |
||||
* |
||||
* @param int $length |
||||
* |
||||
* @return string |
||||
* |
||||
* @throws RuntimeException |
||||
* |
||||
* @codeCoverageIgnore |
||||
* |
||||
* @throws \Exception |
||||
*/ |
||||
public static function randomBytes($length = 16) |
||||
{ |
||||
if (function_exists('random_bytes')) { |
||||
$bytes = random_bytes($length); |
||||
} elseif (function_exists('openssl_random_pseudo_bytes')) { |
||||
$bytes = openssl_random_pseudo_bytes($length, $strong); |
||||
if (false === $bytes || false === $strong) { |
||||
throw new RuntimeException('Unable to generate random string.'); |
||||
} |
||||
} else { |
||||
throw new RuntimeException('OpenSSL extension is required for PHP 5 users.'); |
||||
} |
||||
|
||||
return $bytes; |
||||
} |
||||
|
||||
/** |
||||
* Generate a "random" alpha-numeric string. |
||||
* |
||||
* Should not be considered sufficient for cryptography, etc. |
||||
* |
||||
* @param int $length |
||||
* |
||||
* @return string |
||||
*/ |
||||
public static function quickRandom($length = 16) |
||||
{ |
||||
$pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; |
||||
|
||||
return substr(str_shuffle(str_repeat($pool, $length)), 0, $length); |
||||
} |
||||
|
||||
/** |
||||
* Convert the given string to upper-case. |
||||
* |
||||
* @param string $value |
||||
* |
||||
* @return string |
||||
*/ |
||||
public static function upper($value) |
||||
{ |
||||
return mb_strtoupper($value); |
||||
} |
||||
|
||||
/** |
||||
* Convert the given string to title case. |
||||
* |
||||
* @param string $value |
||||
* |
||||
* @return string |
||||
*/ |
||||
public static function title($value) |
||||
{ |
||||
return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8'); |
||||
} |
||||
|
||||
/** |
||||
* Convert a string to snake case. |
||||
* |
||||
* @param string $value |
||||
* @param string $delimiter |
||||
* |
||||
* @return string |
||||
*/ |
||||
public static function snake($value, $delimiter = '_') |
||||
{ |
||||
$key = $value.$delimiter; |
||||
|
||||
if (isset(static::$snakeCache[$key])) { |
||||
return static::$snakeCache[$key]; |
||||
} |
||||
|
||||
if (!ctype_lower($value)) { |
||||
$value = strtolower(preg_replace('/(.)(?=[A-Z])/', '$1'.$delimiter, $value)); |
||||
} |
||||
|
||||
return static::$snakeCache[$key] = trim($value, '_'); |
||||
} |
||||
|
||||
/** |
||||
* Convert a value to studly caps case. |
||||
* |
||||
* @param string $value |
||||
* |
||||
* @return string |
||||
*/ |
||||
public static function studly($value) |
||||
{ |
||||
$key = $value; |
||||
|
||||
if (isset(static::$studlyCache[$key])) { |
||||
return static::$studlyCache[$key]; |
||||
} |
||||
|
||||
$value = ucwords(str_replace(['-', '_'], ' ', $value)); |
||||
|
||||
return static::$studlyCache[$key] = str_replace(' ', '', $value); |
||||
} |
||||
} |
@ -0,0 +1,167 @@
@@ -0,0 +1,167 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Support; |
||||
|
||||
use SimpleXMLElement; |
||||
|
||||
/** |
||||
* Class XML. |
||||
*/ |
||||
class XML |
||||
{ |
||||
/** |
||||
* XML to array. |
||||
* |
||||
* @param string $xml XML string |
||||
* |
||||
* @return array |
||||
*/ |
||||
public static function parse($xml) |
||||
{ |
||||
$backup = libxml_disable_entity_loader(true); |
||||
|
||||
$result = self::normalize(simplexml_load_string(self::sanitize($xml), 'SimpleXMLElement', LIBXML_COMPACT | LIBXML_NOCDATA | LIBXML_NOBLANKS)); |
||||
|
||||
libxml_disable_entity_loader($backup); |
||||
|
||||
return $result; |
||||
} |
||||
|
||||
/** |
||||
* XML encode. |
||||
* |
||||
* @param mixed $data |
||||
* @param string $root |
||||
* @param string $item |
||||
* @param string $attr |
||||
* @param string $id |
||||
* |
||||
* @return string |
||||
*/ |
||||
public static function build( |
||||
$data, |
||||
$root = 'xml', |
||||
$item = 'item', |
||||
$attr = '', |
||||
$id = 'id' |
||||
) { |
||||
if (is_array($attr)) { |
||||
$_attr = []; |
||||
|
||||
foreach ($attr as $key => $value) { |
||||
$_attr[] = "{$key}=\"{$value}\""; |
||||
} |
||||
|
||||
$attr = implode(' ', $_attr); |
||||
} |
||||
|
||||
$attr = trim($attr); |
||||
$attr = empty($attr) ? '' : " {$attr}"; |
||||
$xml = "<{$root}{$attr}>"; |
||||
$xml .= self::data2Xml($data, $item, $id); |
||||
$xml .= "</{$root}>"; |
||||
|
||||
return $xml; |
||||
} |
||||
|
||||
/** |
||||
* Build CDATA. |
||||
* |
||||
* @param string $string |
||||
* |
||||
* @return string |
||||
*/ |
||||
public static function cdata($string) |
||||
{ |
||||
return sprintf('<![CDATA[%s]]>', $string); |
||||
} |
||||
|
||||
/** |
||||
* Object to array. |
||||
* |
||||
* |
||||
* @param SimpleXMLElement $obj |
||||
* |
||||
* @return array |
||||
*/ |
||||
protected static function normalize($obj) |
||||
{ |
||||
$result = null; |
||||
|
||||
if (is_object($obj)) { |
||||
$obj = (array) $obj; |
||||
} |
||||
|
||||
if (is_array($obj)) { |
||||
foreach ($obj as $key => $value) { |
||||
$res = self::normalize($value); |
||||
if (('@attributes' === $key) && ($key)) { |
||||
$result = $res; // @codeCoverageIgnore |
||||
} else { |
||||
$result[$key] = $res; |
||||
} |
||||
} |
||||
} else { |
||||
$result = $obj; |
||||
} |
||||
|
||||
return $result; |
||||
} |
||||
|
||||
/** |
||||
* Array to XML. |
||||
* |
||||
* @param array $data |
||||
* @param string $item |
||||
* @param string $id |
||||
* |
||||
* @return string |
||||
*/ |
||||
protected static function data2Xml($data, $item = 'item', $id = 'id') |
||||
{ |
||||
$xml = $attr = ''; |
||||
|
||||
foreach ($data as $key => $val) { |
||||
if (is_numeric($key)) { |
||||
$id && $attr = " {$id}=\"{$key}\""; |
||||
$key = $item; |
||||
} |
||||
|
||||
$xml .= "<{$key}{$attr}>"; |
||||
|
||||
if ((is_array($val) || is_object($val))) { |
||||
$xml .= self::data2Xml((array) $val, $item, $id); |
||||
} else { |
||||
$xml .= is_numeric($val) ? $val : self::cdata($val); |
||||
} |
||||
|
||||
$xml .= "</{$key}>"; |
||||
} |
||||
|
||||
return $xml; |
||||
} |
||||
|
||||
/** |
||||
* Delete invalid characters in XML. |
||||
* |
||||
* @see https://www.w3.org/TR/2008/REC-xml-20081126/#charsets - XML charset range |
||||
* @see http://php.net/manual/en/regexp.reference.escape.php - escape in UTF-8 mode |
||||
* |
||||
* @param string $xml |
||||
* |
||||
* @return string |
||||
*/ |
||||
public static function sanitize($xml) |
||||
{ |
||||
return preg_replace('/[^\x{9}\x{A}\x{D}\x{20}-\x{D7FF}\x{E000}-\x{FFFD}\x{10000}-\x{10FFFF}]+/u', '', $xml); |
||||
} |
||||
} |
@ -0,0 +1,251 @@
@@ -0,0 +1,251 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Traits; |
||||
|
||||
use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; |
||||
use EasyWeChat\Kernel\Support\Arr; |
||||
use EasyWeChat\Kernel\Support\Str; |
||||
|
||||
/** |
||||
* Trait Attributes. |
||||
*/ |
||||
trait HasAttributes |
||||
{ |
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $attributes = []; |
||||
|
||||
/** |
||||
* @var bool |
||||
*/ |
||||
protected $snakeable = true; |
||||
|
||||
/** |
||||
* Set Attributes. |
||||
* |
||||
* @param array $attributes |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function setAttributes(array $attributes = []) |
||||
{ |
||||
$this->attributes = $attributes; |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Set attribute. |
||||
* |
||||
* @param string $attribute |
||||
* @param string $value |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function setAttribute($attribute, $value) |
||||
{ |
||||
Arr::set($this->attributes, $attribute, $value); |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Get attribute. |
||||
* |
||||
* @param string $attribute |
||||
* @param mixed $default |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public function getAttribute($attribute, $default = null) |
||||
{ |
||||
return Arr::get($this->attributes, $attribute, $default); |
||||
} |
||||
|
||||
/** |
||||
* @param string $attribute |
||||
* |
||||
* @return bool |
||||
*/ |
||||
public function isRequired($attribute) |
||||
{ |
||||
return in_array($attribute, $this->getRequired(), true); |
||||
} |
||||
|
||||
/** |
||||
* @return array|mixed |
||||
*/ |
||||
public function getRequired() |
||||
{ |
||||
return property_exists($this, 'required') ? $this->required : []; |
||||
} |
||||
|
||||
/** |
||||
* Set attribute. |
||||
* |
||||
* @param string $attribute |
||||
* @param mixed $value |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function with($attribute, $value) |
||||
{ |
||||
$this->snakeable && $attribute = Str::snake($attribute); |
||||
|
||||
$this->setAttribute($attribute, $value); |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Override parent set() method. |
||||
* |
||||
* @param string $attribute |
||||
* @param mixed $value |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function set($attribute, $value) |
||||
{ |
||||
$this->setAttribute($attribute, $value); |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Override parent get() method. |
||||
* |
||||
* @param string $attribute |
||||
* @param mixed $default |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public function get($attribute, $default = null) |
||||
{ |
||||
return $this->getAttribute($attribute, $default); |
||||
} |
||||
|
||||
/** |
||||
* @param string $key |
||||
* |
||||
* @return bool |
||||
*/ |
||||
public function has(string $key) |
||||
{ |
||||
return Arr::has($this->attributes, $key); |
||||
} |
||||
|
||||
/** |
||||
* @param array $attributes |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function merge(array $attributes) |
||||
{ |
||||
$this->attributes = array_merge($this->attributes, $attributes); |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* @param array|string $keys |
||||
* |
||||
* @return array |
||||
*/ |
||||
public function only($keys) |
||||
{ |
||||
return Arr::only($this->attributes, $keys); |
||||
} |
||||
|
||||
/** |
||||
* Return all items. |
||||
* |
||||
* @return array |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
*/ |
||||
public function all() |
||||
{ |
||||
$this->checkRequiredAttributes(); |
||||
|
||||
return $this->attributes; |
||||
} |
||||
|
||||
/** |
||||
* Magic call. |
||||
* |
||||
* @param string $method |
||||
* @param array $args |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function __call($method, $args) |
||||
{ |
||||
if (0 === stripos($method, 'with')) { |
||||
return $this->with(substr($method, 4), array_shift($args)); |
||||
} |
||||
|
||||
throw new \BadMethodCallException(sprintf('Method "%s" does not exists.', $method)); |
||||
} |
||||
|
||||
/** |
||||
* Magic get. |
||||
* |
||||
* @param string $property |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public function __get($property) |
||||
{ |
||||
return $this->get($property); |
||||
} |
||||
|
||||
/** |
||||
* Magic set. |
||||
* |
||||
* @param string $property |
||||
* @param mixed $value |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function __set($property, $value) |
||||
{ |
||||
return $this->with($property, $value); |
||||
} |
||||
|
||||
/** |
||||
* Whether or not an data exists by key. |
||||
* |
||||
* @param string $key |
||||
* |
||||
* @return bool |
||||
*/ |
||||
public function __isset($key) |
||||
{ |
||||
return isset($this->attributes[$key]); |
||||
} |
||||
|
||||
/** |
||||
* Check required attributes. |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
*/ |
||||
protected function checkRequiredAttributes() |
||||
{ |
||||
foreach ($this->getRequired() as $attribute) { |
||||
if (is_null($this->get($attribute))) { |
||||
throw new InvalidArgumentException(sprintf('"%s" cannot be empty.', $attribute)); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,231 @@
@@ -0,0 +1,231 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Traits; |
||||
|
||||
use GuzzleHttp\Client; |
||||
use GuzzleHttp\ClientInterface; |
||||
use GuzzleHttp\HandlerStack; |
||||
use Psr\Http\Message\ResponseInterface; |
||||
|
||||
/** |
||||
* Trait HasHttpRequests. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
trait HasHttpRequests |
||||
{ |
||||
use ResponseCastable; |
||||
|
||||
/** |
||||
* @var \GuzzleHttp\ClientInterface |
||||
*/ |
||||
protected $httpClient; |
||||
|
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $middlewares = []; |
||||
|
||||
/** |
||||
* @var \GuzzleHttp\HandlerStack |
||||
*/ |
||||
protected $handlerStack; |
||||
|
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected static $defaults = [ |
||||
'curl' => [ |
||||
CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4, |
||||
], |
||||
]; |
||||
|
||||
/** |
||||
* Set guzzle default settings. |
||||
* |
||||
* @param array $defaults |
||||
*/ |
||||
public static function setDefaultOptions($defaults = []) |
||||
{ |
||||
self::$defaults = $defaults; |
||||
} |
||||
|
||||
/** |
||||
* Return current guzzle default settings. |
||||
* |
||||
* @return array |
||||
*/ |
||||
public static function getDefaultOptions(): array |
||||
{ |
||||
return self::$defaults; |
||||
} |
||||
|
||||
/** |
||||
* Set GuzzleHttp\Client. |
||||
* |
||||
* @param \GuzzleHttp\ClientInterface $httpClient |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function setHttpClient(ClientInterface $httpClient) |
||||
{ |
||||
$this->httpClient = $httpClient; |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Return GuzzleHttp\ClientInterface instance. |
||||
* |
||||
* @return ClientInterface |
||||
*/ |
||||
public function getHttpClient(): ClientInterface |
||||
{ |
||||
if (!($this->httpClient instanceof ClientInterface)) { |
||||
if (property_exists($this, 'app') && $this->app['http_client']) { |
||||
$this->httpClient = $this->app['http_client']; |
||||
} else { |
||||
$this->httpClient = new Client(['handler' => HandlerStack::create($this->getGuzzleHandler())]); |
||||
} |
||||
} |
||||
|
||||
return $this->httpClient; |
||||
} |
||||
|
||||
/** |
||||
* Add a middleware. |
||||
* |
||||
* @param callable $middleware |
||||
* @param string $name |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function pushMiddleware(callable $middleware, string $name = null) |
||||
{ |
||||
if (!is_null($name)) { |
||||
$this->middlewares[$name] = $middleware; |
||||
} else { |
||||
array_push($this->middlewares, $middleware); |
||||
} |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Return all middlewares. |
||||
* |
||||
* @return array |
||||
*/ |
||||
public function getMiddlewares(): array |
||||
{ |
||||
return $this->middlewares; |
||||
} |
||||
|
||||
/** |
||||
* Make a request. |
||||
* |
||||
* @param string $url |
||||
* @param string $method |
||||
* @param array $options |
||||
* |
||||
* @return \Psr\Http\Message\ResponseInterface |
||||
* |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function request($url, $method = 'GET', $options = []): ResponseInterface |
||||
{ |
||||
$method = strtoupper($method); |
||||
|
||||
$options = array_merge(self::$defaults, $options, ['handler' => $this->getHandlerStack()]); |
||||
|
||||
$options = $this->fixJsonIssue($options); |
||||
|
||||
if (property_exists($this, 'baseUri') && !is_null($this->baseUri)) { |
||||
$options['base_uri'] = $this->baseUri; |
||||
} |
||||
|
||||
$response = $this->getHttpClient()->request($method, $url, $options); |
||||
$response->getBody()->rewind(); |
||||
|
||||
return $response; |
||||
} |
||||
|
||||
/** |
||||
* @param \GuzzleHttp\HandlerStack $handlerStack |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function setHandlerStack(HandlerStack $handlerStack) |
||||
{ |
||||
$this->handlerStack = $handlerStack; |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Build a handler stack. |
||||
* |
||||
* @return \GuzzleHttp\HandlerStack |
||||
*/ |
||||
public function getHandlerStack(): HandlerStack |
||||
{ |
||||
if ($this->handlerStack) { |
||||
return $this->handlerStack; |
||||
} |
||||
|
||||
$this->handlerStack = HandlerStack::create($this->getGuzzleHandler()); |
||||
|
||||
foreach ($this->middlewares as $name => $middleware) { |
||||
$this->handlerStack->push($middleware, $name); |
||||
} |
||||
|
||||
return $this->handlerStack; |
||||
} |
||||
|
||||
/** |
||||
* @param array $options |
||||
* |
||||
* @return array |
||||
*/ |
||||
protected function fixJsonIssue(array $options): array |
||||
{ |
||||
if (isset($options['json']) && is_array($options['json'])) { |
||||
$options['headers'] = array_merge($options['headers'] ?? [], ['Content-Type' => 'application/json']); |
||||
|
||||
if (empty($options['json'])) { |
||||
$options['body'] = \GuzzleHttp\json_encode($options['json'], JSON_FORCE_OBJECT); |
||||
} else { |
||||
$options['body'] = \GuzzleHttp\json_encode($options['json'], JSON_UNESCAPED_UNICODE); |
||||
} |
||||
|
||||
unset($options['json']); |
||||
} |
||||
|
||||
return $options; |
||||
} |
||||
|
||||
/** |
||||
* Get guzzle handler. |
||||
* |
||||
* @return callable |
||||
*/ |
||||
protected function getGuzzleHandler() |
||||
{ |
||||
if (property_exists($this, 'app') && isset($this->app['guzzle_handler'])) { |
||||
return is_string($handler = $this->app->raw('guzzle_handler')) |
||||
? new $handler() |
||||
: $handler; |
||||
} |
||||
|
||||
return \GuzzleHttp\choose_handler(); |
||||
} |
||||
} |
@ -0,0 +1,105 @@
@@ -0,0 +1,105 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Traits; |
||||
|
||||
use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; |
||||
use EasyWeChat\Kernel\ServiceContainer; |
||||
use Psr\Cache\CacheItemPoolInterface; |
||||
use Psr\SimpleCache\CacheInterface as SimpleCacheInterface; |
||||
use Symfony\Component\Cache\Adapter\FilesystemAdapter; |
||||
use Symfony\Component\Cache\Psr16Cache; |
||||
use Symfony\Component\Cache\Simple\FilesystemCache; |
||||
|
||||
/** |
||||
* Trait InteractsWithCache. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
trait InteractsWithCache |
||||
{ |
||||
/** |
||||
* @var \Psr\SimpleCache\CacheInterface |
||||
*/ |
||||
protected $cache; |
||||
|
||||
/** |
||||
* Get cache instance. |
||||
* |
||||
* @return \Psr\SimpleCache\CacheInterface |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
*/ |
||||
public function getCache() |
||||
{ |
||||
if ($this->cache) { |
||||
return $this->cache; |
||||
} |
||||
|
||||
if (property_exists($this, 'app') && $this->app instanceof ServiceContainer && isset($this->app['cache'])) { |
||||
$this->setCache($this->app['cache']); |
||||
|
||||
// Fix PHPStan error |
||||
assert($this->cache instanceof \Psr\SimpleCache\CacheInterface); |
||||
|
||||
return $this->cache; |
||||
} |
||||
|
||||
return $this->cache = $this->createDefaultCache(); |
||||
} |
||||
|
||||
/** |
||||
* Set cache instance. |
||||
* |
||||
* @param \Psr\SimpleCache\CacheInterface|\Psr\Cache\CacheItemPoolInterface $cache |
||||
* |
||||
* @return $this |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
*/ |
||||
public function setCache($cache) |
||||
{ |
||||
if (empty(\array_intersect([SimpleCacheInterface::class, CacheItemPoolInterface::class], \class_implements($cache)))) { |
||||
throw new InvalidArgumentException(\sprintf('The cache instance must implements %s or %s interface.', SimpleCacheInterface::class, CacheItemPoolInterface::class)); |
||||
} |
||||
|
||||
if ($cache instanceof CacheItemPoolInterface) { |
||||
if (!$this->isSymfony43OrHigher()) { |
||||
throw new InvalidArgumentException(sprintf('The cache instance must implements %s', SimpleCacheInterface::class)); |
||||
} |
||||
$cache = new Psr16Cache($cache); |
||||
} |
||||
|
||||
$this->cache = $cache; |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* @return \Psr\SimpleCache\CacheInterface |
||||
*/ |
||||
protected function createDefaultCache() |
||||
{ |
||||
if ($this->isSymfony43OrHigher()) { |
||||
return new Psr16Cache(new FilesystemAdapter('easywechat', 1500)); |
||||
} |
||||
|
||||
return new FilesystemCache(); |
||||
} |
||||
|
||||
/** |
||||
* @return bool |
||||
*/ |
||||
protected function isSymfony43OrHigher(): bool |
||||
{ |
||||
return \class_exists('Symfony\Component\Cache\Psr16Cache'); |
||||
} |
||||
} |
@ -0,0 +1,285 @@
@@ -0,0 +1,285 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Traits; |
||||
|
||||
use EasyWeChat\Kernel\Clauses\Clause; |
||||
use EasyWeChat\Kernel\Contracts\EventHandlerInterface; |
||||
use EasyWeChat\Kernel\Decorators\FinallyResult; |
||||
use EasyWeChat\Kernel\Decorators\TerminateResult; |
||||
use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; |
||||
use EasyWeChat\Kernel\ServiceContainer; |
||||
|
||||
/** |
||||
* Trait Observable. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
trait Observable |
||||
{ |
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $handlers = []; |
||||
|
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $clauses = []; |
||||
|
||||
/** |
||||
* @param \Closure|EventHandlerInterface|callable|string $handler |
||||
* @param \Closure|EventHandlerInterface|callable|string $condition |
||||
* |
||||
* @return \EasyWeChat\Kernel\Clauses\Clause |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \ReflectionException |
||||
*/ |
||||
public function push($handler, $condition = '*') |
||||
{ |
||||
list($handler, $condition) = $this->resolveHandlerAndCondition($handler, $condition); |
||||
|
||||
if (!isset($this->handlers[$condition])) { |
||||
$this->handlers[$condition] = []; |
||||
} |
||||
|
||||
array_push($this->handlers[$condition], $handler); |
||||
|
||||
return $this->newClause($handler); |
||||
} |
||||
|
||||
/** |
||||
* @param array $handlers |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function setHandlers(array $handlers = []) |
||||
{ |
||||
$this->handlers = $handlers; |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* @param \Closure|EventHandlerInterface|string $handler |
||||
* @param \Closure|EventHandlerInterface|string $condition |
||||
* |
||||
* @return \EasyWeChat\Kernel\Clauses\Clause |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \ReflectionException |
||||
*/ |
||||
public function unshift($handler, $condition = '*') |
||||
{ |
||||
list($handler, $condition) = $this->resolveHandlerAndCondition($handler, $condition); |
||||
|
||||
if (!isset($this->handlers[$condition])) { |
||||
$this->handlers[$condition] = []; |
||||
} |
||||
|
||||
array_unshift($this->handlers[$condition], $handler); |
||||
|
||||
return $this->newClause($handler); |
||||
} |
||||
|
||||
/** |
||||
* @param string $condition |
||||
* @param \Closure|EventHandlerInterface|string $handler |
||||
* |
||||
* @return \EasyWeChat\Kernel\Clauses\Clause |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \ReflectionException |
||||
*/ |
||||
public function observe($condition, $handler) |
||||
{ |
||||
return $this->push($handler, $condition); |
||||
} |
||||
|
||||
/** |
||||
* @param string $condition |
||||
* @param \Closure|EventHandlerInterface|string $handler |
||||
* |
||||
* @return \EasyWeChat\Kernel\Clauses\Clause |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \ReflectionException |
||||
*/ |
||||
public function on($condition, $handler) |
||||
{ |
||||
return $this->push($handler, $condition); |
||||
} |
||||
|
||||
/** |
||||
* @param string|int $event |
||||
* @param mixed ...$payload |
||||
* |
||||
* @return mixed|null |
||||
*/ |
||||
public function dispatch($event, $payload) |
||||
{ |
||||
return $this->notify($event, $payload); |
||||
} |
||||
|
||||
/** |
||||
* @param string|int $event |
||||
* @param mixed ...$payload |
||||
* |
||||
* @return mixed|null |
||||
*/ |
||||
public function notify($event, $payload) |
||||
{ |
||||
$result = null; |
||||
|
||||
foreach ($this->handlers as $condition => $handlers) { |
||||
if ('*' === $condition || ($condition & $event) === $event) { |
||||
foreach ($handlers as $handler) { |
||||
if ($clause = $this->clauses[$this->getHandlerHash($handler)] ?? null) { |
||||
if ($clause->intercepted($payload)) { |
||||
continue; |
||||
} |
||||
} |
||||
|
||||
$response = $this->callHandler($handler, $payload); |
||||
|
||||
switch (true) { |
||||
case $response instanceof TerminateResult: |
||||
return $response->content; |
||||
case true === $response: |
||||
continue 2; |
||||
case false === $response: |
||||
break 2; |
||||
case !empty($response) && !($result instanceof FinallyResult): |
||||
$result = $response; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
return $result instanceof FinallyResult ? $result->content : $result; |
||||
} |
||||
|
||||
/** |
||||
* @return array |
||||
*/ |
||||
public function getHandlers() |
||||
{ |
||||
return $this->handlers; |
||||
} |
||||
|
||||
/** |
||||
* @param mixed $handler |
||||
* |
||||
* @return \EasyWeChat\Kernel\Clauses\Clause |
||||
*/ |
||||
protected function newClause($handler): Clause |
||||
{ |
||||
return $this->clauses[$this->getHandlerHash($handler)] = new Clause(); |
||||
} |
||||
|
||||
/** |
||||
* @param mixed $handler |
||||
* |
||||
* @return string |
||||
*/ |
||||
protected function getHandlerHash($handler) |
||||
{ |
||||
if (is_string($handler)) { |
||||
return $handler; |
||||
} |
||||
|
||||
if (is_array($handler)) { |
||||
return is_string($handler[0]) |
||||
? $handler[0].'::'.$handler[1] |
||||
: get_class($handler[0]).$handler[1]; |
||||
} |
||||
|
||||
return spl_object_hash($handler); |
||||
} |
||||
|
||||
/** |
||||
* @param callable $handler |
||||
* @param mixed $payload |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
protected function callHandler(callable $handler, $payload) |
||||
{ |
||||
try { |
||||
return call_user_func_array($handler, [$payload]); |
||||
} catch (\Exception $e) { |
||||
if (property_exists($this, 'app') && $this->app instanceof ServiceContainer) { |
||||
$this->app['logger']->error($e->getCode().': '.$e->getMessage(), [ |
||||
'code' => $e->getCode(), |
||||
'message' => $e->getMessage(), |
||||
'file' => $e->getFile(), |
||||
'line' => $e->getLine(), |
||||
]); |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @param mixed $handler |
||||
* |
||||
* @return \Closure |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \ReflectionException |
||||
*/ |
||||
protected function makeClosure($handler) |
||||
{ |
||||
if (is_callable($handler)) { |
||||
return $handler; |
||||
} |
||||
|
||||
if (is_string($handler) && '*' !== $handler) { |
||||
if (!class_exists($handler)) { |
||||
throw new InvalidArgumentException(sprintf('Class "%s" not exists.', $handler)); |
||||
} |
||||
|
||||
if (!in_array(EventHandlerInterface::class, (new \ReflectionClass($handler))->getInterfaceNames(), true)) { |
||||
throw new InvalidArgumentException(sprintf('Class "%s" not an instance of "%s".', $handler, EventHandlerInterface::class)); |
||||
} |
||||
|
||||
return function ($payload) use ($handler) { |
||||
return (new $handler($this->app ?? null))->handle($payload); |
||||
}; |
||||
} |
||||
|
||||
if ($handler instanceof EventHandlerInterface) { |
||||
return function () use ($handler) { |
||||
return $handler->handle(...func_get_args()); |
||||
}; |
||||
} |
||||
|
||||
throw new InvalidArgumentException('No valid handler is found in arguments.'); |
||||
} |
||||
|
||||
/** |
||||
* @param mixed $handler |
||||
* @param mixed $condition |
||||
* |
||||
* @return array |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \ReflectionException |
||||
*/ |
||||
protected function resolveHandlerAndCondition($handler, $condition): array |
||||
{ |
||||
if (is_int($handler) || (is_string($handler) && !class_exists($handler))) { |
||||
list($handler, $condition) = [$condition, $handler]; |
||||
} |
||||
|
||||
return [$this->makeClosure($handler), $condition]; |
||||
} |
||||
} |
@ -0,0 +1,93 @@
@@ -0,0 +1,93 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\Kernel\Traits; |
||||
|
||||
use EasyWeChat\Kernel\Contracts\Arrayable; |
||||
use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; |
||||
use EasyWeChat\Kernel\Exceptions\InvalidConfigException; |
||||
use EasyWeChat\Kernel\Http\Response; |
||||
use EasyWeChat\Kernel\Support\Collection; |
||||
use Psr\Http\Message\ResponseInterface; |
||||
|
||||
/** |
||||
* Trait ResponseCastable. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
trait ResponseCastable |
||||
{ |
||||
/** |
||||
* @param \Psr\Http\Message\ResponseInterface $response |
||||
* @param string|null $type |
||||
* |
||||
* @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
*/ |
||||
protected function castResponseToType(ResponseInterface $response, $type = null) |
||||
{ |
||||
$response = Response::buildFromPsrResponse($response); |
||||
$response->getBody()->rewind(); |
||||
|
||||
switch ($type ?? 'array') { |
||||
case 'collection': |
||||
return $response->toCollection(); |
||||
case 'array': |
||||
return $response->toArray(); |
||||
case 'object': |
||||
return $response->toObject(); |
||||
case 'raw': |
||||
return $response; |
||||
default: |
||||
if (!is_subclass_of($type, Arrayable::class)) { |
||||
throw new InvalidConfigException(sprintf('Config key "response_type" classname must be an instanceof %s', Arrayable::class)); |
||||
} |
||||
|
||||
return new $type($response); |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* @param mixed $response |
||||
* @param string|null $type |
||||
* |
||||
* @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
*/ |
||||
protected function detectAndCastResponseToType($response, $type = null) |
||||
{ |
||||
switch (true) { |
||||
case $response instanceof ResponseInterface: |
||||
$response = Response::buildFromPsrResponse($response); |
||||
|
||||
break; |
||||
case $response instanceof Arrayable: |
||||
$response = new Response(200, [], json_encode($response->toArray())); |
||||
|
||||
break; |
||||
case ($response instanceof Collection) || is_array($response) || is_object($response): |
||||
$response = new Response(200, [], json_encode($response)); |
||||
|
||||
break; |
||||
case is_scalar($response): |
||||
$response = new Response(200, [], (string) $response); |
||||
|
||||
break; |
||||
default: |
||||
throw new InvalidArgumentException(sprintf('Unsupported response type "%s"', gettype($response))); |
||||
} |
||||
|
||||
return $this->castResponseToType($response, $type); |
||||
} |
||||
} |
@ -0,0 +1,173 @@
@@ -0,0 +1,173 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\MicroMerchant; |
||||
|
||||
use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; |
||||
use EasyWeChat\Kernel\ServiceContainer; |
||||
use EasyWeChat\Kernel\Support; |
||||
use EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidSignException; |
||||
|
||||
/** |
||||
* Class Application. |
||||
* |
||||
* @author liuml <liumenglei0211@gmail.com> |
||||
* |
||||
* @property \EasyWeChat\MicroMerchant\Certficates\Client $certficates |
||||
* @property \EasyWeChat\MicroMerchant\Material\Client $material |
||||
* @property \EasyWeChat\MicroMerchant\MerchantConfig\Client $merchantConfig |
||||
* @property \EasyWeChat\MicroMerchant\Withdraw\Client $withdraw |
||||
* @property \EasyWeChat\MicroMerchant\Media\Client $media |
||||
* |
||||
* @method mixed submitApplication(array $params) |
||||
* @method mixed getStatus(string $applymentId, string $businessCode = '') |
||||
* @method mixed upgrade(array $params) |
||||
* @method mixed getUpgradeStatus(string $subMchId = '') |
||||
*/ |
||||
class Application extends ServiceContainer |
||||
{ |
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $providers = [ |
||||
// Base services |
||||
Base\ServiceProvider::class, |
||||
Certficates\ServiceProvider::class, |
||||
MerchantConfig\ServiceProvider::class, |
||||
Material\ServiceProvider::class, |
||||
Withdraw\ServiceProvider::class, |
||||
Media\ServiceProvider::class, |
||||
]; |
||||
|
||||
/** |
||||
* @var array |
||||
*/ |
||||
protected $defaultConfig = [ |
||||
'http' => [ |
||||
'base_uri' => 'https://api.mch.weixin.qq.com/', |
||||
], |
||||
'log' => [ |
||||
'default' => 'dev', // 默认使用的 channel,生产环境可以改为下面的 prod |
||||
'channels' => [ |
||||
// 测试环境 |
||||
'dev' => [ |
||||
'driver' => 'single', |
||||
'path' => '/tmp/easywechat.log', |
||||
'level' => 'debug', |
||||
], |
||||
// 生产环境 |
||||
'prod' => [ |
||||
'driver' => 'daily', |
||||
'path' => '/tmp/easywechat.log', |
||||
'level' => 'info', |
||||
], |
||||
], |
||||
], |
||||
]; |
||||
|
||||
/** |
||||
* @return string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
*/ |
||||
public function getKey() |
||||
{ |
||||
$key = $this['config']->key; |
||||
|
||||
if (empty($key)) { |
||||
throw new InvalidArgumentException('config key connot be empty.'); |
||||
} |
||||
|
||||
if (32 !== strlen($key)) { |
||||
throw new InvalidArgumentException(sprintf("'%s' should be 32 chars length.", $key)); |
||||
} |
||||
|
||||
return $key; |
||||
} |
||||
|
||||
/** |
||||
* set sub-mch-id and appid. |
||||
* |
||||
* @param string $subMchId Identification Number of Small and Micro Businessmen Reported by Service Providers |
||||
* @param string $appId Public Account ID of Service Provider |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function setSubMchId(string $subMchId, string $appId = '') |
||||
{ |
||||
$this['config']->set('sub_mch_id', $subMchId); |
||||
if ($appId) { |
||||
$this['config']->set('appid', $appId); |
||||
} |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* setCertificate. |
||||
* |
||||
* @param string $certificate |
||||
* @param string $serialNo |
||||
* |
||||
* @return $this |
||||
*/ |
||||
public function setCertificate(string $certificate, string $serialNo) |
||||
{ |
||||
$this['config']->set('certificate', $certificate); |
||||
$this['config']->set('serial_no', $serialNo); |
||||
|
||||
return $this; |
||||
} |
||||
|
||||
/** |
||||
* Returning true indicates that the verification is successful, |
||||
* returning false indicates that the signature field does not exist or is empty, |
||||
* and if the signature verification is wrong, the InvalidSignException will be thrown directly. |
||||
* |
||||
* @param array $data |
||||
* |
||||
* @return bool |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidSignException |
||||
*/ |
||||
public function verifySignature(array $data) |
||||
{ |
||||
if (!isset($data['sign']) || empty($data['sign'])) { |
||||
return false; |
||||
} |
||||
|
||||
$sign = $data['sign']; |
||||
unset($data['sign']); |
||||
|
||||
$signType = strlen($sign) > 32 ? 'HMAC-SHA256' : 'MD5'; |
||||
$secretKey = $this->getKey(); |
||||
|
||||
$encryptMethod = Support\get_encrypt_method($signType, $secretKey); |
||||
|
||||
if (Support\generate_sign($data, $secretKey, $encryptMethod) === $sign) { |
||||
return true; |
||||
} |
||||
|
||||
throw new InvalidSignException('return value signature verification error'); |
||||
} |
||||
|
||||
/** |
||||
* @param string $name |
||||
* @param array $arguments |
||||
* |
||||
* @return mixed |
||||
*/ |
||||
public function __call($name, $arguments) |
||||
{ |
||||
return call_user_func_array([$this['base'], $name], $arguments); |
||||
} |
||||
} |
@ -0,0 +1,126 @@
@@ -0,0 +1,126 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\MicroMerchant\Base; |
||||
|
||||
use EasyWeChat\MicroMerchant\Kernel\BaseClient; |
||||
|
||||
/** |
||||
* Class Client. |
||||
* |
||||
* @author liuml <liumenglei0211@163.com> |
||||
* @DateTime 2019-05-30 14:19 |
||||
*/ |
||||
class Client extends BaseClient |
||||
{ |
||||
/** |
||||
* apply to settle in to become a small micro merchant. |
||||
* |
||||
* @param array $params |
||||
* |
||||
* @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\EncryptException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function submitApplication(array $params) |
||||
{ |
||||
$params = $this->processParams(array_merge($params, [ |
||||
'version' => '3.0', |
||||
'cert_sn' => '', |
||||
'sign_type' => 'HMAC-SHA256', |
||||
'nonce_str' => uniqid('micro'), |
||||
])); |
||||
|
||||
return $this->safeRequest('applyment/micro/submit', $params); |
||||
} |
||||
|
||||
/** |
||||
* query application status. |
||||
* |
||||
* @param string $applymentId |
||||
* @param string $businessCode |
||||
* |
||||
* @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function getStatus(string $applymentId, string $businessCode = '') |
||||
{ |
||||
if (!empty($applymentId)) { |
||||
$params = [ |
||||
'applyment_id' => $applymentId, |
||||
]; |
||||
} else { |
||||
$params = [ |
||||
'business_code' => $businessCode, |
||||
]; |
||||
} |
||||
|
||||
$params = array_merge($params, [ |
||||
'version' => '1.0', |
||||
'sign_type' => 'HMAC-SHA256', |
||||
'nonce_str' => uniqid('micro'), |
||||
]); |
||||
|
||||
return $this->safeRequest('applyment/micro/getstate', $params); |
||||
} |
||||
|
||||
/** |
||||
* merchant upgrade api. |
||||
* |
||||
* @param array $params |
||||
* |
||||
* @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\EncryptException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function upgrade(array $params) |
||||
{ |
||||
$params['sub_mch_id'] = $params['sub_mch_id'] ?? $this->app['config']->sub_mch_id; |
||||
$params = $this->processParams(array_merge($params, [ |
||||
'version' => '1.0', |
||||
'cert_sn' => '', |
||||
'sign_type' => 'HMAC-SHA256', |
||||
'nonce_str' => uniqid('micro'), |
||||
])); |
||||
|
||||
return $this->safeRequest('applyment/micro/submitupgrade', $params); |
||||
} |
||||
|
||||
/** |
||||
* get upgrade status. |
||||
* |
||||
* @param string $subMchId |
||||
* |
||||
* @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function getUpgradeStatus(string $subMchId = '') |
||||
{ |
||||
return $this->safeRequest('applyment/micro/getupgradestate', [ |
||||
'version' => '1.0', |
||||
'sign_type' => 'HMAC-SHA256', |
||||
'sub_mch_id' => $subMchId ?: $this->app['config']->sub_mch_id, |
||||
'nonce_str' => uniqid('micro'), |
||||
]); |
||||
} |
||||
} |
@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\MicroMerchant\Base; |
||||
|
||||
use Pimple\Container; |
||||
use Pimple\ServiceProviderInterface; |
||||
|
||||
/** |
||||
* Class ServiceProvider. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class ServiceProvider implements ServiceProviderInterface |
||||
{ |
||||
/** |
||||
* {@inheritdoc}. |
||||
*/ |
||||
public function register(Container $app) |
||||
{ |
||||
$app['base'] = function ($app) { |
||||
return new Client($app); |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,93 @@
@@ -0,0 +1,93 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\MicroMerchant\Certficates; |
||||
|
||||
use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; |
||||
use EasyWeChat\MicroMerchant\Kernel\BaseClient; |
||||
use EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidExtensionException; |
||||
|
||||
/** |
||||
* Class Client. |
||||
* |
||||
* @author liuml <liumenglei0211@163.com> |
||||
* @DateTime 2019-05-30 14:19 |
||||
*/ |
||||
class Client extends BaseClient |
||||
{ |
||||
/** |
||||
* get certficates. |
||||
* |
||||
* @param bool $returnRaw |
||||
* |
||||
* @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidExtensionException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function get(bool $returnRaw = false) |
||||
{ |
||||
$params = [ |
||||
'sign_type' => 'HMAC-SHA256', |
||||
'nonce_str' => uniqid('micro'), |
||||
]; |
||||
|
||||
if (true === $returnRaw) { |
||||
return $this->requestRaw('risk/getcertficates', $params); |
||||
} |
||||
/** @var array $response */ |
||||
$response = $this->requestArray('risk/getcertficates', $params); |
||||
|
||||
if ('SUCCESS' !== $response['return_code']) { |
||||
throw new InvalidArgumentException(sprintf('Failed to get certificate. return_code_msg: "%s" .', $response['return_code'].'('.$response['return_msg'].')')); |
||||
} |
||||
if ('SUCCESS' !== $response['result_code']) { |
||||
throw new InvalidArgumentException(sprintf('Failed to get certificate. result_err_code_desc: "%s" .', $response['result_code'].'('.$response['err_code'].'['.$response['err_code_desc'].'])')); |
||||
} |
||||
$certificates = \GuzzleHttp\json_decode($response['certificates'], true)['data'][0]; |
||||
$ciphertext = $this->decrypt($certificates['encrypt_certificate']); |
||||
unset($certificates['encrypt_certificate']); |
||||
$certificates['certificates'] = $ciphertext; |
||||
|
||||
return $certificates; |
||||
} |
||||
|
||||
/** |
||||
* decrypt ciphertext. |
||||
* |
||||
* @param array $encryptCertificate |
||||
* |
||||
* @return string |
||||
* |
||||
* @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidExtensionException |
||||
*/ |
||||
public function decrypt(array $encryptCertificate) |
||||
{ |
||||
if (false === extension_loaded('sodium')) { |
||||
throw new InvalidExtensionException('sodium extension is not installed,Reference link https://php.net/manual/zh/book.sodium.php'); |
||||
} |
||||
|
||||
if (false === sodium_crypto_aead_aes256gcm_is_available()) { |
||||
throw new InvalidExtensionException('aes256gcm is not currently supported'); |
||||
} |
||||
|
||||
// sodium_crypto_aead_aes256gcm_decrypt function needs to open libsodium extension. |
||||
// https://www.php.net/manual/zh/function.sodium-crypto-aead-aes256gcm-decrypt.php |
||||
return sodium_crypto_aead_aes256gcm_decrypt( |
||||
base64_decode($encryptCertificate['ciphertext'], true), |
||||
$encryptCertificate['associated_data'], |
||||
$encryptCertificate['nonce'], |
||||
$this->app['config']->apiv3_key |
||||
); |
||||
} |
||||
} |
@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\MicroMerchant\Certficates; |
||||
|
||||
use Pimple\Container; |
||||
use Pimple\ServiceProviderInterface; |
||||
|
||||
/** |
||||
* Class ServiceProvider. |
||||
* |
||||
* @author overtrue <i@overtrue.me> |
||||
*/ |
||||
class ServiceProvider implements ServiceProviderInterface |
||||
{ |
||||
/** |
||||
* {@inheritdoc}. |
||||
*/ |
||||
public function register(Container $app) |
||||
{ |
||||
$app['certficates'] = function ($app) { |
||||
return new Client($app); |
||||
}; |
||||
} |
||||
} |
@ -0,0 +1,256 @@
@@ -0,0 +1,256 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\MicroMerchant\Kernel; |
||||
|
||||
use EasyWeChat\Kernel\Exceptions\InvalidArgumentException; |
||||
use EasyWeChat\Kernel\Support; |
||||
use EasyWeChat\MicroMerchant\Application; |
||||
use EasyWeChat\MicroMerchant\Kernel\Exceptions\EncryptException; |
||||
use EasyWeChat\Payment\Kernel\BaseClient as PaymentBaseClient; |
||||
|
||||
/** |
||||
* Class BaseClient. |
||||
* |
||||
* @author liuml <liumenglei0211@163.com> |
||||
* @DateTime 2019-07-10 12:06 |
||||
*/ |
||||
class BaseClient extends PaymentBaseClient |
||||
{ |
||||
/** |
||||
* @var string |
||||
*/ |
||||
protected $certificates; |
||||
|
||||
/** |
||||
* BaseClient constructor. |
||||
* |
||||
* @param \EasyWeChat\MicroMerchant\Application $app |
||||
*/ |
||||
public function __construct(Application $app) |
||||
{ |
||||
$this->app = $app; |
||||
|
||||
$this->setHttpClient($this->app['http_client']); |
||||
} |
||||
|
||||
/** |
||||
* Extra request params. |
||||
* |
||||
* @return array |
||||
*/ |
||||
protected function prepends() |
||||
{ |
||||
return []; |
||||
} |
||||
|
||||
/** |
||||
* httpUpload. |
||||
* |
||||
* @param string $url |
||||
* @param array $files |
||||
* @param array $form |
||||
* @param array $query |
||||
* @param bool $returnResponse |
||||
* |
||||
* @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidSignException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
public function httpUpload(string $url, array $files = [], array $form = [], array $query = [], $returnResponse = false) |
||||
{ |
||||
$multipart = []; |
||||
|
||||
foreach ($files as $name => $path) { |
||||
$multipart[] = [ |
||||
'name' => $name, |
||||
'contents' => fopen($path, 'r'), |
||||
]; |
||||
} |
||||
|
||||
$base = [ |
||||
'mch_id' => $this->app['config']['mch_id'], |
||||
]; |
||||
|
||||
$form = array_merge($base, $form); |
||||
|
||||
$form['sign'] = $this->getSign($form); |
||||
|
||||
foreach ($form as $name => $contents) { |
||||
$multipart[] = compact('name', 'contents'); |
||||
} |
||||
|
||||
$options = [ |
||||
'query' => $query, |
||||
'multipart' => $multipart, |
||||
'connect_timeout' => 30, |
||||
'timeout' => 30, |
||||
'read_timeout' => 30, |
||||
'cert' => $this->app['config']->get('cert_path'), |
||||
'ssl_key' => $this->app['config']->get('key_path'), |
||||
]; |
||||
|
||||
$this->pushMiddleware($this->logMiddleware(), 'log'); |
||||
|
||||
$response = $this->performRequest($url, 'POST', $options); |
||||
|
||||
$result = $returnResponse ? $response : $this->castResponseToType($response, $this->app->config->get('response_type')); |
||||
// auto verify signature |
||||
if ($returnResponse || 'array' !== ($this->app->config->get('response_type') ?? 'array')) { |
||||
$this->app->verifySignature($this->castResponseToType($response, 'array')); |
||||
} else { |
||||
$this->app->verifySignature($result); |
||||
} |
||||
|
||||
return $result; |
||||
} |
||||
|
||||
/** |
||||
* request. |
||||
* |
||||
* @param string $endpoint |
||||
* @param array $params |
||||
* @param string $method |
||||
* @param array $options |
||||
* @param bool $returnResponse |
||||
* |
||||
* @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException |
||||
* @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\InvalidSignException |
||||
* @throws \GuzzleHttp\Exception\GuzzleException |
||||
*/ |
||||
protected function request(string $endpoint, array $params = [], $method = 'post', array $options = [], $returnResponse = false) |
||||
{ |
||||
$base = [ |
||||
'mch_id' => $this->app['config']['mch_id'], |
||||
]; |
||||
|
||||
$params = array_merge($base, $this->prepends(), $params); |
||||
$params['sign'] = $this->getSign($params); |
||||
$options = array_merge([ |
||||
'body' => Support\XML::build($params), |
||||
], $options); |
||||
|
||||
$this->pushMiddleware($this->logMiddleware(), 'log'); |
||||
$response = $this->performRequest($endpoint, $method, $options); |
||||
$result = $returnResponse ? $response : $this->castResponseToType($response, $this->app->config->get('response_type')); |
||||
// auto verify signature |
||||
if ($returnResponse || 'array' !== ($this->app->config->get('response_type') ?? 'array')) { |
||||
$this->app->verifySignature($this->castResponseToType($response, 'array')); |
||||
} else { |
||||
$this->app->verifySignature($result); |
||||
} |
||||
|
||||
return $result; |
||||
} |
||||
|
||||
/** |
||||
* processing parameters contain fields that require sensitive information encryption. |
||||
* |
||||
* @param array $params |
||||
* |
||||
* @return array |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\EncryptException |
||||
*/ |
||||
protected function processParams(array $params) |
||||
{ |
||||
$serial_no = $this->app['config']->get('serial_no'); |
||||
if (null === $serial_no) { |
||||
throw new InvalidArgumentException('config serial_no connot be empty.'); |
||||
} |
||||
|
||||
$params['cert_sn'] = $serial_no; |
||||
$sensitive_fields = $this->getSensitiveFieldsName(); |
||||
foreach ($params as $k => $v) { |
||||
if (in_array($k, $sensitive_fields, true)) { |
||||
$params[$k] = $this->encryptSensitiveInformation($v); |
||||
} |
||||
} |
||||
|
||||
return $params; |
||||
} |
||||
|
||||
/** |
||||
* To id card, mobile phone number and other fields sensitive information encryption. |
||||
* |
||||
* @param string $string |
||||
* |
||||
* @return string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
* @throws \EasyWeChat\MicroMerchant\Kernel\Exceptions\EncryptException |
||||
*/ |
||||
protected function encryptSensitiveInformation(string $string) |
||||
{ |
||||
$certificates = $this->app['config']->get('certificate'); |
||||
if (null === $certificates) { |
||||
throw new InvalidArgumentException('config certificate connot be empty.'); |
||||
} |
||||
|
||||
$encrypted = ''; |
||||
$publicKeyResource = openssl_get_publickey($certificates); |
||||
$f = openssl_public_encrypt($string, $encrypted, $publicKeyResource); |
||||
openssl_free_key($publicKeyResource); |
||||
if ($f) { |
||||
return base64_encode($encrypted); |
||||
} |
||||
|
||||
throw new EncryptException('Encryption of sensitive information failed'); |
||||
} |
||||
|
||||
/** |
||||
* get sensitive fields name. |
||||
* |
||||
* @return array |
||||
*/ |
||||
protected function getSensitiveFieldsName() |
||||
{ |
||||
return [ |
||||
'id_card_name', |
||||
'id_card_number', |
||||
'account_name', |
||||
'account_number', |
||||
'contact', |
||||
'contact_phone', |
||||
'contact_email', |
||||
'legal_person', |
||||
'mobile_phone', |
||||
'email', |
||||
]; |
||||
} |
||||
|
||||
/** |
||||
* getSign. |
||||
* |
||||
* @param array $params |
||||
* |
||||
* @return string |
||||
* |
||||
* @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException |
||||
*/ |
||||
protected function getSign(array $params) |
||||
{ |
||||
$params = array_filter($params); |
||||
|
||||
$key = $this->app->getKey(); |
||||
|
||||
$encryptMethod = Support\get_encrypt_method(Support\Arr::get($params, 'sign_type', 'MD5'), $key); |
||||
|
||||
return Support\generate_sign($params, $key, $encryptMethod); |
||||
} |
||||
} |
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\MicroMerchant\Kernel\Exceptions; |
||||
|
||||
use EasyWeChat\Kernel\Exceptions\Exception; |
||||
|
||||
/** |
||||
* Class EncryptException. |
||||
* |
||||
* @author liuml <liumenglei0211@163.com> |
||||
*/ |
||||
class EncryptException extends Exception |
||||
{ |
||||
} |
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\MicroMerchant\Kernel\Exceptions; |
||||
|
||||
use EasyWeChat\Kernel\Exceptions\Exception; |
||||
|
||||
/** |
||||
* Class InvalidExtensionException. |
||||
* |
||||
* @author liuml <liumenglei0211@163.com> |
||||
*/ |
||||
class InvalidExtensionException extends Exception |
||||
{ |
||||
} |
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
<?php |
||||
|
||||
/* |
||||
* This file is part of the overtrue/wechat. |
||||
* |
||||
* (c) overtrue <i@overtrue.me> |
||||
* |
||||
* This source file is subject to the MIT license that is bundled |
||||
* with this source code in the file LICENSE. |
||||
*/ |
||||
|
||||
namespace EasyWeChat\MicroMerchant\Kernel\Exceptions; |
||||
|
||||
use EasyWeChat\Kernel\Exceptions\Exception; |
||||
|
||||
/** |
||||
* Class InvalidSignException. |
||||
* |
||||
* @author liuml <liumenglei0211@163.com> |
||||
*/ |
||||
class InvalidSignException extends Exception |
||||
{ |
||||
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue