Passed
Push — master ( c1a8c1...b5fef0 )
by Shahrad
02:24
created

WebhookHandler::kill()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 0
dl 0
loc 3
rs 10
1
<?php
2
3
namespace TelegramBot;
4
5
use TelegramBot\Entities\Update;
6
use TelegramBot\Exception\InvalidBotTokenException;
7
use TelegramBot\Util\Common;
8
use TelegramBot\Util\CrossData;
9
use TelegramBot\Util\DotEnv;
10
11
/**
12
 * Class Webhook
13
 *
14
 * @link    https://github.com/telegram-bot-php/core
15
 * @author  Shahrad Elahi (https://github.com/shahradelahi)
16
 * @license https://github.com/telegram-bot-php/core/blob/master/LICENSE (MIT License)
17
 */
18
abstract class WebhookHandler extends Telegram
19
{
20
21
	/**
22
	 * @var ?Update
23
	 */
24
	protected ?Update $update;
25
26
	/**
27
	 * @var array<Plugin>
28
	 */
29
	private array $plugins = [];
30
31
	/**
32
	 * @var bool
33
	 */
34
	private bool $isActive = false;
35
36
	/**
37
	 * The default configuration of the webhook.
38
	 *
39
	 * @var array
40
	 */
41
	private array $config = [
42
		'autoload_env_file' => false,
43
		'env_file_path' => null,
44
	];
45
46
	/**
47
	 * Webhook constructor.
48
	 *
49
	 * @param string $api_key The API key of the bot. Leave it blank for autoload from .env file.
50
	 */
51
	public function __construct(string $api_key = '')
52
	{
53
		parent::__construct($api_key);
54
55
		if (!Telegram::validateToken(self::getApiKey())) {
56
			throw new InvalidBotTokenException();
57
		}
58
	}
59
60
	/**
61
	 * Initialize the receiver.
62
	 *
63
	 * @return void
64
	 */
65
	public function init(): void
66
	{
67
		$this->config['env_file_path'] = $_SERVER['DOCUMENT_ROOT'] . '/.env';
68
	}
69
70
	/**
71
	 * Add a plugin to the receiver
72
	 *
73
	 * @param array<Plugin> $plugins
74
	 */
75
	public function addPlugin(array $plugins): void
76
	{
77
		foreach ($plugins as $plugin) {
78
			if (!is_subclass_of($plugin, Plugin::class)) {
79
				throw new \RuntimeException(
80
					sprintf('The plugin %s must be an instance of %s', get_class($plugin), Plugin::class)
81
				);
82
			}
83
			$this->plugins[] = $plugin;
84
		}
85
	}
86
87
	/**
88
	 * Match the update with the given plugins
89
	 *
90
	 * @param array<Plugin> $plugins
91
	 * @param ?Update $update The update to match
92
	 * @return void
93
	 */
94
	public function loadPluginsWith(array $plugins, Update $update = null): void
95
	{
96
		$update = $update ?? ($this->update ?? Telegram::getUpdate());
97
98
		foreach ($plugins as $plugin) {
99
			if (!is_subclass_of($plugin, Plugin::class)) {
100
				throw new \InvalidArgumentException(
101
					sprintf('The plugin %s must be an instance of %s', get_class($plugin), Plugin::class)
102
				);
103
			}
104
		}
105
106
		if ($update instanceof Update) {
107
			$this->spreadUpdateWith($update, $plugins);
108
		}
109
	}
110
111
	/**
112
	 * Match the message to the plugins
113
	 *
114
	 * @param ?Update $update The update to match
115
	 * @return void
116
	 */
117
	public function loadPlugins(Update $update = null): void
118
	{
119
		$update = $update ?? ($this->update ?? Telegram::getUpdate());
120
		$this->loadPluginsWith($this->plugins, $update);
0 ignored issues
show
Bug introduced by
It seems like $update can also be of type false; however, parameter $update of TelegramBot\WebhookHandler::loadPluginsWith() does only seem to accept TelegramBot\Entities\Update|null, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

120
		$this->loadPluginsWith($this->plugins, /** @scrutinizer ignore-type */ $update);
Loading history...
121
	}
122
123
	/**
124
	 * Load the environment's
125
	 *
126
	 * @param string $path
127
	 * @retrun void
128
	 */
129
	public function loadFileOfEnv(string $path): void
130
	{
131
		DotEnv::load($path);
132
	}
133
134
	/**
135
	 * Update the configuration
136
	 *
137
	 * @param array $configuration
138
	 * @return void
139
	 */
140
	public function updateConfiguration(array $configuration): void
141
	{
142
		$this->config = array_merge($this->config, $configuration);
143
	}
144
145
	/**
146
	 * Resolve the request.
147
	 *
148
	 * @param ?Update $update The custom to work with
149
	 * @param array $config The configuration of the receiver
150
	 *
151
	 * @retrun void
152
	 */
153
	public function resolve(Update $update = null, array $config = []): void
154
	{
155
		$method = '__process';
156
		if (!method_exists($this, $method)) {
157
			throw new \RuntimeException(sprintf('The method %s does not exist', $method));
158
		}
159
160
		if (is_array($config)) $this->updateConfiguration($config);
0 ignored issues
show
introduced by
The condition is_array($config) is always true.
Loading history...
161
162
		if (!empty($update)) $this->update = $update;
163
		else $this->update = Telegram::getUpdate() !== false ? Telegram::getUpdate() : null;
0 ignored issues
show
Documentation Bug introduced by
It seems like TelegramBot\Telegram::ge...ram::getUpdate() : null can also be of type false. However, the property $update is declared as type TelegramBot\Entities\Update|null. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
164
165
		if (empty($this->update)) {
166
			TelegramLog::error(
167
				'The update is empty, the request is not processed'
168
			);
169
			return;
170
		}
171
172
		try {
173
174
			Common::arrest($this, $method, $this->update);
175
176
		} catch (\RuntimeException $exception) {
177
178
			$e = $exception->getPrevious();
179
			TelegramLog::error(($message = sprintf(
180
				'%s(%d): %s\n%s', $e->getFile(), $e->getLine(), $e->getMessage(), $e->getTraceAsString()
181
			)));
182
183
			if (defined('TG_ADMIN_ID') && TG_ADMIN_ID && defined('DEBUG_MODE') && DEBUG_MODE) {
184
				file_put_contents(
185
					($file = getcwd() . '/' . uniqid('error_') . '.log'),
186
					$message . PHP_EOL . PHP_EOL . $update->getRawData(false)
0 ignored issues
show
Bug introduced by
The method getRawData() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

186
					$message . PHP_EOL . PHP_EOL . $update->/** @scrutinizer ignore-call */ getRawData(false)

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
187
				);
188
				Request::sendMessage([
189
					'chat_id' => TG_ADMIN_ID,
190
					'text' => $message,
191
				]);
192
				Request::sendDocument([
193
					'chat_id' => TG_ADMIN_ID,
194
					'document' => $file,
195
				]);
196
				unlink($file);
197
			}
198
		}
199
	}
200
201
	/**
202
	 * This function will get update and spread it to the plugins
203
	 *
204
	 * @param Update $update
205
	 * @param array<Plugin> $plugins
206
	 * @return void
207
	 */
208
	private function spreadUpdateWith(Update $update, array $plugins): void
209
	{
210
		$this->isActive = true;
211
212
		foreach ($plugins as $plugin) {
213
			/** @var Plugin $plugin */
214
			(new $plugin())->__execute($this, $update);
215
			if ($this->isActive === false) break;
216
		}
217
218
		$this->isActive = false;
219
	}
220
221
	/**
222
	 * Kill the speeding process
223
	 *
224
	 * @return void
225
	 */
226
	public function kill(): void
227
	{
228
		$this->isActive = false;
229
	}
230
231
	/**
232
	 * Use this method on the webhook class to get the updates
233
	 *
234
	 * @param Update $update
235
	 * @return void
236
	 */
237
	abstract public function __process(Update $update): void;
238
239
	/**
240
	 * put CrossData into the plugins
241
	 *
242
	 * @param string $key
243
	 * @param mixed $value
244
	 * @return void
245
	 */
246
	public function putCrossData(string $key, mixed $value): void
247
	{
248
		CrossData::put($key, $value);
249
	}
250
251
	/**
252
	 * get CrossData from the plugins
253
	 *
254
	 * @param string $key
255
	 * @return string|array|bool|null
256
	 */
257
	public function getCrossData(string $key): string|array|bool|null
258
	{
259
		return CrossData::get($key);
260
	}
261
262
}