Passed
Pull Request — master (#713)
by
unknown
07:33
created

Tools::setSignAlgorithm()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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