Completed
Push — master ( 7ae040...f59f35 )
by Esteban De La Fuente
02:52
created

Sii::getVerificarSSL()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
/**
4
 * LibreDTE
5
 * Copyright (C) SASCO SpA (https://sasco.cl)
6
 *
7
 * Este programa es software libre: usted puede redistribuirlo y/o
8
 * modificarlo bajo los términos de la Licencia Pública General Affero de GNU
9
 * publicada por la Fundación para el Software Libre, ya sea la versión
10
 * 3 de la Licencia, o (a su elección) cualquier versión posterior de la
11
 * misma.
12
 *
13
 * Este programa se distribuye con la esperanza de que sea útil, pero
14
 * SIN GARANTÍA ALGUNA; ni siquiera la garantía implícita
15
 * MERCANTIL o de APTITUD PARA UN PROPÓSITO DETERMINADO.
16
 * Consulte los detalles de la Licencia Pública General Affero de GNU para
17
 * obtener una información más detallada.
18
 *
19
 * Debería haber recibido una copia de la Licencia Pública General Affero de GNU
20
 * junto a este programa.
21
 * En caso contrario, consulte <http://www.gnu.org/licenses/agpl.html>.
22
 */
23
24
namespace sasco\LibreDTE;
25
26
/**
27
 * Clase para acciones genéricas asociadas al SII de Chile
28
 * @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl)
29
 * @version 2016-07-25
30
 */
31
class Sii
32
{
33
34
    private static $config = [
35
        'wsdl' => [
36
            '*' => 'https://{servidor}.sii.cl/DTEWS/{servicio}.jws?WSDL',
37
            'QueryEstDteAv' => 'https://{servidor}.sii.cl/DTEWS/services/{servicio}?WSDL',
38
            'wsDTECorreo' => 'https://{servidor}.sii.cl/DTEWS/services/{servicio}?WSDL',
39
        ],
40
        'servidor' => ['palena', 'maullin'], ///< servidores 0: producción, 1: certificación
41
        'certs' => [300, 100], ///< certificados 0: producción, 1: certificación
42
    ];
43
44
    const PRODUCCION = 0; ///< Constante para indicar ambiente de producción
45
    const CERTIFICACION = 1; ///< Constante para indicar ambiente de desarrollo
46
47
    const IVA = 19; ///< Tasa de IVA
48
49
    private static $retry = 10; ///< Veces que se reintentará conectar a SII al usar el servicio web
50
    private static $verificar_ssl = true; ///< Indica si se deberá verificar o no el certificado SSL del SII
51
    private static $ambiente = self::PRODUCCION; ///< Ambiente que se utilizará
52
53
    private static $direcciones_regionales = [
54
        'CHILLÁN VIEJO' => 'CHILLÁN',
55
        'HUECHURABA' => 'SANTIAGO NORTE',
56
        'LA CISTERNA' => 'SANTIAGO SUR',
57
        'LAS CONDES' => 'SANTIAGO ORIENTE',
58
        'LO ESPEJO' => 'SANTIAGO SUR',
59
        'PEÑALOLÉN' => 'ÑUÑOA',
60
        'PUDAHUEL' => 'SANTIAGO PONIENTE',
61
        'RECOLETA' => 'SANTIAGO NORTE',
62
        'SANTIAGO' => 'SANTIAGO CENTRO',
63
        'SAN MIGUEL' => 'SANTIAGO SUR',
64
        'SAN VICENTE' => 'SAN VICENTE TAGUA TAGUA',
65
        'TALTAL' => 'ANTOFAGASTA',
66
        'VITACURA' => 'SANTIAGO ORIENTE',
67
    ]; /// Direcciones regionales del SII según la comuna
68
69
    /**
70
     * Método que permite asignar el nombre del servidor del SII que se
71
     * usará para las consultas al SII
72
     * @param servidor Servidor que se usará, si es https://maullin2.sii.cl, entonces se debe pasar como valor maullin2
73
     * @param certificacion Permite definir si se está cambiando el servidor de certificación o el de producción
74
     * @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl)
75
     * @version 2015-09-14
76
     */
77
    public static function setServidor($servidor = 'maullin', $certificacion = Sii::CERTIFICACION)
78
    {
79
        self::$config['servidor'][$certificacion] = $servidor;
80
    }
81
82
    /**
83
     * Método que entrega el nombre del servidor a usar según el ambiente
84
     * @param ambiente Ambiente que se desea obtener el servidor, si es null se autodetectará
85
     * @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl)
86
     * @version 2016-08-01
87
     */
88
    public static function getServidor($ambiente = null)
89
    {
90
        return self::$config['servidor'][self::getAmbiente($ambiente)];
91
    }
92
93
    /**
94
     * Método para obtener el WSDL
95
     *
96
     * \code{.php}
97
     *   $wsdl = \sasco\LibreDTE\Sii::wsdl('CrSeed'); // WSDL para pedir semilla
98
     * \endcode
99
     *
100
     * Para forzar el uso del WSDL de certificación hay dos maneras, una es
101
     * pasando un segundo parámetro al método get con valor Sii::CERTIFICACION:
102
     *
103
     * \code{.php}
104
     *   $wsdl = \sasco\LibreDTE\Sii::wsdl('CrSeed', \sasco\LibreDTE\Sii::CERTIFICACION);
105
     * \endcode
106
     *
107
     * La otra manera, para evitar este segundo parámetro, es asignar el valor a
108
     * través de la configuración:
109
     *
110
     * \code{.php}
111
     *   \sasco\LibreDTE\Sii::setAmbiente(\sasco\LibreDTE\Sii::CERTIFICACION);
112
     * \endcode
113
     *
114
     * @param servicio Servicio por el cual se está solicitando su WSDL
115
     * @param ambiente Ambiente a usar: Sii::PRODUCCION o Sii::CERTIFICACION o null (para detección automática)
116
     * @return URL del WSDL del servicio según ambiente solicitado
117
     * @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl)
118
     * @version 2016-06-11
119
     */
120
    public static function wsdl($servicio, $ambiente = null)
121
    {
122
        // determinar ambiente que se debe usar
123
        $ambiente = self::getAmbiente($ambiente);
124
        // entregar WSDL local (modificados para ambiente de certificación)
125
        if ($ambiente==self::CERTIFICACION) {
126
            $wsdl = dirname(dirname(__FILE__)).'/wsdl/'.self::$config['servidor'][$ambiente].'/'.$servicio.'.jws';
127
            if (is_readable($wsdl))
128
                return $wsdl;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return $wsdl; (string) is incompatible with the return type documented by sasco\LibreDTE\Sii::wsdl of type sasco\LibreDTE\URL.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
129
        }
130
        // entregar WSDL oficial desde SII
131
        $location = isset(self::$config['wsdl'][$servicio]) ? self::$config['wsdl'][$servicio] : self::$config['wsdl']['*'];
132
        $wsdl = str_replace(
133
            ['{servidor}', '{servicio}'],
134
            [self::$config['servidor'][$ambiente], $servicio],
135
            $location
136
        );
137
        // entregar wsdl
138
        return $wsdl;
139
    }
140
141
    /**
142
     * Método para realizar una solicitud al servicio web del SII
143
     * @param wsdl Nombre del WSDL que se usará
144
     * @param request Nombre de la función que se ejecutará en el servicio web
145
     * @param args Argumentos que se pasarán al servicio web
146
     * @param retry Intentos que se realizarán como máximo para obtener respuesta
147
     * @return Objeto SimpleXMLElement con la espuesta del servicio web consultado
148
     * @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl)
149
     * @version 2016-08-28
150
     */
151
    public static function request($wsdl, $request, $args = null, $retry = null)
152
    {
153
        if (is_numeric($args)) {
154
            $retry = (int)$args;
155
            $args = null;
156
        }
157
        if (!$retry)
158
            $retry = self::$retry;
159
        if ($args and !is_array($args))
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
160
            $args = [$args];
161
        if (!self::$verificar_ssl) {
162
            if (self::getAmbiente()==self::PRODUCCION) {
163
                $msg = Estado::get(Estado::ENVIO_SSL_SIN_VERIFICAR);
164
                \sasco\LibreDTE\Log::write(Estado::ENVIO_SSL_SIN_VERIFICAR, $msg, LOG_WARNING);
0 ignored issues
show
Documentation introduced by
$msg is of type integer|string, but the function expects a object<sasco\LibreDTE\Mensaje>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
LOG_WARNING is of type integer, but the function expects a object<sasco\LibreDTE\Gravedad>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
165
            }
166
            $options = ['stream_context' => stream_context_create([
167
                'ssl' => [
168
                    'verify_peer' => false,
169
                    'verify_peer_name' => false,
170
                    'allow_self_signed' => true
171
                ]
172
            ])];
173
        } else {
174
            $options = [];
175
        }
176
        try {
177
            $soap = new \SoapClient(self::wsdl($wsdl), $options);
178
        } catch (\Exception $e) {
179
            $msg = $e->getMessage();
180 View Code Duplication
            if (isset($e->getTrace()[0]['args'][1]) and is_string($e->getTrace()[0]['args'][1])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
181
                $msg .= ': '.$e->getTrace()[0]['args'][1];
182
            }
183
            \sasco\LibreDTE\Log::write(Estado::REQUEST_ERROR_SOAP, Estado::get(Estado::REQUEST_ERROR_SOAP, $msg));
0 ignored issues
show
Documentation introduced by
\sasco\LibreDTE\Estado::...QUEST_ERROR_SOAP, $msg) is of type integer|string, but the function expects a object<sasco\LibreDTE\Mensaje>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
184
            return false;
185
        }
186
        for ($i=0; $i<$retry; $i++) {
187
            try {
188
                if ($args) {
189
                    $body = call_user_func_array([$soap, $request], $args);
190
                } else {
191
                    $body = $soap->$request();
192
                }
193
                break;
194
            } catch (\Exception $e) {
195
                $msg = $e->getMessage();
196 View Code Duplication
                if (isset($e->getTrace()[0]['args'][1]) and is_string($e->getTrace()[0]['args'][1])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
197
                    $msg .= ': '.$e->getTrace()[0]['args'][1];
198
                }
199
                \sasco\LibreDTE\Log::write(Estado::REQUEST_ERROR_SOAP, Estado::get(Estado::REQUEST_ERROR_SOAP, $msg));
0 ignored issues
show
Documentation introduced by
\sasco\LibreDTE\Estado::...QUEST_ERROR_SOAP, $msg) is of type integer|string, but the function expects a object<sasco\LibreDTE\Mensaje>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
200
                $body = null;
201
            }
202
        }
203
        if ($body===null) {
0 ignored issues
show
Bug introduced by
The variable $body does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
204
            \sasco\LibreDTE\Log::write(Estado::REQUEST_ERROR_BODY, Estado::get(Estado::REQUEST_ERROR_BODY, $wsdl, $retry));
0 ignored issues
show
Documentation introduced by
\sasco\LibreDTE\Estado::...OR_BODY, $wsdl, $retry) is of type integer|string, but the function expects a object<sasco\LibreDTE\Mensaje>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
205
            return false;
206
        }
207
        return new \SimpleXMLElement($body, LIBXML_COMPACT);
208
    }
209
210
    /**
211
     * Método que permite indicar si se debe o no verificar el certificado SSL
212
     * del SII
213
     * @param verificar =true si se quiere verificar certificado, =false en caso que no (por defecto se verifica)
214
     * @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl)
215
     * @version 2015-09-16
216
     */
217
    public static function setVerificarSSL($verificar = true)
218
    {
219
        self::$verificar_ssl = $verificar;
220
    }
221
222
    /**
223
     * Método que indica si se está o no verificando el SSL en las conexiones al SII
224
     * @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl)
225
     * @version 2017-05-11
226
     */
227
    public static function getVerificarSSL()
228
    {
229
        return self::$verificar_ssl;
230
    }
231
232
    /**
233
     * Método que realiza el envío de un DTE al SII
234
     * Referencia: http://www.sii.cl/factura_electronica/factura_mercado/envio.pdf
235
     * @param usuario RUN del usuario que envía el DTE
236
     * @param empresa RUT de la empresa emisora del DTE
237
     * @param dte Documento XML con el DTE que se desea enviar a SII
238
     * @param token Token de autenticación automática ante el SII
239
     * @param retry Intentos que se realizarán como máximo para obtener respuesta
240
     * @return Respuesta XML desde SII o bien null si no se pudo obtener respuesta
241
     * @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl)
242
     * @version 2016-08-06
243
     */
244
    public static function enviar($usuario, $empresa, $dte, $token, $retry = null)
245
    {
246
        // definir datos que se usarán en el envío
247
        list($rutSender, $dvSender) = explode('-', str_replace('.', '', $usuario));
248
        list($rutCompany, $dvCompany) = explode('-', str_replace('.', '', $empresa));
249
        if (strpos($dte, '<?xml')===false) {
250
            $dte = '<?xml version="1.0" encoding="ISO-8859-1"?>'."\n".$dte;
251
        }
252 View Code Duplication
        do {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
253
            $file = sys_get_temp_dir().'/dte_'.md5(microtime().$token.$dte).'.xml';
254
        } while (file_exists($file));
255
        file_put_contents($file, $dte);
256
        $data = [
257
            'rutSender' => $rutSender,
258
            'dvSender' => $dvSender,
259
            'rutCompany' => $rutCompany,
260
            'dvCompany' => $dvCompany,
261
            'archivo' => curl_file_create(
262
                $file,
263
                'application/xml',
264
                basename($file)
265
            ),
266
        ];
267
        // definir reintentos si no se pasaron
268
        if (!$retry)
269
            $retry = self::$retry;
270
        // crear sesión curl con sus opciones
271
        $curl = curl_init();
272
        $header = [
273
            'User-Agent: Mozilla/4.0 (compatible; PROG 1.0; LibreDTE)',
274
            'Referer: https://libredte.cl',
275
            'Cookie: TOKEN='.$token,
276
        ];
277
        $url = 'https://'.self::$config['servidor'][self::getAmbiente()].'.sii.cl/cgi_dte/UPL/DTEUpload';
278
        curl_setopt($curl, CURLOPT_POST, true);
279
        curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
280
        curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
281
        curl_setopt($curl, CURLOPT_URL, $url);
282
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
283
        // si no se debe verificar el SSL se asigna opción a curl, además si
284
        // se está en el ambiente de producción y no se verifica SSL se
285
        // generará una entrada en el log
286 View Code Duplication
        if (!self::$verificar_ssl) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
287
            if (self::getAmbiente()==self::PRODUCCION) {
288
                $msg = Estado::get(Estado::ENVIO_SSL_SIN_VERIFICAR);
289
                \sasco\LibreDTE\Log::write(Estado::ENVIO_SSL_SIN_VERIFICAR, $msg, LOG_WARNING);
0 ignored issues
show
Documentation introduced by
$msg is of type integer|string, but the function expects a object<sasco\LibreDTE\Mensaje>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
LOG_WARNING is of type integer, but the function expects a object<sasco\LibreDTE\Gravedad>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
290
            }
291
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
292
        }
293
        // enviar XML al SII
294 View Code Duplication
        for ($i=0; $i<$retry; $i++) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
295
            $response = curl_exec($curl);
296
            if ($response and $response!='Error 500')
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as and instead of && is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
297
                break;
298
        }
299
        unlink($file);
300
        // verificar respuesta del envío y entregar error en caso que haya uno
301
        if (!$response or $response=='Error 500') {
0 ignored issues
show
Comprehensibility Best Practice introduced by
Using logical operators such as or instead of || is generally not recommended.

PHP has two types of connecting operators (logical operators, and boolean operators):

  Logical Operators Boolean Operator
AND - meaning and &&
OR - meaning or ||

The difference between these is the order in which they are executed. In most cases, you would want to use a boolean operator like &&, or ||.

Let’s take a look at a few examples:

// Logical operators have lower precedence:
$f = false or true;

// is executed like this:
($f = false) or true;


// Boolean operators have higher precedence:
$f = false || true;

// is executed like this:
$f = (false || true);

Logical Operators are used for Control-Flow

One case where you explicitly want to use logical operators is for control-flow such as this:

$x === 5
    or die('$x must be 5.');

// Instead of
if ($x !== 5) {
    die('$x must be 5.');
}

Since die introduces problems of its own, f.e. it makes our code hardly testable, and prevents any kind of more sophisticated error handling; you probably do not want to use this in real-world code. Unfortunately, logical operators cannot be combined with throw at this point:

// The following is currently a parse error.
$x === 5
    or throw new RuntimeException('$x must be 5.');

These limitations lead to logical operators rarely being of use in current PHP code.

Loading history...
302
            if (!$response)
0 ignored issues
show
Bug introduced by
The variable $response does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
303
                \sasco\LibreDTE\Log::write(Estado::ENVIO_ERROR_CURL, Estado::get(Estado::ENVIO_ERROR_CURL, curl_error($curl)));
0 ignored issues
show
Documentation introduced by
\sasco\LibreDTE\Estado::...URL, curl_error($curl)) is of type integer|string, but the function expects a object<sasco\LibreDTE\Mensaje>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
304
            if ($response=='Error 500')
305
                \sasco\LibreDTE\Log::write(Estado::ENVIO_ERROR_500, Estado::get(Estado::ENVIO_ERROR_500));
0 ignored issues
show
Documentation introduced by
\sasco\LibreDTE\Estado::...stado::ENVIO_ERROR_500) is of type integer|string, but the function expects a object<sasco\LibreDTE\Mensaje>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
306
            return false;
307
        }
308
        // cerrar sesión curl
309
        curl_close($curl);
310
        // crear XML con la respuesta y retornar
311
        try {
312
            $xml = new \SimpleXMLElement($response, LIBXML_COMPACT);
313
        } catch (Exception $e) {
0 ignored issues
show
Bug introduced by
The class sasco\LibreDTE\Exception does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
314
            \sasco\LibreDTE\Log::write(Estado::ENVIO_ERROR_XML, Estado::get(Estado::ENVIO_ERROR_XML, $e->getMessage()));
0 ignored issues
show
Documentation introduced by
\sasco\LibreDTE\Estado::..._XML, $e->getMessage()) is of type integer|string, but the function expects a object<sasco\LibreDTE\Mensaje>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
315
            return false;
316
        }
317
        if ($xml->STATUS!=0) {
318
            \sasco\LibreDTE\Log::write(
319
                $xml->STATUS,
320
                Estado::get($xml->STATUS).(isset($xml->DETAIL)?'. '.implode("\n", (array)$xml->DETAIL->ERROR):'')
0 ignored issues
show
Documentation introduced by
\sasco\LibreDTE\Estado::...l->DETAIL->ERROR) : '') is of type string, but the function expects a object<sasco\LibreDTE\Mensaje>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
321
            );
322
        }
323
        return $xml;
324
    }
325
326
    /**
327
     * Método para obtener la clave pública (certificado X.509) del SII
328
     *
329
     * \code{.php}
330
     *   $pub_key = \sasco\LibreDTE\Sii::cert(100); // Certificado IDK 100 (certificación)
331
     * \endcode
332
     *
333
     * @param idk IDK de la clave pública del SII. Si no se indica se tratará de determinar con el ambiente que se esté usando
334
     * @return Contenido del certificado
335
     * @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl)
336
     * @version 2015-09-16
337
     */
338
    public static function cert($idk = null)
339
    {
340
        // si se pasó un idk y existe el archivo asociado se entrega
341
        if ($idk) {
342
            $cert = dirname(dirname(__FILE__)).'/certs/'.$idk.'.cer';
343
            if (is_readable($cert))
344
                return file_get_contents($cert);
345
        }
346
        // buscar certificado y entregar si existe o =false si no
347
        $ambiente = self::getAmbiente();
348
        $cert = dirname(dirname(__FILE__)).'/certs/'.self::$config['certs'][$ambiente].'.cer';
349
        if (!is_readable($cert)) {
350
            \sasco\LibreDTE\Log::write(Estado::SII_ERROR_CERTIFICADO, Estado::get(Estado::SII_ERROR_CERTIFICADO, self::$config['certs'][$ambiente]));
0 ignored issues
show
Documentation introduced by
\sasco\LibreDTE\Estado::...ig['certs'][$ambiente]) is of type integer|string, but the function expects a object<sasco\LibreDTE\Mensaje>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
351
            return false;
352
        }
353
        return file_get_contents($cert);
354
    }
355
356
    /**
357
     * Método que asigna el ambiente que se usará por defecto (si no está
358
     * asignado con la constante _LibreDTE_CERTIFICACION_)
359
     * @param ambiente Ambiente a usar: Sii::PRODUCCION o Sii::CERTIFICACION
360
     * @warning No se está verificando SSL en ambiente de certificación
361
     * @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl)
362
     * @version 2016-08-28
363
     */
364
    public static function setAmbiente($ambiente = self::PRODUCCION)
365
    {
366
        $ambiente = $ambiente ? self::CERTIFICACION : self::PRODUCCION;
367
        if ($ambiente==self::CERTIFICACION) {
368
            self::setVerificarSSL(false);
369
        }
370
        self::$ambiente = $ambiente;
371
    }
372
373
    /**
374
     * Método que determina el ambiente que se debe utilizar: producción o
375
     * certificación
376
     * @param ambiente Ambiente a usar: Sii::PRODUCCION o Sii::CERTIFICACION o null (para detección automática)
377
     * @return Ambiente que se debe utilizar
378
     * @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl)
379
     * @version 2015-09-07
380
     */
381
    public static function getAmbiente($ambiente = null)
382
    {
383
        if ($ambiente===null) {
384
            if (defined('_LibreDTE_CERTIFICACION_'))
385
                $ambiente = (int)_LibreDTE_CERTIFICACION_;
0 ignored issues
show
Bug Compatibility introduced by
The expression (int) _LibreDTE_CERTIFICACION_; of type integer adds the type integer to the return on line 389 which is incompatible with the return type documented by sasco\LibreDTE\Sii::getAmbiente of type sasco\LibreDTE\Ambiente.
Loading history...
386
            else
387
                $ambiente = self::$ambiente;
0 ignored issues
show
Bug Compatibility introduced by
The expression self::$ambiente; of type integer adds the type integer to the return on line 389 which is incompatible with the return type documented by sasco\LibreDTE\Sii::getAmbiente of type sasco\LibreDTE\Ambiente.
Loading history...
388
        }
389
        return $ambiente;
390
    }
391
392
    /**
393
     * Método que entrega la tasa de IVA vigente
394
     * @return Tasa de IVA vigente
395
     * @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl)
396
     * @version 2015-09-03
397
     */
398
    public static function getIVA()
399
    {
400
        return self::IVA;
401
    }
402
403
    /**
404
     * Método que entrega un arreglo con todos los datos de los contribuyentes
405
     * que operan con factura electrónica descargados desde el SII
406
     * @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl)
407
     * @version 2016-10-28
408
     */
409
    public static function getContribuyentes(\sasco\LibreDTE\FirmaElectronica $Firma, $ambiente = null, $dia = null)
410
    {
411
        // solicitar token
412
        $token = \sasco\LibreDTE\Sii\Autenticacion::getToken($Firma);
0 ignored issues
show
Documentation introduced by
$Firma is of type object<sasco\LibreDTE\FirmaElectronica>, but the function expects a object<sasco\LibreDTE\Sii\objeto>|array.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
413
        if (!$token)
0 ignored issues
show
Bug Best Practice introduced by
The expression $token of type false|string is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
414
            return false;
415
        // definir ambiente y servidor
416
        $ambiente = self::getAmbiente($ambiente);
417
        $servidor = self::$config['servidor'][$ambiente];
418
        // preparar consulta curl
419
        $curl = curl_init();
420
        $header = [
421
            'User-Agent: Mozilla/4.0 (compatible; PROG 1.0; LibreDTE)',
422
            'Referer: https://'.$servidor.'.sii.cl/cvc/dte/ee_empresas_dte.html',
423
            'Cookie: TOKEN='.$token,
424
            'Accept-Encoding' => 'gzip, deflate, sdch',
425
        ];
426
        $dia = $dia===null ? date('Ymd') : str_replace('-', '', $dia);
427
        $url = 'https://'.$servidor.'.sii.cl/cvc_cgi/dte/ee_consulta_empresas_dwnld?NOMBRE_ARCHIVO=ce_empresas_dwnld_'.$dia.'.csv';
428
        curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
429
        curl_setopt($curl, CURLOPT_URL, $url);
430
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
431
        // si no se debe verificar el SSL se asigna opción a curl, además si
432
        // se está en el ambiente de producción y no se verifica SSL se
433
        // generará un error de nivel E_USER_NOTICE
434 View Code Duplication
        if (!self::$verificar_ssl) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
435
            if ($ambiente==self::PRODUCCION) {
436
                $msg = Estado::get(Estado::ENVIO_SSL_SIN_VERIFICAR);
437
                \sasco\LibreDTE\Log::write(Estado::ENVIO_SSL_SIN_VERIFICAR, $msg, LOG_WARNING);
0 ignored issues
show
Documentation introduced by
$msg is of type integer|string, but the function expects a object<sasco\LibreDTE\Mensaje>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
LOG_WARNING is of type integer, but the function expects a object<sasco\LibreDTE\Gravedad>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
438
            }
439
            curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
440
        }
441
        // realizar consulta curl
442
        $response = curl_exec($curl);
443
        if (!$response)
444
            return false;
445
        // cerrar sesión curl
446
        curl_close($curl);
447
        // entregar datos del archivo CSV
448
        ini_set('memory_limit', '1024M');
449
        $lines = explode("\n", $response);
450
        $n_lines = count($lines);
451
        $data = [];
452
        for ($i=1; $i<$n_lines; $i++) {
453
            $row = str_getcsv($lines[$i], ';', '');
454
            unset($lines[$i]);
455
            if (!isset($row[5]))
456
                continue;
457
            for ($j=0; $j<6; $j++)
458
                $row[$j] = trim($row[$j]);
459
            $row[1] = utf8_decode($row[1]);
460
            $row[4] = strtolower($row[4]);
461
            $row[5] = strtolower($row[5]);
462
            $data[] = $row;
463
        }
464
        return $data;
465
    }
466
467
    /**
468
     * Método que entrega la dirección regional según la comuna que se esté
469
     * consultando
470
     * @param comuna de la sucursal del emior o bien código de la sucursal del SII
471
     * @return Dirección regional del SII
472
     * @author Esteban De La Fuente Rubio, DeLaF (esteban[at]sasco.cl)
473
     * @version 2016-06-03
474
     */
475
    public static function getDireccionRegional($comuna)
476
    {
477
        if (!is_numeric($comuna)) {
478
            $direccion = mb_strtoupper($comuna, 'UTF-8');
479
            return isset(self::$direcciones_regionales[$direccion]) ? self::$direcciones_regionales[$direccion] : $direccion;
480
        }
481
        return 'SUC '.$comuna;
0 ignored issues
show
Bug Best Practice introduced by
The return type of return 'SUC ' . $comuna; (string) is incompatible with the return type documented by sasco\LibreDTE\Sii::getDireccionRegional of type sasco\LibreDTE\Dirección.

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
482
    }
483
484
}
485