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