Completed
Push — develop ( c4ec59...c4a518 )
by Romain
03:01
created

GcmComponent::_getHttpOptions()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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