Caf::loadXml()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.1481

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 6
nc 2
nop 1
dl 0
loc 10
ccs 4
cts 6
cp 0.6667
crap 2.1481
rs 10
c 1
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * LibreDTE: Biblioteca PHP (Núcleo).
7
 * Copyright (C) LibreDTE <https://www.libredte.cl>
8
 *
9
 * Este programa es software libre: usted puede redistribuirlo y/o modificarlo
10
 * bajo los términos de la Licencia Pública General Affero de GNU publicada por
11
 * la Fundación para el Software Libre, ya sea la versión 3 de la Licencia, o
12
 * (a su elección) cualquier versión posterior de la misma.
13
 *
14
 * Este programa se distribuye con la esperanza de que sea útil, pero SIN
15
 * GARANTÍA ALGUNA; ni siquiera la garantía implícita MERCANTIL o de APTITUD
16
 * PARA UN PROPÓSITO DETERMINADO. Consulte los detalles de la Licencia Pública
17
 * General Affero de GNU para obtener una información más detallada.
18
 *
19
 * Debería haber recibido una copia de la Licencia Pública General Affero de
20
 * GNU junto a este programa.
21
 *
22
 * En caso contrario, consulte <http://www.gnu.org/licenses/agpl.html>.
23
 */
24
25
namespace libredte\lib\Core\Package\Billing\Component\Identifier\Entity;
26
27
use DateTime;
28
use Derafu\Xml\Contract\XmlDocumentInterface;
29
use Derafu\Xml\XmlDocument;
30
use libredte\lib\Core\Package\Billing\Component\Identifier\Contract\CafInterface;
31
use libredte\lib\Core\Package\Billing\Component\Identifier\Exception\CafException;
32
use libredte\lib\Core\Package\Billing\Component\Identifier\Support\CafFaker;
33
use libredte\lib\Core\Package\Billing\Component\Integration\Enum\SiiAmbiente;
34
35
/**
36
 * Entidad que representa un Código de Autorización de Folios (CAF).
37
 *
38
 * Un CAF es un archivo XML que contiene los folios autorizados por el Servicio
39
 * de Impuestos Internos (SII) de Chile para la emisión de Documentos
40
 * Tributarios Electrónicos (DTE).
41
 */
42
class Caf implements CafInterface
43
{
44
    /**
45
     * Ambiente de certificación del SII.
46
     *
47
     * Este valor se utiliza para identificar que el CAF pertenece al ambiente
48
     * de pruebas o certificación.
49
     */
50
    private const IDK_CERTIFICACION = 100;
51
52
    /**
53
     * Ambiente de producción del SII.
54
     *
55
     * Este valor se utiliza para identificar que el CAF pertenece al ambiente
56
     * de producción.
57
     */
58
    private const IDK_PRODUCCION = 300;
59
60
    /**
61
     * Mapa de ambientes disponibles para el CAF.
62
     *
63
     * Asocia los valores de los ambientes con las configuraciones
64
     * correspondientes de conexión al SII (certificación o producción).
65
     *
66
     * @var array<int, SiiAmbiente>
67
     */
68
    private const AMBIENTES = [
69
        self::IDK_CERTIFICACION => SiiAmbiente::CERTIFICACION,
70
        self::IDK_PRODUCCION => SiiAmbiente::PRODUCCION,
71
    ];
72
73
    /**
74
     * Documento XML del CAF.
75
     *
76
     * Este objeto representa el XML cargado del CAF, utilizado para
77
     * interactuar con el contenido y extraer los datos necesarios.
78
     *
79
     * @var XmlDocumentInterface
80
     */
81
    private XmlDocumentInterface $xmlDocument;
82
83
    /**
84
     * Constructor del CAF.
85
     *
86
     * @param string|XmlDocumentInterface $xml Documento XML del CAF.
87
     */
88 53
    public function __construct(string|XmlDocumentInterface $xml)
89
    {
90 53
        $this->loadXml($xml);
91
    }
92
93
    /**
94
     * Carga un documento XML de un CAF en la instancia de la entidad Caf.
95
     *
96
     * @param string|XmlDocumentInterface $xml Documento XML del CAF.
97
     * @return static
98
     */
99 53
    private function loadXml(string|XmlDocumentInterface $xml): static
100
    {
101 53
        if (is_string($xml)) {
102
            $this->xmlDocument = new XmlDocument();
103
            $this->xmlDocument->loadXml($xml);
104
        } else {
105 53
            $this->xmlDocument = $xml;
106
        }
107
108 53
        return $this;
109
    }
110
111
    /**
112
     * {@inheritDoc}
113
     */
114 53
    public function getXmlDocument(): XmlDocumentInterface
115
    {
116 53
        return $this->xmlDocument;
117
    }
118
119
    /**
120
     * {@inheritDoc}
121
     */
122
    public function getXml(): string
123
    {
124
        return $this->xmlDocument->saveXml();
125
    }
126
127
    /**
128
     * {@inheritDoc}
129
     */
130
    public function getId(): string
131
    {
132
        return sprintf(
133
            'CAF%dD%dH%d',
134
            $this->getTipoDocumento(),
135
            $this->getFolioDesde(),
136
            $this->getFolioHasta()
137
        );
138
    }
139
140
    /**
141
     * {@inheritDoc}
142
     */
143 53
    public function getEmisor(): array
144
    {
145 53
        return [
146 53
            'rut' => $this->xmlDocument->query('//AUTORIZACION/CAF/DA/RE'),
147 53
            'razon_social' => $this->xmlDocument->query('//AUTORIZACION/CAF/DA/RS'),
148 53
        ];
149
    }
150
151
    /**
152
     * {@inheritDoc}
153
     */
154 53
    public function getTipoDocumento(): int
155
    {
156 53
        return (int) $this->xmlDocument->query('//AUTORIZACION/CAF/DA/TD');
157
    }
158
159
    /**
160
     * {@inheritDoc}
161
     */
162 53
    public function getFolioDesde(): int
163
    {
164 53
        return (int) $this->xmlDocument->query('//AUTORIZACION/CAF/DA/RNG/D');
165
    }
166
167
    /**
168
     * {@inheritDoc}
169
     */
170 53
    public function getFolioHasta(): int
171
    {
172 53
        return (int) $this->xmlDocument->query('//AUTORIZACION/CAF/DA/RNG/H');
173
    }
174
175
    /**
176
     * {@inheritDoc}
177
     */
178
    public function getCantidadFolios(): int
179
    {
180
        $desde = $this->getFolioDesde();
181
        $hasta = $this->getFolioHasta();
182
183
        return $hasta - $desde + 1;
184
    }
185
186
    /**
187
     * {@inheritDoc}
188
     */
189 53
    public function enRango(int $folio): bool
190
    {
191 53
        return $folio >= $this->getFolioDesde() && $folio <= $this->getFolioHasta();
192
    }
193
194
    /**
195
     * {@inheritDoc}
196
     */
197 31
    public function getFechaAutorizacion(): string
198
    {
199 31
        return $this->xmlDocument->query('//AUTORIZACION/CAF/DA/FA');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->xmlDocumen...UTORIZACION/CAF/DA/FA') could return the type array|null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
200
    }
201
202
    /**
203
     * {@inheritDoc}
204
     */
205 31
    public function getFechaVencimiento(): ?string
206
    {
207 31
        if (!$this->vence()) {
208
            return null;
209
        }
210
211 31
        $fecha_autorizacion = $this->getFechaAutorizacion();
212 31
        if (!$fecha_autorizacion) {
213
            throw new CafException(sprintf(
214
                'No fue posible obtener la fecha de autorización del CAF %s.',
215
                $this->getID()
216
            ));
217
        }
218
219
        // Los folios vencen en 6 meses (6 * 30 días).
220 31
        return date('Y-m-d', strtotime($fecha_autorizacion. ' + 180 days'));
221
    }
222
223
    /**
224
     * {@inheritDoc}
225
     */
226
    public function getMesesAutorizacion(): float
227
    {
228
        $d1 = new DateTime($this->getFechaAutorizacion());
229
        $d2 = new DateTime(date('Y-m-d'));
230
        $diff = $d1->diff($d2);
231
        $meses = $diff->m + ($diff->y * 12);
232
233
        if ($diff->d) {
234
            $meses += round($diff->d / 30, 2);
235
        }
236
237
        return $meses;
238
    }
239
240
    /**
241
     * {@inheritDoc}
242
     */
243 53
    public function vigente(?string $timestamp = null): bool
244
    {
245 53
        if (!$this->vence()) {
246 22
            return true;
247
        }
248
249 31
        if ($timestamp === null) {
250
            $timestamp = date('Y-m-d\TH:i:s');
251
        }
252
253 31
        if (!isset($timestamp[10])) {
254
            $timestamp .= 'T00:00:00';
255
        }
256
257 31
        return $timestamp < ($this->getFechaVencimiento() . 'T00:00:00');
258
    }
259
260
    /**
261
     * {@inheritDoc}
262
     */
263 53
    public function vence(): bool
264
    {
265 53
        $vencen = [33, 43, 46, 56, 61];
266
267 53
        return in_array($this->getTipoDocumento(), $vencen);
268
    }
269
270
    /**
271
     * {@inheritDoc}
272
     */
273
    public function getIdk(): int
274
    {
275
        return (int) $this->xmlDocument->query('//AUTORIZACION/CAF/DA/IDK');
276
    }
277
278
    /**
279
     * {@inheritDoc}
280
     */
281
    public function getAmbiente(): ?SiiAmbiente
282
    {
283
        $idk = $this->getIDK();
284
285
        return $idk === CafFaker::IDK ? null : self::AMBIENTES[$idk];
286
    }
287
288
    /**
289
     * {@inheritDoc}
290
     */
291
    public function getCertificacion(): ?int
292
    {
293
        return $this->getAmbiente()->value;
294
    }
295
296
    /**
297
     * {@inheritDoc}
298
     */
299
    public function getAutorizacion(): array
300
    {
301
        return $this->xmlDocument->query('//AUTORIZACION/CAF');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->xmlDocumen...y('//AUTORIZACION/CAF') could return the type null|string which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
302
    }
303
304
    /**
305
     * {@inheritDoc}
306
     */
307
    public function getPublicKey(): string
308
    {
309
        $publicKey = $this->xmlDocument->query('//AUTORIZACION/RSAPUBK');
310
311
        // Restaurar el formato PEM correcto con saltos de línea.
312
        if (
313
            !str_contains($publicKey, "\n")
0 ignored issues
show
Bug introduced by
It seems like $publicKey can also be of type array and null; however, parameter $haystack of str_contains() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

313
            !str_contains(/** @scrutinizer ignore-type */ $publicKey, "\n")
Loading history...
314
            && str_contains($publicKey, '-----BEGIN PUBLIC KEY-----')
315
        ) {
316
            // Extraer la parte codificada en base64 (entre los headers).
317
            $start = strpos($publicKey, '-----BEGIN PUBLIC KEY-----') + 26;
0 ignored issues
show
Bug introduced by
It seems like $publicKey can also be of type array and null; however, parameter $haystack of strpos() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

317
            $start = strpos(/** @scrutinizer ignore-type */ $publicKey, '-----BEGIN PUBLIC KEY-----') + 26;
Loading history...
318
            $end = strpos($publicKey, '-----END PUBLIC KEY-----');
319
            $base64Content = substr($publicKey, $start, $end - $start);
0 ignored issues
show
Bug introduced by
It seems like $publicKey can also be of type array and null; however, parameter $string of substr() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

319
            $base64Content = substr(/** @scrutinizer ignore-type */ $publicKey, $start, $end - $start);
Loading history...
320
321
            // Limpiar espacios en blanco y caracteres extra.
322
            $base64Content = trim($base64Content);
323
324
            // Dividir en líneas de 64 caracteres.
325
            $chunks = str_split($base64Content, 64);
326
            $formattedContent = implode("\n", $chunks);
0 ignored issues
show
Bug introduced by
It seems like $chunks can also be of type true; however, parameter $pieces of implode() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

326
            $formattedContent = implode("\n", /** @scrutinizer ignore-type */ $chunks);
Loading history...
327
328
            // Reconstruir la clave con formato PEM correcto.
329
            $publicKey = "-----BEGIN PUBLIC KEY-----\n"
330
                . $formattedContent
331
                . "\n-----END PUBLIC KEY-----"
332
            ;
333
        }
334
335
        return $publicKey;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $publicKey could return the type array|null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
336
    }
337
338
    /**
339
     * {@inheritDoc}
340
     */
341 53
    public function getPrivateKey(): string
342
    {
343 53
        $privateKey = $this->xmlDocument->query('//AUTORIZACION/RSASK');
344
345
        // Restaurar el formato PEM correcto con saltos de línea.
346
        if (
347 53
            !str_contains($privateKey, "\n")
0 ignored issues
show
Bug introduced by
It seems like $privateKey can also be of type array and null; however, parameter $haystack of str_contains() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

347
            !str_contains(/** @scrutinizer ignore-type */ $privateKey, "\n")
Loading history...
348 53
            && str_contains($privateKey, '-----BEGIN PRIVATE KEY-----')
349
        ) {
350
            // Extraer la parte codificada en base64 (entre los headers).
351 53
            $start = strpos($privateKey, '-----BEGIN PRIVATE KEY-----') + 27;
0 ignored issues
show
Bug introduced by
It seems like $privateKey can also be of type array and null; however, parameter $haystack of strpos() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

351
            $start = strpos(/** @scrutinizer ignore-type */ $privateKey, '-----BEGIN PRIVATE KEY-----') + 27;
Loading history...
352 53
            $end = strpos($privateKey, '-----END PRIVATE KEY-----');
353 53
            $base64Content = substr($privateKey, $start, $end - $start);
0 ignored issues
show
Bug introduced by
It seems like $privateKey can also be of type array and null; however, parameter $string of substr() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

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

353
            $base64Content = substr(/** @scrutinizer ignore-type */ $privateKey, $start, $end - $start);
Loading history...
354
355
            // Limpiar espacios en blanco y caracteres extra.
356 53
            $base64Content = trim($base64Content);
357
358
            // Dividir en líneas de 64 caracteres.
359 53
            $chunks = str_split($base64Content, 64);
360 53
            $formattedContent = implode("\n", $chunks);
0 ignored issues
show
Bug introduced by
It seems like $chunks can also be of type true; however, parameter $pieces of implode() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

360
            $formattedContent = implode("\n", /** @scrutinizer ignore-type */ $chunks);
Loading history...
361
362
            // Reconstruir la clave con formato PEM correcto.
363 53
            $privateKey = "-----BEGIN PRIVATE KEY-----\n"
364 53
                . $formattedContent
365 53
                . "\n-----END PRIVATE KEY-----"
366 53
            ;
367
        }
368
369 53
        return $privateKey;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $privateKey could return the type array|null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
370
    }
371
372
    /**
373
     * {@inheritDoc}
374
     */
375
    public function getFirma(): string
376
    {
377
        return $this->xmlDocument->query('//AUTORIZACION/CAF/FRMA');
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->xmlDocumen...AUTORIZACION/CAF/FRMA') could return the type array|null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
378
    }
379
}
380