Passed
Push — master ( eb8237...9d55e5 )
by Roberto
04:42 queued 02:34
created

Tools::correctNFeForContingencyMode()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 2
cp 0
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
crap 6
1
<?php
2
3
namespace NFePHP\NFe\Common;
4
5
/**
6
 * Class base responsible for communication with SEFAZ
7
 *
8
 * @category  NFePHP
9
 * @package   NFePHP\NFe\Common\Tools
10
 * @copyright NFePHP Copyright (c) 2008-2019
11
 * @license   http://www.gnu.org/licenses/lgpl.txt LGPLv3+
12
 * @license   https://opensource.org/licenses/MIT MIT
13
 * @license   http://www.gnu.org/licenses/gpl.txt GPLv3+
14
 * @author    Roberto L. Machado <linux.rlm at gmail dot com>
15
 * @link      http://github.com/nfephp-org/sped-nfe for the canonical source repository
16
 */
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 58
    public function __construct($configJson, Certificate $certificate, Contingency $contingency = null)
185
    {
186 58
        $this->pathwsfiles = realpath(__DIR__ . '/../../storage') . '/';
187
        //valid config json string
188 58
        $this->config = Config::validate($configJson);
189 58
        $this->version($this->config->versao);
190 58
        $this->setEnvironmentTimeZone($this->config->siglaUF);
191 58
        $this->certificate = $certificate;
192 58
        $this->typePerson = $this->getTypeOfPersonFromCertificate();
193 58
        $this->setEnvironment($this->config->tpAmb);
194 58
        if (empty($contingency)) {
195 58
            $this->contingency = new Contingency();
196
        }
197 58
    }
198
199
    /**
200
     * Sets environment time zone
201
     * @param string $acronym (ou seja a sigla do estado)
202
     * @return void
203
     */
204 58
    public function setEnvironmentTimeZone($acronym)
205
    {
206 58
        $this->timezone = TimeZoneByUF::get($acronym);
207 58
    }
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 58
    public function getTypeOfPersonFromCertificate()
216
    {
217 58
        $cnpj = $this->certificate->getCnpj();
218 58
        $type = 'J';
219 58
        if (empty($cnpj)) {
220
            //não é CNPJ, então verificar se é CPF
221 58
            $cpf = $this->certificate->getCpf();
222 58
            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 58
                $type = '';
228
            }
229
        }
230 58
        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
    public function model($model = null)
271
    {
272
        if ($model == 55 || $model == 65) {
273
            $this->modelo = $model;
274
        }
275
        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 58
    public function version($version = null)
285
    {
286 58
        if (null === $version) {
287
            return $this->versao;
288
        }
289
        //Verify version template is defined
290 58
        if (false === isset($this->availableVersions[$version])) {
291
            throw new \InvalidArgumentException('Essa versão de layout não está disponível');
292
        }
293
294 58
        $this->versao = $version;
295 58
        $this->config->schemes = $this->availableVersions[$version];
296 58
        $this->pathschemes = realpath(
297 58
            __DIR__ . '/../../schemes/'. $this->config->schemes
298 58
        ).'/';
299
300 58
        return $this->versao;
301
    }
302
303
    /**
304
     * Recover cUF number from state acronym
305
     * @param string $acronym Sigla do estado
306
     * @return int number cUF
307
     */
308
    public function getcUF($acronym)
309
    {
310
        return UFlist::getCodeByUF($acronym);
311
    }
312
313
    /**
314
     * Recover state acronym from cUF number
315
     * @param int $cUF
316
     * @return string acronym sigla
317
     */
318
    public function getAcronym($cUF)
319
    {
320
        return UFlist::getUFByCode($cUF);
321
    }
322
323
    /**
324
     * Validate cUF from the key content and returns the state acronym
325
     * @param string $chave
326
     * @return string
327
     * @throws InvalidArgumentException
328
     */
329
    public function validKeyByUF($chave)
330
    {
331
        $uf = $this->config->siglaUF;
332
        if ($uf != UFList::getUFByCode(substr($chave, 0, 2))) {
333
            throw new \InvalidArgumentException(
334
                "A chave da NFe indicada [$chave] não pertence a [$uf]."
335
            );
336
        }
337
        return $uf;
338
    }
339
340
    /**
341
     * Sign NFe or NFCe
342
     * @param  string  $xml NFe xml content
343
     * @return string signed NFe xml
344
     * @throws RuntimeException
345
     */
346
    public function signNFe($xml)
347
    {
348
        if (empty($xml)) {
349
            throw new InvalidArgumentException('O argumento xml passado para ser assinado está vazio.');
350
        }
351
        //remove all invalid strings
352
        $xml = Strings::clearXmlString($xml);
353
        if ($this->contingency->type !== '') {
354
            $xml = ContingencyNFe::adjust($xml, $this->contingency);
355
        }
356
        $signed = Signer::sign(
357
            $this->certificate,
358
            $xml,
359
            'infNFe',
360
            'Id',
361
            $this->algorithm,
362
            $this->canonical
363
        );
364
        $dom = new DOMDocument('1.0', 'UTF-8');
365
        $dom->preserveWhiteSpace = false;
366
        $dom->formatOutput = false;
367
        $dom->loadXML($signed);
368
        $modelo = $dom->getElementsByTagName('mod')->item(0)->nodeValue;
369
        $isInfNFeSupl = !empty($dom->getElementsByTagName('infNFeSupl')->item(0));
370
        if ($modelo == 65 && !$isInfNFeSupl) {
371
            $signed = $this->addQRCode($dom);
372
        }
373
        //exception will be throw if NFe is not valid
374
        $this->isValid($this->versao, $signed, 'nfe');
375
        return $signed;
376
    }
377
378
    /**
379
     * Corrects NFe fields when in contingency mode
380
     * @param string $xml NFe xml content
381
     * @return string
382
     */
383
    protected function correctNFeForContingencyMode($xml)
384
    {
385
        return $this->contingency->type == '' ? $xml : $this->signNFe($xml);
386
    }
387
388
    /**
389
     * Performs xml validation with its respective XSD structure definition document
390
     * NOTE: if don't exists the XSD file will return true
391
     * @param string $version layout version
392
     * @param string $body
393
     * @param string $method
394
     * @return bool
395
     */
396
    protected function isValid($version, $body, $method)
397
    {
398
        $schema = $this->pathschemes.$method."_v$version.xsd";
399
        if (!is_file($schema)) {
400
            return true;
401
        }
402
        return Validator::isValid($body, $schema);
403
    }
404
405
    /**
406
     * Verifies the existence of the service
407
     * @param string $service
408
     * @throws RuntimeException
409
     */
410
    protected function checkContingencyForWebServices($service)
411
    {
412
        $permit = [
413
            55 => ['SVCAN', 'SVCRS', 'EPEC', 'FSDA'],
414
            65 => ['FSDA', 'EPEC', 'OFFLINE']
415
        ];
416
417
        $type = $this->contingency->type;
418
        $mod = $this->modelo;
419
        if (!empty($type)) {
420
            if (array_search($type, $permit[$mod]) === false) {
421
                throw new RuntimeException(
422
                    "Esse modo de contingência [$type] não é aceito "
423
                    . "para o modelo [$mod]"
424
                );
425
            }
426
        }
427
428
        //se a contingencia é OFFLINE ou FSDA nenhum servidor está disponivel
429
        //se a contigencia EPEC está ativa apenas o envio de Lote está ativo,
430
        //então gerar um RunTimeException
431
        if ($type == 'FSDA'
432
            || $type == 'OFFLINE'
433
            || ($type == 'EPEC' && $service != 'RecepcaoEvento')
434
        ) {
435
            throw new RuntimeException(
436
                "Quando operando em modo de contingência ["
437
                . $this->contingency->type
438
                . "], este serviço [$service] não está disponível."
439
            );
440
        }
441
    }
442
443
    /**
444
     * Alter environment from "homologacao" to "producao" and vice-versa
445
     * @param int $tpAmb
446
     * @return void
447
     */
448 58
    public function setEnvironment($tpAmb = 2)
449
    {
450 58
        if (!empty($tpAmb) && ($tpAmb == 1 || $tpAmb == 2)) {
451 58
            $this->tpAmb = $tpAmb;
452 58
            $this->ambiente = ($tpAmb == 1) ? 'producao' : 'homologacao';
453
        }
454 58
    }
455
456
    /**
457
     * Set option for canonical transformation see C14n
458
     * @param array $opt
459
     * @return array
460
     */
461
    public function canonicalOptions(array $opt = [true, false, null, null])
462
    {
463
        if (!empty($opt) && is_array($opt)) {
464
            $this->canonical = $opt;
465
        }
466
        return $this->canonical;
467
    }
468
469
    /**
470
     * Assembles all the necessary parameters for soap communication
471
     * @param string $service
472
     * @param string $uf
473
     * @param int $tpAmb 1-Production or 2-Homologation
474
     * @param bool $ignoreContingency
475
     * @throws RuntimeException
476
     * @return void
477
     */
478
    protected function servico($service, $uf, $tpAmb, $ignoreContingency = false)
479
    {
480
        $webs = new Webservices($this->getXmlUrlPath());
481
        $sigla = $uf;
482
        if (!$ignoreContingency) {
483
            $contType = $this->contingency->type;
484
            if (!empty($contType) && ($contType == 'SVCRS' || $contType == 'SVCAN')) {
485
                $sigla = $contType;
486
            }
487
        }
488
        $stdServ = $webs->get($sigla, $tpAmb, $this->modelo);
489
        if (empty($stdServ->$service->url)) {
490
            throw new \RuntimeException("Servico [$service] indisponivel UF [$uf] ou modelo [$this->modelo]");
491
        }
492
        $this->urlcUF = $this->getcUF($uf); //recuperação do cUF
493
        if ($this->urlcUF > 91) {
494
            $this->urlcUF = $this->getcUF($this->config->siglaUF); //foi solicitado dado de SVCRS ou SVCAN
495
        }
496
        $this->urlVersion = $stdServ->$service->version; //recuperação da versão
497
        $this->urlService = $stdServ->$service->url; //recuperação da url do serviço
498
        $this->urlMethod = $stdServ->$service->method; //recuperação do método
499
        $this->urlOperation = $stdServ->$service->operation; //recuperação da operação
500
        $this->urlNamespace = sprintf("%s/wsdl/%s", $this->urlPortal, $this->urlOperation); //monta namespace
501
        //montagem do cabeçalho da comunicação SOAP
502
        $this->urlHeader = Header::get($this->urlNamespace, $this->urlcUF, $this->urlVersion);
503
        $this->urlAction = "\"$this->urlNamespace/$this->urlMethod\"";
504
    }
505
506
    /**
507
     * Send request message to webservice
508
     * @param string $request
509
     * @param array $parameters
510
     * @return string
511
     * @throws RuntimeException
512
     */
513
    protected function sendRequest($request, array $parameters = [])
514
    {
515
        if (in_array($this->contingency->tpEmis, [Contingency::TPEMIS_FSDA, Contingency::TPEMIS_OFFLINE])) {
516
            throw new \RuntimeException('Em contingencia FSDA ou OFFLINE não é possivel acessar os webservices.');
517
        }
518
        $this->checkSoap();
519
        $response = (string) $this->soap->send(
520
            $this->urlService,
521
            $this->urlMethod,
522
            $this->urlAction,
523
            SOAP_1_2,
524
            $parameters,
525
            $this->soapnamespaces,
526
            $request,
527
            $this->objHeader
528
        );
529
        return Strings::normalize($response);
530
    }
531
532
    /**
533
     * Recover path to xml data base with list of soap services
534
     * @return string
535
     */
536
    protected function getXmlUrlPath()
537
    {
538
        $file = "wsnfe_" . $this->versao . "_mod55.xml";
539
        if ($this->modelo == 65) {
540
            $file = str_replace('55', '65', $file);
541
        }
542
        
543
        $path = $this->pathwsfiles . $file;
544
        if (! file_exists($path)) {
545
            return '';
546
        }
547
        return file_get_contents($path);
548
    }
549
550
    /**
551
     * Add QRCode Tag to signed XML from a NFCe
552
     * @param DOMDocument $dom
553
     * @return string
554
     */
555
    protected function addQRCode(DOMDocument $dom)
556
    {
557
        if (empty($this->config->CSC) || empty($this->config->CSCid)) {
558
            throw new \RuntimeException("O QRCode não pode ser criado pois faltam dados CSC e/ou CSCId");
559
        }
560
        $memmod = $this->modelo;
561
        $this->modelo = 65;
562
        $cUF = $dom->getElementsByTagName('cUF')->item(0)->nodeValue;
563
        $tpAmb = $dom->getElementsByTagName('tpAmb')->item(0)->nodeValue;
564
        $uf = UFList::getUFByCode($cUF);
565
        $this->servico('NfeConsultaQR', $uf, $tpAmb);
566
        $signed = QRCode::putQRTag(
567
            $dom,
568
            $this->config->CSC,
569
            $this->config->CSCid,
570
            $this->urlVersion,
571
            $this->urlService,
572
            $this->getURIConsultaNFCe($uf, $tpAmb)
573
        );
574
        $this->modelo = $memmod;
575
        return Strings::clearXmlString($signed);
576
    }
577
578
    /**
579
     * Get URI for search NFCe by key (chave)
580
     * @param string $uf Abbreviation of the UF
581
     * @param string $tpAmb SEFAZ environment, 1-Production or 2-Homologation
582
     * @return string
583
     */
584 54
    protected function getURIConsultaNFCe($uf, $tpAmb)
585
    {
586 54
        $arr = json_decode(file_get_contents($this->pathwsfiles . 'uri_consulta_nfce.json'), true);
587 54
        $std = json_decode(json_encode($arr[$tpAmb]));
588 54
        return $std->$uf;
589
    }
590
591
    /**
592
     * Verify if SOAP class is loaded, if not, force load SoapCurl
593
     */
594
    protected function checkSoap()
595
    {
596
        if (empty($this->soap)) {
597
            $this->soap = new SoapCurl($this->certificate);
0 ignored issues
show
Documentation Bug introduced by
It seems like new \NFePHP\Common\Soap\...url($this->certificate) of type object<NFePHP\Common\Soap\SoapCurl> is incompatible with the declared type object<NFePHP\Common\Soap\SoapInterface> of property $soap.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
598
        }
599
    }
600
    
601
    /**
602
     * Verify if xml model is equal as modelo property
603
     * @param string $xml
604
     * @return bool
605
     */
606
    protected function checkModelFromXml($xml)
607
    {
608
        $dom = new \DOMDocument();
609
        $dom->loadXML($xml);
610
        $model = $dom->getElementsByTagName('mod')->item(0)->nodeValue;
611
        return $model == $this->modelo;
612
    }
613
}
614