Completed
Branch develop (410e9a)
by Romain
03:11
created

GcmComponent::_executePush()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 21
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
c 4
b 0
f 0
dl 0
loc 21
rs 9.3142
cc 3
eloc 12
nc 3
nop 1
1
<?php
2
/**
3
 * CakePHP(tm) : Rapid Development Framework (http://cakephp.org)
4
 * Copyright (c) Romain Monteil
5
 *
6
 * Licensed under The MIT License
7
 * For full copyright and license information, please see the LICENSE.txt
8
 * Redistributions of files must retain the above copyright notice.
9
 *
10
 * @copyright     Copyright (c) Romain Monteil
11
 * @link          http://cakephp.org CakePHP(tm) Project
12
 * @license       http://www.opensource.org/licenses/mit-license.php MIT License
13
 */
14
namespace CakeGcm\Controller\Component;
15
16
use Cake\Controller\Component;
17
use Cake\Controller\ComponentRegistry;
18
use Cake\Network\Http\Client;
19
use Cake\Utility\Hash;
20
21
/**
22
 * Gcm Component
23
 *
24
 */
25
class GcmComponent extends Component
26
{
27
28
    /**
29
     * Default config
30
     *
31
     * @var array
32
     */
33
    protected $_defaultConfig = [
34
        'api' => [
35
            'key' => null,
36
            'url' => 'https://gcm-http.googleapis.com/gcm/send'
37
        ],
38
        'parameters' => [
39
            'collapse_key' => null,
40
            'priority' => 'normal',
41
            'delay_while_idle' => false,
42
            'dry_run' => false,
43
            'time_to_live' => 0,
44
            'restricted_package_name' => null
45
        ]
46
    ];
47
48
    /**
49
     * List of parameters available to use in notification messages.
50
     *
51
     * @var array
52
     */
53
    protected $_allowedNotificationParameters = [
54
        'title',
55
        'body',
56
        'icon',
57
        'sound',
58
        'badge',
59
        'tag',
60
        'color',
61
        'click_action',
62
        'body_loc_key',
63
        'body_loc_args',
64
        'title_loc_key',
65
        'title_loc_args'
66
    ];
67
68
    /**
69
     * Error code and message.
70
     *
71
     * @var array
72
     */
73
    protected $_errorMessages = [];
74
75
    /**
76
     * Response of the request
77
     *
78
     * @var object
79
     */
80
    protected $_response = null;
81
82
    /**
83
     * Constructor
84
     *
85
     * @param ComponentRegistry $registry A ComponentRegistry
86
     * @param array $config Array of configuration settings
87
     */
88
    public function __construct(ComponentRegistry $registry, array $config = [])
89
    {
90
        parent::__construct($registry, $config);
91
        $this->_errorMessages = [
92
            '400' => __('Error 400. The request could not be parsed as JSON.'),
93
            '401' => __('Error 401. Unable to authenticating the sender account.'),
94
            '500' => __('Error 500. Internal Server Error.'),
95
            '503' => __('Error 503. Service Unavailable.')
96
        ];
97
    }
98
99
    /**
100
     * send method
101
     *
102
     * @param string|array $ids
103
     * @param array $payload
104
     * @param array $parameters
105
     * @throws Exception
106
     * @return boolean
107
     */
108
    public function send($ids = null, array $payload = [], array $parameters = [])
109
    {
110
        if (!is_string($ids) || !is_array($ids) || empty($ids)) {
111
            throw new Exception(__('Ids must be a string or an array with at least 1 token.'));
112
        }
113
114
        if (is_string($ids)) {
115
            $ids = (array)$ids;
116
        }
117
118
        if (is_array($ids) && count($ids) > 1000) {
119
            throw new Exception(__('Ids must contain at least 1 and at most 1000 registration tokens.'));
120
        }
121
122
        if (!is_array($payload)) {
123
            throw new Exception(__('Payload must be an array.'));
124
        }
125
126
        if (!is_array($parameters)) {
127
            throw new Exception(__('Parameters must be an array.'));
128
        }
129
130 View Code Duplication
        if (isset($payload['notification'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
131
            $payload['notification'] = $this->_checkNotification($payload['notification']);
132
            if (!$payload['notification']) {
133
                throw new Exception(__("Unable to check notification."));
134
            }
135
        }
136
137 View Code Duplication
        if (isset($payload['data'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
138
            $payload['data'] = $this->_checkData($payload['data']);
139
            if (!$payload['data']) {
140
                throw new Exception(__("Unable to check data."));
141
            }
142
        }
143
144
        $parameters = $this->_checkParameters($parameters);
145
        if (!$parameters) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $parameters of type array<string,null|integer|boolean> is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
146
            throw new Exception(__('Unable to check parameters.'));
147
        }
148
149
        $message = $this->_buildMessage($ids, $payload, $parameters);
150
151
        return $this->_executePush($message);
152
    }
153
154
    /**
155
     * sendNotification method
156
     *
157
     * @param string|array $ids
158
     * @param array $notification
159
     * @param array $parameters
160
     * @return boolean
161
     */
162
    public function sendNotification($ids = null, array $notification = [], array $parameters = [])
163
    {
164
        return $this->send($ids, ['notification' => $notification], $parameters);
165
    }
166
167
    /**
168
     * sendData method
169
     *
170
     * @param string|array $ids
171
     * @param array $data
172
     * @param array $parameters
173
     * @return boolean
174
     */
175
    public function sendData($ids = null, array $data = [], array $parameters = [])
176
    {
177
        return $this->send($ids, ['data' => $data], $parameters);
178
    }
179
180
    /**
181
     * response method
182
     *
183
     * @return string
184
     */
185
    public function response()
186
    {
187
        if (array_key_exists($this->_response->code, $this->_errorMessages)) {
188
            return $this->_errorMessages[$this->_response->code];
189
        }
190
191
        return json_decode($this->_response->body, true);
192
    }
193
194
    /**
195
     * _executePush method
196
     *
197
     * @param string $message
198
     * @throws Exception
199
     * @return boolean
200
     */
201
    protected function _executePush($message)
202
    {
203
        if ($this->config('api.key') === null) {
204
            throw new Exception(__('No API key set. Push not triggered'));
205
        }
206
207
        $http = new Client();
208
        $this->_response = $http->post($this->config('api.url'), $message, [
209
            'type' => 'json',
210
            'headers' => [
211
                'Authorization' => 'key=' . $this->config('api.key'),
212
                'Content-Type' => 'application/json'
213
            ]
214
        ]);
215
216
        if ($this->_response->code === '200') {
217
            return true;
218
        }
219
220
        return false;
221
    }
222
223
    /**
224
     * _buildMessage method
225
     *
226
     * @param array|string $ids
227
     * @param array $payload
228
     * @param array $parameters
229
     * @return string
230
     */
231
    protected function _buildMessage($ids, $payload, $parameters)
232
    {
233
        $message = (count($ids) > 1) ? ['registration_ids' => $ids] : ['to' => current($ids)];
234
235
        if (!empty($payload)) {
236
            $message += $payload;
237
        }
238
239
        if (!empty($parameters)) {
240
            $message += $parameters;
241
        }
242
243
        return json_encode($message);
244
    }
245
246
    /**
247
     * _checkNotification method
248
     *
249
     * @param array $notification
250
     * @throws Exception
251
     * @return array $notification
252
     */
253
    protected function _checkNotification(array $notification = [])
254
    {
255
        if (!is_array($notification)) {
256
            throw new Exception('Notification must be an array.');
257
        }
258
259
        if (empty($notification) || !isset($notification['title'])) {
260
            throw new Exception('Notification\'s array must contain at least a key title.');
261
        }
262
263
        if (!isset($notification['icon'])) {
264
            $notification['icon'] = 'myicon';
265
        }
266
267
        foreach ($notification as $key => $value) {
268
            if (!in_array($key, $this->_allowedNotificationParameters)) {
269
                throw new Exception("The key {$key} is not allowed in notifications.");
270
            }
271
        }
272
273
        return $notification;
274
    }
275
276
    /**
277
     * _checkData method
278
     *
279
     * @param array $data
280
     * @throws Exception
281
     * @return array $data
282
     */
283
    public function _checkData(array $data = [])
284
    {
285
        if (!is_array($data)) {
286
            throw new Exception('Data must ba an array.');
287
        }
288
289
        if (empty($data)) {
290
            throw new Exception('Data\'s array can\'t be empty.');
291
        }
292
293
        // Convert all data into string
294
        foreach ($data as $key => $value) {
295
            $data[$key] = (string)$value;
296
        }
297
298
        return $data;
299
    }
300
301
    /**
302
     * _checkParameters method
303
     *
304
     * @param array $parameters
305
     * @return array $parameters
306
     */
307
    protected function _checkParameters(array $parameters = [])
308
    {
309
        $parameters = Hash::merge($this->config('parameters'), $parameters);
310
        $parameters = array_filter($parameters);
311
312
        if (isset($parameters['time_to_live']) && !is_int($parameters['time_to_live'])) {
313
            $parameters['time_to_live'] = (int)$parameters['time_to_live'];
314
        }
315
316
        if (isset($parameters['delay_while_idle']) && !is_bool($parameters['delay_while_idle'])) {
317
            $parameters['delay_while_idle'] = (bool)$parameters['delay_while_idle'];
318
        }
319
320
        if (isset($parameters['dry_run']) && !is_bool($parameters['dry_run'])) {
321
            $parameters['dry_run'] = (bool)$parameters['dry_run'];
322
        }
323
324
        return $parameters;
325
    }
326
}
327