SmsDev::makeRequest()   A
last analyzed

Complexity

Conditions 4
Paths 3

Size

Total Lines 19
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 4

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 10
c 1
b 0
f 0
nc 3
nop 1
dl 0
loc 19
ccs 12
cts 12
cp 1
crap 4
rs 9.9332
1
<?php
2
3
namespace enricodias;
4
5
use GuzzleHttp\Client;
6
use GuzzleHttp\Psr7\Request;
7
8
/**
9
 * SmsDev.
10
 *
11
 * Send and receive SMS using SmsDev.com.br
12
 *
13
 * @see https://www.smsdev.com.br/
14
 *
15
 * @author Enrico Dias <[email protected]>
16
 */
17
class SmsDev
18
{
19
    /**
20
     * @var string
21
     */
22
    private $apiUrl = 'https://api.smsdev.com.br/v1';
23
24
    /**
25
     * @var string
26
     */
27
    private $apiKey = '';
28
29
    /**
30
     * Whether or not to validate phone numbers locally before sending.
31
     *
32
     * @var bool
33
     */
34
    private $numberValidation = true;
35
36
    /**
37
     * @var \DateTimeZone
38
     */
39
    private $apiTimeZone;
40
41
    /**
42
     * Date format to be used in all date functions.
43
     *
44
     * @var string
45
     */
46
    private $dateFormat = 'U';
47
48
    /**
49
     * Query string to be sent to the API as a search filter.
50
     *
51
     * The default 'status' = 1 will return all received messages.
52
     *
53
     * @var array
54
     */
55
    private $query = [
56
        'status' => 1
57
    ];
58
59
    /**
60
     * Raw API response.
61
     *
62
     * @var array
63
     */
64
    private $_result = [];
65
66
    /**
67
     * Creates a new SmsDev instance with an API key and sets the default API timezone.
68
     *
69
     * @param string $apiKey
70
     */
71 53
    public function __construct($apiKey = '')
72
    {
73 53
        if ($apiKey === '' && \array_key_exists('SMSDEV_API_KEY', $_SERVER) === true) {
74 1
            $apiKey = $_SERVER['SMSDEV_API_KEY'];
75 1
        }
76
77 53
        $this->apiKey = $apiKey;
78
79 53
        $this->apiTimeZone = new \DateTimeZone('America/Sao_Paulo');
80 53
    }
81
82
    /**
83
     * Send an SMS message.
84
     *
85
     * This method does not guarantee that the recipient received the massage since the message delivery is async.
86
     *
87
     * @param int $number
88
     * @param string $message
89
     * @param string $refer (optional) User reference for message identification.
90
     *
91
     * @return bool true if the API accepted the request.
92
     */
93 35
    public function send($number, $message, $refer = null)
94
    {
95 35
        $this->_result = [];
96
97 35
        if ($this->numberValidation === true) {
98
            try {
99 20
                $number = $this->validatePhoneNumber($number);
100 20
            } catch (\Exception $e) {
101 18
                return false;
102
            } catch (\Throwable $e) {
103
                return false;
104
            }
105 2
        }
106
107
        $params = [
108 17
            'key'    => $this->apiKey,
109 17
            'type'   => 9,
110 17
            'number' => $number,
111 17
            'msg'    => $message,
112 17
        ];
113
114 17
        if ($refer) $params['refer'] = $refer;
115
116 17
        $request = new Request(
117 17
            'POST',
118 17
            $this->apiUrl.'/send',
119
            [
120 17
                'Accept' => 'application/json',
121 17
            ],
122 17
            \json_encode($params)
123 17
        );
124
125 17
        if ($this->makeRequest($request) === false || $this->_result['situacao'] !== 'OK') {
126 9
            return false;
127
        }
128
129 8
        return true;
130
    }
131
132
    /**
133
     * Enables or disables the phone number validation.
134
     *
135
     * @param bool $shouldValidate
136
     *
137
     * @return void
138
     */
139 15
    public function setNumberValidation($shouldValidate = true)
140
    {
141 15
        $this->numberValidation = (bool) $shouldValidate;
142 15
    }
143
144
    /**
145
     * Sets the date format to be used in all date functions.
146
     *
147
     * @param string $dateFormat A valid date format (ex: Y-m-d).
148
     * @return SmsDev
149
     */
150 9
    public function setDateFormat($dateFormat)
151
    {
152 9
        $this->dateFormat = $dateFormat;
153
154 9
        return $this;
155
    }
156
157
    /**
158
     * Resets the search filter.
159
     *
160
     * @return SmsDev
161
     */
162 11
    public function setFilter()
163
    {
164 11
        $this->query = [
165 11
            'status' => 1,
166
        ];
167
168 11
        return $this;
169
    }
170
171
    /**
172
     * Sets the search filter to return unread messages only.
173
     *
174
     * @return SmsDev
175
     */
176 3
    public function isUnread()
177
    {
178 3
        $this->query['status'] = 0;
179
180 3
        return $this;
181
    }
182
183
    /**
184
     * Sets the search filter to return a message with a specific id.
185
     *
186
     * @param int $id
187
     *
188
     * @return SmsDev
189
     */
190 3
    public function byId($id)
191
    {
192 3
        $id = \intval($id);
193
194 3
        if ($id > 0) {
195 3
            $this->query['id'] = $id;
196 3
        }
197
198 3
        return $this;
199
    }
200
201
    /**
202
     * Sets the search filter to return messages older than a specific date.
203
     *
204
     * @param string $date
205
     *
206
     * @return SmsDev
207
     */
208 5
    public function dateFrom($date)
209
    {
210 5
        return $this->parseDate('date_from', $date);
211
    }
212
213
    /**
214
     * Sets the search filter to return messages newer than a specific date.
215
     *
216
     * @param string $date
217
     *
218
     * @return SmsDev
219
     */
220 4
    public function dateTo($date)
221
    {
222 4
        return $this->parseDate('date_to', $date);
223
    }
224
225
    /**
226
     * Sets the search filter to return messages between a specific date interval.
227
     *
228
     * @param string $dateFrom
229
     * @param string $dateTo
230
     *
231
     * @return SmsDev
232
     */
233 2
    public function dateBetween($dateFrom, $dateTo)
234
    {
235 2
        return $this->dateFrom($dateFrom)->dateTo($dateTo);
236
    }
237
238
    /**
239
     * Query the API for received messages using search filters.
240
     *
241
     * @see SmsDev::$query Search filters.
242
     * @see SmsDev::$_result API response.
243
     *
244
     * @return bool True if the request was successful.
245
     */
246 13
    public function fetch()
247
    {
248 13
        $this->_result = [];
249
250 13
        $this->query['key'] = $this->apiKey;
251
252 13
        $request = new Request(
253 13
            'GET',
254 13
            $this->apiUrl.'/inbox',
255
            [
256 13
                'Accept' => 'application/json',
257 13
            ],
258 13
            \json_encode(
259 13
                $this->query
260 13
            )
261 13
        );
262
263 13
        if ($this->makeRequest($request) === false) {
264 9
            return false;
265
        }
266
267
        // resets the filters
268 4
        $this->setFilter();
269
270 4
        if (\is_array($this->_result) === true) {
271 4
            return true;
272
        }
273
274
        return false;
275
    }
276
277
    /**
278
     * Parse the received messages in a more useful format with the fields date, number and message.
279
     *
280
     * The dates received by the API are converted to SmsDev::$dateFormat.
281
     *
282
     * @see SmsDev::$dateFormat Date format to be used in all date functions.
283
     *
284
     * @return array List of received messages.
285
     */
286 5
    public function parsedMessages()
287
    {
288 5
        $localTimeZone = new \DateTimeZone(\date_default_timezone_get());
289
290 5
        $messages = [];
291
292 5
        foreach ($this->_result as $key => $result) {
293 3
            if (\is_array($result) === false || \array_key_exists('id_sms_read', $result) === false) {
294 1
                continue;
295
            }
296
297 2
            $id = $result['id_sms_read'];
298 2
            $date = \DateTime::createFromFormat('d/m/Y H:i:s', $result['data_read'], $this->apiTimeZone);
299
300 2
            $date->setTimezone($localTimeZone);
301
302 2
            $messages[$id] = [
303 2
                'date'    => $date->format($this->dateFormat),
304 2
                'number'  => $result['telefone'],
305 2
                'message' => $result['descricao'],
306
            ];
307 5
        }
308
309 5
        return $messages;
310
    }
311
312
    /**
313
     * Get the current balance/credits.
314
     *
315
     * @return int Current balance in BRL cents.
316
     */
317 6
    public function getBalance()
318
    {
319 6
        $this->_result = [];
320
321 6
        $request = new Request(
322 6
            'GET',
323 6
            $this->apiUrl.'/balance',
324
            [
325 6
                'Accept' => 'application/json',
326 6
            ],
327 6
            \json_encode([
328 6
                'key'    => $this->apiKey,
329 6
                'action' => 'saldo',
330 6
            ])
331 6
        );
332
333 6
        $this->makeRequest($request);
334
335 6
        if (\array_key_exists('saldo_sms', $this->_result) === false) {
336 5
            return 0;
337
        }
338
339 1
        return (int) $this->_result['saldo_sms'];
340
    }
341
342
    /**
343
     * Get the raw API response from the last response received.
344
     *
345
     * @see SmsDev::$_result Raw API response.
346
     *
347
     * @return array Raw API response.
348
     */
349 2
    public function getResult()
350
    {
351 2
        return $this->_result;
352
    }
353
354
    /**
355
     * Verifies if a phone number is valid.
356
     *
357
     * @see https://github.com/giggsey/libphonenumber-for-php libphonenumber for PHP repository.
358
     *
359
     * @param int $number
360
     *
361
     * @return int A valid mobile phone number.
362
     *
363
     * @throws \libphonenumber\NumberParseException If the number is not valid.
364
     * @throws \Exception If the number is not a valid brazilian mobile number.
365
     */
366 20
    private function validatePhoneNumber($number)
367
    {
368 20
        if (\class_exists('\libphonenumber\PhoneNumberUtil') === true) {
369 20
            $phoneNumberUtil = /** @scrutinizer ignore-call */ \libphonenumber\PhoneNumberUtil::getInstance();
370 20
            $mobilePhoneNumber = /** @scrutinizer ignore-call */ \libphonenumber\PhoneNumberType::MOBILE;
371
372 20
            $phoneNumberObject = $phoneNumberUtil->parse($number, 'BR');
373
374 15
            if ($phoneNumberUtil->isValidNumber($phoneNumberObject) === false || $phoneNumberUtil->getNumberType($phoneNumberObject) !== $mobilePhoneNumber) {
375 13
                throw new \Exception('Invalid phone number.');
376
            }
377
378 2
            $number = $phoneNumberObject->getCountryCode().$phoneNumberObject->getNationalNumber();
379 2
        }
380
381 2
        return (int) $number;
382
    }
383
384
    /**
385
     * Convert a date to format supported by the API.
386
     *
387
     * The API requires the date format d/m/Y, but in this class any valid date format is supported.
388
     * Since the API is always using the timezone America/Sao_Paulo, this function must also do timezone conversions.
389
     *
390
     * @see SmsDev::$dateFormat Date format to be used in all date functions.
391
     *
392
     * @param string $key The filter key to be set as a search filter.
393
     * @param string $date
394
     *
395
     * @return SmsDev
396
     */
397 5
    private function parseDate($key, $date)
398
    {
399 5
        $parsedDate = \DateTime::createFromFormat($this->dateFormat, $date);
400
401 5
        if ($parsedDate !== false) {
402 5
            $parsedDate->setTimezone($this->apiTimeZone);
403
404 5
            $this->query[$key] = $parsedDate->format('d/m/Y');
405 5
        }
406
407 5
        return $this;
408
    }
409
410
    /**
411
     * Sends a request to the smsdev.com.br API.
412
     *
413
     * @param \GuzzleHttp\Psr7\Request $request
414
     *
415
     * @return bool
416
     */
417 35
    private function makeRequest($request)
418
    {
419 35
        $client = $this->getGuzzleClient();
420
421 1
        try {
422 35
            $response = $client->send($request);
423 35
        } catch (\Exception $e) {
424 1
            return false;
425 1
        }
426
427 35
        $response = \json_decode($response->getBody(), true);
428
429 35
        if (\json_last_error() !== JSON_ERROR_NONE || \is_array($response) === false) {
430 15
            return false;
431
        }
432
433 20
        $this->_result = $response;
434
435 20
        return true;
436
    }
437
438
    /**
439
     * Creates GuzzleHttp\Client to be used in API requests.
440
     * This method is needed to test API calls in unit tests.
441
     *
442
     * @return object \GuzzleHttp\Client
443
     *
444
     * @codeCoverageIgnore
445
     */
446
    protected function getGuzzleClient()
447
    {
448
        return new Client();
449
    }
450
}
451