├── .gitignore
├── .php_cs
├── .travis.yml
├── LICENSE
├── README.md
├── composer.json
├── phpunit.xml
├── src
├── Commands
│ ├── ExtensionsCommand.php
│ └── Provider.php
├── Contracts
│ └── Encrypter.php
├── Delegation
│ ├── DelegationOptions.php
│ ├── DelegationTo.php
│ └── Hydrate.php
├── EasyWeChat.php
├── Encryption
│ └── DefaultEncrypter.php
├── Exceptions
│ ├── DecryptException.php
│ ├── DelegationException.php
│ └── EncryptException.php
├── Extension.php
├── Http
│ ├── DelegationResponse.php
│ └── Response.php
├── Laravel
│ ├── Http
│ │ └── Controllers
│ │ │ └── DelegatesController.php
│ ├── ServiceProvider.php
│ ├── config.php
│ └── routes.php
├── ManifestManager.php
├── Plugin.php
└── Traits
│ ├── MakesHttpRequests.php
│ └── WithAggregator.php
└── tests
└── ManifestManagerTest.php
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea/
2 | /vendor
3 | composer.lock
4 | extensions.php
5 | .php_cs.cache
6 |
--------------------------------------------------------------------------------
/.php_cs:
--------------------------------------------------------------------------------
1 |
7 |
8 | This source file is subject to the MIT license that is bundled
9 | with this source code in the file LICENSE.
10 | EOF;
11 |
12 | return PhpCsFixer\Config::create()
13 | ->setRiskyAllowed(true)
14 | ->setRules([
15 | '@Symfony' => true,
16 | 'header_comment' => ['header' => $header],
17 | 'declare_strict_types' => true,
18 | 'ordered_imports' => true,
19 | 'strict_comparison' => true,
20 | 'no_empty_comment' => false,
21 | 'yoda_style' => false,
22 | ])
23 | ->setFinder(
24 | PhpCsFixer\Finder::create()
25 | ->exclude('vendor')
26 | ->notPath('src/Laravel/config.php', 'src/Laravel/routes.php')
27 | ->in(__DIR__)
28 | )
29 | ;
30 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 7.0
5 | - 7.1
6 | - 7.2
7 | - 7.3
8 |
9 | install:
10 | - travis_retry composer install --no-interaction --no-suggest
11 |
12 | script: ./vendor/bin/phpunit
13 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 张铭阳
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
EasyWeChat Composer Plugin
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | Usage
14 | ---
15 |
16 | Set the `type` to be `easywechat-extension` in your package composer.json file:
17 |
18 | ```json
19 | {
20 | "name": "your/package",
21 | "type": "easywechat-extension"
22 | }
23 | ```
24 |
25 | Specify server observer classes in the extra section:
26 |
27 | ```json
28 | {
29 | "name": "your/package",
30 | "type": "easywechat-extension",
31 | "extra": {
32 | "observers": [
33 | "Acme\\Observers\\Handler"
34 | ]
35 | }
36 | }
37 | ```
38 |
39 | Examples
40 | ---
41 | * [easywechat-composer/open-platform-testcase](https://github.com/mingyoung/open-platform-testcase)
42 |
43 | Server Delegation
44 | ---
45 |
46 | > 目前仅支持 Laravel
47 |
48 | 1. 在 `config/app.php` 中添加 `EasyWeChatComposer\Laravel\ServiceProvider::class`
49 |
50 | 2. 在**本地项目**的 `.env` 文件中添加如下配置:
51 |
52 | ```
53 | EASYWECHAT_DELEGATION=true # false 则不启用
54 | EASYWECHAT_DELEGATION_HOST=https://example.com # 线上域名
55 | ```
56 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "easywechat-composer/easywechat-composer",
3 | "description": "The composer plugin for EasyWeChat",
4 | "type": "composer-plugin",
5 | "license": "MIT",
6 | "authors": [
7 | {
8 | "name": "张铭阳",
9 | "email": "mingyoungcheung@gmail.com"
10 | }
11 | ],
12 | "require": {
13 | "php": ">=7.0",
14 | "composer-plugin-api": "^1.0 || ^2.0"
15 | },
16 | "require-dev": {
17 | "composer/composer": "^1.0 || ^2.0",
18 | "phpunit/phpunit": "^6.5 || ^7.0"
19 | },
20 | "autoload": {
21 | "psr-4": {
22 | "EasyWeChatComposer\\": "src/"
23 | }
24 | },
25 | "autoload-dev": {
26 | "psr-4": {
27 | "EasyWeChatComposer\\Tests\\": "tests/"
28 | }
29 | },
30 | "extra": {
31 | "class": "EasyWeChatComposer\\Plugin"
32 | },
33 | "minimum-stability": "dev",
34 | "prefer-stable": true
35 | }
36 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 | tests
13 |
14 |
15 |
16 |
17 | src
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/Commands/ExtensionsCommand.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * This source file is subject to the MIT license that is bundled
11 | * with this source code in the file LICENSE.
12 | */
13 |
14 | namespace EasyWeChatComposer\Commands;
15 |
16 | use Composer\Command\BaseCommand;
17 | use Symfony\Component\Console\Helper\Table;
18 | use Symfony\Component\Console\Input\InputInterface;
19 | use Symfony\Component\Console\Output\OutputInterface;
20 |
21 | class ExtensionsCommand extends BaseCommand
22 | {
23 | /**
24 | * Configures the current command.
25 | */
26 | protected function configure()
27 | {
28 | $this->setName('easywechat:extensions')
29 | ->setDescription('Lists all installed extensions.');
30 | }
31 |
32 | /**
33 | * Executes the current command.
34 | *
35 | * @param InputInterface $input
36 | * @param OutputInterface $output
37 | */
38 | protected function execute(InputInterface $input, OutputInterface $output)
39 | {
40 | $extensions = require __DIR__.'/../../extensions.php';
41 |
42 | if (empty($extensions) || !is_array($extensions)) {
43 | return $output->writeln('No extension installed.');
44 | }
45 |
46 | $table = new Table($output);
47 | $table->setHeaders(['Name', 'Observers'])
48 | ->setRows(
49 | array_map([$this, 'getRows'], array_keys($extensions), $extensions)
50 | )->render();
51 | }
52 |
53 | /**
54 | * @param string $name
55 | * @param array $extension
56 | *
57 | * @return array
58 | */
59 | protected function getRows($name, $extension)
60 | {
61 | return [$name, implode("\n", $extension['observers'] ?? [])];
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/Commands/Provider.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * This source file is subject to the MIT license that is bundled
11 | * with this source code in the file LICENSE.
12 | */
13 |
14 | namespace EasyWeChatComposer\Commands;
15 |
16 | use Composer\Plugin\Capability\CommandProvider;
17 |
18 | class Provider implements CommandProvider
19 | {
20 | /**
21 | * Retrieves an array of commands.
22 | *
23 | * @return \Composer\Command\BaseCommand[]
24 | */
25 | public function getCommands()
26 | {
27 | return [
28 | new ExtensionsCommand(),
29 | ];
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/Contracts/Encrypter.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * This source file is subject to the MIT license that is bundled
11 | * with this source code in the file LICENSE.
12 | */
13 |
14 | namespace EasyWeChatComposer\Contracts;
15 |
16 | interface Encrypter
17 | {
18 | /**
19 | * Encrypt the given value.
20 | *
21 | * @param string $value
22 | *
23 | * @return string
24 | */
25 | public function encrypt($value);
26 |
27 | /**
28 | * Decrypt the given value.
29 | *
30 | * @param string $payload
31 | *
32 | * @return string
33 | */
34 | public function decrypt($payload);
35 | }
36 |
--------------------------------------------------------------------------------
/src/Delegation/DelegationOptions.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * This source file is subject to the MIT license that is bundled
11 | * with this source code in the file LICENSE.
12 | */
13 |
14 | namespace EasyWeChatComposer\Delegation;
15 |
16 | use EasyWeChatComposer\EasyWeChat;
17 |
18 | class DelegationOptions
19 | {
20 | /**
21 | * @var array
22 | */
23 | protected $config = [
24 | 'enabled' => false,
25 | ];
26 |
27 | /**
28 | * @return $this
29 | */
30 | public function enable()
31 | {
32 | $this->config['enabled'] = true;
33 |
34 | return $this;
35 | }
36 |
37 | /**
38 | * @return $this
39 | */
40 | public function disable()
41 | {
42 | $this->config['enabled'] = false;
43 |
44 | return $this;
45 | }
46 |
47 | /**
48 | * @param bool $ability
49 | *
50 | * @return $this
51 | */
52 | public function ability($ability)
53 | {
54 | $this->config['enabled'] = (bool) $ability;
55 |
56 | return $this;
57 | }
58 |
59 | /**
60 | * @param string $host
61 | *
62 | * @return $this
63 | */
64 | public function toHost($host)
65 | {
66 | $this->config['host'] = $host;
67 |
68 | return $this;
69 | }
70 |
71 | /**
72 | * Destructor.
73 | */
74 | public function __destruct()
75 | {
76 | EasyWeChat::mergeConfig([
77 | 'delegation' => $this->config,
78 | ]);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/src/Delegation/DelegationTo.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * This source file is subject to the MIT license that is bundled
11 | * with this source code in the file LICENSE.
12 | */
13 |
14 | namespace EasyWeChatComposer\Delegation;
15 |
16 | use EasyWeChatComposer\Traits\MakesHttpRequests;
17 |
18 | class DelegationTo
19 | {
20 | use MakesHttpRequests;
21 |
22 | /**
23 | * @var \EasyWeChat\Kernel\ServiceContainer
24 | */
25 | protected $app;
26 |
27 | /**
28 | * @var array
29 | */
30 | protected $identifiers = [];
31 |
32 | /**
33 | * @param \EasyWeChat\Kernel\ServiceContainer $app
34 | * @param string $identifier
35 | */
36 | public function __construct($app, $identifier)
37 | {
38 | $this->app = $app;
39 |
40 | $this->push($identifier);
41 | }
42 |
43 | /**
44 | * @param string $identifier
45 | */
46 | public function push($identifier)
47 | {
48 | $this->identifiers[] = $identifier;
49 | }
50 |
51 | /**
52 | * @param string $identifier
53 | *
54 | * @return $this
55 | */
56 | public function __get($identifier)
57 | {
58 | $this->push($identifier);
59 |
60 | return $this;
61 | }
62 |
63 | /**
64 | * @param string $method
65 | * @param array $arguments
66 | *
67 | * @return mixed
68 | */
69 | public function __call($method, $arguments)
70 | {
71 | $config = array_intersect_key($this->app->getConfig(), array_flip(['app_id', 'secret', 'token', 'aes_key', 'response_type', 'component_app_id', 'refresh_token']));
72 |
73 | $data = [
74 | 'config' => $config,
75 | 'application' => get_class($this->app),
76 | 'identifiers' => $this->identifiers,
77 | 'method' => $method,
78 | 'arguments' => $arguments,
79 | ];
80 |
81 | return $this->request('easywechat-composer/delegate', $data);
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/Delegation/Hydrate.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * This source file is subject to the MIT license that is bundled
11 | * with this source code in the file LICENSE.
12 | */
13 |
14 | namespace EasyWeChatComposer\Delegation;
15 |
16 | use EasyWeChat;
17 | use EasyWeChatComposer\Http\DelegationResponse;
18 |
19 | class Hydrate
20 | {
21 | /**
22 | * @var array
23 | */
24 | protected $attributes;
25 |
26 | /**
27 | * @param array $attributes
28 | */
29 | public function __construct(array $attributes)
30 | {
31 | $this->attributes = $attributes;
32 | }
33 |
34 | /**
35 | * @return array
36 | */
37 | public function handle()
38 | {
39 | $app = $this->createsApplication()->shouldntDelegate();
40 |
41 | foreach ($this->attributes['identifiers'] as $identifier) {
42 | $app = $app->$identifier;
43 | }
44 |
45 | return call_user_func_array([$app, $this->attributes['method']], $this->attributes['arguments']);
46 | }
47 |
48 | /**
49 | * @return \EasyWeChat\Kernel\ServiceContainer
50 | */
51 | protected function createsApplication()
52 | {
53 | $application = $this->attributes['application'];
54 |
55 | if ($application === EasyWeChat\OpenPlatform\Authorizer\OfficialAccount\Application::class) {
56 | return $this->createsOpenPlatformApplication('officialAccount');
57 | }
58 |
59 | if ($application === EasyWeChat\OpenPlatform\Authorizer\MiniProgram\Application::class) {
60 | return $this->createsOpenPlatformApplication('miniProgram');
61 | }
62 |
63 | return new $application($this->buildConfig($this->attributes['config']));
64 | }
65 |
66 | protected function createsOpenPlatformApplication($type)
67 | {
68 | $config = $this->attributes['config'];
69 |
70 | $authorizerAppId = $config['app_id'];
71 |
72 | $config['app_id'] = $config['component_app_id'];
73 |
74 | return EasyWeChat\Factory::openPlatform($this->buildConfig($config))->$type($authorizerAppId, $config['refresh_token']);
75 | }
76 |
77 | protected function buildConfig(array $config)
78 | {
79 | $config['response_type'] = DelegationResponse::class;
80 |
81 | return $config;
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/src/EasyWeChat.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * This source file is subject to the MIT license that is bundled
11 | * with this source code in the file LICENSE.
12 | */
13 |
14 | namespace EasyWeChatComposer;
15 |
16 | use EasyWeChatComposer\Delegation\DelegationOptions;
17 |
18 | class EasyWeChat
19 | {
20 | /**
21 | * @var array
22 | */
23 | protected static $config = [];
24 |
25 | /**
26 | * Encryption key.
27 | *
28 | * @var string
29 | */
30 | protected static $encryptionKey;
31 |
32 | /**
33 | * @param array $config
34 | */
35 | public static function mergeConfig(array $config)
36 | {
37 | static::$config = array_merge(static::$config, $config);
38 | }
39 |
40 | /**
41 | * @return array
42 | */
43 | public static function config()
44 | {
45 | return static::$config;
46 | }
47 |
48 | /**
49 | * Set encryption key.
50 | *
51 | * @param string $key
52 | *
53 | * @return static
54 | */
55 | public static function setEncryptionKey(string $key)
56 | {
57 | static::$encryptionKey = $key;
58 |
59 | return new static();
60 | }
61 |
62 | /**
63 | * Get encryption key.
64 | *
65 | * @return string
66 | */
67 | public static function getEncryptionKey(): string
68 | {
69 | return static::$encryptionKey;
70 | }
71 |
72 | /**
73 | * @return \EasyWeChatComposer\Delegation\DelegationOptions
74 | */
75 | public static function withDelegation()
76 | {
77 | return new DelegationOptions();
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/src/Encryption/DefaultEncrypter.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * This source file is subject to the MIT license that is bundled
11 | * with this source code in the file LICENSE.
12 | */
13 |
14 | namespace EasyWeChatComposer\Encryption;
15 |
16 | use EasyWeChatComposer\Contracts\Encrypter;
17 | use EasyWeChatComposer\Exceptions\DecryptException;
18 | use EasyWeChatComposer\Exceptions\EncryptException;
19 |
20 | class DefaultEncrypter implements Encrypter
21 | {
22 | /**
23 | * @var string
24 | */
25 | protected $key;
26 |
27 | /**
28 | * @var string
29 | */
30 | protected $cipher;
31 |
32 | /**
33 | * @param string $key
34 | * @param string $cipher
35 | */
36 | public function __construct($key, $cipher = 'AES-256-CBC')
37 | {
38 | $this->key = $key;
39 | $this->cipher = $cipher;
40 | }
41 |
42 | /**
43 | * Encrypt the given value.
44 | *
45 | * @param string $value
46 | *
47 | * @return string
48 | *
49 | * @throws \EasyWeChatComposer\Exceptions\EncryptException
50 | */
51 | public function encrypt($value)
52 | {
53 | $iv = random_bytes(openssl_cipher_iv_length($this->cipher));
54 |
55 | $value = openssl_encrypt($value, $this->cipher, $this->key, 0, $iv);
56 |
57 | if ($value === false) {
58 | throw new EncryptException('Could not encrypt the data.');
59 | }
60 |
61 | $iv = base64_encode($iv);
62 |
63 | return base64_encode(json_encode(compact('iv', 'value')));
64 | }
65 |
66 | /**
67 | * Decrypt the given value.
68 | *
69 | * @param string $payload
70 | *
71 | * @return string
72 | *
73 | * @throws \EasyWeChatComposer\Exceptions\DecryptException
74 | */
75 | public function decrypt($payload)
76 | {
77 | $payload = json_decode(base64_decode($payload), true);
78 |
79 | $iv = base64_decode($payload['iv']);
80 |
81 | $decrypted = openssl_decrypt($payload['value'], $this->cipher, $this->key, 0, $iv);
82 |
83 | if ($decrypted === false) {
84 | throw new DecryptException('Could not decrypt the data.');
85 | }
86 |
87 | return $decrypted;
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/src/Exceptions/DecryptException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * This source file is subject to the MIT license that is bundled
11 | * with this source code in the file LICENSE.
12 | */
13 |
14 | namespace EasyWeChatComposer\Exceptions;
15 |
16 | use Exception;
17 |
18 | class DecryptException extends Exception
19 | {
20 | //
21 | }
22 |
--------------------------------------------------------------------------------
/src/Exceptions/DelegationException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * This source file is subject to the MIT license that is bundled
11 | * with this source code in the file LICENSE.
12 | */
13 |
14 | namespace EasyWeChatComposer\Exceptions;
15 |
16 | use Exception;
17 |
18 | class DelegationException extends Exception
19 | {
20 | /**
21 | * @var string
22 | */
23 | protected $exception;
24 |
25 | /**
26 | * @param string $exception
27 | */
28 | public function setException($exception)
29 | {
30 | $this->exception = $exception;
31 |
32 | return $this;
33 | }
34 |
35 | /**
36 | * @return string
37 | */
38 | public function getException()
39 | {
40 | return $this->exception;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Exceptions/EncryptException.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * This source file is subject to the MIT license that is bundled
11 | * with this source code in the file LICENSE.
12 | */
13 |
14 | namespace EasyWeChatComposer\Exceptions;
15 |
16 | use Exception;
17 |
18 | class EncryptException extends Exception
19 | {
20 | //
21 | }
22 |
--------------------------------------------------------------------------------
/src/Extension.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * This source file is subject to the MIT license that is bundled
11 | * with this source code in the file LICENSE.
12 | */
13 |
14 | namespace EasyWeChatComposer;
15 |
16 | use EasyWeChat\Kernel\Contracts\EventHandlerInterface;
17 | use Pimple\Container;
18 | use ReflectionClass;
19 |
20 | class Extension
21 | {
22 | /**
23 | * @var \Pimple\Container
24 | */
25 | protected $app;
26 |
27 | /**
28 | * @var string
29 | */
30 | protected $manifestPath;
31 |
32 | /**
33 | * @var array|null
34 | */
35 | protected $manifest;
36 |
37 | /**
38 | * @param \Pimple\Container $app
39 | */
40 | public function __construct(Container $app)
41 | {
42 | $this->app = $app;
43 | $this->manifestPath = __DIR__.'/../extensions.php';
44 | }
45 |
46 | /**
47 | * Get observers.
48 | *
49 | * @return array
50 | */
51 | public function observers(): array
52 | {
53 | if ($this->shouldIgnore()) {
54 | return [];
55 | }
56 |
57 | $observers = [];
58 |
59 | foreach ($this->getManifest() as $name => $extra) {
60 | $observers = array_merge($observers, $extra['observers'] ?? []);
61 | }
62 |
63 | return array_map([$this, 'listObserver'], array_filter($observers, [$this, 'validateObserver']));
64 | }
65 |
66 | /**
67 | * @param mixed $observer
68 | *
69 | * @return bool
70 | */
71 | protected function isDisable($observer): bool
72 | {
73 | return in_array($observer, $this->app->config->get('disable_observers', []));
74 | }
75 |
76 | /**
77 | * Get the observers should be ignore.
78 | *
79 | * @return bool
80 | */
81 | protected function shouldIgnore(): bool
82 | {
83 | return !file_exists($this->manifestPath) || $this->isDisable('*');
84 | }
85 |
86 | /**
87 | * Validate the given observer.
88 | *
89 | * @param mixed $observer
90 | *
91 | * @return bool
92 | *
93 | * @throws \ReflectionException
94 | */
95 | protected function validateObserver($observer): bool
96 | {
97 | return !$this->isDisable($observer)
98 | && (new ReflectionClass($observer))->implementsInterface(EventHandlerInterface::class)
99 | && $this->accessible($observer);
100 | }
101 |
102 | /**
103 | * Determine whether the given observer is accessible.
104 | *
105 | * @param string $observer
106 | *
107 | * @return bool
108 | */
109 | protected function accessible($observer): bool
110 | {
111 | if (!method_exists($observer, 'getAccessor')) {
112 | return true;
113 | }
114 |
115 | return in_array(get_class($this->app), (array) $observer::getAccessor());
116 | }
117 |
118 | /**
119 | * @param mixed $observer
120 | *
121 | * @return array
122 | */
123 | protected function listObserver($observer): array
124 | {
125 | $condition = method_exists($observer, 'onCondition') ? $observer::onCondition() : '*';
126 |
127 | return [$observer, $condition];
128 | }
129 |
130 | /**
131 | * Get the easywechat manifest.
132 | *
133 | * @return array
134 | */
135 | protected function getManifest(): array
136 | {
137 | if (!is_null($this->manifest)) {
138 | return $this->manifest;
139 | }
140 |
141 | return $this->manifest = file_exists($this->manifestPath) ? require $this->manifestPath : [];
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/src/Http/DelegationResponse.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * This source file is subject to the MIT license that is bundled
11 | * with this source code in the file LICENSE.
12 | */
13 |
14 | namespace EasyWeChatComposer\Http;
15 |
16 | class DelegationResponse extends Response
17 | {
18 | /**
19 | * @return string
20 | */
21 | public function getBodyContents()
22 | {
23 | return $this->response->getBodyContents();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/src/Http/Response.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * This source file is subject to the MIT license that is bundled
11 | * with this source code in the file LICENSE.
12 | */
13 |
14 | namespace EasyWeChatComposer\Http;
15 |
16 | use EasyWeChat\Kernel\Contracts\Arrayable;
17 | use EasyWeChat\Kernel\Http\Response as HttpResponse;
18 | use JsonSerializable;
19 |
20 | class Response implements Arrayable, JsonSerializable
21 | {
22 | /**
23 | * @var \EasyWeChat\Kernel\Http\Response
24 | */
25 | protected $response;
26 |
27 | /**
28 | * @var array
29 | */
30 | protected $array;
31 |
32 | /**
33 | * @param \EasyWeChat\Kernel\Http\Response $response
34 | */
35 | public function __construct(HttpResponse $response)
36 | {
37 | $this->response = $response;
38 | }
39 |
40 | /**
41 | * @see \ArrayAccess::offsetExists
42 | *
43 | * @param string $offset
44 | *
45 | * @return bool
46 | */
47 | public function offsetExists($offset)
48 | {
49 | return isset($this->toArray()[$offset]);
50 | }
51 |
52 | /**
53 | * @see \ArrayAccess::offsetGet
54 | *
55 | * @param string $offset
56 | *
57 | * @return mixed
58 | */
59 | public function offsetGet($offset)
60 | {
61 | return $this->toArray()[$offset] ?? null;
62 | }
63 |
64 | /**
65 | * @see \ArrayAccess::offsetSet
66 | *
67 | * @param string $offset
68 | * @param mixed $value
69 | */
70 | public function offsetSet($offset, $value)
71 | {
72 | //
73 | }
74 |
75 | /**
76 | * @see \ArrayAccess::offsetUnset
77 | *
78 | * @param string $offset
79 | */
80 | public function offsetUnset($offset)
81 | {
82 | //
83 | }
84 |
85 | /**
86 | * Get the instance as an array.
87 | *
88 | * @return array
89 | */
90 | public function toArray()
91 | {
92 | return $this->array ?: $this->array = $this->response->toArray();
93 | }
94 |
95 | /**
96 | * Convert the object into something JSON serializable.
97 | *
98 | * @return array
99 | */
100 | public function jsonSerialize()
101 | {
102 | return $this->toArray();
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/src/Laravel/Http/Controllers/DelegatesController.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * This source file is subject to the MIT license that is bundled
11 | * with this source code in the file LICENSE.
12 | */
13 |
14 | namespace EasyWeChatComposer\Laravel\Http\Controllers;
15 |
16 | use EasyWeChatComposer\Delegation\Hydrate;
17 | use EasyWeChatComposer\Encryption\DefaultEncrypter;
18 | use Illuminate\Http\Request;
19 | use Throwable;
20 |
21 | class DelegatesController
22 | {
23 | /**
24 | * @param \Illuminate\Http\Request $request
25 | * @param \EasyWeChatComposer\Encryption\DefaultEncrypter $encrypter
26 | *
27 | * @return \Illuminate\Http\Response
28 | */
29 | public function __invoke(Request $request, DefaultEncrypter $encrypter)
30 | {
31 | try {
32 | $data = json_decode($encrypter->decrypt($request->get('encrypted')), true);
33 |
34 | $hydrate = new Hydrate($data);
35 |
36 | $response = $hydrate->handle();
37 |
38 | return response()->json([
39 | 'response_type' => get_class($response),
40 | 'response' => $encrypter->encrypt($response->getBodyContents()),
41 | ]);
42 | } catch (Throwable $t) {
43 | return [
44 | 'exception' => get_class($t),
45 | 'message' => $t->getMessage(),
46 | ];
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/Laravel/ServiceProvider.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * This source file is subject to the MIT license that is bundled
11 | * with this source code in the file LICENSE.
12 | */
13 |
14 | namespace EasyWeChatComposer\Laravel;
15 |
16 | use EasyWeChatComposer\EasyWeChat;
17 | use EasyWeChatComposer\Encryption\DefaultEncrypter;
18 | use Illuminate\Foundation\Application;
19 | use Illuminate\Support\Arr;
20 | use Illuminate\Support\Facades\Cache;
21 | use Illuminate\Support\Facades\Route;
22 | use Illuminate\Support\ServiceProvider as LaravelServiceProvider;
23 | use RuntimeException;
24 |
25 | class ServiceProvider extends LaravelServiceProvider
26 | {
27 | /**
28 | * Bootstrap any application services.
29 | */
30 | public function boot()
31 | {
32 | $this->registerRoutes();
33 | $this->publishes([
34 | __DIR__.'/config.php' => config_path('easywechat-composer.php'),
35 | ]);
36 |
37 | EasyWeChat::setEncryptionKey(
38 | $defaultKey = $this->getKey()
39 | );
40 |
41 | EasyWeChat::withDelegation()
42 | ->toHost($this->config('delegation.host'))
43 | ->ability($this->config('delegation.enabled'));
44 |
45 | $this->app->when(DefaultEncrypter::class)->needs('$key')->give($defaultKey);
46 | }
47 |
48 | /**
49 | * Register routes.
50 | */
51 | protected function registerRoutes()
52 | {
53 | Route::prefix('easywechat-composer')->namespace('EasyWeChatComposer\Laravel\Http\Controllers')->group(function () {
54 | $this->loadRoutesFrom(__DIR__.'/routes.php');
55 | });
56 | }
57 |
58 | /**
59 | * Register any application services.
60 | */
61 | public function register()
62 | {
63 | $this->configure();
64 | }
65 |
66 | /**
67 | * Register config.
68 | */
69 | protected function configure()
70 | {
71 | $this->mergeConfigFrom(
72 | __DIR__.'/config.php', 'easywechat-composer'
73 | );
74 | }
75 |
76 | /**
77 | * Get the specified configuration value.
78 | *
79 | * @param string|null $key
80 | * @param mixed $default
81 | *
82 | * @return mixed
83 | */
84 | protected function config($key = null, $default = null)
85 | {
86 | $config = $this->app['config']->get('easywechat-composer');
87 |
88 | if (is_null($key)) {
89 | return $config;
90 | }
91 |
92 | return Arr::get($config, $key, $default);
93 | }
94 |
95 | /**
96 | * @return string
97 | */
98 | protected function getKey()
99 | {
100 | return $this->config('encryption.key') ?: $this->getMd5Key();
101 | }
102 |
103 | /**
104 | * @return string
105 | */
106 | protected function getMd5Key()
107 | {
108 | $ttl = (version_compare(Application::VERSION, '5.8') === -1) ? 30 : 1800;
109 |
110 | return Cache::remember('easywechat-composer.encryption_key', $ttl, function () {
111 | throw_unless(file_exists($path = base_path('composer.lock')), RuntimeException::class, 'No encryption key provided.');
112 |
113 | return md5_file($path);
114 | });
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/src/Laravel/config.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * This source file is subject to the MIT license that is bundled
11 | * with this source code in the file LICENSE.
12 | */
13 |
14 | return [
15 |
16 | 'encryption' => [
17 |
18 | 'key' => env('EASYWECHAT_KEY'),
19 |
20 | ],
21 |
22 | 'delegation' => [
23 |
24 | 'enabled' => env('EASYWECHAT_DELEGATION', false),
25 |
26 | 'host' => env('EASYWECHAT_DELEGATION_HOST'),
27 | ],
28 |
29 | ];
30 |
--------------------------------------------------------------------------------
/src/Laravel/routes.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * This source file is subject to the MIT license that is bundled
11 | * with this source code in the file LICENSE.
12 | */
13 |
14 | use Illuminate\Support\Facades\Route;
15 |
16 | Route::post('delegate', 'DelegatesController');
17 |
--------------------------------------------------------------------------------
/src/ManifestManager.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * This source file is subject to the MIT license that is bundled
11 | * with this source code in the file LICENSE.
12 | */
13 |
14 | namespace EasyWeChatComposer;
15 |
16 | use Composer\Plugin\PluginInterface;
17 |
18 | class ManifestManager
19 | {
20 | const PACKAGE_TYPE = 'easywechat-extension';
21 |
22 | const EXTRA_OBSERVER = 'observers';
23 |
24 | /**
25 | * The vendor path.
26 | *
27 | * @var string
28 | */
29 | protected $vendorPath;
30 |
31 | /**
32 | * The manifest path.
33 | *
34 | * @var string
35 | */
36 | protected $manifestPath;
37 |
38 | /**
39 | * @param string $vendorPath
40 | * @param string|null $manifestPath
41 | */
42 | public function __construct(string $vendorPath, string $manifestPath = null)
43 | {
44 | $this->vendorPath = $vendorPath;
45 | $this->manifestPath = $manifestPath ?: $vendorPath.'/easywechat-composer/easywechat-composer/extensions.php';
46 | }
47 |
48 | /**
49 | * Remove manifest file.
50 | *
51 | * @return $this
52 | */
53 | public function unlink()
54 | {
55 | if (file_exists($this->manifestPath)) {
56 | @unlink($this->manifestPath);
57 | }
58 |
59 | return $this;
60 | }
61 |
62 | /**
63 | * Build the manifest file.
64 | */
65 | public function build()
66 | {
67 | $packages = [];
68 |
69 | if (file_exists($installed = $this->vendorPath.'/composer/installed.json')) {
70 | $packages = json_decode(file_get_contents($installed), true);
71 | if (version_compare(PluginInterface::PLUGIN_API_VERSION, '2.0.0', 'ge')) {
72 | $packages = $packages['packages'];
73 | }
74 | }
75 |
76 | $this->write($this->map($packages));
77 | }
78 |
79 | /**
80 | * @param array $packages
81 | *
82 | * @return array
83 | */
84 | protected function map(array $packages): array
85 | {
86 | $manifest = [];
87 |
88 | $packages = array_filter($packages, function ($package) {
89 | if(isset($package['type'])){
90 | return $package['type'] === self::PACKAGE_TYPE;
91 | }
92 | });
93 |
94 | foreach ($packages as $package) {
95 | $manifest[$package['name']] = [self::EXTRA_OBSERVER => $package['extra'][self::EXTRA_OBSERVER] ?? []];
96 | }
97 |
98 | return $manifest;
99 | }
100 |
101 | /**
102 | * Write the manifest array to a file.
103 | *
104 | * @param array $manifest
105 | */
106 | protected function write(array $manifest)
107 | {
108 | file_put_contents(
109 | $this->manifestPath,
110 | 'invalidate($this->manifestPath);
114 | }
115 |
116 | /**
117 | * Invalidate the given file.
118 | *
119 | * @param string $file
120 | */
121 | protected function invalidate($file)
122 | {
123 | if (function_exists('opcache_invalidate')) {
124 | @opcache_invalidate($file, true);
125 | }
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/Plugin.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * This source file is subject to the MIT license that is bundled
11 | * with this source code in the file LICENSE.
12 | */
13 |
14 | namespace EasyWeChatComposer;
15 |
16 | use Composer\Composer;
17 | use Composer\EventDispatcher\EventSubscriberInterface;
18 | use Composer\Installer\PackageEvent;
19 | use Composer\Installer\PackageEvents;
20 | use Composer\IO\IOInterface;
21 | use Composer\Plugin\Capable;
22 | use Composer\Plugin\PluginInterface;
23 | use Composer\Script\Event;
24 | use Composer\Script\ScriptEvents;
25 |
26 | class Plugin implements PluginInterface, EventSubscriberInterface, Capable
27 | {
28 | /**
29 | * @var bool
30 | */
31 | protected $activated = true;
32 |
33 | /**
34 | * Apply plugin modifications to Composer.
35 | */
36 | public function activate(Composer $composer, IOInterface $io)
37 | {
38 | //
39 | }
40 |
41 | /**
42 | * Remove any hooks from Composer.
43 | *
44 | * This will be called when a plugin is deactivated before being
45 | * uninstalled, but also before it gets upgraded to a new version
46 | * so the old one can be deactivated and the new one activated.
47 | */
48 | public function deactivate(Composer $composer, IOInterface $io)
49 | {
50 | //
51 | }
52 |
53 | /**
54 | * Prepare the plugin to be uninstalled.
55 | *
56 | * This will be called after deactivate.
57 | */
58 | public function uninstall(Composer $composer, IOInterface $io)
59 | {
60 | }
61 |
62 | /**
63 | * @return array
64 | */
65 | public function getCapabilities()
66 | {
67 | return [
68 | 'Composer\Plugin\Capability\CommandProvider' => 'EasyWeChatComposer\Commands\Provider',
69 | ];
70 | }
71 |
72 | /**
73 | * Listen events.
74 | *
75 | * @return array
76 | */
77 | public static function getSubscribedEvents()
78 | {
79 | return [
80 | PackageEvents::PRE_PACKAGE_UNINSTALL => 'prePackageUninstall',
81 | ScriptEvents::POST_AUTOLOAD_DUMP => 'postAutoloadDump',
82 | ];
83 | }
84 |
85 | /**
86 | * @param \Composer\Installer\PackageEvent
87 | */
88 | public function prePackageUninstall(PackageEvent $event)
89 | {
90 | if ($event->getOperation()->getPackage()->getName() === 'overtrue/wechat') {
91 | $this->activated = false;
92 | }
93 | }
94 |
95 | public function postAutoloadDump(Event $event)
96 | {
97 | if (!$this->activated) {
98 | return;
99 | }
100 |
101 | $manifest = new ManifestManager(
102 | rtrim($event->getComposer()->getConfig()->get('vendor-dir'), '/')
103 | );
104 |
105 | $manifest->unlink()->build();
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/Traits/MakesHttpRequests.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * This source file is subject to the MIT license that is bundled
11 | * with this source code in the file LICENSE.
12 | */
13 |
14 | namespace EasyWeChatComposer\Traits;
15 |
16 | use EasyWeChat\Kernel\Http\StreamResponse;
17 | use EasyWeChat\Kernel\Traits\ResponseCastable;
18 | use EasyWeChatComposer\Contracts\Encrypter;
19 | use EasyWeChatComposer\EasyWeChat;
20 | use EasyWeChatComposer\Encryption\DefaultEncrypter;
21 | use EasyWeChatComposer\Exceptions\DelegationException;
22 | use GuzzleHttp\Client;
23 | use GuzzleHttp\ClientInterface;
24 |
25 | trait MakesHttpRequests
26 | {
27 | use ResponseCastable;
28 |
29 | /**
30 | * @var \GuzzleHttp\ClientInterface
31 | */
32 | protected $httpClient;
33 |
34 | /**
35 | * @var \EasyWeChatComposer\Contracts\Encrypter
36 | */
37 | protected $encrypter;
38 |
39 | /**
40 | * @param string $endpoint
41 | * @param array $payload
42 | *
43 | * @return array|\EasyWeChat\Kernel\Support\Collection|object|\Psr\Http\Message\ResponseInterface|string
44 | *
45 | * @throws \EasyWeChat\Kernel\Exceptions\InvalidArgumentException
46 | * @throws \EasyWeChat\Kernel\Exceptions\InvalidConfigException
47 | * @throws \GuzzleHttp\Exception\GuzzleException
48 | */
49 | protected function request($endpoint, array $payload)
50 | {
51 | $response = $this->getHttpClient()->request('POST', $endpoint, [
52 | 'form_params' => $this->buildFormParams($payload),
53 | ]);
54 |
55 | $parsed = $this->parseResponse($response);
56 |
57 | return $this->detectAndCastResponseToType(
58 | $this->getEncrypter()->decrypt($parsed['response']),
59 | ($parsed['response_type'] === StreamResponse::class) ? 'raw' : $this->app['config']['response_type']
60 | );
61 | }
62 |
63 | /**
64 | * @param array $payload
65 | *
66 | * @return array
67 | */
68 | protected function buildFormParams($payload)
69 | {
70 | return [
71 | 'encrypted' => $this->getEncrypter()->encrypt(json_encode($payload)),
72 | ];
73 | }
74 |
75 | /**
76 | * @param \Psr\Http\Message\ResponseInterface $response
77 | *
78 | * @return array
79 | */
80 | protected function parseResponse($response)
81 | {
82 | $result = json_decode((string) $response->getBody(), true);
83 |
84 | if (isset($result['exception'])) {
85 | throw (new DelegationException($result['message']))->setException($result['exception']);
86 | }
87 |
88 | return $result;
89 | }
90 |
91 | /**
92 | * @return \GuzzleHttp\ClientInterface
93 | */
94 | protected function getHttpClient(): ClientInterface
95 | {
96 | return $this->httpClient ?: $this->httpClient = new Client([
97 | 'base_uri' => $this->app['config']['delegation']['host'],
98 | ]);
99 | }
100 |
101 | /**
102 | * @return \EasyWeChatComposer\Contracts\Encrypter
103 | */
104 | protected function getEncrypter(): Encrypter
105 | {
106 | return $this->encrypter ?: $this->encrypter = new DefaultEncrypter(
107 | EasyWeChat::getEncryptionKey()
108 | );
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/src/Traits/WithAggregator.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * This source file is subject to the MIT license that is bundled
11 | * with this source code in the file LICENSE.
12 | */
13 |
14 | namespace EasyWeChatComposer\Traits;
15 |
16 | use EasyWeChat\Kernel\BaseClient;
17 | use EasyWeChatComposer\Delegation\DelegationTo;
18 | use EasyWeChatComposer\EasyWeChat;
19 |
20 | trait WithAggregator
21 | {
22 | /**
23 | * Aggregate.
24 | */
25 | protected function aggregate()
26 | {
27 | foreach (EasyWeChat::config() as $key => $value) {
28 | $this['config']->set($key, $value);
29 | }
30 | }
31 |
32 | /**
33 | * @return bool
34 | */
35 | public function shouldDelegate($id)
36 | {
37 | return $this['config']->get('delegation.enabled')
38 | && $this->offsetGet($id) instanceof BaseClient;
39 | }
40 |
41 | /**
42 | * @return $this
43 | */
44 | public function shouldntDelegate()
45 | {
46 | $this['config']->set('delegation.enabled', false);
47 |
48 | return $this;
49 | }
50 |
51 | /**
52 | * @param string $id
53 | *
54 | * @return \EasyWeChatComposer\Delegation
55 | */
56 | public function delegateTo($id)
57 | {
58 | return new DelegationTo($this, $id);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/tests/ManifestManagerTest.php:
--------------------------------------------------------------------------------
1 |
9 | *
10 | * This source file is subject to the MIT license that is bundled
11 | * with this source code in the file LICENSE.
12 | */
13 |
14 | namespace EasyWeChatComposer\Tests;
15 |
16 | use EasyWeChatComposer\ManifestManager;
17 | use PHPUnit\Framework\TestCase;
18 |
19 | class ManifestManagerTest extends TestCase
20 | {
21 | private $vendorPath;
22 | private $manifestPath;
23 |
24 | protected function getManifestManager()
25 | {
26 | return new ManifestManager(
27 | $this->vendorPath = __DIR__.'/__fixtures__/vendor/',
28 | $this->manifestPath = __DIR__.'/__fixtures__/extensions.php'
29 | );
30 | }
31 |
32 | public function testUnlink()
33 | {
34 | $this->assertInstanceOf(ManifestManager::class, $this->getManifestManager()->unlink());
35 | $this->assertFalse(file_exists($this->manifestPath));
36 | }
37 | }
38 |
--------------------------------------------------------------------------------