Completed
Pull Request — master (#34)
by Romain
02:42
created

Webhook::getInstance()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 5
crap 2
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 getChallenge()
105
    {
106 1
        $params = $this->request->getQueryParams();
107
108 1
        return $params['hub_challenge'] ?? null;
109
    }
110
111
    /**
112
     * @return \Kerox\Messenger\Response\WebhookResponse
113
     */
114 1
    public function sendSubscribe(): WebhookResponse
115
    {
116 1
        $request = new WebhookRequest($this->pageToken);
117 1
        $response = $this->client->post('me/subscribed_apps', $request->build());
118
119 1
        return new WebhookResponse($response);
120
    }
121
122
    /**
123
     * @return bool
124
     */
125 1
    public function isValidCallback(): bool
126
    {
127 1
        if (!$this->isValidHubSignature()) {
128
            return false;
129
        }
130
131 1
        $decodedBody = $this->getDecodedBody();
132
133 1
        $object = $decodedBody['object'] ?? null;
134 1
        $entry = $decodedBody['entry'] ?? null;
135
136 1
        return ($object === 'page' && $entry !== null);
137
    }
138
139
    /**
140
     * @return string
141
     */
142 4
    public function getBody(): string
143
    {
144 4
        if ($this->body === null) {
145 4
            $this->body = (string) $this->request->getBody();
146
        }
147
148 4
        return $this->body;
149
    }
150
151
    /**
152
     * @return array
153
     * @throws \Exception
154
     */
155 4
    public function getDecodedBody(): array
156
    {
157 4
        if ($this->decodedBody === null) {
158 4
            $decodedBody = json_decode($this->getBody(), true);
159 4
            if (json_last_error() !== JSON_ERROR_NONE || $decodedBody === null) {
160
                $decodedBody = [];
161
            }
162
163 4
            $this->decodedBody = $decodedBody;
164
        }
165
166 4
        return $this->decodedBody;
167
    }
168
169
    /**
170
     * @return \Kerox\Messenger\Model\Entry[]
171
     */
172 1
    public function getCallbackEntries(): array
173
    {
174 1
        return $this->getHydratedEntries();
175
    }
176
177
    /**
178
     * @return array
179
     */
180 1
    public function getCallbackEvents(): array
181
    {
182 1
        $events = [];
183 1
        foreach ($this->getHydratedEntries() as $hydratedEntry) {
184
            /** @var \Kerox\Messenger\Model\Callback\Entry $hydratedEntry */
185 1
            $events = array_merge($events, $hydratedEntry->getEvents());
186
        }
187
188 1
        return $events;
189
    }
190
191
    /**
192
     * @return \Kerox\Messenger\Model\Callback\Entry[]
193
     */
194 2
    private function getHydratedEntries(): array
195
    {
196 2
        if ($this->hydratedEntries === null) {
197 2
            $decodedBody = $this->getDecodedBody();
198
199 2
            $hydrated = [];
200 2
            foreach ($decodedBody['entry'] as $entry) {
201 2
                $hydrated[] = Entry::create($entry);
202
            }
203
204 2
            $this->hydratedEntries = $hydrated;
205
        }
206
207 2
        return $this->hydratedEntries;
208
    }
209
210
    /**
211
     * @return bool
212
     */
213 1
    private function isValidHubSignature(): bool
214
    {
215 1
        $headers = $this->request->getHeader('X-Hub-Signature');
216 1
        $content = $this->getBody();
217
218 1
        if (empty($headers)) {
219
            return false;
220
        }
221
222 1
        list($algorithm, $hash) = explode('=', $headers[0]);
223
224 1
        return hash_equals(hash_hmac($algorithm, $content, $this->appSecret), $hash);
225
    }
226
}
227