1 | <?php |
||
2 | namespace phpbu\App\Log; |
||
3 | |||
4 | use phpbu\App\Exception; |
||
5 | use phpbu\App\Event; |
||
6 | use phpbu\App\Listener; |
||
7 | use phpbu\App\Result; |
||
8 | use phpbu\App\Util\Arr; |
||
9 | use Throwable; |
||
10 | |||
11 | /** |
||
12 | * Webhook Logger |
||
13 | * |
||
14 | * @package phpbu |
||
15 | * @subpackage Log |
||
16 | * @author Cees Vogel <[email protected]> |
||
17 | * @author Sebastian Feldmann <[email protected]> |
||
18 | * @copyright Sebastian Feldmann <[email protected]> |
||
19 | * @license https://opensource.org/licenses/MIT The MIT License (MIT) |
||
20 | * @link https://phpbu.de/ |
||
21 | * @since Class available since Release 5.0.0 |
||
22 | */ |
||
23 | class Webhook implements Listener, Logger |
||
24 | { |
||
25 | /** |
||
26 | * Start time |
||
27 | * |
||
28 | * @var float |
||
29 | */ |
||
30 | private $start; |
||
31 | |||
32 | /** |
||
33 | * Uri to call |
||
34 | * |
||
35 | * @var string |
||
36 | */ |
||
37 | private $uri; |
||
38 | |||
39 | /** |
||
40 | * Request method GET|POST |
||
41 | * |
||
42 | * @var string |
||
43 | */ |
||
44 | private $method; |
||
45 | |||
46 | /** |
||
47 | * Request timeout (seconds) |
||
48 | * |
||
49 | * @var int |
||
50 | */ |
||
51 | private $timeout; |
||
52 | |||
53 | /** |
||
54 | * Basic auth username |
||
55 | * |
||
56 | * @var string |
||
57 | */ |
||
58 | private $username; |
||
59 | |||
60 | /** |
||
61 | * Basic auth password |
||
62 | * |
||
63 | * @var string |
||
64 | */ |
||
65 | private $password; |
||
66 | |||
67 | /** |
||
68 | * Request content type |
||
69 | * |
||
70 | * @var string |
||
71 | */ |
||
72 | private $contentType; |
||
73 | |||
74 | /** |
||
75 | * Body template to use |
||
76 | * |
||
77 | * @var string |
||
78 | */ |
||
79 | private $template; |
||
80 | |||
81 | /** |
||
82 | * List of available default formatter |
||
83 | * |
||
84 | * @var array |
||
85 | */ |
||
86 | private $availableFormatter = [ |
||
87 | 'multipart/form-data' => '\\phpbu\\App\\Log\\ResultFormatter\\FormData', |
||
88 | 7 | 'application/json' => '\\phpbu\\App\\Log\\ResultFormatter\\Json', |
|
89 | 'application/xml' => '\\phpbu\\App\\Log\\ResultFormatter\\Xml' |
||
90 | 7 | ]; |
|
91 | 7 | ||
92 | /** |
||
93 | * Constructor will only set the start time to be able to log duration |
||
94 | */ |
||
95 | public function __construct() |
||
96 | { |
||
97 | $this->start = microtime(true); |
||
98 | } |
||
99 | |||
100 | /** |
||
101 | * Returns an array of event names this subscriber wants to listen to. |
||
102 | * |
||
103 | * The array keys are event names and the value can be: |
||
104 | * |
||
105 | 1 | * - The method name to call (priority defaults to 0) |
|
106 | * - An array composed of the method name to call and the priority |
||
107 | * - An array of arrays composed of the method names to call and respective |
||
108 | 1 | * priorities, or 0 if unset |
|
109 | * |
||
110 | * @return array The event names to listen to |
||
111 | */ |
||
112 | public static function getSubscribedEvents(): array |
||
113 | { |
||
114 | return [ |
||
115 | 'phpbu.app_end' => 'onPhpbuEnd', |
||
116 | ]; |
||
117 | } |
||
118 | |||
119 | 7 | /** |
|
120 | * Setup the logger. |
||
121 | 7 | * |
|
122 | 1 | * @see \phpbu\App\Log\Logger::setup |
|
123 | * @param array $options |
||
124 | 6 | * @throws \phpbu\App\Exception |
|
125 | 6 | */ |
|
126 | 6 | public function setup(array $options) |
|
127 | 6 | { |
|
128 | 6 | if (empty($options['uri'])) { |
|
129 | 6 | throw new Exception('no uri given'); |
|
130 | 6 | } |
|
131 | |||
132 | // PHP >7.2 deprecated the filter options and enabled them by default |
||
133 | $filterOptions = version_compare(PHP_VERSION, '7.2.0', '<') |
||
134 | ? FILTER_FLAG_SCHEME_REQUIRED | FILTER_FLAG_HOST_REQUIRED |
||
0 ignored issues
–
show
introduced
by
![]() |
|||
135 | : null; |
||
136 | |||
137 | 6 | if (!filter_var($options['uri'], FILTER_VALIDATE_URL, $filterOptions)) { |
|
138 | throw new Exception('webhook URI is invalid'); |
||
139 | 6 | } |
|
140 | 6 | ||
141 | 6 | $this->uri = $options['uri']; |
|
142 | 6 | $this->method = Arr::getValue($options, 'method', 'GET'); |
|
143 | 5 | $this->username = Arr::getValue($options, 'username', ''); |
|
144 | $this->password = Arr::getValue($options, 'password', ''); |
||
145 | 5 | $this->template = Arr::getValue($options, 'template', ''); |
|
146 | 1 | $this->contentType = Arr::getValue($options, 'contentType', 'multipart/form-data'); |
|
147 | $this->timeout = Arr::getValue($options, 'timeout', ''); |
||
0 ignored issues
–
show
It seems like
phpbu\App\Util\Arr::getV...options, 'timeout', '') can also be of type string . However, the property $timeout is declared as type integer . 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 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;
}
![]() |
|||
148 | } |
||
149 | |||
150 | /** |
||
151 | * phpbu end event. |
||
152 | * |
||
153 | * @param \phpbu\App\Event\App\End $event |
||
154 | 2 | */ |
|
155 | public function onPhpbuEnd(Event\App\End $event) |
||
156 | 2 | { |
|
157 | 2 | $result = $event->getResult(); |
|
158 | $data = $this->getQueryStringData($result); |
||
159 | $uri = $this->method === 'GET' ? $this->buildGetUri($data) : $this->uri; |
||
160 | $formatter = $this->getBodyFormatter(); |
||
161 | $body = $formatter->format($result); |
||
162 | |||
163 | $this->fireRequest($uri, $body); |
||
164 | } |
||
165 | |||
166 | /** |
||
167 | 6 | * Builds the final request uri for GET requests. |
|
168 | * |
||
169 | 6 | * @param array $data |
|
170 | 1 | * @return string |
|
171 | */ |
||
172 | private function buildGetUri(array $data) : string |
||
173 | 5 | { |
|
174 | 1 | $glue = strpos($this->uri, '?') !== false ? '&' : '?'; |
|
175 | return $this->uri . $glue . http_build_query($data); |
||
176 | 4 | } |
|
177 | 4 | ||
178 | /** |
||
179 | * Return the request body template. |
||
180 | * If template and body are set the body supersedes the template setting. |
||
181 | * |
||
182 | * @return \phpbu\App\Log\ResultFormatter |
||
183 | * @throws \phpbu\App\Exception |
||
184 | */ |
||
185 | private function getBodyFormatter() : ResultFormatter |
||
186 | 6 | { |
|
187 | if (!empty($this->template)) { |
||
188 | 6 | return new ResultFormatter\Template($this->template); |
|
189 | } |
||
190 | |||
191 | 6 | if (!isset($this->availableFormatter[$this->contentType])) { |
|
192 | 6 | throw new Exception('no default formatter for content-type: ' . $this->contentType); |
|
193 | 6 | } |
|
194 | 6 | $class = $this->availableFormatter[$this->contentType]; |
|
195 | 6 | return new $class(); |
|
196 | 6 | } |
|
197 | |||
198 | /** |
||
199 | * Returns some basic statistics as GET query string. |
||
200 | * |
||
201 | * @param \phpbu\App\Result $result |
||
202 | * @return array |
||
203 | */ |
||
204 | private function getQueryStringData(Result $result) : array |
||
205 | { |
||
206 | $end = microtime(true); |
||
207 | |||
208 | 5 | return [ |
|
209 | 'status' => $result->allOk() ? 0 : 1, |
||
210 | 5 | 'timestamp' => (int) $this->start, |
|
211 | 'duration' => round($end - $this->start, 4), |
||
212 | 'err-cnt' => $result->errorCount(), |
||
213 | 5 | 'bak-cnt' => count($result->getBackups()), |
|
214 | 'bak-fail' => $result->backupsFailedCount(), |
||
215 | ]; |
||
216 | } |
||
217 | 5 | ||
218 | 5 | ||
219 | 5 | /** |
|
220 | * Execute the request to the webhook uri. |
||
221 | * |
||
222 | 5 | * @param string $uri |
|
223 | 1 | * @param string $body |
|
224 | * @throws \phpbu\App\Exception |
||
225 | */ |
||
226 | 5 | protected function fireRequest(string $uri, string $body = '') |
|
227 | 5 | { |
|
228 | $headers = []; |
||
229 | $options = [ |
||
230 | 5 | 'http' => [ |
|
231 | 4 | 'method' => strtoupper($this->method), |
|
232 | 4 | ] |
|
233 | ]; |
||
234 | 1 | ||
235 | if (!empty($body)) { |
||
236 | $headers[] = 'Content-Type: ' . $this->contentType; |
||
237 | $options['http']['content'] = $body; |
||
238 | } |
||
239 | |||
240 | if (!empty($this->timeout)) { |
||
241 | $options['http']['timeout'] = $this->timeout; |
||
242 | } |
||
243 | |||
244 | if (!empty($this->username)) { |
||
245 | $headers[] = 'Authorization: Basic ' . base64_encode($this->username . ':' . $this->password); |
||
246 | } |
||
247 | |||
248 | $options['http']['header'] = implode("\r\n", $headers); |
||
249 | $context = stream_context_create($options); |
||
250 | |||
251 | try { |
||
252 | file_get_contents($uri, false, $context); |
||
253 | } catch (Throwable $t) { |
||
254 | throw new Exception('could not reach webhook: ' . $this->uri); |
||
255 | } |
||
256 | } |
||
257 | } |
||
258 |