Issues (25)

Security Analysis    no request data  

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  Header Injection
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/Cfdi.php (25 issues)

1
<?php
2
3
/**
4
 * This file is part of the CFDI Wrapper library.
5
 *
6
 * @copyright 2015 César Antáres <[email protected]>
7
 * @license http://opensource.org/licenses/MIT The MIT License.
8
 */
9
10
namespace ZzAntares\CfdiWrapper;
11
12
use ZzAntares\CfdiWrapper\Exceptions\UndefinedAttributeException;
13
use ZzAntares\CfdiWrapper\Exceptions\MalformedCfdiException;
14
15
class Cfdi
16
{
17
    /**
18
     * Holds the instance used by all the methods to acces to the properties of
19
     * the CFDI XSD.
20
     *
21
     * @var \SimpleXMLElement
22
     */
23
    private $cfdi;
24
25
    /**
26
     * Contains a map between CFDI XSD paths and an easier to read dotted
27
     * representation of the paths.
28
     *
29
     * @var array
30
     */
31
    private $paths = [
32
        'cfdi' => '//cfdi:Comprobante',
33
        'cfdi.issuing' => '//cfdi:Comprobante//cfdi:Emisor',
34
        'cfdi.issuing.address' => '//cfdi:Comprobante//cfdi:Emisor//cfdi:DomicilioFiscal',
35
        'cfdi.issuing.issued_at' => '//cfdi:Comprobante//cfdi:Emisor//cfdi:ExpedidoEn',
36
        'cfdi.issuing.regimen' => '//cfdi:Comprobante//cfdi:Emisor//cfdi:RegimenFiscal',
37
        'cfdi.receiver' => '//cfdi:Comprobante//cfdi:Receptor',
38
        'cfdi.receiver.address' => '//cfdi:Comprobante//cfdi:Receptor//cfdi:Domicilio',
39
        'cfdi.items' => '//cfdi:Comprobante//cfdi:Conceptos//cfdi:Concepto',
40
        'cfdi.taxes' => '//cfdi:Comprobante//cfdi:Impuestos',
41
        'cfdi.taxes.holdbacks' => '//cfdi:Comprobante//cfdi:Impuestos//cfdi:Retenciones//cfdi:Retencion',
42
        'cfdi.taxes.transfers' => '//cfdi:Comprobante//cfdi:Impuestos//cfdi:Traslados//cfdi:Traslado',
43
        'cfdi.addon.taxes' => '//cfdi:Comprobante//cfdi:Complemento//implocal:ImpuestosLocales',
44
        // @codingStandardsIgnoreLine
45
        'cfdi.addon.taxes.holdbacks' => '//cfdi:Comprobante//cfdi:Complemento//implocal:ImpuestosLocales//implocal:RetencionesLocales',
46
    ];
47
48
    /**
49
     * Contains the allowed attributes on the CFDI Wrapper instance.
50
     *
51
     * @var array
52
     */
53
    private $allowedAttributes = [
54
        'version',
55
        'serie',
56
        'folio',
57
        'fecha',
58
        'subTotal',
59
        'subtotal',  // Alias of subTotal
60
        'total',
61
        'certificado' ,
62
        'noCertificado',
63
        'condicionesDePago',
64
        'descuento',
65
        'motivoDescuento',
66
        'TipoCambio',
67
        'tipoCambio',  // Alias of TipoCambio
68
        'Moneda',
69
        'moneda',  // Alias of Moneda
70
        'metodoDePago',
71
        'sello' ,
72
        'tipoDeComprobante',
73
        'formaDePago',
74
        'LugarExpedicion',
75
        'lugarExpedicion',  // Alias of LugarExpedcion
76
        'NumCtaPago',
77
        'numCtaPago',  // Alias of numCtaPago
78
        'cadenaOriginal',  // Dinamically generated
79
        'leyenda',  // Dinamically generated
80
        'iva',  // Dinamically generated
81
    ];
82
83
    /**
84
     * List the nested objects vailable on the CFDI Wrapper instance.
85
     *
86
     * @var array
87
     */
88
    private $nestedObjects = [
89
        'emisor',
90
        'receptor',
91
        'conceptos',
92
        'impuestos',
93
        'impuestosLocales',
94
        'timbre',
95
        'timbreFiscalDigital',
96
    ];
97
98
99
    /**
100
     * Constructor.
101
     *
102
     * @return void
103
     */
104 22
    public function __construct($pathOrContent)
105
    {
106 22
        if (file_exists($pathOrContent)) {
107 3
            $this->loadFromFile($pathOrContent);
108 2
        } else {
109 22
            $this->load($pathOrContent);
110
        }
111 22
    }
112
113
    /**
114
     * Allows to change the loaded CFDI by specifying a new string whose content
115
     * is the new CDFI to set.
116
     *
117
     * @param string $xmlContent
118
     *
119
     * @return bool 'true' if load was successful, 'false' otherwise.
120
     */
121 22
    public function load($xmlContent)
122
    {
123 22
        $this->xmlContent = $this->sanitizeXML($xmlContent);
0 ignored issues
show
Bug Best Practice introduced by
The property xmlContent does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
124 22
        $cfdi = simplexml_load_string($this->xmlContent);
125 22
        $this->cfdi = $cfdi;
126
127 22
        return $this->isValid(true);
0 ignored issues
show
Are you sure the usage of $this->isValid(true) targeting ZzAntares\CfdiWrapper\Cfdi::isValid() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Bug Best Practice introduced by
The expression return $this->isValid(true) returns the type void which is incompatible with the documented return type boolean.
Loading history...
128
    }
129
130
    /**
131
     * Allows to change the loaded CFDI by specifying the file path.
132
     *
133
     * @param string $path
134
     *
135
     * @return bool 'true' if load was successful, 'false' otherwise.
136
     */
137 3
    public function loadFromFile($path)
138
    {
139 3
        return $this->load(file_get_contents($path));
140
    }
141
142
    /**
143
     * Saves the XML representation of the CFDI into a file.
144
     *
145
     * @throws RuntimeException If Overwritte can't happen and file exists.
146
     *
147
     * @param string $filepath Full path and filename where to save the CFDI.
148
     * @param bool $overwrite If the given file path exists and this is set to
149
     *             'true' then file will be overwritten, otherwise an exception
150
     *             will be thrown.
151
     *
152
     * @return return integer Bytes written to file or 'false' if writting
0 ignored issues
show
The type ZzAntares\CfdiWrapper\return was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
153
     *                        wasn't possible.
154
     */
155 3
    public function toFile($filepath, $overwrite = false)
156
    {
157 3
        if (file_exists($filepath) and !$overwrite) {
158 1
            throw new \RuntimeException($filepath . ' already exists.');
159
        }
160
161 2
        return file_put_contents($filepath, $this->xmlContent);
0 ignored issues
show
Bug Best Practice introduced by
The expression return file_put_contents...ath, $this->xmlContent) returns the type integer which is incompatible with the documented return type ZzAntares\CfdiWrapper\return.
Loading history...
162
    }
163
164
165
    /**
166
     * Checks if the given CFDI is valid by searching the needed namespaces.
167
     *
168
     * @throws ZzAntares\CfdiWrapper\Exceptions\MalformedCfdiException
169
     *
170
     * @param SimpleXMLElement $cfdi
0 ignored issues
show
The type ZzAntares\CfdiWrapper\SimpleXMLElement was not found. Did you mean SimpleXMLElement? If so, make sure to prefix the type with \.
Loading history...
171
     *
172
     * @return void
173
     */
174 22
    public function isValid($throwException = false)
175
    {
176 22
        $namespaces = array_keys($this->cfdi->getNamespaces(true));
177
178 22
        if (in_array_all(['tfd', 'xsi', 'cfdi', 'implocal'], $namespaces)) {
179 22
            return true;
0 ignored issues
show
Bug Best Practice introduced by
The expression return true returns the type true which is incompatible with the documented return type void.
Loading history...
180
        }
181
182 1
        if ($throwException) {
183 1
            throw new MalformedCfdiException();
184
        }
185
186
        return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type void.
Loading history...
187
    }
188
189
    /**
190
     * Gets the specified attribute.
191
     *
192
     * @param string $attribute Property on the CFDI to retrieve.
193
     *
194
     * @return void
195
     */
196 8
    private function getAttribute($attribute)
197
    {
198 8
        $comprobante = $this->cfdi->xpath($this->paths['cfdi']);
199
200
        switch ($attribute) {
201 8
            case 'subtotal':
202 1
                $attribute = 'subTotal';
203 1
                break;
204
205 8
            case 'tipoCambio':
206 1
                $attribute = 'TipoCambio';
207 1
                break;
208
209 8
            case 'moneda':
210 1
                $attribute = 'Moneda';
211 1
                break;
212
213 8
            case 'lugarExpedicion':
214 1
                $attribute = 'LugarExpedicion';
215 1
                break;
216
217 8
            case 'numCtaPago':
218 1
                $attribute = 'NumCtaPago';
219 1
                break;
220
221 8
            case 'cadenaOriginal':
222 1
                return $this->getCadenaOriginal();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getCadenaOriginal() returns the type string which is incompatible with the documented return type void.
Loading history...
223
                break;
0 ignored issues
show
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
224
225 7
            case 'leyenda':
226 1
                return 'Este documento es una representación impresa de un CFDI';
0 ignored issues
show
Bug Best Practice introduced by
The expression return 'Este documento e...ón impresa de un CFDI' returns the type string which is incompatible with the documented return type void.
Loading history...
227
                break;
228
229 6
            case 'iva':
230 1
                return $this->getImpuesto('iva');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getImpuesto('iva') returns the type object which is incompatible with the documented return type void.
Loading history...
231
                break;
232
        }
233
234 5
        return $comprobante[0][$attribute]->__toString();
235
    }
236
237
    /**
238
     * Retrieves the propper nested object under the CFDI.
239
     *
240
     * @return object
241
     */
242 11
    private function getNestedObject($attribute)
243
    {
244
        switch ($attribute) {
245 11
            case 'emisor':
246 4
                return $this->getEmisor();
247
                break;
0 ignored issues
show
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

Loading history...
248
249 10
            case 'receptor':
250 4
                return $this->getReceptor();
251
                break;
252
253 9
            case 'conceptos':
254 1
                return $this->getConceptos();
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->getConceptos() returns the type array which is incompatible with the documented return type object.
Loading history...
255
                break;
256
257 8
            case 'impuestos':
258 2
                return $this->getImpuestos();
259
                break;
260
261 6
            case 'impuestosLocales':
262 1
                return $this->getImpuestosLocales();
263
                break;
264
265 5
            case 'timbre':
266 5
            case 'timbreFiscalDigital':
267 5
                return $this->getTimbreFiscalDigital();
268
                break;
269
        }
270
    }
271
272
    /**
273
     * Magic method to get attributes on the CFDI.
274
     *
275
     * @throws UndefinedAttributeException when attribute is not in the XML.
276
     *
277
     * @param string $attribute
278
     *
279
     * @return mixed A string or a nested object.
280
     */
281 15
    public function __get($attribute)
282
    {
283 15
        if (in_array($attribute, $this->allowedAttributes)) {
284 8
            return $this->getAttribute($attribute);
0 ignored issues
show
Are you sure the usage of $this->getAttribute($attribute) targeting ZzAntares\CfdiWrapper\Cfdi::getAttribute() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
285
        }
286
287 12
        if (in_array($attribute, $this->nestedObjects)) {
288 11
            return $this->getNestedObject($attribute);
289
        }
290
291 1
        throw new UndefinedAttributeException();
292
    }
293
294
    /**
295
     * Retrieves the string representation of the CFDI.
296
     *
297
     * @return string
298
     */
299 3
    public function __toString()
300
    {
301 3
        return $this->sanitizeXML($this->xmlContent);
302
    }
303
304
    /**
305
     * Removes new lines and spaces from the XML string representation.
306
     *
307
     * @param  string $xmlString XML string.
308
     * @return string
309
     */
310 22
    public function sanitizeXML($xmlString)
311
    {
312 22
        $xmlHeader = '<?xml version="1.0" encoding="UTF-8"?>';
313
314 22
        $data = str_replace($xmlHeader, '', $xmlString);
315 22
        $data = str_replace("\n", '', $data);
316 22
        $data = str_replace("\r", '', $data);
317 22
        $data = preg_replace('~\s*(<([^>]*)>[^<]*</\2>|<[^>]*>)\s*~', '$1', $data);
318 22
        $data = preg_replace('/\r\n/', "\n", $data);
319
320 22
        return $xmlHeader . PHP_EOL . $data;
321
    }
322
323
    /**
324
     * Retrieves the 'cfdi.addon.digital_stamp' path in the CFDI.
325
     *
326
     * @return object
327
     */
328 5
    private function getTimbreFiscalDigital()
329
    {
330
        // $stamp = $this->cfdi->xpath($this->paths['cfdi.addon.digital_stamp']);
331
332 5
        $ns = $this->cfdi->getNamespaces(true);
333 5
        $this->cfdi->registerXPathNamespace('tfd', $ns['tfd']);
334 5
        $stamp = $this->cfdi->xpath('//tfd:TimbreFiscalDigital')[0];
335
336
        return (object) [
337 5
            'version'          => $stamp['version']->__toString(),
338 5
            'uuid'             => $stamp['UUID']->__toString(),
339 5
            'UUID'             => $stamp['UUID']->__toString(),
340 5
            'fecha'            => $stamp['FechaTimbrado']->__toString(),
341 5
            'fechaTimbrado'    => $stamp['FechaTimbrado']->__toString(),
342 5
            'selloCFD'         => $stamp['selloCFD']->__toString(),
343 5
            'cfd'              => $stamp['selloCFD']->__toString(),
344 5
            'noCertificadoSAT' => $stamp['noCertificadoSAT']->__toString(),
345 5
            'selloSAT'         => $stamp['selloSAT']->__toString(),
346 5
            'sat'              => $stamp['selloSAT']->__toString(),
347 5
        ];
348
    }
349
350
    /**
351
     * Retrieves the 'cfdi.addon.taxes' path in the CFDI.
352
     *
353
     * @return object
354
     */
355 1
    private function getImpuestosLocales()
356
    {
357 1
        $addon = $this->cfdi->xpath($this->paths['cfdi.addon.taxes'])[0];
358
359
        return (object) [
360 1
            'version'            => $addon['version'],
361 1
            'totalDeRetenciones' => $addon['TotaldeRetenciones'],
362 1
            'totaldeRetenciones' => $addon['TotaldeRetenciones'],
363 1
            'retenciones'        => $addon['TotaldeRetenciones'],
364 1
            'totalDeTraslados'   => $addon['TotaldeTraslados'],
365 1
            'totaldeTraslados'   => $addon['TotaldeTraslados'],
366 1
            'traslados'          => $addon['TotaldeTraslados'],
367 1
            'retencionesLocales' => $this->getRetencionesLocales(),
368 1
        ];
369
    }
370
371
    /**
372
     * Retrieves the 'cfdi.addon.taxes.holdbacks' path in the CFDI.
373
     *
374
     * @return object
375
     */
376 1
    private function getRetencionesLocales()
377
    {
378 1
        $holdbacks = $this->cfdi->xpath($this->paths['cfdi.addon.taxes.holdbacks'])[0];
379
380
        return (object) [
381 1
            'impuesto' => $holdbacks['ImpLocRetenido']->__toString(),
382 1
            'importe'  => $holdbacks['Importe']->__toString(),
383 1
            'tasa'     => $holdbacks['TasadeRetencion']->__toString(),
384 1
        ];
385
    }
386
387
    /**
388
     * Retrieves all the 'cfdi.taxes' with it's nested objects like
389
     * 'retenciones' and 'traslados'.
390
     *
391
     * @return object
392
     */
393 2
    private function getImpuestos()
394
    {
395 2
        $impuestos = $this->cfdi->xpath($this->paths['cfdi.taxes'])[0];
396
397
        return (object) [
398 2
            'totalImpuestosTrasladados' => $impuestos['totalImpuestosTrasladados']->__toString(),
399 2
            'totalImpuestosRetenidos'   => $impuestos['totalImpuestosRetenidos']->__toString(),
400 2
            'retenciones'               => $this->getRetenciones(),
401 2
            'traslados'                 => $this->getTraslados(),
402 2
        ];
403
    }
404
405
    /**
406
     * Retrieves all 'cfdi.taxes.transfers' in the CFDI.
407
     *
408
     * @return array
409
     */
410 2 View Code Duplication
    private function getTraslados()
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
411
    {
412 2
        $transfers = $this->cfdi->xpath($this->paths['cfdi.taxes.transfers']);
413
414 2
        $traslados = [];
415 2
        foreach ($transfers as $transfer) {
416
            $traslado = [
417 2
                'impuesto' => $transfer['impuesto']->__toString(),
418 2
                'importe'  => $transfer['importe']->__toString(),
419 2
                'tasa'     => $transfer['tasa']->__toString(),
420 2
            ];
421
422 2
            $traslados[] = (object) $traslado;
423 2
        }
424
425 2
        return $traslados;
426
    }
427
428
    /**
429
     * Retrieves the path 'cfdi.taxes.holdbacks'.
430
     *
431
     * @return array
432
     */
433 2 View Code Duplication
    private function getRetenciones()
0 ignored issues
show
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
434
    {
435 2
        $holdbacks = $this->cfdi->xpath($this->paths['cfdi.taxes.holdbacks']);
436
437 2
        $retenciones = [];
438 2
        foreach ($holdbacks as $holdback) {
439
            $retencion = [
440 2
                'impuesto' => $holdback['impuesto']->__toString(),
441 2
                'importe'  => $holdback['importe']->__toString(),
442 2
            ];
443
444 2
            $retenciones[] = (object) $retencion;
445 2
        }
446
447 2
        return $retenciones;
448
    }
449
450
    /**
451
     * Retrieves all 'cfdi.items' in the CFDI.
452
     *
453
     * @return array
454
     */
455 1
    private function getConceptos()
456
    {
457 1
        $items = $this->cfdi->xpath($this->paths['cfdi.items']);
458 1
        $conceptos = [];
459 1
        foreach ($items as $item) {
460
            $concepto = [
461 1
                'cantidad'      => $item['cantidad']->__toString(),
462 1
                'unidad'        => $item['unidad']->__toString(),
463 1
                'descripcion'   => $item['descripcion']->__toString(),
464 1
                'valorUnitario' => $item['valorUnitario']->__toString(),
465 1
                'importe'       => $item['importe']->__toString(),
466 1
            ];
467
468 1
            $conceptos[] = (object) $concepto;
469 1
        }
470
471 1
        return $conceptos;
472
    }
473
474
    /**
475
     * Retrieves the 'cfdi.receiver' path, including it's nested resources like
476
     * 'domicilio'.
477
     *
478
     * @return object
479
     */
480 4
    private function getReceptor()
481
    {
482 4
        $comprobante = $this->cfdi->xpath($this->paths['cfdi.receiver']);
483 4
        $domicilio = $this->getDomicilioFiscal('cfdi.receiver.address');
484
485
        $receptor = [
486 4
            'rfc'             => $comprobante[0]['rfc']->__toString(),
487 4
            'nombre'          => $comprobante[0]['nombre']->__toString(),
488 4
            'domicilioFiscal' => $domicilio,
489 4
            'domicilio'       => $domicilio,
490 4
        ];
491
492 4
        return (object) $receptor;
493
    }
494
495
    /**
496
     * Retrieves the 'cfdi.issuing' path and all it's nested resources like
497
     * 'comprobante', 'domicilio', 'regimen', etc.
498
     *
499
     * @return object
500
     */
501 4
    private function getEmisor()
502
    {
503 4
        $comprobante = $this->cfdi->xpath($this->paths['cfdi.issuing']);
504 4
        $domicilio = $this->getDomicilioFiscal('cfdi.issuing.address');
505 4
        $regimenFiscal = $this->getRegimenFiscal();
506
507
        $emisor = [
508 4
            'rfc'             => $comprobante[0]['rfc']->__toString(),
509 4
            'nombre'          => $comprobante[0]['nombre']->__toString(),
510 4
            'domicilioFiscal' => $domicilio,
511 4
            'domicilio'       => $domicilio,
512 4
            'expedidoEn'      => $this->getExpedidoEn(),
513 4
            'regimenFiscal'   => $regimenFiscal,
514 4
            'regimen'         => $regimenFiscal,
515 4
        ];
516
517 4
        return (object) $emisor;
518
    }
519
520
    /**
521
     * Get the nested object domicilioFiscal for the specified path, this can be
522
     * either 'cfdi.issuing.address' or 'cfdi.receiver.address'.
523
     *
524
     * @param string $path 'cfdi.receiver.address' or 'cfdi.issuing.address'.
525
     *
526
     * @return object
527
     */
528 5
    private function getDomicilioFiscal($path)
529
    {
530 5
        $address = $this->cfdi->xpath($this->paths[$path])[0];
531
532
        $domicilio = [
533 5
            'calle'        => $address['calle']->__toString(),
534 5
            'colonia'      => $address['colonia']->__toString(),
535 5
            'localidad'    => '',
536 5
            'municipio'    => $address['municipio']->__toString(),
537 5
            'noExterior'   => $address['noExterior']->__toString(),
538 5
            'noInterior'   => '',
539 5
            'estado'       => $address['estado']->__toString(),
540 5
            'pais'         => $address['pais']->__toString(),
541 5
            'codigoPostal' => $address['codigoPostal']->__toString(),
542 5
        ];
543
544 5
        if (isset($address['localidad'])) {
545 5
            $domicilio['localidad'] = $address['localidad']->__toString();
546 5
        }
547
548 5
        if (isset($address['noInterior'])) {
549 4
            $domicilio['noInterior'] = $address['noInterior']->__toString();
550 4
        }
551
552 5
        return (object) $domicilio;
553
    }
554
555
    /**
556
     * Retrieves the info located at 'cfdi.issuing.issued_at'.
557
     *
558
     * @return object
559
     */
560 4
    private function getExpedidoEn()
561
    {
562 4
        $expedidoEn = $this->cfdi->xpath($this->paths['cfdi.issuing.issued_at']);
563
564
        return (object) [
565 4
            'pais' => $expedidoEn[0]['pais']->__toString(),
566 4
        ];
567
    }
568
569
    /**
570
     * Retrieves the info located at 'cfdi.issuing.regimen'.
571
     *
572
     * @return object
573
     */
574 4
    private function getRegimenFiscal()
575
    {
576 4
        $regimenFiscal = $this->cfdi->xpath($this->paths['cfdi.issuing.regimen']);
577
578
        return (object) [
579 4
            'regimen' => $regimenFiscal[0]['Regimen']->__toString(),
580 4
            'Regimen' => $regimenFiscal[0]['Regimen']->__toString(),
581 4
        ];
582
    }
583
584
    /**
585
     * Retrieves the field known as 'Cadena original de complemento de
586
     * certificación del SAT'.
587
     *
588
     * @return string
589
     */
590 1
    private function getCadenaOriginal()
591
    {
592 1
        return sprintf(
593 1
            '||%s|%s|%s|%s|%s||',
594 1
            $this->timbre->version,
0 ignored issues
show
Bug Best Practice introduced by
The property timbre does not exist on ZzAntares\CfdiWrapper\Cfdi. Since you implemented __get, consider adding a @property annotation.
Loading history...
595 1
            $this->timbre->uuid,
596 1
            $this->timbre->fechaTimbrado,
597 1
            $this->timbre->selloCFD,
598 1
            $this->timbre->noCertificadoSAT
599 1
        );
600
    }
601
602
    /**
603
     * Gives a stdClass object with information of the given tax like 'tasa',
604
     * 'importe', etc.
605
     *
606
     * @throws UndefinedAttributeException When tax is not found in the CFDI.
607
     *
608
     * @param $taxName Which tax to retrieve, this could be 'iva', 'ieps', etc.
0 ignored issues
show
The type ZzAntares\CfdiWrapper\Which was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
609
     *
610
     * @return object
611
     */
612 1
    private function getImpuesto($taxName)
613
    {
614
        // Only 'iva' is supported for now
615 1
        if ($taxName != 'iva') {
616
            throw new UndefinedAttributeException();
617
        }
618
619 1
        $taxName = strtoupper($taxName);
620
621 1
        foreach ($this->impuestos->traslados as $tax) {
0 ignored issues
show
Bug Best Practice introduced by
The property impuestos does not exist on ZzAntares\CfdiWrapper\Cfdi. Since you implemented __get, consider adding a @property annotation.
Loading history...
622 1
            if ($tax->impuesto == $taxName) {
623 1
                return $tax;
624
            }
625
        }
626
627
        throw new UndefinedAttributeException();
628
    }
629
630
    /**
631
     * Gets the Qr string, this is the string that should be read when scaning
632
     * the QR code image.
633
     *
634
     * @return string
635
     */
636 3
    public function getQrString()
637
    {
638 3
        return '?re=' . $this->emisor->rfc
0 ignored issues
show
Bug Best Practice introduced by
The property emisor does not exist on ZzAntares\CfdiWrapper\Cfdi. Since you implemented __get, consider adding a @property annotation.
Loading history...
639 3
            . '&rr=' . $this->receptor->rfc
0 ignored issues
show
Bug Best Practice introduced by
The property receptor does not exist on ZzAntares\CfdiWrapper\Cfdi. Since you implemented __get, consider adding a @property annotation.
Loading history...
640 3
            . '&tt=' . $this->total
0 ignored issues
show
Are you sure $this->total of type object can be used in concatenation? ( Ignorable by Annotation )

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

640
            . '&tt=' . /** @scrutinizer ignore-type */ $this->total
Loading history...
Bug Best Practice introduced by
The property total does not exist on ZzAntares\CfdiWrapper\Cfdi. Since you implemented __get, consider adding a @property annotation.
Loading history...
641 3
            . '&id=' . $this->timbre->uuid;
0 ignored issues
show
Bug Best Practice introduced by
The property timbre does not exist on ZzAntares\CfdiWrapper\Cfdi. Since you implemented __get, consider adding a @property annotation.
Loading history...
642
    }
643
644
    /**
645
     * Gets the qr code in string format.
646
     *
647
     * @param integer $width Width of the QR code image.
648
     * @param integer $height Height of the QR code image.
649
     * @param bool $base64 If you want raw bytes then set this to 'false'.
650
     *
651
     * @return string
652
     */
653 2
    public function qr($width = 256, $height = 256, $base64 = true)
654
    {
655 2
        $renderer = new \BaconQrCode\Renderer\Image\Png();
656 2
        $renderer->setWidth($width);
657 2
        $renderer->setHeight($height);
658
659 2
        $writer = new \BaconQrCode\Writer($renderer);
660 2
        $qrString = $writer->writeString($this->getQrString());
661
662 2
        if (!$base64) {
663 1
            return $qrString;
664
        }
665
666 1
        return base64_encode($qrString);
667
    }
668
669
    /**
670
     * Writes the QR code string (bytes) to a file in order to generate the
671
     * QR code image.
672
     *
673
     * @param string $filepath Full path to file location with filename included.
674
     * @param integer $width Width of the QR code image.
675
     * @param integer $height Height of the QR code image.
676
     *
677
     * @return integer Bytes written or 'false' on failure.
678
     */
679 1
    public function qrCode($filepath, $width = 256, $height = 256)
680
    {
681 1
        return file_put_contents($filepath, $this->qr($width, $height, false));
682
    }
683
}
684