Passed
Pull Request — master (#8)
by Enrico
06:53
created

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