Passed
Push — master ( 469104...8dd18e )
by Romain
38s
created

Webhook::subscribe()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 0
crap 1
1
<?php
2
namespace Kerox\Messenger\Api;
3
4
use GuzzleHttp\ClientInterface;
5
use GuzzleHttp\Psr7\ServerRequest;
6
use Kerox\Messenger\Model\Callback\Entry;
7
use Kerox\Messenger\Request\WebhookRequest;
8
use Kerox\Messenger\Response\WebhookResponse;
9
use Psr\Http\Message\ServerRequestInterface;
10
11
class Webhook extends AbstractApi
12
{
13
14
    /**
15
     * @var null|\Kerox\Messenger\Api\Webhook
16
     */
17
    private static $_instance;
18
19
    /**
20
     * @var string
21
     */
22
    protected $appSecret;
23
24
    /**
25
     * @var string
26
     */
27
    protected $verifyToken;
28
29
    /**
30
     * @var \Psr\Http\Message\ServerRequestInterface
31
     */
32
    protected $request;
33
34
    /**
35
     * @var string
36
     */
37
    protected $body;
38
39
    /**
40
     * @var array
41
     */
42
    protected $decodedBody;
43
44
    /**
45
     * @var \Kerox\Messenger\Model\Entry[]
46
     */
47
    protected $hydratedEntries;
48
49
    /**
50
     * Webhook constructor.
51
     *
52
     * @param string $appSecret
53
     * @param string $verifyToken
54
     * @param string $pageToken
55
     * @param \GuzzleHttp\ClientInterface $client
56
     * @param \Psr\Http\Message\ServerRequestInterface $request
57
     */
58 7
    public function __construct(string $appSecret, string $verifyToken, string $pageToken, ClientInterface $client, ServerRequestInterface $request = null)
59
    {
60 7
        parent::__construct($pageToken, $client);
61
62 7
        $this->appSecret = $appSecret;
63 7
        $this->verifyToken = $verifyToken;
64 7
        $this->request = $request ?: ServerRequest::fromGlobals();
65 7
    }
66
67
    /**
68
     * @param string $appSecret
69
     * @param string $verifyToken
70
     * @param string $pageToken
71
     * @param \GuzzleHttp\ClientInterface $client
72
     * @param \Psr\Http\Message\ServerRequestInterface $request
73
     * @return \Kerox\Messenger\Api\Webhook
74
     */
75 1
    public static function getInstance(string $appSecret, string $verifyToken, string $pageToken, ClientInterface $client, ServerRequestInterface $request = null): Webhook
76
    {
77 1
        if (self::$_instance === null) {
78 1
            self::$_instance = new Webhook($appSecret, $verifyToken, $pageToken, $client, $request);
79
        }
80
81 1
        return self::$_instance;
82
    }
83
84
    /**
85
     * @return bool
86
     */
87 1
    public function isValidToken(): bool
88
    {
89 1
        if ($this->request->getMethod() !== 'GET') {
90
            return false;
91
        }
92
93 1
        $params = $this->request->getQueryParams();
94 1
        if (!isset($params['hub_verify_token'])) {
95
            return false;
96
        }
97
98 1
        return ($params['hub_mode'] === 'subscribe' && $params['hub_verify_token'] === $this->verifyToken);
99
    }
100
101
    /**
102
     * @return string|null
103
     */
104 1
    public function challenge()
105
    {
106 1
        $params = $this->request->getQueryParams();
107
108 1
        return $params['hub_challenge'] ?? null;
109
    }
110
111
    /**
112
     * @deprecated since 1.2.0 and will be removed in 1.3.0.
113
     * @see challenge()
114
     * @return null|string
115
     */
116
    public function getChallenge()
117
    {
118
        return $this->challenge();
119
    }
120
121
    /**
122
     * @return \Kerox\Messenger\Response\WebhookResponse
123
     */
124 1
    public function subscribe(): WebhookResponse
125
    {
126 1
        $request = new WebhookRequest($this->pageToken);
127 1
        $response = $this->client->post('me/subscribed_apps', $request->build());
128
129 1
        return new WebhookResponse($response);
130
    }
131
132
    /**
133
     * @deprecated since 1.2.0 and will be removed in 1.3.0.
134
     * @see subscribe()
135
     * @return \Kerox\Messenger\Response\WebhookResponse
136
     */
137
    public function sendSubscribe(): WebhookResponse
138
    {
139
        return $this->subscribe();
140
    }
141
142
    /**
143
     * @return bool
144
     */
145 1
    public function isValidCallback(): bool
146
    {
147 1
        if (!$this->isValidHubSignature()) {
148
            return false;
149
        }
150
151 1
        $decodedBody = $this->getDecodedBody();
152
153 1
        $object = $decodedBody['object'] ?? null;
154 1
        $entry = $decodedBody['entry'] ?? null;
155
156 1
        return ($object === 'page' && $entry !== null);
157
    }
158
159
    /**
160
     * @return string
161
     */
162 4
    public function getBody(): string
163
    {
164 4
        if ($this->body === null) {
165 4
            $this->body = (string) $this->request->getBody();
166
        }
167
168 4
        return $this->body;
169
    }
170
171
    /**
172
     * @return array
173
     * @throws \Exception
174
     */
175 4
    public function getDecodedBody(): array
176
    {
177 4
        if ($this->decodedBody === null) {
178 4
            $decodedBody = json_decode($this->getBody(), true);
179 4
            if (json_last_error() !== JSON_ERROR_NONE || $decodedBody === null) {
180
                $decodedBody = [];
181
            }
182
183 4
            $this->decodedBody = $decodedBody;
184
        }
185
186 4
        return $this->decodedBody;
187
    }
188
189
    /**
190
     * @return \Kerox\Messenger\Model\Entry[]
191
     */
192 1
    public function getCallbackEntries(): array
193
    {
194 1
        return $this->getHydratedEntries();
195
    }
196
197
    /**
198
     * @return array
199
     */
200 1
    public function getCallbackEvents(): array
201
    {
202 1
        $events = [];
203 1
        foreach ($this->getHydratedEntries() as $hydratedEntry) {
204
            /** @var \Kerox\Messenger\Model\Callback\Entry $hydratedEntry */
205 1
            $events = array_merge($events, $hydratedEntry->getEvents());
206
        }
207
208 1
        return $events;
209
    }
210
211
    /**
212
     * @return \Kerox\Messenger\Model\Callback\Entry[]
213
     */
214 2
    private function getHydratedEntries(): array
215
    {
216 2
        if ($this->hydratedEntries === null) {
217 2
            $decodedBody = $this->getDecodedBody();
218
219 2
            $hydrated = [];
220 2
            foreach ($decodedBody['entry'] as $entry) {
221 2
                $hydrated[] = Entry::create($entry);
222
            }
223
224 2
            $this->hydratedEntries = $hydrated;
225
        }
226
227 2
        return $this->hydratedEntries;
228
    }
229
230
    /**
231
     * @return bool
232
     */
233 1
    private function isValidHubSignature(): bool
234
    {
235 1
        $headers = $this->request->getHeader('X-Hub-Signature');
236 1
        $content = $this->getBody();
237
238 1
        if (empty($headers)) {
239
            return false;
240
        }
241
242 1
        list($algorithm, $hash) = explode('=', $headers[0]);
243
244 1
        return hash_equals(hash_hmac($algorithm, $content, $this->appSecret), $hash);
245
    }
246
}
247