Completed
Push — master ( 0dcfc2...68430c )
by Romain
10:26
created

FcmAdapter::_checkTokens()   B

Complexity

Conditions 5
Paths 3

Size

Total Lines 15
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 15
rs 8.8571
c 0
b 0
f 0
cc 5
eloc 8
nc 3
nop 1
1
<?php
2
3
namespace ker0x\Push\Adapter;
4
5
use Cake\Http\Client;
6
use Cake\Utility\Hash;
7
use ker0x\Push\AdapterInterface;
8
use ker0x\Push\Exception;
9
10
class FcmAdapter implements AdapterInterface
11
{
12
13
    use InstanceConfigTrait;
14
15
    /**
16
     * Default config
17
     *
18
     * @var array
19
     */
20
    protected $_defaultConfig = [
21
        'api' => [
22
            'key' => null,
23
            'url' => 'https://fcm.googleapis.com/fcm/send',
24
        ],
25
        'parameters' => [
26
            'collapse_key' => null,
27
            'priority' => 'normal',
28
            'delay_while_idle' => false,
29
            'dry_run' => false,
30
            'time_to_live' => 0,
31
            'restricted_package_name' => null,
32
        ],
33
        'http' => [],
34
    ];
35
36
    /**
37
     * List of parameters available to use in notification messages.
38
     *
39
     * @var array
40
     */
41
    protected $_allowedNotificationParameters = [
42
        'title',
43
        'body',
44
        'icon',
45
        'sound',
46
        'badge',
47
        'tag',
48
        'color',
49
        'click_action',
50
        'body_loc_key',
51
        'body_loc_args',
52
        'title_loc_key',
53
        'title_loc_args',
54
    ];
55
56
    /**
57
     * Error code and message.
58
     *
59
     * @var array
60
     */
61
    protected $_errorMessages = [];
62
63
    /**
64
     * Response of the request
65
     *
66
     * @var object
67
     */
68
    protected $_response = null;
69
70
    /**
71
     * Constructor
72
     *
73
     * @param array $config Array of configuration settings
74
     */
75
    public function __construct(array $config = [])
76
    {
77
        $this->config($config);
78
79
        $this->_errorMessages = [
80
            '400' => __('Error 400. The request could not be parsed as JSON.'),
81
            '401' => __('Error 401. Unable to authenticating the sender account.'),
82
            '500' => __('Error 500. Internal Server Error.'),
83
            '503' => __('Error 503. Service Unavailable.'),
84
        ];
85
    }
86
87
    /**
88
     * @inheritdoc
89
     */
90
    public function send($tokens = null, array $payload = [], array $parameters = [])
91
    {
92
        $tokens = $this->_checkTokens($tokens);
93
94
        if (!is_array($payload)) {
95
            throw new Exception(__('Payload must be an array.'));
96
        }
97
98
        if (isset($payload['notification'])) {
99
            $payload['notification'] = $this->_checkNotification($payload['notification']);
100
        }
101
102
        if (isset($payload['data'])) {
103
            $payload['data'] = $this->_checkData($payload['data']);
104
        }
105
106
        $parameters = $this->_checkParameters($parameters);
107
108
        $message = $this->_buildMessage($tokens, $payload, $parameters);
109
110
        return $this->_executePush($message);
111
    }
112
113
    /**
114
     * @inheritdoc
115
     */
116
    public function response()
117
    {
118
        if ($this->_response->code !== '200') {
119
            $errorMessage = __('There was an error while sending the push');
120
            if (array_key_exists($this->_response->code, $this->_errorMessages)) {
121
                $errorMessage = $this->_errorMessages[$this->_response->code];
122
            }
123
            throw new Exception($errorMessage);
124
        }
125
126
        return json_decode($this->_response->body, true);
127
    }
128
129
    /**
130
     * Send the message throught a POST request to the GCM servers
131
     *
132
     * @param string $message The message to send
133
     * @throws Exception
134
     * @return bool
135
     */
136
    protected function _executePush($message)
137
    {
138
        $options = $this->_getHttpOptions();
139
140
        $http = new Client();
141
        $this->_response = $http->post($this->config('api.url'), $message, $options);
142
143
        return ($this->_response->code === '200') ? true : false;
144
    }
145
146
    /**
147
     * Build the message from the ids, payload and parameters
148
     *
149
     * @param array|string $tokens Devices'ids
150
     * @param array $payload The notification and/or some datas
151
     * @param array $parameters Parameters for the GCM request
152
     * @return string
153
     */
154
    protected function _buildMessage($tokens, $payload, $parameters)
155
    {
156
        $message = (count($tokens) > 1) ? ['registration_ids' => $tokens] : ['to' => current($tokens)];
157
158
        if (!empty($payload)) {
159
            $message += $payload;
160
        }
161
162
        if (!empty($parameters)) {
163
            $message += $parameters;
164
        }
165
166
        return json_encode($message);
167
    }
168
169
    /**
170
     * Check if the tokens are correct
171
     *
172
     * @param mixed $tokens Device's token
173
     * @throws Exception
174
     * @return array
175
     */
176
    protected function _checkTokens($tokens)
177
    {
178
        if (!is_string($tokens) || !is_array($tokens)) {
179
            throw new Exception(__('Tokens must be a string or an array with at least 1 token.'));
180
        }
181
182
        $tokens = (array) $tokens;
183
184
        $totalTokens = count($tokens);
185
        if ($totalTokens === 0 || $totalTokens > 1000) {
186
            throw new Exception(__('Tokens must contain at least 1 and at most 1000 registration tokens.'));
187
        }
188
189
        return $tokens;
190
    }
191
192
    /**
193
     * Check if the notification array is correctly build
194
     *
195
     * @param array $notification The notification
196
     * @throws Exception
197
     * @return array $notification
198
     */
199
    protected function _checkNotification(array $notification = [])
200
    {
201
        if (!is_array($notification)) {
202
            throw new Exception('Notification must be an array.');
203
        }
204
205
        if (empty($notification) || !isset($notification['title'])) {
206
            throw new Exception('Notification\'s array must contain at least a key title.');
207
        }
208
209
        if (!isset($notification['icon'])) {
210
            $notification['icon'] = 'myicon';
211
        }
212
213
        foreach ($notification as $key => $value) {
214
            if (!in_array($key, $this->_allowedNotificationParameters)) {
215
                throw new Exception("The key {$key} is not allowed in notifications.");
216
            }
217
        }
218
219
        return $notification;
220
    }
221
222
    /**
223
     * Check if the data array is correctly build
224
     *
225
     * @param array $data Some datas
226
     * @throws Exception
227
     * @return array $data
228
     */
229
    protected function _checkData(array $data = [])
230
    {
231
        if (!is_array($data)) {
232
            throw new Exception('Data must ba an array.');
233
        }
234
235
        if (empty($data)) {
236
            throw new Exception('Data\'s array can\'t be empty.');
237
        }
238
239
        // Convert all data into string
240
        foreach ($data as $key => $value) {
241
            $data[$key] = (string) $value;
242
        }
243
244
        return $data;
245
    }
246
247
    /**
248
     * Check the parameters for the message
249
     *
250
     * @param array $parameters Parameters for the GCM request
251
     * @throws Exception
252
     * @return array $parameters
253
     */
254
    protected function _checkParameters(array $parameters = [])
255
    {
256
        if (!is_array($parameters)) {
257
            throw new Exception(__('Parameters must be an array.'));
258
        }
259
260
        $parameters = Hash::merge($this->config('parameters'), $parameters);
261
262
        if (isset($parameters['time_to_live']) && !is_int($parameters['time_to_live'])) {
263
            $parameters['time_to_live'] = (int) $parameters['time_to_live'];
264
        }
265
266
        if (isset($parameters['delay_while_idle']) && !is_bool($parameters['delay_while_idle'])) {
267
            $parameters['delay_while_idle'] = (bool) $parameters['delay_while_idle'];
268
        }
269
270
        if (isset($parameters['dry_run']) && !is_bool($parameters['dry_run'])) {
271
            $parameters['dry_run'] = (bool) $parameters['dry_run'];
272
        }
273
274
        return $parameters;
275
    }
276
277
    /**
278
     * Return options for the HTTP request
279
     *
280
     * @throws Exception
281
     * @return array $options
282
     */
283
    protected function _getHttpOptions()
284
    {
285
        if ($this->config('api.key') === null) {
286
            throw new Exception(__('No API key set. Push not triggered'));
287
        }
288
289
        $options = Hash::merge($this->config('http'), [
290
            'type' => 'json',
291
            'headers' => [
292
                'Authorization' => 'key=' . $this->config('api.key'),
293
                'Content-Type' => 'application/json',
294
            ],
295
        ]);
296
297
        return $options;
298
    }
299
300
}
301