Passed
Pull Request — master (#16)
by Enrico
09:43
created

SmsDev::dateBetween()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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