Completed
Push — master ( d2a091...3584ea )
by Romain
02:14
created

GcmComponent::_checkData()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 20
Code Lines 10

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 20
rs 8.8571
cc 5
eloc 10
nc 5
nop 1
1
<?php
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 9 and the first side effect is on line 2.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
App::uses('Component', 'Controller');
3
App::uses('HttpSocket', 'Network/Http');
4
App::uses('Hash', 'Utility');
5
6
/**
7
* Gcm Exception classes
8
*/
9
class GcmException extends CakeException {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
10
}
11
12
/**
13
 * Gcm Component
14
 *
15
 */
16
class GcmComponent extends Component {
0 ignored issues
show
Coding Style Compatibility introduced by
PSR1 recommends that each class should be in its own file to aid autoloaders.

Having each class in a dedicated file usually plays nice with PSR autoloaders and is therefore a well established practice. If you use other autoloaders, you might not want to follow this rule.

Loading history...
Coding Style Compatibility introduced by
PSR1 recommends that each class must be in a namespace of at least one level to avoid collisions.

You can fix this by adding a namespace to your class:

namespace YourVendor;

class YourClass { }

When choosing a vendor namespace, try to pick something that is not too generic to avoid conflicts with other libraries.

Loading history...
17
18
	/**
19
	 * Default options
20
	 *
21
	 * @var array
22
	 */
23
	protected $_defaults = array(
24
		'api' => array(
25
			'key' => '',
26
			'url' => 'https://gcm-http.googleapis.com/gcm/send'
27
		),
28
		'parameters' => array(
29
			'collapse_key' => null,
30
			'priority' => 'normal',
31
			'delay_while_idle' => false,
32
			'dry_run' => false,
33
			'time_to_live' => 2419200,
34
			'restricted_package_name' => null
35
		)
36
	);
37
38
	/**
39
	 * Error code and message.
40
	 *
41
	 * @var array
42
	 */
43
	protected $_errorMessages = array();
44
45
	/**
46
	 * Response of the request
47
	 *
48
	 * @var object
49
	 */
50
	protected $_response = null;
51
52
	/**
53
	 * Controller reference
54
	 */
55
	protected $Controller = null;
56
57
	/**
58
	 * A Component collection, used to get more components.
59
	 *
60
	 * @var ComponentCollection
61
	 */
62
	protected $Collection;
63
64
	/**
65
	 * Constructor
66
	 *
67
	 * @param ComponentCollection $collection
68
	 * @param array $settings
69
	 */
70
	public function __construct(ComponentCollection $collection, $settings = array()) {
71
		$this->Collection = $collection;
72
		$this->_defaults = Hash::merge($this->_defaults, $settings);
73
74
		$this->_errorMessages = array(
75
			'400' => __('Error 400. The request could not be parsed as JSON or contained invalid fields.'),
76
			'401' => __('Error 401. Unable to authenticating the sender account.'),
77
			'500' => __('Error 500. Internal Server Error.'),
78
			'503' => __('Error 503. Service Unavailable.')
79
		);
80
	}
81
82
	/**
83
	 * Called before the Controller::beforeFilter().
84
	 *
85
	 * @param Controller $controller Controller with components to initialize
86
	 * @return void
87
	 */
88
	public function initialize(Controller $controller) {
89
		$this->Controller = $controller;
90
	}
91
92
	/**
93
	 * send method
94
	 *
95
	 * @param string|array $ids
96
	 * @param array $payload
97
	 * @param array $parameters
98
	 * @return boolean
99
	 */
100
	public function send($ids = false, $payload = array(), $parameters = array()) {
101
102
		if (is_string($ids)) {
103
			$ids = (array)$ids;
104
		}
105
106
		if ($ids === false || !is_array($ids) || empty($ids)) {
107
			throw new GcmException(__("Ids must be a string or an array."));
108
		}
109
110
		if (!is_array($payload)) {
111
			throw new GcmException(__("Payload must be an array."));
112
		}
113
114
		if (!is_array($parameters)) {
115
			throw new GcmException(__("Parameters must be an array."));
116
		}
117
118
		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...
119
			$payload['notification'] = $this->_checkNotification($payload['notification']);
120
			if (!$payload['notification']) {
121
				throw new GcmException(__("Unable to check notification."));
122
			}
123
		}
124
125
		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...
126
			$payload['data'] = $this->_checkData($payload['data']);
127
			if (!$payload['data']) {
128
				throw new GcmException(__("Unable to check data."));
129
			}
130
		}
131
132
		$parameters = $this->_checkParameters($parameters);
133
		if (!$parameters) {
134
			throw new GcmException(__("Unable to check parameters."));
135
		}
136
137
		$notification = $this->_buildMessage($ids, $payload, $parameters);
138
		if ($notification === false) {
139
			throw new GcmException(__("Unable to build the message."));
140
		}
141
142
		return $this->_executePush($notification);
143
	}
144
145
	/**
146
	 * sendNotification method
147
	 *
148
	 * @param string|array $ids
149
	 * @param array $notification
150
	 * @param array $parameters
151
	 * @return boolean
152
	 */
153
	public function sendNotification($ids = false, $notification = array(), $parameters = array()) {
154
		return $this->send($ids, array('notification' => $notification), $parameters);
155
	}
156
157
	/**
158
	 * sendData method
159
	 *
160
	 * @param string|array $ids
161
	 * @param array $data
162
	 * @param array $parameters
163
	 * @return boolean
164
	 */
165
	public function sendData($ids = false, $data = array(), $parameters = array()) {
166
		return $this->send($ids, array('data' => $data), $parameters);
167
	}
168
169
	/**
170
	 * response method
171
	 *
172
	 * @return string
173
	 */
174
	public function response() {
175
		if (array_key_exists($this->_response->code, $this->_errorMessages)) {
176
			return $this->_errorMessages[$this->_response->code];
177
		}
178
179
		return json_decode($this->_response->body, true);
180
	}
181
182
	/**
183
	 * _executePush method
184
	 *
185
	 * @param json $notification
186
	 * @return bool
187
	 */
188
	protected function _executePush($notification = false) {
189
		if ($notification === false) {
190
			return false;
191
		}
192
193
		if ($this->_defaults['api']['key'] === null) {
194
			throw new GcmException(__("No API key set. Push not triggered"));
195
		}
196
197
		$httpSocket = new HttpSocket();
198
		$this->_response = $httpSocket->post($this->_defaults['api']['url'], $notification, array(
199
			'header' => array(
200
				'Authorization' => 'key=' . $this->_defaults['api']['key'],
201
				'Content-Type' => 'application/json'
202
			)
203
		));
204
205
		if ($this->_response->code === '200') {
206
			return true;
207
		}
208
209
		return false;
210
	}
211
212
	/**
213
	 * _buildMessage method
214
	 *
215
	 * @param array $ids
216
	 * @param array $payload
217
	 * @param array $parameters
218
	 * @return false|string
219
	 */
220
	protected function _buildMessage($ids = false, $payload = false, $parameters = false) {
221
		if ($ids === false) {
222
			return false;
223
		}
224
225
		$message = array('registration_ids' => $ids);
226
227
		if (!empty($payload)) {
228
			$message += $payload;
229
		}
230
231
		if (!empty($parameters)) {
232
			$message += $parameters;
233
		}
234
235
		return json_encode($message);
236
	}
237
238
	/**
239
	 * _checkNotification method
240
	 *
241
	 * @param array $notification
242
	 * @return array $notification
243
	 */
244
	protected function _checkNotification($notification = false) {
245
		if ($notification === false) {
246
			return false;
247
		}
248
249
		if (!is_array($notification)) {
250
			throw new GcmException("Notification must be an array.");
251
		}
252
253
		if (empty($notification) || !isset($notification['title'])) {
254
			throw new GcmException("Notification's array must contain at least a key title.");
255
		}
256
257
		if (!isset($notification['icon'])) {
258
			$notification['icon'] = 'myicon';
259
		}
260
261
		return $notification;
262
	}
263
264
	/**
265
	 * _checkData method
266
	 *
267
	 * @param array $data
268
	 * @return array $data
269
	 */
270
	public function _checkData($data = false) {
271
		if ($data === false) {
272
			return false;
273
		}
274
275
		if (!is_array($data)) {
276
			throw new GcmException("Data must ba an array.");
277
		}
278
279
		if (empty($data)) {
280
			throw new GcmException("Data's array can't be empty.");
281
		}
282
283
		// Convert all data into string
284
		foreach ($data as $key => $value) {
285
			$data[$key] = strval($value);
286
		}
287
288
		return $data;
289
	}
290
291
	/**
292
	 * _checkParameters method
293
	 *
294
	 * @param array $parameters
295
	 * @return array $parameters
296
	 */
297
	protected function _checkParameters($parameters = false) {
298
		if ($parameters === false) {
299
			return false;
300
		}
301
302
		$parameters = Hash::merge($this->_defaults['parameters'], $parameters);
303
		$parameters = array_filter($parameters);
304
305
		if (isset($parameters['time_to_live']) && !is_int($parameters['time_to_live'])) {
306
			$parameters['time_to_live'] = (int)$parameters['time_to_live'];
307
		}
308
309
		if (isset($parameters['delay_while_idle']) && !is_bool($parameters['delay_while_idle'])) {
310
			$parameters['delay_while_idle'] = (bool)$parameters['delay_while_idle'];
311
		}
312
313
		if (isset($parameters['dry_run']) && !is_bool($parameters['dry_run'])) {
314
			$parameters['dry_run'] = (bool)$parameters['dry_run'];
315
		}
316
317
		return $parameters;
318
	}
319
}