Completed
Push — master ( 88fe7e...615209 )
by William Johnson S.
02:00
created

HttpApi::createPunchesDate()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 9
ccs 0
cts 6
cp 0
rs 9.6666
cc 2
eloc 5
nc 2
nop 2
crap 6
1
<?php
2
3
namespace Katapoka\Ahgora;
4
5
use DOMDocument;
6
use InvalidArgumentException;
7
use Katapoka\Ahgora\Contracts\IHttpClient;
8
use Katapoka\Ahgora\Contracts\IHttpResponse;
9
10
/**
11
 * Class responsible for getting the data from the Ahgora system.
12
 */
13
class HttpApi extends AbstractApi
14
{
15
    const AHGORA_BASE_URL = 'https://www.ahgora.com.br';
16
    const AHGORA_COMPANY_URL = '%s/externo/index/%s';
17
    const AHGORA_LOGIN_URL = '%s/externo/login';
18
    const AHGORA_PUNCHS_URL = '%s/externo/batidas/%d-%d';
19
20
    /** @var \Katapoka\Ahgora\Contracts\IHttpClient */
21
    private $httpClient;
22
    /** @var string */
23
    private $password;
24
    /** @var string */
25
    private $companyId;
26
    /** @var string */
27
    private $username;
28
    /** @var bool */
29
    private $loggedIn = false;
30
31
    /**
32
     * Api constructor.
33
     *
34
     * @param IHttpClient $httpClient
35
     */
36 1
    public function __construct(IHttpClient $httpClient)
37
    {
38 1
        $this->httpClient = $httpClient;
39 1
        $this->debug('Api instance created');
40 1
    }
41
42
    /**
43
     * Set the company id of the ahgora system.
44
     *
45
     * @param string $companyId
46
     *
47
     * @return $this
48
     */
49
    public function setCompanyId($companyId)
50
    {
51
        $this->companyId = $companyId;
52
        $this->debug('Company ID set', ['company_id' => $companyId]);
53
54
        return $this;
55
    }
56
57
    /**
58
     * Set the username of the employee, from the company set at the setCompanyId.
59
     *
60
     * @param string $username
61
     *
62
     * @return $this
63
     */
64
    public function setUsername($username)
65
    {
66
        $this->username = $username;
67
        $this->debug('Username set', ['username' => $username]);
68
69
        return $this;
70
    }
71
72
    /**
73
     * Set the password of the employee, from the company set at the setCompanyId.
74
     *
75
     * @param string $password
76
     *
77
     * @return $this
78
     */
79
    public function setPassword($password)
80
    {
81
        $this->password = $password;
82
        $this->debug('Password set', ['password' => $password]);
83
84
        return $this;
85
    }
86
87
    /**
88
     * Try to execute the login on the page.
89
     * To execute some actions the user needs to be loggedin.
90
     * After a successful login, the status loggedin is saved as true.
91
     *
92
     * @return bool Returns true if the login was successful and false otherwise
93
     */
94
    public function doLogin()
95
    {
96
        $hasLoggedIn = false;
97
        $this->debug('Started login proccess');
98
99
        $accessEnabled = $this->checkAccessEnabled();
100
101
        if ($accessEnabled) {
102
            $response = $this->executeLogin();
103
            $hasLoggedIn = $this->checkLoginStatus($response);
104
        }
105
106
        $this->debug($accessEnabled ? "Company has external access enabled" : "Company hasn't external access enabled");
107
108
        $this->setLoggedIn($hasLoggedIn);
109
110
        return $hasLoggedIn;
111
    }
112
113
    /**
114
     * Get the punchs at the given parameters.
115
     *
116
     * @param int|null $month The month you want to get the punchs - Must be between 01 and 12 (both included)
117
     * @param int|null $year  The year you want to get the punchs
118
     *
119
     * @return array
120
     */
121
    public function getPunchs($month = null, $year = null)
122
    {
123
        $month = $month !== null ? $month : (int)date('m');
124
        $year = $year !== null ? $year : (int)date('Y');
125
126
        if (!$this->isValidPeriod($month, $year)) {
127
            throw new InvalidArgumentException('Invalid period of time');
128
        }
129
130
        $punchsPageResponse = $this->getPunchsPage($month, $year);
131
132
        return $this->getPunchsFromPage($punchsPageResponse);
133
    }
134
135
    /**
136
     * Gets the employer name.
137
     *
138
     * @return string
139
     */
140
    public function getEmployeeRole()
141
    {
142
        return "NOT IMPLEMENTED YET";
143
    }
144
145
    /**
146
     * Get the employer department.
147
     *
148
     * @return string
149
     */
150
    public function getDepartment()
151
    {
152
        return "NOT IMPLEMENTED YET";
153
    }
154
155
    /**
156
     * Execute the login on the server and returns the server response.
157
     *
158
     * @return IHttpResponse
159
     */
160
    private function executeLogin()
161
    {
162
        return $this->httpClient->post($this->loginUrl(), [
163
            'empresa'   => $this->companyId,
164
            'matricula' => $this->username,
165
            'senha'     => $this->password,
166
        ]);
167
    }
168
169
    /**
170
     * Check if the company has external access on the Ahgora system.
171
     *
172
     * @return bool
173
     */
174
    private function checkAccessEnabled()
175
    {
176
        $response = $this->httpClient->get($this->companyUrl());
177
178
        return stripos($response->getBody(), 'Sua Empresa não liberou o acesso a essa ferramenta') === false;
179
    }
180
181
    /**
182
     * Check the return of the login action.
183
     * How it works: If statusCode 200 and no body, login ok, otherwise, login failed.
184
     * Should return a json with property "r" with "error" and "text" with the message
185
     *
186
     * @param IHttpResponse $response
187
     *
188
     * @return bool
189
     */
190
    private function checkLoginStatus(IHttpResponse $response)
191
    {
192
        try {
193
            return $response->getHttpStatus() === IHttpClient::HTTP_STATUS_OK && $this->getResponseLoginStatus($response);
194
        } catch (InvalidArgumentException $iaex) {
195
            $this->error('checkLoginStatus', ['expcetion' => $iaex]);
196
197
            return false;
198
        }
199
    }
200
201
    /**
202
     * Check if the response can be decoded as json, has the property r and r is 'success'.
203
     *
204
     * @param IHttpResponse $response
205
     *
206
     * @return bool
207
     */
208
    private function getResponseLoginStatus(IHttpResponse $response)
209
    {
210
        try {
211
            $json = $response->json();
212
213
            return array_key_exists('r', $json) && $json->r === 'success';
214
        } catch (InvalidArgumentException $iaex) {
215
            $this->debug('getResponseLoginStatus', ['exception', $iaex]);
216
217
            return false;
218
        }
219
    }
220
221
    /**
222
     * Safely set if the user is loggedin or not.
223
     * Did a separate method do eventually trigger events, if necessary.
224
     *
225
     * @param bool $loggedIn
226
     *
227
     * @return $this
228
     */
229
    private function setLoggedIn($loggedIn = true)
230
    {
231
        if (!is_bool($loggedIn)) {
232
            throw new InvalidArgumentException('LoggedIn parameter must be boolean');
233
        }
234
235
        $this->debug('setLoggedIn', ['logged_in' => $loggedIn]);
236
        $this->loggedIn = $loggedIn;
237
238
        return $this;
239
    }
240
241
    /**
242
     * Build the company url string.
243
     *
244
     * @return string
245
     */
246
    private function companyUrl()
247
    {
248
        $companyUrl = sprintf(self::AHGORA_COMPANY_URL, self::AHGORA_BASE_URL, $this->companyId);
249
        $this->debug('CompanyURL', ['company_url' => $companyUrl]);
250
251
        return $companyUrl;
252
    }
253
254
    /**
255
     * Build the login url.
256
     *
257
     * @return string
258
     */
259
    private function loginUrl()
260
    {
261
        $loginUrl = sprintf(self::AHGORA_LOGIN_URL, self::AHGORA_BASE_URL);
262
        $this->debug('loginUrl', ['login_url' => $loginUrl]);
263
264
        return $loginUrl;
265
    }
266
267
    /**
268
     * Get the built punchsUrl with the given month and year.
269
     *
270
     * @param int $month
271
     * @param int $year
272
     *
273
     * @return string
274
     */
275
    private function punchsUrl($month, $year)
276
    {
277
        $month = str_pad($month, 2, '0', STR_PAD_LEFT);
278
        $punchsUrl = sprintf(self::AHGORA_PUNCHS_URL, self::AHGORA_BASE_URL, $month, $year);
279
        $this->debug('punchsUrl', ['punchs_url' => $punchsUrl]);
280
281
        return $punchsUrl;
282
    }
283
284
    /**
285
     * Make the request to the punchs page of the requested time period.
286
     *
287
     * @param int $month
288
     * @param int $year
289
     *
290
     * @return IHttpResponse
291
     */
292
    private function getPunchsPage($month, $year)
293
    {
294
        return $this->httpClient->get($this->punchsUrl($month, $year));
295
    }
296
297
    /**
298
     * Get the punches from the given response of the punchs page.
299
     *
300
     * @param IHttpResponse $punchsPageResponse
301
     *
302
     * @return array
303
     */
304
    private function getPunchsFromPage(IHttpResponse $punchsPageResponse)
305
    {
306
        if ($punchsPageResponse->getHttpStatus() !== IHttpClient::HTTP_STATUS_OK) {
307
            throw new InvalidArgumentException('The request returned http status ' . $punchsPageResponse->getHttpStatus());
308
        }
309
310
        $punchs = $this->parsePunchsPage($punchsPageResponse);
311
312
        return [
313
            'punchs' => $punchs,
314
        ];
315
    }
316
317
    private function parsePunchsPage(IHttpResponse $punchsPageResponse)
318
    {
319
        $punchsTableHtml = $this->getPunchsTableHtml($punchsPageResponse);
320
321
        $dom = new DOMDocument();
322
        @$dom->loadHTML($punchsTableHtml);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
323
324
        $rows = $dom->getElementsByTagName('tr');
325
326
        $punchCollection = [];
327
328
        /** @var \DOMElement $row */
329
        foreach ($rows as $row) {
330
            $cols = $row->getElementsByTagName('td');
331
            if ($cols->length !== 8) {
332
                continue;
333
            }
334
335
            $date = trim($cols->item(0)->nodeValue);
336
            $punches = $this->parsePunchs($cols->item(2)->nodeValue);
337
338
            $punchCollection = array_merge($punchCollection, $this->createPunchesDate($date, $punches));
339
        }
340
341
        return $punchCollection;
342
    }
343
344
    private function getPunchsTableHtml(IHttpResponse $punchsPageResponse)
345
    {
346
        $tables = $this->getPageTables($punchsPageResponse);
347
348
        //A primeira posição é a table
349
        return $tables['punchs'];
350
    }
351
352
    /**
353
     * Get both tables and return the strings into an array with the properties 'summary' and 'punchs'.
354
     *
355
     * @param IHttpResponse $punchsPageResponse
356
     *
357
     * @return array
358
     */
359
    private function getPageTables(IHttpResponse $punchsPageResponse)
360
    {
361
        $regex = '/<table.*?>.*?<\/table>/si';
362
363
        if (!preg_match_all($regex, $punchsPageResponse->getBody(), $matches)) {
364
            throw new InvalidArgumentException('Pattern not found in the response');
365
        }
366
367
        return [
368
            'summary' => $matches[0][0],
369
            'punchs'  => $matches[0][1],
370
        ];
371
    }
372
373
    /**
374
     * Retrive all the punches for the given string.
375
     *
376
     * @param string $punchsStr
377
     *
378
     * @return array
379
     */
380
    private function parsePunchs($punchsStr)
381
    {
382
        $punches = [];
383
        if (!!preg_match_all('/(\d{2}:\d{2})/is', $punchsStr, $matches)) {
384
            $punches = $matches[0];
385
        }
386
387
        return $punches;
388
    }
389
390
    private function createPunchesDate($date, array $punches = [])
391
    {
392
        $dates = [];
393
        foreach ($punches as $punch) {
394
            $dates[] = \DateTime::createFromFormat($this->datetimeFormat, sprintf('%s %s', $date, $punch));
395
        }
396
397
        return $dates;
398
    }
399
400
}
401