Cfdi::load()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 4
c 1
b 0
f 0
nc 1
nop 1
dl 0
loc 7
ccs 5
cts 5
cp 1
crap 1
rs 9.4285
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
Bug introduced by
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
Bug introduced by
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
Bug introduced by
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
Unused Code introduced by
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
Unused Code introduced by
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
Bug introduced by
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
Duplication introduced by
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
Duplication introduced by
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
Bug introduced by
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
Bug introduced by
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