Passed
Pull Request — master (#921)
by
unknown
08:01
created

Tools::model()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 3

Importance

Changes 0
Metric Value
eloc 3
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 10
c 0
b 0
f 0
cc 3
nc 2
nop 1
crap 3
1
<?php
2
3
/**
4
 * Class base responsible for communication with SEFAZ
5
 *
6
 * @category  NFePHP
7
 * @package   NFePHP\NFe\Common\Tools
8
 * @copyright NFePHP Copyright (c) 2008-2019
9
 * @license   http://www.gnu.org/licenses/lgpl.txt LGPLv3+
10
 * @license   https://opensource.org/licenses/MIT MIT
11
 * @license   http://www.gnu.org/licenses/gpl.txt GPLv3+
12
 * @author    Roberto L. Machado <linux.rlm at gmail dot com>
13
 * @link      http://github.com/nfephp-org/sped-nfe for the canonical source repository
14
 */
15
16
namespace NFePHP\NFe\Common;
17
18
use DOMDocument;
19
use InvalidArgumentException;
20
use RuntimeException;
21
use NFePHP\Common\Certificate;
22
use NFePHP\Common\Signer;
23
use NFePHP\Common\Soap\SoapCurl;
24
use NFePHP\Common\Soap\SoapInterface;
25
use NFePHP\Common\Strings;
26
use NFePHP\Common\TimeZoneByUF;
27
use NFePHP\Common\UFList;
28
use NFePHP\Common\Validator;
29
use NFePHP\NFe\Factories\Contingency;
30
use NFePHP\NFe\Factories\ContingencyNFe;
31
use NFePHP\NFe\Factories\Header;
32
use NFePHP\NFe\Factories\QRCode;
33
34
class Tools
35
{
36
    /**
37
     * config class
38
     * @var \stdClass
39
     */
40
    public $config;
41
    /**
42
     * Path to storage folder
43
     * @var string
44
     */
45
    public $pathwsfiles = '';
46
    /**
47
     * Path to schemes folder
48
     * @var string
49
     */
50
    public $pathschemes = '';
51
    /**
52
     * ambiente
53
     * @var string
54
     */
55
    public $ambiente = 'homologacao';
56
    /**
57
     * Environment
58
     * @var int
59
     */
60
    public $tpAmb = 2;
61
    /**
62
     * contingency class
63
     * @var Contingency
64
     */
65
    public $contingency;
66
    /**
67
     * soap class
68
     * @var ?SoapInterface
69
     */
70
    public $soap;
71
    /**
72
     * Application version
73
     * @var string
74
     */
75
    public $verAplic = '';
76
    /**
77
     * last soap request
78
     * @var string
79
     */
80
    public $lastRequest = '';
81
    /**
82
     * last soap response
83
     * @var string
84
     */
85
    public $lastResponse = '';
86
    /**
87
     * certificate class
88
     * @var Certificate
89
     */
90
    protected $certificate;
91
    /**
92
     * Sign algorithm from OPENSSL
93
     * @var int
94
     */
95
    protected $algorithm = OPENSSL_ALGO_SHA1;
96
    /**
97
     * Canonical conversion options
98
     * @var array
99
     */
100
    protected $canonical = [true,false,null,null];
101
    /**
102
     * Model of NFe 55 or 65
103
     * @var int
104
     */
105
    protected $modelo = 55;
106
    /**
107
     * Version of layout
108
     * @var string
109
     */
110
    protected $versao = '4.00';
111
    /**
112
     * urlPortal
113
     * Instância do WebService
114
     *
115
     * @var string
116
     */
117
    protected $urlPortal = 'http://www.portalfiscal.inf.br/nfe';
118
    /**
119
     * urlcUF
120
     * @var int
121
     */
122
    protected $urlcUF;
123
    /**
124
     * urlVersion
125
     * @var string
126
     */
127
    protected $urlVersion = '';
128
    /**
129
     * urlService
130
     * @var string
131
     */
132
    protected $urlService = '';
133
    /**
134
     * @var string
135
     */
136
    protected $urlMethod = '';
137
    /**
138
     * @var string
139
     */
140
    protected $urlOperation = '';
141
    /**
142
     * @var string
143
     */
144
    protected $urlNamespace = '';
145
    /**
146
     * @var string
147
     */
148
    protected $urlAction = '';
149
    /**
150
     * @var \SoapHeader | null
151
     */
152
    protected $objHeader = null;
153
    /**
154
     * @var string
155
     */
156
    protected $urlHeader = '';
157
    /**
158
     * @var array
159
     */
160
    protected $soapnamespaces = [
161
        'xmlns:xsi' => "http://www.w3.org/2001/XMLSchema-instance",
162
        'xmlns:xsd' => "http://www.w3.org/2001/XMLSchema",
163
        'xmlns:soap' => "http://www.w3.org/2003/05/soap-envelope"
164
    ];
165
    /**
166
     * @var array
167
     */
168
    protected $availableVersions = ['4.00' => 'PL_009_V4'];
169
    /**
170
     * @var string
171
     */
172
    protected $typePerson = 'J';
173
    /**
174
     * @var string
175
     */
176
    protected $timezone;
177
178
    /**
179
     * Loads configurations and Digital Certificate, map all paths, set timezone and instanciate Contingency::class
180
     * @param string $configJson content of config in json format
181
     * @param Certificate $certificate
182
     * @param Contingency|null $contingency
183
     */
184 65
    public function __construct($configJson, Certificate $certificate, Contingency $contingency = null)
185
    {
186 65
        $this->pathwsfiles = realpath(__DIR__ . '/../../storage') . '/';
187
        //valid config json string
188 65
        $this->config = Config::validate($configJson);
189 65
        $this->version($this->config->versao);
190 65
        $this->setEnvironmentTimeZone($this->config->siglaUF);
191 65
        $this->certificate = $certificate;
192 65
        $this->typePerson = $this->getTypeOfPersonFromCertificate();
193 65
        $this->setEnvironment($this->config->tpAmb);
194 65
        if (empty($contingency)) {
195 65
            $this->contingency = new Contingency();
196
        }
197 65
    }
198
199
    /**
200
     * Sets environment time zone
201
     * @param string $acronym (ou seja a sigla do estado)
202
     * @return void
203
     */
204 65
    public function setEnvironmentTimeZone($acronym)
205
    {
206 65
        $this->timezone = TimeZoneByUF::get($acronym);
207 65
    }
208
209
    /**
210
     * Return J or F from existing type in ASN.1 certificate
211
     * J - pessoa juridica (CNPJ)
212
     * F - pessoa física (CPF)
213
     * @return string
214
     */
215 65
    public function getTypeOfPersonFromCertificate()
216
    {
217 65
        $cnpj = $this->certificate->getCnpj();
218 65
        $type = 'J';
219 65
        if (empty($cnpj)) {
220
            //não é CNPJ, então verificar se é CPF
221 65
            $cpf = $this->certificate->getCpf();
222 65
            if (!empty($cpf)) {
223
                $type = 'F';
224
            } else {
225
                //não foi localizado nem CNPJ e nem CPF esse certificado não é usável
226
                //throw new RuntimeException('Faltam elementos CNPJ/CPF no certificado digital.');
227 65
                $type = '';
228
            }
229
        }
230 65
        return $type;
231
    }
232
233
    /**
234
     * Set application version
235
     * @param string $ver
236
     */
237
    public function setVerAplic($ver)
238
    {
239
        $this->verAplic = $ver;
240
    }
241
242
    /**
243
     * Load Soap Class
244
     * Soap Class may be \NFePHP\Common\Soap\SoapNative
245
     * or \NFePHP\Common\Soap\SoapCurl
246
     * @param SoapInterface $soap
247
     * @return void
248
     */
249
    public function loadSoapClass(SoapInterface $soap)
250
    {
251
        $this->soap = $soap;
252
        $this->soap->loadCertificate($this->certificate);
253
    }
254
255
    /**
256
     * Set OPENSSL Algorithm using OPENSSL constants
257
     * @param int $algorithm
258
     * @return void
259
     */
260
    public function setSignAlgorithm($algorithm = OPENSSL_ALGO_SHA1)
261
    {
262
        $this->algorithm = $algorithm;
263
    }
264
265
    /**
266
     * Set or get model of document NFe = 55 or NFCe = 65
267
     * @param int $model
268
     * @return int modelo class parameter
269
     */
270 3
    public function model($model = null)
271
    {
272 3
        if ($model == 55 || $model == 65) {
273 3
            $this->modelo = $model;
274
        }
275 3
        return $this->modelo;
276
    }
277
278
    /**
279
     * Set or get parameter layout version
280
     * @param string $version
281
     * @return string
282
     * @throws InvalidArgumentException
283
     */
284 65
    public function version($version = null)
285
    {
286 65
        if (null === $version) {
287
            return $this->versao;
288
        }
289
        //Verify version template is defined
290 65
        if (false === isset($this->availableVersions[$version])) {
291
            throw new \InvalidArgumentException('Essa versão de layout não está disponível');
292
        }
293
294 65
        $this->versao = $version;
295 65
        if (empty($this->config->schemes)) {
296
            $this->config->schemes = $this->availableVersions[$version];
297
        }
298 65
        $this->pathschemes = realpath(
299 65
            __DIR__ . '/../../schemes/' . $this->config->schemes
300 65
        ) . '/';
301
302 65
        return $this->versao;
303
    }
304
305
    /**
306
     * Recover cUF number from state acronym
307
     * @param string $acronym Sigla do estado
308
     * @return int number cUF
309
     */
310 5
    public function getcUF($acronym)
311
    {
312 5
        return UFlist::getCodeByUF($acronym);
313
    }
314
315
    /**
316
     * Recover state acronym from cUF number
317
     * @param int $cUF
318
     * @return string acronym sigla
319
     */
320
    public function getAcronym($cUF)
321
    {
322
        return UFlist::getUFByCode($cUF);
323
    }
324
325
    /**
326
     * Validate cUF from the key content and returns the state acronym
327
     * @param string $chave
328
     * @return string
329
     * @throws InvalidArgumentException
330
     */
331
    public function validKeyByUF($chave)
332
    {
333
        $uf = $this->config->siglaUF;
334
        if ($uf != UFList::getUFByCode(substr($chave, 0, 2))) {
0 ignored issues
show
Bug introduced by
substr($chave, 0, 2) of type string is incompatible with the type integer expected by parameter $code of NFePHP\Common\UFList::getUFByCode(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

334
        if ($uf != UFList::getUFByCode(/** @scrutinizer ignore-type */ substr($chave, 0, 2))) {
Loading history...
335
            throw new \InvalidArgumentException(
336
                "A chave da NFe indicada [$chave] não pertence a [$uf]."
337
            );
338
        }
339
        return $uf;
340
    }
341
342
    /**
343
     * Sign NFe or NFCe
344
     * @param  string  $xml NFe xml content
345
     * @return string signed NFe xml
346
     * @throws RuntimeException
347
     */
348
    public function signNFe($xml)
349
    {
350
        if (empty($xml)) {
351
            throw new InvalidArgumentException('O argumento xml passado para ser assinado está vazio.');
352
        }
353
        //remove all invalid strings
354
        $xml = Strings::clearXmlString($xml);
355
        if ($this->contingency->type !== '') {
356
            $xml = ContingencyNFe::adjust($xml, $this->contingency);
357
        }
358
        $signed = Signer::sign(
359
            $this->certificate,
360
            $xml,
361
            'infNFe',
362
            'Id',
363
            $this->algorithm,
364
            $this->canonical
365
        );
366
        $dom = new DOMDocument('1.0', 'UTF-8');
367
        $dom->preserveWhiteSpace = false;
368
        $dom->formatOutput = false;
369
        $dom->loadXML($signed);
370
        $modelo = $dom->getElementsByTagName('mod')->item(0)->nodeValue;
371
        $isInfNFeSupl = !empty($dom->getElementsByTagName('infNFeSupl')->item(0));
372
        if ($modelo == 65 && !$isInfNFeSupl) {
373
            $signed = $this->addQRCode($dom);
374
        }
375
        //exception will be throw if NFe is not valid
376
        $this->isValid($this->versao, $signed, 'nfe');
377
        return $signed;
378
    }
379
380
    /**
381
     * Corrects NFe fields when in contingency mode
382
     * @param string $xml NFe xml content
383
     * @return string
384
     */
385
    protected function correctNFeForContingencyMode($xml)
386
    {
387
        return $this->contingency->type == '' ? $xml : $this->signNFe($xml);
388
    }
389
390
    /**
391
     * Performs xml validation with its respective XSD structure definition document
392
     * NOTE: if don't exists the XSD file will return true
393
     * @param string $version layout version
394
     * @param string $body
395
     * @param string $method
396
     * @return bool
397
     */
398 5
    protected function isValid($version, $body, $method)
399
    {
400 5
        $schema = $this->pathschemes . $method . "_v$version.xsd";
401 5
        if (!is_file($schema)) {
402
            return true;
403
        }
404 5
        return Validator::isValid($body, $schema);
405
    }
406
407
    /**
408
     * Verifies the existence of the service
409
     * @param string $service
410
     * @throws RuntimeException
411
     */
412 5
    protected function checkContingencyForWebServices($service)
413
    {
414
        $permit = [
415 5
            55 => ['SVCAN', 'SVCRS', 'EPEC', 'FSDA'],
416
            65 => ['FSDA', 'EPEC', 'OFFLINE']
417
        ];
418
419 5
        $type = $this->contingency->type;
420 5
        $mod = $this->modelo;
421 5
        if (!empty($type)) {
422
            if (array_search($type, $permit[$mod]) === false) {
423
                throw new RuntimeException(
424
                    "Esse modo de contingência [$type] não é aceito "
425
                    . "para o modelo [$mod]"
426
                );
427
            }
428
        }
429
430
        //se a contingencia é OFFLINE ou FSDA nenhum servidor está disponivel
431
        //se a contigencia EPEC está ativa apenas o envio de Lote está ativo,
432
        //então gerar um RunTimeException
433
        if (
434 5
            $type == 'FSDA'
435 5
            || $type == 'OFFLINE'
436 5
            || ($type == 'EPEC' && $service != 'RecepcaoEvento')
437
        ) {
438
            throw new RuntimeException(
439
                "Quando operando em modo de contingência ["
440
                . $this->contingency->type
441
                . "], este serviço [$service] não está disponível."
442
            );
443
        }
444 5
    }
445
446
    /**
447
     * Alter environment from "homologacao" to "producao" and vice-versa
448
     * @param int $tpAmb
449
     * @return void
450
     */
451 65
    public function setEnvironment($tpAmb = 2)
452
    {
453 65
        if (!empty($tpAmb) && ($tpAmb == 1 || $tpAmb == 2)) {
454 65
            $this->tpAmb = $tpAmb;
455 65
            $this->ambiente = ($tpAmb == 1) ? 'producao' : 'homologacao';
456
        }
457 65
    }
458
459
    /**
460
     * Set option for canonical transformation see C14n
461
     * @param array $opt
462
     * @return array
463
     */
464
    public function canonicalOptions(array $opt = [true, false, null, null])
465
    {
466
        if (!empty($opt) && is_array($opt)) {
467
            $this->canonical = $opt;
468
        }
469
        return $this->canonical;
470
    }
471
472
    /**
473
     * Assembles all the necessary parameters for soap communication
474
     * @param string $service
475
     * @param string $uf
476
     * @param int $tpAmb 1-Production or 2-Homologation
477
     * @param bool $ignoreContingency
478
     * @throws RuntimeException
479
     * @return void
480
     */
481 5
    protected function servico($service, $uf, $tpAmb, $ignoreContingency = false)
482
    {
483 5
        $webs = new Webservices($this->getXmlUrlPath());
484 5
        $sigla = $uf;
485 5
        if (!$ignoreContingency) {
486 5
            $contType = $this->contingency->type;
487 5
            if (!empty($contType) && ($contType == 'SVCRS' || $contType == 'SVCAN')) {
488
                $sigla = $contType;
489
            }
490
        }
491 5
        $stdServ = $webs->get($sigla, $tpAmb, $this->modelo);
492 5
        if (empty($stdServ->$service->url)) {
493
            throw new \RuntimeException("Servico [$service] indisponivel UF [$uf] ou modelo [$this->modelo]");
494
        }
495 5
        $this->urlcUF = $this->getcUF($uf); //recuperação do cUF
496 5
        if ($this->urlcUF > 91) {
497
            $this->urlcUF = $this->getcUF($this->config->siglaUF); //foi solicitado dado de SVCRS ou SVCAN
498
        }
499 5
        $this->urlVersion = $stdServ->$service->version; //recuperação da versão
500 5
        $this->urlService = $stdServ->$service->url; //recuperação da url do serviço
501 5
        $this->urlMethod = $stdServ->$service->method; //recuperação do método
502 5
        $this->urlOperation = $stdServ->$service->operation; //recuperação da operação
503 5
        $this->urlNamespace = sprintf("%s/wsdl/%s", $this->urlPortal, $this->urlOperation); //monta namespace
504
        //montagem do cabeçalho da comunicação SOAP
505 5
        $this->urlHeader = Header::get($this->urlNamespace, $this->urlcUF, $this->urlVersion);
506 5
        $this->urlAction = "\"$this->urlNamespace/$this->urlMethod\"";
507 5
    }
508
509
    /**
510
     * Send request message to webservice
511
     * @param string $request
512
     * @param array $parameters
513
     * @return string
514
     * @throws RuntimeException
515
     */
516 5
    protected function sendRequest($request, array $parameters = [])
517
    {
518 5
        if (in_array($this->contingency->tpEmis, [Contingency::TPEMIS_FSDA, Contingency::TPEMIS_OFFLINE])) {
519
            throw new \RuntimeException('Em contingencia FSDA ou OFFLINE não é possivel acessar os webservices.');
520
        }
521 5
        $this->checkSoap();
522 5
        $response = (string) $this->soap->send(
523 5
            $this->urlService,
524 5
            $this->urlMethod,
525 5
            $this->urlAction,
526 5
            SOAP_1_2,
527 5
            $parameters,
528 5
            $this->soapnamespaces,
529 5
            $request,
530 5
            $this->objHeader
531
        );
532 5
        return Strings::normalize($response);
533
    }
534
535
    /**
536
     * Recover path to xml data base with list of soap services
537
     * @return string
538
     */
539 5
    protected function getXmlUrlPath()
540
    {
541 5
        $file = "wsnfe_" . $this->versao . "_mod55.xml";
542 5
        if ($this->modelo == 65) {
543 1
            $file = str_replace('55', '65', $file);
544
        }
545
546 5
        $path = $this->pathwsfiles . $file;
547 5
        if (! file_exists($path)) {
548
            return '';
549
        }
550 5
        return file_get_contents($path);
551
    }
552
553
    /**
554
     * Add QRCode Tag to signed XML from a NFCe
555
     * @param DOMDocument $dom
556
     * @return string
557
     */
558
    protected function addQRCode(DOMDocument $dom)
559
    {
560
        if (empty($this->config->CSC) || empty($this->config->CSCid)) {
561
            throw new \RuntimeException("O QRCode não pode ser criado pois faltam dados CSC e/ou CSCId");
562
        }
563
        $memmod = $this->modelo;
564
        $this->modelo = 65;
565
        $cUF = $dom->getElementsByTagName('cUF')->item(0)->nodeValue;
566
        $tpAmb = $dom->getElementsByTagName('tpAmb')->item(0)->nodeValue;
567
        $uf = UFList::getUFByCode($cUF);
0 ignored issues
show
Bug introduced by
$cUF of type string is incompatible with the type integer expected by parameter $code of NFePHP\Common\UFList::getUFByCode(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

567
        $uf = UFList::getUFByCode(/** @scrutinizer ignore-type */ $cUF);
Loading history...
568
        $this->servico('NfeConsultaQR', $uf, $tpAmb);
0 ignored issues
show
Bug introduced by
$tpAmb of type string is incompatible with the type integer expected by parameter $tpAmb of NFePHP\NFe\Common\Tools::servico(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

568
        $this->servico('NfeConsultaQR', $uf, /** @scrutinizer ignore-type */ $tpAmb);
Loading history...
569
        $signed = QRCode::putQRTag(
570
            $dom,
571
            $this->config->CSC,
572
            $this->config->CSCid,
573
            $this->urlVersion,
574
            $this->urlService,
575
            $this->getURIConsultaNFCe($uf, $tpAmb)
576
        );
577
        $this->modelo = $memmod;
578
        return Strings::clearXmlString($signed);
579
    }
580
581
    /**
582
     * Get URI for search NFCe by key (chave)
583
     * @param string $uf Abbreviation of the UF
584
     * @param string $tpAmb SEFAZ environment, 1-Production or 2-Homologation
585
     * @return string
586
     */
587 54
    protected function getURIConsultaNFCe($uf, $tpAmb)
588
    {
589 54
        $arr = json_decode(file_get_contents($this->pathwsfiles . 'uri_consulta_nfce.json'), true);
590 54
        $std = json_decode(json_encode($arr[$tpAmb]));
591 54
        return $std->$uf;
592
    }
593
594
    /**
595
     * Verify if SOAP class is loaded, if not, force load SoapCurl
596
     */
597 5
    protected function checkSoap()
598
    {
599 5
        if (!$this->soap) {
600
            $this->soap = new SoapCurl($this->certificate);
601
        }
602 5
    }
603
604
    /**
605
     * Verify if xml model is equal as modelo property
606
     * @param string $xml
607
     * @return bool
608
     */
609 3
    protected function checkModelFromXml($xml)
610
    {
611 3
        $dom = new \DOMDocument();
612 3
        $dom->loadXML($xml);
613 3
        $model = $dom->getElementsByTagName('mod')->item(0)->nodeValue;
614 3
        $check = $model == $this->modelo;
615 3
        $correct = $this->modelo == 55 ? 65 : 55;
616 3
        if (!$check) {
617
            throw new InvalidArgumentException('Você passou um XML de modelo incorreto. '
618
                . "Use o método \$tools->model({$correct}), para selecionar o "
619
                . 'modelo correto a ser usado');
620
        }
621 3
    }
622
}
623