Completed
Push — master ( adc3b6...605ecb )
by cam
06:00
created

sqlite_generique.php ➔ spip_sqlite_error()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
nc 6
nop 2
dl 0
loc 18
rs 9.6666
c 0
b 0
f 0
1
<?php
2
3
/* *************************************************************************\
4
 *  SPIP, Systeme de publication pour l'internet                           *
5
 *                                                                         *
6
 *  Copyright (c) 2001-2018                                                *
7
 *  Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James  *
8
 *                                                                         *
9
 *  Ce programme est un logiciel libre distribue sous licence GNU/GPL.     *
10
 *  Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne.   *
11
\***************************************************************************/
12
13
/**
14
 * Ce fichier contient les fonctions gérant
15
 * les instructions SQL pour Sqlite
16
 *
17
 * @package SPIP\Core\SQL\SQLite
18
 */
19
20
if (!defined('_ECRIRE_INC_VERSION')) {
21
	return;
22
}
23
24
// TODO: get/set_caracteres ?
25
26
27
/*
28
 * regroupe le maximum de fonctions qui peuvent cohabiter
29
 * D'abord les fonctions d'abstractions de SPIP
30
 */
31
32
/**
33
 * Connecteur à une base SQLite
34
 *
35
 * @param string $addr
36
 * @param int $port
37
 * @param string $login
38
 * @param string $pass
39
 * @param string $db
40
 * @param string $prefixe
41
 * @param string $sqlite_version
42
 * @return array|bool
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use false|array<string,string|PDO|resource|integer>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
43
 */
44
function req_sqlite_dist($addr, $port, $login, $pass, $db = '', $prefixe = '', $sqlite_version = '') {
45
	static $last_connect = array();
46
47
	// si provient de selectdb
48
	// un code pour etre sur que l'on vient de select_db()
49
	if (strpos($db, $code = '@selectdb@') !== false) {
50 View Code Duplication
		foreach (array('addr', 'port', 'login', 'pass', 'prefixe') as $a) {
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...
51
			$$a = $last_connect[$a];
52
		}
53
		$db = str_replace($code, '', $db);
54
	}
55
56
	/*
57
	 * En sqlite, seule l'adresse du fichier est importante.
58
	 * Ce sera $db le nom,
59
	 * le path est $addr
60
	 * (_DIR_DB si $addr est vide)
61
	 */
62
	_sqlite_init();
63
64
	// determiner le dossier de la base : $addr ou _DIR_DB
65
	$f = _DIR_DB;
66
	if ($addr and strpos($addr, '/') !== false) {
67
		$f = rtrim($addr, '/') . '/';
68
	}
69
70
	// un nom de base demande et impossible d'obtenir la base, on s'en va :
71
	// il faut que la base existe ou que le repertoire parent soit writable
72
	if ($db and !is_file($f .= $db . '.sqlite') and !is_writable(dirname($f))) {
73
		spip_log("base $f non trouvee ou droits en ecriture manquants", 'sqlite.' . _LOG_HS);
74
75
		return false;
76
	}
77
78
	// charger les modules sqlite au besoin
79
	if (!_sqlite_charger_version($sqlite_version)) {
80
		spip_log("Impossible de trouver/charger le module SQLite ($sqlite_version)!", 'sqlite.' . _LOG_HS);
81
82
		return false;
83
	}
84
85
	// chargement des constantes
86
	// il ne faut pas definir les constantes avant d'avoir charge les modules sqlite
87
	$define = "spip_sqlite" . $sqlite_version . "_constantes";
88
	$define();
89
90
	$ok = false;
0 ignored issues
show
Unused Code introduced by
$ok is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
91
	if (!$db) {
92
		// si pas de db ->
93
		// base temporaire tant qu'on ne connait pas son vrai nom
94
		// pour tester la connexion
95
		$db = "_sqlite" . $sqlite_version . "_install";
96
		$tmp = _DIR_DB . $db . ".sqlite";
97
		$ok = $link = new PDO("sqlite:$tmp");
98
	} else {
99
		// Ouvrir (eventuellement creer la base)
100
		$ok = $link = new PDO("sqlite:$f");
101
	}
102
103
	if (!$ok) {
104
		$e = _sqlite_last_error_from_link($link);
105
		spip_log("Impossible d'ouvrir la base SQLite($sqlite_version) $f : $e", 'sqlite.' . _LOG_HS);
106
107
		return false;
108
	}
109
110
	if ($link) {
111
		$last_connect = array(
112
			'addr' => $addr,
113
			'port' => $port,
114
			'login' => $login,
115
			'pass' => $pass,
116
			'db' => $db,
117
			'prefixe' => $prefixe,
118
		);
119
		// etre sur qu'on definit bien les fonctions a chaque nouvelle connexion
120
		include_spip('req/sqlite_fonctions');
121
		_sqlite_init_functions($link);
122
	}
123
124
	return array(
125
		'db' => $db,
126
		'prefixe' => $prefixe ? $prefixe : $db,
127
		'link' => $link,
128
		'total_requetes' => 0,
129
	);
130
}
131
132
133
/**
134
 * Fonction de requete generale, munie d'une trace a la demande
135
 *
136
 * @param string $query
137
 *    Requete a executer
138
 * @param string $serveur
139
 *    Nom du connecteur
140
 * @param bool $requeter
141
 *    Effectuer la requete ?
142
 *    - true pour executer
143
 *    - false pour retourner le texte de la requete
144
 * @return bool|SQLiteResult|string
145
 *    Resultat de la requete
146
 */
147
function spip_sqlite_query($query, $serveur = '', $requeter = true) {
148
	#spip_log("spip_sqlite_query() > $query",'sqlite.'._LOG_DEBUG);
149
	#_sqlite_init(); // fait la premiere fois dans spip_sqlite
150
	$query = spip_sqlite::traduire_requete($query, $serveur);
151
	if (!$requeter) {
152
		return $query;
153
	}
154
155
	return spip_sqlite::executer_requete($query, $serveur);
156
}
157
158
159
/* ordre alphabetique pour les autres */
160
161
/**
162
 * Modifie une structure de table SQLite
163
 *
164
 * @param string $query Requête SQL (sans 'ALTER ')
165
 * @param string $serveur Nom de la connexion
166
 * @param bool $requeter inutilisé
167
 * @return bool
168
 *     False si erreur dans l'exécution, true sinon
169
 */
170
function spip_sqlite_alter($query, $serveur = '', $requeter = true) {
0 ignored issues
show
Unused Code introduced by
The parameter $requeter is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
171
172
	$query = spip_sqlite_query("ALTER $query", $serveur, false);
173
	// traduire la requete pour recuperer les bons noms de table
174
	$query = spip_sqlite::traduire_requete($query, $serveur);
175
176
	/*
177
		 * la il faut faire les transformations
178
		 * si ALTER TABLE x (DROP|CHANGE) y
179
		 *
180
		 * 1) recuperer "ALTER TABLE table "
181
		 * 2) spliter les sous requetes (,)
182
		 * 3) faire chaque requete independemment
183
		 */
184
185
	// 1
186
	if (preg_match("/\s*(ALTER(\s*IGNORE)?\s*TABLE\s*([^\s]*))\s*(.*)?/is", $query, $regs)) {
187
		$debut = $regs[1];
188
		$table = $regs[3];
189
		$suite = $regs[4];
190
	} else {
191
		spip_log("SQLite : Probleme de ALTER TABLE mal forme dans $query", 'sqlite.' . _LOG_ERREUR);
192
193
		return false;
194
	}
195
196
	// 2
197
	// il faudrait une regexp pour eviter de spliter ADD PRIMARY KEY (colA, colB)
198
	// tout en cassant "ADD PRIMARY KEY (colA, colB), ADD INDEX (chose)"... en deux
199
	// ou revoir l'api de sql_alter en creant un 
200
	// sql_alter_table($table,array($actions));
201
	$todo = explode(',', $suite);
202
203
	// on remet les morceaux dechires ensembles... que c'est laid !
204
	$todo2 = array();
205
	$i = 0;
206
	$ouverte = false;
207 View Code Duplication
	while ($do = array_shift($todo)) {
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...
208
		$todo2[$i] = isset($todo2[$i]) ? $todo2[$i] . "," . $do : $do;
209
		$o = (false !== strpos($do, "("));
210
		$f = (false !== strpos($do, ")"));
211
		if ($o and !$f) {
212
			$ouverte = true;
213
		} elseif ($f) {
214
			$ouverte = false;
215
		}
216
		if (!$ouverte) {
217
			$i++;
218
		}
219
	}
220
221
	// 3
222
	$resultats = array();
0 ignored issues
show
Unused Code introduced by
$resultats is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
223
	foreach ($todo2 as $do) {
224
		$do = trim($do);
225
		if (!preg_match('/(DROP PRIMARY KEY|DROP KEY|DROP INDEX|DROP COLUMN|DROP'
226
			. '|CHANGE COLUMN|CHANGE|MODIFY|RENAME TO|RENAME'
227
			. '|ADD PRIMARY KEY|ADD KEY|ADD INDEX|ADD UNIQUE KEY|ADD UNIQUE'
228
			. '|ADD COLUMN|ADD'
229
			. ')\s*([^\s]*)\s*(.*)?/i', $do, $matches)
230
		) {
231
			spip_log("SQLite : Probleme de ALTER TABLE, utilisation non reconnue dans : $do \n(requete d'origine : $query)",
232
				'sqlite.' . _LOG_ERREUR);
233
234
			return false;
235
		}
236
237
		$cle = strtoupper($matches[1]);
238
		$colonne_origine = $matches[2];
239
		$colonne_destination = '';
0 ignored issues
show
Unused Code introduced by
$colonne_destination is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
240
241
		$def = $matches[3];
242
243
		// eluder une eventuelle clause before|after|first inutilisable
244
		$defr = rtrim(preg_replace('/(BEFORE|AFTER|FIRST)(.*)$/is', '', $def));
245
		$defo = $defr; // garder la def d'origine pour certains cas
246
		// remplacer les definitions venant de mysql
247
		$defr = _sqlite_remplacements_definitions_table($defr);
248
249
		// reinjecter dans le do
250
		$do = str_replace($def, $defr, $do);
251
		$def = $defr;
252
253
		switch ($cle) {
254
			// suppression d'un index
255
			case 'DROP KEY':
256
			case 'DROP INDEX':
257
				$nom_index = $colonne_origine;
258
				spip_sqlite_drop_index($nom_index, $table, $serveur);
259
				break;
260
261
			// suppression d'une pk
262 View Code Duplication
			case 'DROP PRIMARY KEY':
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...
263
				if (!_sqlite_modifier_table(
264
					$table,
265
					$colonne_origine,
266
					array('key' => array('PRIMARY KEY' => '')),
267
					$serveur)
268
				) {
269
					return false;
270
				}
271
				break;
272
			// suppression d'une colonne
273
			case 'DROP COLUMN':
274 View Code Duplication
			case 'DROP':
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...
275
				if (!_sqlite_modifier_table(
276
					$table,
277
					array($colonne_origine => ""),
278
					array(),
279
					$serveur)
280
				) {
281
					return false;
282
				}
283
				break;
284
285
			case 'CHANGE COLUMN':
286
			case 'CHANGE':
287
				// recuperer le nom de la future colonne
288
				// on reprend la def d'origine car _sqlite_modifier_table va refaire la translation
289
				// en tenant compte de la cle primaire (ce qui est mieux)
290
				$def = trim($defo);
291
				$colonne_destination = substr($def, 0, strpos($def, ' '));
292
				$def = substr($def, strlen($colonne_destination) + 1);
293
294
				if (!_sqlite_modifier_table(
295
					$table,
296
					array($colonne_origine => $colonne_destination),
297
					array('field' => array($colonne_destination => $def)),
298
					$serveur)
299
				) {
300
					return false;
301
				}
302
				break;
303
304 View Code Duplication
			case 'MODIFY':
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...
305
				// on reprend la def d'origine car _sqlite_modifier_table va refaire la translation
306
				// en tenant compte de la cle primaire (ce qui est mieux)
307
				if (!_sqlite_modifier_table(
308
					$table,
309
					$colonne_origine,
310
					array('field' => array($colonne_origine => $defo)),
311
					$serveur)
312
				) {
313
					return false;
314
				}
315
				break;
316
317
			// pas geres en sqlite2
318
			case 'RENAME':
319
				$do = "RENAME TO" . substr($do, 6);
320
			case 'RENAME TO':
321 View Code Duplication
				if (!spip_sqlite::executer_requete("$debut $do", $serveur)) {
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...
322
					spip_log("SQLite : Erreur ALTER TABLE / RENAME : $query", 'sqlite.' . _LOG_ERREUR);
323
324
					return false;
325
				}
326
				break;
327
328
			// ajout d'une pk
329
			case 'ADD PRIMARY KEY':
330
				$pk = trim(substr($do, 16));
331
				$pk = ($pk[0] == '(') ? substr($pk, 1, -1) : $pk;
332
				if (!_sqlite_modifier_table(
333
					$table,
334
					$colonne_origine,
335
					array('key' => array('PRIMARY KEY' => $pk)),
336
					$serveur)
337
				) {
338
					return false;
339
				}
340
				break;
341
			// ajout d'un index
342
			case 'ADD UNIQUE KEY':
343
			case 'ADD UNIQUE':
344
				$unique = true;
345
			case 'ADD INDEX':
346
			case 'ADD KEY':
347
				if (!isset($unique)) {
348
					$unique = false;
349
				}
350
				// peut etre "(colonne)" ou "nom_index (colonnes)"
351
				// bug potentiel si qqn met "(colonne, colonne)"
352
				//
353
				// nom_index (colonnes)
354
				if ($def) {
355
					$colonnes = substr($def, 1, -1);
356
					$nom_index = $colonne_origine;
357
				} else {
358
					// (colonne)
359
					if ($colonne_origine[0] == "(") {
360
						$colonnes = substr($colonne_origine, 1, -1);
361 View Code Duplication
						if (false !== strpos(",", $colonnes)) {
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...
362
							spip_log(_LOG_GRAVITE_ERREUR, "SQLite : Erreur, impossible de creer un index sur plusieurs colonnes"
363
								. " sans qu'il ait de nom ($table, ($colonnes))", 'sqlite');
0 ignored issues
show
Unused Code introduced by
The call to spip_log() has too many arguments starting with 'sqlite'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
364
							break;
365
						} else {
366
							$nom_index = $colonnes;
367
						}
368
					} // nom_index
369
					else {
370
						$nom_index = $colonnes = $colonne_origine;
371
					}
372
				}
373
				spip_sqlite_create_index($nom_index, $table, $colonnes, $unique, $serveur);
374
				break;
375
376
			// pas geres en sqlite2
377
			case 'ADD COLUMN':
378
				$do = "ADD" . substr($do, 10);
379
			case 'ADD':
380
			default:
381
				if (!preg_match(',primary\s+key,i', $do)) {
382 View Code Duplication
					if (!spip_sqlite::executer_requete("$debut $do", $serveur)) {
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...
383
						spip_log("SQLite : Erreur ALTER TABLE / ADD : $query", 'sqlite.' . _LOG_ERREUR);
384
385
						return false;
386
					}
387
					break;
388
389
				}
390
				// ou si la colonne est aussi primary key
391
				// cas du add id_truc int primary key
392
				// ajout d'une colonne qui passe en primary key directe
393
				else {
394
					$def = trim(substr($do, 3));
395
					$colonne_ajoutee = substr($def, 0, strpos($def, ' '));
396
					$def = substr($def, strlen($colonne_ajoutee) + 1);
397
					$opts = array();
398
					if (preg_match(',primary\s+key,i', $def)) {
399
						$opts['key'] = array('PRIMARY KEY' => $colonne_ajoutee);
400
						$def = preg_replace(',primary\s+key,i', '', $def);
401
					}
402
					$opts['field'] = array($colonne_ajoutee => $def);
403
					if (!_sqlite_modifier_table($table, array($colonne_ajoutee), $opts, $serveur)) {
404
						spip_log("SQLite : Erreur ALTER TABLE / ADD : $query", 'sqlite.' . _LOG_ERREUR);
405
406
						return false;
407
					}
408
				}
409
				break;
410
		}
411
		// tout est bon, ouf !
412
		spip_log("SQLite ($serveur) : Changements OK : $debut $do", 'sqlite.' . _LOG_INFO);
413
	}
414
415
	spip_log("SQLite ($serveur) : fin ALTER TABLE OK !", 'sqlite.' . _LOG_INFO);
416
417
	return true;
418
}
419
420
421
/**
422
 * Crée une table SQL
423
 *
424
 * Crée une table SQL nommee `$nom` à partir des 2 tableaux `$champs` et `$cles`
425
 *
426
 * @note Le nom des caches doit être inferieur à 64 caractères
427
 *
428
 * @param string $nom Nom de la table SQL
429
 * @param array $champs Couples (champ => description SQL)
430
 * @param array $cles Couples (type de clé => champ(s) de la clé)
431
 * @param bool $autoinc True pour ajouter un auto-incrément sur la Primary Key
432
 * @param bool $temporary True pour créer une table temporaire
433
 * @param string $serveur Nom de la connexion
434
 * @param bool $requeter Exécuter la requête, sinon la retourner
435
 * @return array|null|resource|string
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|SQLiteResult|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
436
 *     - string Texte de la requête si demandée
437
 *     - true si la requête réussie, false sinon.
438
 */
439
function spip_sqlite_create(
440
	$nom,
441
	$champs,
442
	$cles,
443
	$autoinc = false,
444
	$temporary = false,
445
	$serveur = '',
446
	$requeter = true
447
) {
448
	$query = _sqlite_requete_create($nom, $champs, $cles, $autoinc, $temporary, $ifnotexists = true, $serveur, $requeter);
449
	if (!$query) {
450
		return false;
451
	}
452
	$res = spip_sqlite_query($query, $serveur, $requeter);
0 ignored issues
show
Bug introduced by
It seems like $query defined by _sqlite_requete_create($...e, $serveur, $requeter) on line 448 can also be of type boolean; however, spip_sqlite_query() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
453
454
	// SQLite ne cree pas les KEY sur les requetes CREATE TABLE
455
	// il faut donc les faire creer ensuite
456
	if (!$requeter) {
457
		return $res;
458
	}
459
460
	$ok = $res ? true : false;
461
	if ($ok) {
462
		foreach ($cles as $k => $v) {
463
			if (preg_match(',^(KEY|UNIQUE)\s,i', $k, $m)) {
464
				$index = trim(substr($k, strlen($m[1])));
465
				$unique = (strlen($m[1]) > 3);
466
				$ok &= spip_sqlite_create_index($index, $nom, $v, $unique, $serveur);
467
			}
468
		}
469
	}
470
471
	return $ok ? true : false;
472
}
473
474
/**
475
 * Crée une base de données SQLite
476
 *
477
 * @param string $nom Nom de la base (sans l'extension de fichier)
478
 * @param string $serveur Nom de la connexion
479
 * @param string $option Options
0 ignored issues
show
Documentation introduced by
Should the type for parameter $option not be boolean|string?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
480
 *
481
 * @return bool true si la base est créee.
482
 **/
483
function spip_sqlite_create_base($nom, $serveur = '', $option = true) {
0 ignored issues
show
Unused Code introduced by
The parameter $serveur is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $option is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
484
	$f = $nom . '.sqlite';
485
	if (strpos($nom, "/") === false) {
486
		$f = _DIR_DB . $f;
487
	}
488
489
	$ok = new PDO("sqlite:$f");
490
491
	if ($ok) {
492
		unset($ok);
493
494
		return true;
495
	}
496
	unset($ok);
497
498
	return false;
499
}
500
501
502
/**
503
 * Crée une vue SQL nommée `$nom`
504
 *
505
 * @param string $nom
506
 *    Nom de la vue a creer
507
 * @param string $query_select
508
 *     Texte de la requête de sélection servant de base à la vue
509
 * @param string $serveur
510
 *     Nom du connecteur
511
 * @param bool $requeter
512
 *     Effectuer la requete, sinon la retourner
513
 * @return bool|SQLiteResult|string
514
 *     - true si la vue est créée
515
 *     - false si erreur ou si la vue existe déja
516
 *     - string texte de la requête si $requeter vaut false
517
 */
518 View Code Duplication
function spip_sqlite_create_view($nom, $query_select, $serveur = '', $requeter = true) {
0 ignored issues
show
Duplication introduced by
This function 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...
519
	if (!$query_select) {
520
		return false;
521
	}
522
	// vue deja presente
523
	if (sql_showtable($nom, false, $serveur)) {
524
		spip_log("Echec creation d'une vue sql ($nom) car celle-ci existe deja (serveur:$serveur)",
525
			'sqlite.' . _LOG_ERREUR);
526
527
		return false;
528
	}
529
530
	$query = "CREATE VIEW $nom AS " . $query_select;
531
532
	return spip_sqlite_query($query, $serveur, $requeter);
533
}
534
535
/**
536
 * Fonction de création d'un INDEX
537
 *
538
 * @param string $nom
539
 *     Nom de l'index
540
 * @param string $table
541
 *     Table SQL de l'index
542
 * @param string|array $champs
543
 *     Liste de champs sur lesquels s'applique l'index
544
 * @param string|bool $unique
545
 *     Créer un index UNIQUE ?
546
 * @param string $serveur
547
 *     Nom de la connexion sql utilisee
548
 * @param bool $requeter
549
 *     true pour executer la requête ou false pour retourner le texte de la requête
550
 * @return bool|string
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|SQLiteResult|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
551
 *    string : requête, false si erreur, true sinon.
552
 */
553
function spip_sqlite_create_index($nom, $table, $champs, $unique = '', $serveur = '', $requeter = true) {
554 View Code Duplication
	if (!($nom or $table or $champs)) {
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...
555
		spip_log("Champ manquant pour creer un index sqlite ($nom, $table, (" . join(',', $champs) . "))",
556
			'sqlite.' . _LOG_ERREUR);
557
558
		return false;
559
	}
560
561
	// SQLite ne differentie pas noms des index en fonction des tables
562
	// il faut donc creer des noms uniques d'index pour une base sqlite
563
	$nom = $table . '_' . $nom;
564
	// enlever d'eventuelles parentheses deja presentes sur champs
565
	if (!is_array($champs)) {
566
		if ($champs[0] == "(") {
567
			$champs = substr($champs, 1, -1);
568
		}
569
		$champs = array($champs);
570
		// supprimer l'info de longueur d'index mysql en fin de champ
571
		$champs = preg_replace(",\(\d+\)$,", "", $champs);
572
	}
573
574
	$ifnotexists = "";
575
	$version = spip_sqlite_fetch(spip_sqlite_query("select sqlite_version() AS sqlite_version", $serveur), '', $serveur);
576
	if (!function_exists('spip_version_compare')) {
577
		include_spip('plugins/installer');
578
	}
579
580 View Code Duplication
	if ($version and spip_version_compare($version['sqlite_version'], '3.3.0', '>=')) {
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...
581
		$ifnotexists = ' IF NOT EXISTS';
582
	} else {
583
		/* simuler le IF EXISTS - version 2 et sqlite < 3.3a */
584
		$a = spip_sqlite_showtable($table, $serveur);
585
		if (isset($a['key']['KEY ' . $nom])) {
586
			return true;
587
		}
588
	}
589
590
	$query = "CREATE " . ($unique ? "UNIQUE " : "") . "INDEX$ifnotexists $nom ON $table (" . join(',', $champs) . ")";
591
	$res = spip_sqlite_query($query, $serveur, $requeter);
592
	if (!$requeter) {
593
		return $res;
594
	}
595
	if ($res) {
0 ignored issues
show
Coding Style introduced by
The if-else statement can be simplified to return (bool) $res;.
Loading history...
596
		return true;
597
	} else {
598
		return false;
599
	}
600
}
601
602
/**
603
 * Retourne le nombre de lignes d’une ressource de sélection obtenue
604
 * avec `sql_select()`
605
 *
606
 * En PDO/sqlite3, il faut calculer le count par une requete count(*)
607
 * pour les resultats de SELECT
608
 * cela est fait sans spip_sqlite_query()
609
 *
610
 * @param Ressource|Object $r Ressource de résultat
611
 * @param string $serveur Nom de la connexion
612
 * @param bool $requeter Inutilisé
613
 * @return int                 Nombre de lignes
614
 */
615
function spip_sqlite_count($r, $serveur = '', $requeter = true) {
0 ignored issues
show
Unused Code introduced by
The parameter $serveur is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $requeter is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
616
	if (!$r) {
617
		return 0;
618
	}
619
620
	// select ou autre (insert, update,...) ?
621
	// (link,requete) a compter
622
	if (is_array($r->spipSqliteRowCount)) {
623
		list($link, $query) = $r->spipSqliteRowCount;
624
		// amelioration possible a tester intensivement : pas de order by pour compter !
625
		// $query = preg_replace(",ORDER BY .+(LIMIT\s|HAVING\s|GROUP BY\s|$),Uims","\\1",$query);
626
		$query = "SELECT count(*) as zzzzsqlitecount FROM ($query)";
627
		$l = $link->query($query);
628
		$i = 0;
629
		if ($l and $z = $l->fetch()) {
630
			$i = $z['zzzzsqlitecount'];
631
		}
632
		$r->spipSqliteRowCount = $i;
633
	}
634
	if (isset($r->spipSqliteRowCount)) {
635
		// Ce compte est faux s'il y a des limit dans la requete :(
636
		// il retourne le nombre d'enregistrements sans le limit
637
		return $r->spipSqliteRowCount;
638
	} else {
639
		return $r->rowCount();
640
	}
641
}
642
643
644
/**
645
 * Retourne le nombre de lignes d'une sélection
646
 *
647
 * @param array|string $from Tables à consulter (From)
648
 * @param array|string $where Conditions a remplir (Where)
649
 * @param array|string $groupby Critère de regroupement (Group by)
650
 * @param array $having Tableau des des post-conditions à remplir (Having)
651
 * @param string $serveur Nom de la connexion
652
 * @param bool $requeter Exécuter la requête, sinon la retourner
653
 * @return int|bool|string
654
 *     - String Texte de la requête si demandé
655
 *     - int Nombre de lignes
656
 *     - false si la requête a échouée
657
 **/
658
function spip_sqlite_countsel(
659
	$from = array(),
660
	$where = array(),
661
	$groupby = '',
662
	$having = array(),
663
	$serveur = '',
664
	$requeter = true
665
) {
666
	$c = !$groupby ? '*' : ('DISTINCT ' . (is_string($groupby) ? $groupby : join(',', $groupby)));
667
	$r = spip_sqlite_select("COUNT($c)", $from, $where, '', '', '',
668
		$having, $serveur, $requeter);
669
	if ((is_resource($r) or is_object($r)) && $requeter) { // ressource : sqlite2, object : sqlite3
670
		list($r) = spip_sqlite_fetch($r, SPIP_SQLITE3_NUM, $serveur);
671
	}
672
673
	return $r;
674
}
675
676
677
/**
678
 * Supprime des enregistrements d'une table
679
 *
680
 * @param string $table Nom de la table SQL
681
 * @param string|array $where Conditions à vérifier
682
 * @param string $serveur Nom du connecteur
683
 * @param bool $requeter Exécuter la requête, sinon la retourner
684
 * @return bool|string
685
 *     - int : nombre de suppressions réalisées,
686
 *     - Texte de la requête si demandé,
687
 *     - False en cas d'erreur.
688
 **/
689
function spip_sqlite_delete($table, $where = '', $serveur = '', $requeter = true) {
690
	$res = spip_sqlite_query(
691
		_sqlite_calculer_expression('DELETE FROM', $table, ',')
692
		. _sqlite_calculer_expression('WHERE', $where),
693
		$serveur, $requeter);
694
695
	// renvoyer la requete inerte si demandee
696
	if (!$requeter) {
697
		return $res;
698
	}
699
700
	if ($res) {
701
		$link = _sqlite_link($serveur);
0 ignored issues
show
Unused Code introduced by
$link is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
702
		return $res->rowCount();
703
	} else {
704
		return false;
705
	}
706
}
707
708
709
/**
710
 * Supprime une table SQL
711
 *
712
 * @param string $table Nom de la table SQL
713
 * @param string $exist True pour ajouter un test d'existence avant de supprimer
714
 * @param string $serveur Nom de la connexion
715
 * @param bool $requeter Exécuter la requête, sinon la retourner
716
 * @return bool|string
717
 *     - string Texte de la requête si demandé
718
 *     - true si la requête a réussie, false sinon
719
 */
720 View Code Duplication
function spip_sqlite_drop_table($table, $exist = '', $serveur = '', $requeter = true) {
0 ignored issues
show
Duplication introduced by
This function 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...
721
	if ($exist) {
722
		$exist = " IF EXISTS";
723
	}
724
725
	if (spip_sqlite_query("DROP TABLE$exist $table", $serveur, $requeter)) {
0 ignored issues
show
Coding Style introduced by
The if-else statement can be simplified to return (bool) spip_sqlit..., $serveur, $requeter);.
Loading history...
726
		return true;
727
	} else {
728
		return false;
729
	}
730
}
731
732
733
/**
734
 * Supprime une vue SQL
735
 *
736
 * @param string $view Nom de la vue SQL
737
 * @param string $exist True pour ajouter un test d'existence avant de supprimer
738
 * @param string $serveur Nom de la connexion
739
 * @param bool $requeter Exécuter la requête, sinon la retourner
740
 * @return bool|string
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|SQLiteResult|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
741
 *     - string Texte de la requête si demandé
742
 *     - true si la requête a réussie, false sinon
743
 */
744
function spip_sqlite_drop_view($view, $exist = '', $serveur = '', $requeter = true) {
745
	if ($exist) {
746
		$exist = " IF EXISTS";
747
	}
748
749
	return spip_sqlite_query("DROP VIEW$exist $view", $serveur, $requeter);
750
}
751
752
/**
753
 * Fonction de suppression d'un INDEX
754
 *
755
 * @param string $nom : nom de l'index
756
 * @param string $table : table sql de l'index
757
 * @param string $serveur : nom de la connexion sql utilisee
758
 * @param bool $requeter : true pour executer la requête ou false pour retourner le texte de la requête
759
 *
760
 * @return bool ou requete
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|SQLiteResult|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
761
 */
762
function spip_sqlite_drop_index($nom, $table, $serveur = '', $requeter = true) {
763
	if (!($nom or $table)) {
764
		spip_log("Champ manquant pour supprimer un index sqlite ($nom, $table)", 'sqlite.' . _LOG_ERREUR);
765
766
		return false;
767
	}
768
769
	// SQLite ne differentie pas noms des index en fonction des tables
770
	// il faut donc creer des noms uniques d'index pour une base sqlite
771
	$index = $table . '_' . $nom;
772
	$exist = " IF EXISTS";
773
774
	$query = "DROP INDEX$exist $index";
775
776
	return spip_sqlite_query($query, $serveur, $requeter);
777
}
778
779
/**
780
 * Retourne la dernière erreur generée
781
 *
782
 * @uses sql_error_backtrace()
783
 *
784
 * @param string $query
785
 *     Requête qui était exécutée
786
 * @param string $serveur
787
 *     Nom de la connexion
788
 * @return string
789
 *     Erreur eventuelle
790
 **/
791
function spip_sqlite_error($query = '', $serveur = '') {
792
	$link = _sqlite_link($serveur);
793
794
	if ($link) {
795
		$errs = $link->errorInfo();
0 ignored issues
show
Unused Code introduced by
$errs is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
796
		$s = _sqlite_last_error_from_link($link);
797
	} else {
798
		$s = ": aucune ressource sqlite (link)";
799
	}
800
	if ($s) {
801
		$trace = debug_backtrace();
802
		if ($trace[0]['function'] != "spip_sqlite_error") {
803
			spip_log("$s - $query - " . sql_error_backtrace(), 'sqlite.' . _LOG_ERREUR);
804
		}
805
	}
806
807
	return $s;
808
}
809
810
function _sqlite_last_error_from_link($link) {
811
	if ($link) {
812
		$errs = $link->errorInfo();
813
		/*
814
			$errs[0]
815
				numero SQLState ('HY000' souvent lors d'une erreur)
816
				http://www.easysoft.com/developer/interfaces/odbc/sqlstate_status_return_codes.html
817
			$errs[1]
818
				numéro d'erreur SQLite (souvent 1 lors d'une erreur)
819
				http://www.sqlite.org/c3ref/c_abort.html
820
			$errs[2]
821
				Le texte du message d'erreur
822
		*/
823
		if (ltrim($errs[0], '0')) { // 00000 si pas d'erreur
824
			return "$errs[2]";
825
		}
826
	}
827
	return "";
828
}
829
830
/**
831
 * Retourne le numero de la dernière erreur SQL
832
 *
833
 * Le numéro (en sqlite3/pdo) est un retour ODBC tel que (très souvent) HY000
834
 * http://www.easysoft.com/developer/interfaces/odbc/sqlstate_status_return_codes.html
835
 *
836
 * @param string $serveur
837
 *    nom de la connexion
838
 * @return int|string
839
 *    0 pas d'erreur
840
 *    1 ou autre erreur (en sqlite 2)
841
 *    'HY000/1' : numéro de l'erreur SQLState / numéro d'erreur interne SQLite (en sqlite 3)
842
 **/
843
function spip_sqlite_errno($serveur = '') {
844
	$link = _sqlite_link($serveur);
845
846
	if ($link) {
847
		$t = $link->errorInfo();
848
		$s = ltrim($t[0], '0'); // 00000 si pas d'erreur
849
		if ($s) {
850
			$s .= ' / ' . $t[1];
851
		} // ajoute l'erreur du moteur SQLite
852
	} else {
853
		$s = ": aucune ressource sqlite (link)";
854
	}
855
856
	if ($s) {
857
		spip_log("Erreur sqlite $s", 'sqlite.' . _LOG_ERREUR);
858
	}
859
860
	return $s ? $s : 0;
861
}
862
863
864
/**
865
 * Retourne une explication de requête (Explain) SQLite
866
 *
867
 * @param string $query Texte de la requête
868
 * @param string $serveur Nom de la connexion
869
 * @param bool $requeter Exécuter la requête, sinon la retourner
870
 * @return array|string|bool
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array|string|false.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
871
 *     - array : Tableau de l'explication
872
 *     - string si on retourne le texte de la requête
873
 *     - false si on a pas pu avoir d'explication
874
 */
875
function spip_sqlite_explain($query, $serveur = '', $requeter = true) {
876
	if (strpos(ltrim($query), 'SELECT') !== 0) {
877
		return array();
878
	}
879
880
	$query = spip_sqlite::traduire_requete($query, $serveur);
881
	$query = 'EXPLAIN ' . $query;
882
	if (!$requeter) {
883
		return $query;
884
	}
885
	// on ne trace pas ces requetes, sinon on obtient un tracage sans fin...
886
	$r = spip_sqlite::executer_requete($query, $serveur, false);
887
888
	return $r ? spip_sqlite_fetch($r, null, $serveur) : false; // hum ? etrange ca... a verifier
889
}
890
891
892
/**
893
 * Rècupère une ligne de résultat
894
 *
895
 * Récupère la ligne suivante d'une ressource de résultat
896
 *
897
 * @param Ressource $r Ressource de résultat (issu de sql_select)
898
 * @param string $t Structure de résultat attendu (défaut ASSOC)
899
 * @param string $serveur Nom de la connexion
900
 * @param bool $requeter Inutilisé
901
 * @return array           Ligne de résultat
902
 */
903
function spip_sqlite_fetch($r, $t = '', $serveur = '', $requeter = true) {
0 ignored issues
show
Unused Code introduced by
The parameter $requeter is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
904
905
	$link = _sqlite_link($serveur);
0 ignored issues
show
Unused Code introduced by
$link is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
906
	$t = $t ? $t : SPIP_SQLITE3_ASSOC;
907
908
	$retour = false;
909
	if ($r) {
910
		$retour = $r->fetch($t);
911
	}
912
913
	// Renvoie des 'table.titre' au lieu de 'titre' tout court ! pff !
914
	// suppression de 'table.' pour toutes les cles (c'est un peu violent !)
915
	// c'est couteux : on ne verifie que la premiere ligne pour voir si on le fait ou non
916
	if ($retour
917
		and strpos(implode('', array_keys($retour)), '.') !== false
918
	) {
919
		foreach ($retour as $cle => $val) {
920
			if (($pos = strpos($cle, '.')) !== false) {
921
				$retour[substr($cle, $pos + 1)] = &$retour[$cle];
922
				unset($retour[$cle]);
923
			}
924
		}
925
	}
926
927
	return $retour;
928
}
929
930
/**
931
 * Place le pointeur de résultat sur la position indiquée
932
 *
933
 * @param Ressource $r Ressource de résultat
934
 * @param int $row_number Position. Déplacer le pointeur à cette ligne
935
 * @param string $serveur Nom de la connexion
936
 * @param bool $requeter Inutilisé
937
 * @return bool True si déplacement réussi, false sinon.
938
 **/
939
function spip_sqlite_seek($r, $row_number, $serveur = '', $requeter = true) {
0 ignored issues
show
Unused Code introduced by
The parameter $r is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $row_number is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $serveur is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $requeter is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
940
	// encore un truc de bien fichu : PDO ne PEUT PAS faire de seek ou de rewind...
941
	return false;
942
}
943
944
945
/**
946
 * Libère une ressource de résultat
947
 *
948
 * Indique à SQLite de libérer de sa mémoire la ressoucre de résultat indiquée
949
 * car on n'a plus besoin de l'utiliser.
950
 *
951
 * @param Ressource|Object $r Ressource de résultat
952
 * @param string $serveur Nom de la connexion
953
 * @param bool $requeter Inutilisé
954
 * @return bool                True si réussi
955
 */
956
function spip_sqlite_free(&$r, $serveur = '', $requeter = true) {
0 ignored issues
show
Unused Code introduced by
The parameter $serveur is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $requeter is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
957
	unset($r);
958
959
	return true;
960
	//return sqlite_free_result($r);
961
}
962
963
964
/**
965
 * Teste si le charset indiqué est disponible sur le serveur SQL (aucune action ici)
966
 *
967
 * Cette fonction n'a aucune action actuellement
968
 *
969
 * @param array|string $charset Nom du charset à tester.
970
 * @param string $serveur Nom de la connexion
971
 * @param bool $requeter inutilisé
972
 * @return void
973
 */
974
function spip_sqlite_get_charset($charset = array(), $serveur = '', $requeter = true) {
0 ignored issues
show
Unused Code introduced by
The parameter $charset is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $serveur is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $requeter is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
975
	//$c = !$charset ? '' : (" LIKE "._q($charset['charset']));
976
	//return spip_sqlite_fetch(sqlite_query(_sqlite_link($serveur), "SHOW CHARACTER SET$c"), NULL, $serveur);
977
}
978
979
980
/**
981
 * Prépare une chaîne hexadécimale
982
 *
983
 * Par exemple : FF ==> 255 en SQLite
984
 *
985
 * @param string $v
986
 *     Chaine hexadecimale
987
 * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be integer|double?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
988
 *     Valeur hexadécimale pour SQLite
989
 **/
990
function spip_sqlite_hex($v) {
991
	return hexdec($v);
992
}
993
994
995
/**
996
 * Retourne une expression IN pour le gestionnaire de base de données
997
 *
998
 * IN (...) est limité à 255 éléments, d'où cette fonction assistante
999
 *
1000
 * @param string $val
1001
 *     Colonne SQL sur laquelle appliquer le test
1002
 * @param string|array $valeurs
1003
 *     Liste des valeurs possibles (séparés par des virgules si string)
1004
 * @param string $not
1005
 *     - '' sélectionne les éléments correspondant aux valeurs
1006
 *     - 'NOT' inverse en sélectionnant les éléments ne correspondant pas aux valeurs
1007
 * @param string $serveur
1008
 *     Nom du connecteur
1009
 * @param bool $requeter
1010
 *     Inutilisé
1011
 * @return string
1012
 *     Expression de requête SQL
1013
 **/
1014 View Code Duplication
function spip_sqlite_in($val, $valeurs, $not = '', $serveur = '', $requeter = true) {
0 ignored issues
show
Unused Code introduced by
The parameter $serveur is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $requeter is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
This function 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...
1015
	$n = $i = 0;
1016
	$in_sql = "";
1017
	while ($n = strpos($valeurs, ',', $n + 1)) {
1018
		if ((++$i) >= 255) {
1019
			$in_sql .= "($val $not IN (" .
1020
				substr($valeurs, 0, $n) .
1021
				"))\n" .
1022
				($not ? "AND\t" : "OR\t");
1023
			$valeurs = substr($valeurs, $n + 1);
1024
			$i = $n = 0;
1025
		}
1026
	}
1027
	$in_sql .= "($val $not IN ($valeurs))";
1028
1029
	return "($in_sql)";
1030
}
1031
1032
1033
/**
1034
 * Insère une ligne dans une table
1035
 *
1036
 * @param string $table
1037
 *     Nom de la table SQL
1038
 * @param string $champs
1039
 *     Liste des colonnes impactées,
1040
 * @param string $valeurs
1041
 *     Liste des valeurs,
1042
 * @param array $desc
1043
 *     Tableau de description des colonnes de la table SQL utilisée
1044
 *     (il sera calculé si nécessaire s'il n'est pas transmis).
1045
 * @param string $serveur
1046
 *     Nom du connecteur
1047
 * @param bool $requeter
1048
 *     Exécuter la requête, sinon la retourner
1049
 * @return bool|string|int|array
0 ignored issues
show
Documentation introduced by
Should the return type not be string|boolean|SQLiteResult|integer?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1050
 *     - int|true identifiant de l'élément inséré (si possible), ou true, si réussite
1051
 *     - Texte de la requête si demandé,
1052
 *     - False en cas d'erreur,
1053
 *     - Tableau de description de la requête et du temps d'exécution, si var_profile activé
1054
 **/
1055
function spip_sqlite_insert($table, $champs, $valeurs, $desc = array(), $serveur = '', $requeter = true) {
0 ignored issues
show
Unused Code introduced by
The parameter $desc is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1056
1057
	$query = "INSERT INTO $table " . ($champs ? "$champs VALUES $valeurs" : "DEFAULT VALUES");
1058
	if ($r = spip_sqlite_query($query, $serveur, $requeter)) {
1059
		if (!$requeter) {
1060
			return $r;
1061
		}
1062
		$nb = spip_sqlite::last_insert_id($serveur);
1063
	} else {
1064
		$nb = false;
1065
	}
1066
1067
	$err = spip_sqlite_error($query, $serveur);
0 ignored issues
show
Unused Code introduced by
$err is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1068
1069
	// cas particulier : ne pas substituer la reponse spip_sqlite_query si on est en profilage
1070
	return isset($_GET['var_profile']) ? $r : $nb;
1071
1072
}
1073
1074
1075
/**
1076
 * Insère une ligne dans une table, en protégeant chaque valeur
1077
 *
1078
 * @param string $table
1079
 *     Nom de la table SQL
1080
 * @param string $couples
0 ignored issues
show
Documentation introduced by
Should the type for parameter $couples not be string|array? Also, consider making the array more specific, something like array<String>, or String[].

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
1081
 *    Couples (colonne => valeur)
1082
 * @param array $desc
1083
 *     Tableau de description des colonnes de la table SQL utilisée
1084
 *     (il sera calculé si nécessaire s'il n'est pas transmis).
1085
 * @param string $serveur
1086
 *     Nom du connecteur
1087
 * @param bool $requeter
1088
 *     Exécuter la requête, sinon la retourner
1089
 * @return bool|string|int|array
0 ignored issues
show
Documentation introduced by
Should the return type not be null|string|boolean|SQLiteResult|integer?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1090
 *     - int|true identifiant de l'élément inséré (si possible), ou true, si réussite
1091
 *     - Texte de la requête si demandé,
1092
 *     - False en cas d'erreur,
1093
 *     - Tableau de description de la requête et du temps d'exécution, si var_profile activé
1094
 **/
1095
function spip_sqlite_insertq($table, $couples = array(), $desc = array(), $serveur = '', $requeter = true) {
1096
	if (!$desc) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $desc of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1097
		$desc = description_table($table, $serveur);
1098
	}
1099
	if (!$desc) {
1100
		die("$table insertion sans description");
1101
	}
1102
	$fields = isset($desc['field']) ? $desc['field'] : array();
1103
1104
	foreach ($couples as $champ => $val) {
0 ignored issues
show
Bug introduced by
The expression $couples of type string|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1105
		$couples[$champ] = _sqlite_calculer_cite($val, $fields[$champ]);
1106
	}
1107
1108
	// recherche de champs 'timestamp' pour mise a jour auto de ceux-ci
1109
	$couples = _sqlite_ajouter_champs_timestamp($table, $couples, $desc, $serveur);
1110
1111
	$cles = $valeurs = "";
1112
	if (count($couples)) {
1113
		$cles = "(" . join(',', array_keys($couples)) . ")";
1114
		$valeurs = "(" . join(',', $couples) . ")";
1115
	}
1116
1117
	return spip_sqlite_insert($table, $cles, $valeurs, $desc, $serveur, $requeter);
0 ignored issues
show
Bug introduced by
It seems like $desc defined by description_table($table, $serveur) on line 1097 can also be of type boolean; however, spip_sqlite_insert() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1118
}
1119
1120
1121
/**
1122
 * Insère plusieurs lignes d'un coup dans une table
1123
 *
1124
 * @param string $table
1125
 *     Nom de la table SQL
1126
 * @param array $tab_couples
1127
 *     Tableau de tableaux associatifs (colonne => valeur)
1128
 * @param array $desc
1129
 *     Tableau de description des colonnes de la table SQL utilisée
1130
 *     (il sera calculé si nécessaire s'il n'est pas transmis).
1131
 * @param string $serveur
1132
 *     Nom du connecteur
1133
 * @param bool $requeter
1134
 *     Exécuter la requête, sinon la retourner
1135
 * @return bool|string
0 ignored issues
show
Documentation introduced by
Should the return type not be null|string|boolean|SQLiteResult|integer?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1136
 *     - True en cas de succès,
1137
 *     - Texte de la requête si demandé,
1138
 *     - False en cas d'erreur.
1139
 **/
1140
function spip_sqlite_insertq_multi($table, $tab_couples = array(), $desc = array(), $serveur = '', $requeter = true) {
1141
	if (!$desc) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $desc of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1142
		$desc = description_table($table, $serveur);
1143
	}
1144
	if (!$desc) {
1145
		die("$table insertion sans description");
1146
	}
1147
	if (!isset($desc['field'])) {
1148
		$desc['field'] = array();
1149
	}
1150
1151
	// recuperer les champs 'timestamp' pour mise a jour auto de ceux-ci
1152
	$maj = _sqlite_ajouter_champs_timestamp($table, array(), $desc, $serveur);
1153
1154
	// seul le nom de la table est a traduire ici :
1155
	// le faire une seule fois au debut
1156
	$query_start = "INSERT INTO $table ";
1157
	$query_start = spip_sqlite::traduire_requete($query_start, $serveur);
1158
1159
	// ouvrir une transaction
1160
	if ($requeter) {
1161
		spip_sqlite::demarrer_transaction($serveur);
1162
	}
1163
1164
	while ($couples = array_shift($tab_couples)) {
1165
		foreach ($couples as $champ => $val) {
1166
			$couples[$champ] = _sqlite_calculer_cite($val, $desc['field'][$champ]);
1167
		}
1168
1169
		// inserer les champs timestamp par defaut
1170
		$couples = array_merge($maj, $couples);
1171
1172
		$champs = $valeurs = "";
0 ignored issues
show
Unused Code introduced by
$valeurs is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
Unused Code introduced by
$champs is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1173
		if (count($couples)) {
1174
			$champs = "(" . join(',', array_keys($couples)) . ")";
1175
			$valeurs = "(" . join(',', $couples) . ")";
1176
			$query = $query_start . "$champs VALUES $valeurs";
1177
		} else {
1178
			$query = $query_start . "DEFAULT VALUES";
1179
		}
1180
1181
		if ($requeter) {
1182
			$retour = spip_sqlite::executer_requete($query, $serveur);
1183
		}
1184
1185
		// sur le dernier couple uniquement
1186
		if (!count($tab_couples)) {
1187
			$nb = 0;
0 ignored issues
show
Unused Code introduced by
$nb is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1188
			if ($requeter) {
1189
				$nb = spip_sqlite::last_insert_id($serveur);
1190
			} else {
1191
				return $query;
1192
			}
1193
		}
1194
1195
		$err = spip_sqlite_error($query, $serveur);
0 ignored issues
show
Unused Code introduced by
$err is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1196
	}
1197
1198
	if ($requeter) {
1199
		spip_sqlite::finir_transaction($serveur);
1200
	}
1201
1202
	// renvoie le dernier id d'autoincrement ajoute
1203
	// cas particulier : ne pas substituer la reponse spip_sqlite_query si on est en profilage
1204
	return isset($_GET['var_profile']) ? $retour : $nb;
0 ignored issues
show
Bug introduced by
The variable $retour 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...
Bug introduced by
The variable $nb 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...
1205
}
1206
1207
1208
/**
1209
 * Retourne si le moteur SQL préfère utiliser des transactions.
1210
 *
1211
 * @param string $serveur
1212
 *     Nom du connecteur
1213
 * @param bool $requeter
1214
 *     Inutilisé
1215
 * @return bool
1216
 *     Toujours true.
1217
 **/
1218
function spip_sqlite_preferer_transaction($serveur = '', $requeter = true) {
0 ignored issues
show
Unused Code introduced by
The parameter $serveur is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $requeter is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1219
	return true;
1220
}
1221
1222
/**
1223
 * Démarre une transaction
1224
 *
1225
 * Pratique pour des sql_updateq() dans un foreach,
1226
 * parfois 100* plus rapide s'ils sont nombreux en sqlite !
1227
 *
1228
 * @param string $serveur
1229
 *     Nom du connecteur
1230
 * @param bool $requeter
1231
 *     true pour exécuter la requête ou false pour retourner le texte de la requête
1232
 * @return bool|string
1233
 *     string si texte de la requête demandé, true sinon
1234
 **/
1235
function spip_sqlite_demarrer_transaction($serveur = '', $requeter = true) {
1236
	if (!$requeter) {
1237
		return "BEGIN TRANSACTION";
1238
	}
1239
	spip_sqlite::demarrer_transaction($serveur);
1240
1241
	return true;
1242
}
1243
1244
/**
1245
 * Clôture une transaction
1246
 *
1247
 * @param string $serveur
1248
 *     Nom du connecteur
1249
 * @param bool $requeter
1250
 *     true pour exécuter la requête ou false pour retourner le texte de la requête
1251
 * @return bool|string
1252
 *     string si texte de la requête demandé, true sinon
1253
 **/
1254
function spip_sqlite_terminer_transaction($serveur = '', $requeter = true) {
1255
	if (!$requeter) {
1256
		return "COMMIT";
1257
	}
1258
	spip_sqlite::finir_transaction($serveur);
1259
1260
	return true;
1261
}
1262
1263
1264
/**
1265
 * Liste les bases de données disponibles
1266
 *
1267
 * @param string $serveur
1268
 *     Nom du connecteur
1269
 * @param bool $requeter
1270
 *     Inutilisé
1271
 * @return array
1272
 *     Liste des noms de bases
1273
 **/
1274
function spip_sqlite_listdbs($serveur = '', $requeter = true) {
0 ignored issues
show
Unused Code introduced by
The parameter $serveur is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $requeter is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1275
	_sqlite_init();
1276
1277
	if (!is_dir($d = substr(_DIR_DB, 0, -1))) {
1278
		return array();
1279
	}
1280
1281
	include_spip('inc/flock');
1282
	$bases = preg_files($d, $pattern = '(.*)\.sqlite$');
1283
	$bds = array();
1284
1285
	foreach ($bases as $b) {
1286
		// pas de bases commencant pas sqlite 
1287
		// (on s'en sert pour l'installation pour simuler la presence d'un serveur)
1288
		// les bases sont de la forme _sqliteX_tmp_spip_install.sqlite
1289
		if (strpos($b, '_sqlite')) {
1290
			continue;
1291
		}
1292
		$bds[] = preg_replace(";.*/$pattern;iS", '$1', $b);
1293
	}
1294
1295
	return $bds;
1296
}
1297
1298
1299
/**
1300
 * Retourne l'instruction SQL pour obtenir le texte d'un champ contenant
1301
 * une balise `<multi>` dans la langue indiquée
1302
 *
1303
 * Cette sélection est mise dans l'alias `multi` (instruction AS multi).
1304
 *
1305
 * @param string $objet Colonne ayant le texte
1306
 * @param string $lang Langue à extraire
1307
 * @return string       Texte de sélection pour la requête
1308
 */
1309
function spip_sqlite_multi($objet, $lang) {
1310
	$r = "EXTRAIRE_MULTI(" . $objet . ", '" . $lang . "') AS multi";
1311
1312
	return $r;
1313
}
1314
1315
1316
/**
1317
 * Optimise une table SQL
1318
 *
1319
 * @note
1320
 *   Sqlite optimise TOUT un fichier sinon rien.
1321
 *   On évite donc 2 traitements sur la même base dans un hit.
1322
 *
1323
 * @param $table nom de la table a optimiser
1324
 * @param $serveur nom de la connexion
1325
 * @param $requeter effectuer la requete ? sinon retourner son code
1326
 * @return bool|string true / false / requete
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|SQLiteResult|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1327
 **/
1328
function spip_sqlite_optimize($table, $serveur = '', $requeter = true) {
0 ignored issues
show
Unused Code introduced by
The parameter $table is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1329
	static $do = false;
1330
	if ($requeter and $do) {
1331
		return true;
1332
	}
1333
	if ($requeter) {
1334
		$do = true;
1335
	}
1336
1337
	return spip_sqlite_query("VACUUM", $serveur, $requeter);
1338
}
1339
1340
1341
/**
1342
 * Échapper une valeur selon son type
1343
 * mais pour SQLite avec ses spécificités
1344
 *
1345
 * @param string|array|number $v
1346
 *     Texte, nombre ou tableau à échapper
1347
 * @param string $type
1348
 *     Description du type attendu
1349
 *    (par exemple description SQL de la colonne recevant la donnée)
1350
 * @return string|number
1351
 *    Donnée prête à être utilisée par le gestionnaire SQL
1352
 */
1353 View Code Duplication
function spip_sqlite_quote($v, $type = '') {
0 ignored issues
show
Duplication introduced by
This function 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...
1354
	if (!is_array($v)) {
1355
		return _sqlite_calculer_cite($v, $type);
1356
	}
1357
	// si c'est un tableau, le parcourir en propageant le type
1358
	foreach ($v as $k => $r) {
1359
		$v[$k] = spip_sqlite_quote($r, $type);
1360
	}
1361
1362
	return join(",", $v);
1363
}
1364
1365
1366
/**
1367
 * Tester si une date est proche de la valeur d'un champ
1368
 *
1369
 * @param string $champ
1370
 *     Nom du champ a tester
1371
 * @param int $interval
1372
 *     Valeur de l'intervalle : -1, 4, ...
1373
 * @param string $unite
1374
 *     Utité utilisée (DAY, MONTH, YEAR, ...)
1375
 * @return string
1376
 *     Expression SQL
1377
 **/
1378
function spip_sqlite_date_proche($champ, $interval, $unite) {
1379
	$op = (($interval <= 0) ? '>' : '<');
1380
1381
	return "($champ $op datetime('" . date("Y-m-d H:i:s") . "', '$interval $unite'))";
1382
}
1383
1384
1385
/**
1386
 * Répare une table SQL
1387
 *
1388
 * Il n'y a pas de fonction native repair dans sqlite, mais on profite
1389
 * pour vérifier que tous les champs (text|char) ont bien une clause DEFAULT
1390
 *
1391
 * @param string $table Nom de la table SQL
1392
 * @param string $serveur Nom de la connexion
1393
 * @param bool $requeter Exécuter la requête, sinon la retourner
1394
 * @return string[]
1395
 *     Tableau avec clé 0 pouvant avoir " OK " ou " ERROR " indiquant
1396
 *     l'état de la table après la réparation
1397
 */
1398
function spip_sqlite_repair($table, $serveur = '', $requeter = true) {
0 ignored issues
show
Unused Code introduced by
The parameter $requeter is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1399
	if ($desc = spip_sqlite_showtable($table, $serveur)
1400
		and isset($desc['field'])
1401
		and is_array($desc['field'])
1402
	) {
1403
		foreach ($desc['field'] as $c => $d) {
1404 View Code Duplication
			if (preg_match(",^(tinytext|mediumtext|text|longtext|varchar|char),i", $d)
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...
1405
				and stripos($d, "NOT NULL") !== false
1406
				and stripos($d, "DEFAULT") === false
1407
				/* pas touche aux cles primaires */
1408
				and (!isset($desc['key']['PRIMARY KEY']) or $desc['key']['PRIMARY KEY'] !== $c)
1409
			) {
1410
				spip_sqlite_alter($q = "TABLE $table CHANGE $c $c $d DEFAULT ''", $serveur);
1411
				spip_log("ALTER $q", "repair" . _LOG_INFO_IMPORTANTE);
1412
			}
1413 View Code Duplication
			if (preg_match(",^(INTEGER),i", $d)
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...
1414
				and stripos($d, "NOT NULL") !== false
1415
				and stripos($d, "DEFAULT") === false
1416
				/* pas touche aux cles primaires */
1417
				and (!isset($desc['key']['PRIMARY KEY']) or $desc['key']['PRIMARY KEY'] !== $c)
1418
			) {
1419
				spip_sqlite_alter($q = "TABLE $table CHANGE $c $c $d DEFAULT '0'", $serveur);
1420
				spip_log("ALTER $q", "repair" . _LOG_INFO_IMPORTANTE);
1421
			}
1422 View Code Duplication
			if (preg_match(",^(datetime),i", $d)
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...
1423
				and stripos($d, "NOT NULL") !== false
1424
				and stripos($d, "DEFAULT") === false
1425
				/* pas touche aux cles primaires */
1426
				and (!isset($desc['key']['PRIMARY KEY']) or $desc['key']['PRIMARY KEY'] !== $c)
1427
			) {
1428
				spip_sqlite_alter($q = "TABLE $table CHANGE $c $c $d DEFAULT '0000-00-00 00:00:00'", $serveur);
1429
				spip_log("ALTER $q", "repair" . _LOG_INFO_IMPORTANTE);
1430
			}
1431
		}
1432
1433
		return array(" OK ");
1434
	}
1435
1436
	return array(" ERROR ");
1437
}
1438
1439
1440
/**
1441
 * Insère où met à jour une entrée d’une table SQL
1442
 *
1443
 * La clé ou les cles primaires doivent être présentes dans les données insérés.
1444
 * La fonction effectue une protection automatique des données.
1445
 *
1446
 * Préférer à cette fonction updateq ou insertq.
1447
 *
1448
 * @param string $table
1449
 *     Nom de la table SQL
1450
 * @param array $couples
1451
 *     Couples colonne / valeur à modifier,
1452
 * @param array $desc
1453
 *     Tableau de description des colonnes de la table SQL utilisée
1454
 *     (il sera calculé si nécessaire s'il n'est pas transmis).
1455
 * @param string $serveur
1456
 *     Nom du connecteur
1457
 * @param bool $requeter
1458
 *     Exécuter la requête, sinon la retourner
1459
 * @return bool|string
0 ignored issues
show
Documentation introduced by
Should the return type not be null|boolean|SQLiteResult|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1460
 *     - true si réussite
1461
 *     - Texte de la requête si demandé,
1462
 *     - False en cas d'erreur.
1463
 **/
1464
function spip_sqlite_replace($table, $couples, $desc = array(), $serveur = '', $requeter = true) {
0 ignored issues
show
Unused Code introduced by
The parameter $requeter is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1465
	if (!$desc) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $desc of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1466
		$desc = description_table($table, $serveur);
1467
	}
1468
	if (!$desc) {
1469
		die("$table insertion sans description");
1470
	}
1471
	$fields = isset($desc['field']) ? $desc['field'] : array();
1472
1473
	foreach ($couples as $champ => $val) {
1474
		$couples[$champ] = _sqlite_calculer_cite($val, $fields[$champ]);
1475
	}
1476
1477
	// recherche de champs 'timestamp' pour mise a jour auto de ceux-ci
1478
	$couples = _sqlite_ajouter_champs_timestamp($table, $couples, $desc, $serveur);
1479
1480
	return spip_sqlite_query("REPLACE INTO $table (" . join(',', array_keys($couples)) . ') VALUES (' . join(',',
1481
			$couples) . ')', $serveur);
1482
}
1483
1484
1485
/**
1486
 * Insère où met à jour des entrées d’une table SQL
1487
 *
1488
 * La clé ou les cles primaires doivent être présentes dans les données insérés.
1489
 * La fonction effectue une protection automatique des données.
1490
 *
1491
 * Préférez insertq_multi et sql_updateq
1492
 *
1493
 * @param string $table
1494
 *     Nom de la table SQL
1495
 * @param array $tab_couples
1496
 *     Tableau de tableau (colonne / valeur à modifier),
1497
 * @param array $desc
1498
 *     Tableau de description des colonnes de la table SQL utilisée
1499
 *     (il sera calculé si nécessaire s'il n'est pas transmis).
1500
 * @param string $serveur
1501
 *     Nom du connecteur
1502
 * @param bool $requeter
1503
 *     Exécuter la requête, sinon la retourner
1504
 * @return bool|string
0 ignored issues
show
Documentation introduced by
Should the return type not be null|boolean|SQLiteResult|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1505
 *     - true si réussite
1506
 *     - Texte de la requête si demandé,
1507
 *     - False en cas d'erreur.
1508
 **/
1509
function spip_sqlite_replace_multi($table, $tab_couples, $desc = array(), $serveur = '', $requeter = true) {
1510
1511
	// boucler pour trainter chaque requete independemment
1512
	foreach ($tab_couples as $couples) {
1513
		$retour = spip_sqlite_replace($table, $couples, $desc, $serveur, $requeter);
1514
	}
1515
1516
	// renvoie le dernier id
1517
	return $retour;
0 ignored issues
show
Bug introduced by
The variable $retour 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...
1518
}
1519
1520
1521
/**
1522
 * Exécute une requête de sélection avec SQLite
1523
 *
1524
 * Instance de sql_select (voir ses specs).
1525
 *
1526
 * @see sql_select()
1527
 *
1528
 * @param string|array $select Champs sélectionnés
1529
 * @param string|array $from Tables sélectionnées
1530
 * @param string|array $where Contraintes
1531
 * @param string|array $groupby Regroupements
1532
 * @param string|array $orderby Tris
1533
 * @param string $limit Limites de résultats
1534
 * @param string|array $having Contraintes posts sélections
1535
 * @param string $serveur Nom de la connexion
1536
 * @param bool $requeter Exécuter la requête, sinon la retourner
1537
 * @return array|bool|resource|string
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|SQLiteResult|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1538
 *     - string : Texte de la requête si on ne l'exécute pas
1539
 *     - ressource si requête exécutée, ressource pour fetch()
1540
 *     - false si la requête exécutée a ratée
1541
 *     - array  : Tableau décrivant requête et temps d'exécution si var_profile actif pour tracer.
1542
 */
1543
function spip_sqlite_select(
1544
	$select,
1545
	$from,
1546
	$where = '',
1547
	$groupby = '',
1548
	$orderby = '',
1549
	$limit = '',
1550
	$having = '',
1551
	$serveur = '',
1552
	$requeter = true
1553
) {
1554
1555
	// version() n'est pas connu de sqlite
1556
	$select = str_replace('version()', 'sqlite_version()', $select);
1557
1558
	// recomposer from
1559
	$from = (!is_array($from) ? $from : _sqlite_calculer_select_as($from));
1560
1561
	$query =
1562
		_sqlite_calculer_expression('SELECT', $select, ', ')
1563
		. _sqlite_calculer_expression('FROM', $from, ', ')
1564
		. _sqlite_calculer_expression('WHERE', $where)
1565
		. _sqlite_calculer_expression('GROUP BY', $groupby, ',')
1566
		. _sqlite_calculer_expression('HAVING', $having)
1567
		. ($orderby ? ("\nORDER BY " . _sqlite_calculer_order($orderby)) : '')
1568
		. ($limit ? "\nLIMIT $limit" : '');
1569
1570
	// dans un select, on doit renvoyer la requête en cas d'erreur
1571
	$res = spip_sqlite_query($query, $serveur, $requeter);
1572
	// texte de la requete demande ?
1573
	if (!$requeter) {
1574
		return $res;
1575
	}
1576
	// erreur survenue ?
1577
	if ($res === false) {
1578
		return spip_sqlite::traduire_requete($query, $serveur);
1579
	}
1580
1581
	return $res;
1582
}
1583
1584
1585
/**
1586
 * Sélectionne un fichier de base de données
1587
 *
1588
 * @param string $db
1589
 *     Nom de la base à utiliser
1590
 * @param string $serveur
1591
 *     Nom du connecteur
1592
 * @param bool $requeter
1593
 *     Inutilisé
1594
 *
1595
 * @return bool|string
0 ignored issues
show
Documentation introduced by
Should the return type not be false|string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1596
 *     - Nom de la base en cas de success.
1597
 *     - False en cas d'erreur.
1598
 **/
1599
function spip_sqlite_selectdb($db, $serveur = '', $requeter = true) {
0 ignored issues
show
Unused Code introduced by
The parameter $requeter is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1600
	_sqlite_init();
1601
1602
	// interdire la creation d'une nouvelle base, 
1603
	// sauf si on est dans l'installation
1604
	if (!is_file($f = _DIR_DB . $db . '.sqlite')
1605
		&& (!defined('_ECRIRE_INSTALL') || !_ECRIRE_INSTALL)
1606
	) {
1607
		spip_log("Il est interdit de creer la base $db", 'sqlite.' . _LOG_HS);
1608
1609
		return false;
1610
	}
1611
1612
	// se connecter a la base indiquee
1613
	// avec les identifiants connus
1614
	$index = $serveur ? $serveur : 0;
1615
1616
	if ($link = spip_connect_db('', '', '', '', '@selectdb@' . $db, $serveur, '', '')) {
1617 View Code Duplication
		if (($db == $link['db']) && $GLOBALS['connexions'][$index] = $link) {
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...
1618
			return $db;
1619
		}
1620
	} else {
1621
		spip_log("Impossible de selectionner la base $db", 'sqlite.' . _LOG_HS);
1622
1623
		return false;
1624
	}
1625
1626
}
1627
1628
1629
/**
1630
 * Définit un charset pour la connexion avec SQLite (aucune action ici)
1631
 *
1632
 * Cette fonction n'a aucune action actuellement.
1633
 *
1634
 * @param string $charset Charset à appliquer
1635
 * @param string $serveur Nom de la connexion
1636
 * @param bool $requeter inutilisé
1637
 * @return void
1638
 */
1639
function spip_sqlite_set_charset($charset, $serveur = '', $requeter = true) {
0 ignored issues
show
Unused Code introduced by
The parameter $charset is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $serveur is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $requeter is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1640
	# spip_log("Gestion charset sql a ecrire : "."SET NAMES "._q($charset), 'sqlite.'._LOG_ERREUR);
1641
	# return spip_sqlite_query("SET NAMES ". spip_sqlite_quote($charset), $serveur); //<-- Passe pas !
1642
}
1643
1644
1645
/**
1646
 * Retourne une ressource de la liste des tables de la base de données
1647
 *
1648
 * @param string $match
1649
 *     Filtre sur tables à récupérer
1650
 * @param string $serveur
1651
 *     Connecteur de la base
1652
 * @param bool $requeter
1653
 *     true pour éxecuter la requête
1654
 *     false pour retourner le texte de la requête.
1655
 * @return ressource
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|SQLiteResult|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1656
 *     Ressource à utiliser avec sql_fetch()
1657
 **/
1658
function spip_sqlite_showbase($match, $serveur = '', $requeter = true) {
1659
	// type est le type d'entrée : table / index / view
1660
	// on ne retourne que les tables (?) et non les vues...
1661
	# ESCAPE non supporte par les versions sqlite <3
1662
	#	return spip_sqlite_query("SELECT name FROM sqlite_master WHERE type='table' AND tbl_name LIKE "._q($match)." ESCAPE '\'", $serveur, $requeter);
1663
	$match = preg_quote($match);
1664
	$match = str_replace("\\\_", "[[TIRETBAS]]", $match);
1665
	$match = str_replace("\\\%", "[[POURCENT]]", $match);
1666
	$match = str_replace("_", ".", $match);
1667
	$match = str_replace("%", ".*", $match);
1668
	$match = str_replace("[[TIRETBAS]]", "_", $match);
1669
	$match = str_replace("[[POURCENT]]", "%", $match);
1670
	$match = "^$match$";
1671
1672
	return spip_sqlite_query("SELECT name FROM sqlite_master WHERE type='table' AND tbl_name REGEXP " . _q($match),
1673
		$serveur, $requeter);
1674
}
1675
1676
define('_SQLITE_RE_SHOW_TABLE', '/^[^(),]*\(((?:[^()]*\((?:[^()]*\([^()]*\))?[^()]*\)[^()]*)*[^()]*)\)[^()]*$/');
1677
/**
1678
 * Obtient la description d'une table ou vue SQLite
1679
 *
1680
 * Récupère la définition d'une table ou d'une vue avec colonnes, indexes, etc.
1681
 * au même format que la définition des tables SPIP, c'est à dire
1682
 * un tableau avec les clés
1683
 *
1684
 * - `field` (tableau colonne => description SQL) et
1685
 * - `key` (tableau type de clé => colonnes)
1686
 *
1687
 * @param string $nom_table Nom de la table SQL
1688
 * @param string $serveur Nom de la connexion
1689
 * @param bool $requeter Exécuter la requête, sinon la retourner
1690
 * @return array|string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|boolean|SQLiteResult|array<string,array>?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1691
 *     - chaîne vide si pas de description obtenue
1692
 *     - string Texte de la requête si demandé
1693
 *     - array description de la table sinon
1694
 */
1695
function spip_sqlite_showtable($nom_table, $serveur = '', $requeter = true) {
1696
	$query =
1697
		'SELECT sql, type FROM'
1698
		. ' (SELECT * FROM sqlite_master UNION ALL'
1699
		. ' SELECT * FROM sqlite_temp_master)'
1700
		. " WHERE tbl_name LIKE '$nom_table'"
1701
		. " AND type!='meta' AND sql NOT NULL AND name NOT LIKE 'sqlite_%'"
1702
		. ' ORDER BY substr(type,2,1), name';
1703
1704
	$a = spip_sqlite_query($query, $serveur, $requeter);
1705
	if (!$a) {
1706
		return "";
1707
	}
1708
	if (!$requeter) {
1709
		return $a;
1710
	}
1711
	if (!($a = spip_sqlite_fetch($a, null, $serveur))) {
1712
		return "";
1713
	}
1714
	$vue = ($a['type'] == 'view'); // table | vue
1715
1716
	// c'est une table
1717
	// il faut parser le create
1718
	if (!$vue) {
1719
		if (!preg_match(_SQLITE_RE_SHOW_TABLE, array_shift($a), $r)) {
1720
			return "";
1721
		} else {
1722
			$desc = $r[1];
1723
			// extraction d'une KEY éventuelle en prenant garde de ne pas
1724
			// relever un champ dont le nom contient KEY (ex. ID_WHISKEY)
1725 View Code Duplication
			if (preg_match("/^(.*?),([^,]*\sKEY[ (].*)$/s", $desc, $r)) {
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...
1726
				$namedkeys = $r[2];
1727
				$desc = $r[1];
1728
			} else {
1729
				$namedkeys = "";
1730
			}
1731
1732
			$fields = array();
1733
			$keys = array();
1734
1735
			// enlever les contenus des valeurs DEFAULT 'xxx' qui pourraient perturber
1736
			// par exemple s'il contiennent une virgule.
1737
			// /!\ cela peut aussi echapper le nom des champs si la table a eu des operations avec SQLite Manager !
1738
			list($desc, $echaps) = query_echappe_textes($desc);
1739
1740
			// separer toutes les descriptions de champs, separes par des virgules
1741
			# /!\ explode peut exploser aussi DECIMAL(10,2) !
1742
			$k_precedent = null;
1743
			foreach (explode(",", $desc) as $v) {
1744
1745
				preg_match("/^\s*([^\s]+)\s+(.*)/", $v, $r);
1746
				// Les cles de champs peuvent etre entourees
1747
				// de guillements doubles " , simples ', graves ` ou de crochets [ ],  ou rien.
1748
				// http://www.sqlite.org/lang_keywords.html
1749
				$k = strtolower(query_reinjecte_textes($r[1], $echaps)); // champ, "champ", [champ]...
0 ignored issues
show
Security Bug introduced by
It seems like $echaps can also be of type false; however, query_reinjecte_textes() does only seem to accept array, did you maybe forget to handle an error condition?
Loading history...
1750
				if ($char = strpbrk($k[0], '\'"[`')) {
1751
					$k = trim($k, $char);
1752
					if ($char == '[') {
1753
						$k = rtrim($k, ']');
1754
					}
1755
				}
1756
				$def = query_reinjecte_textes($r[2], $echaps); // valeur du champ
0 ignored issues
show
Security Bug introduced by
It seems like $echaps can also be of type false; however, query_reinjecte_textes() does only seem to accept array, did you maybe forget to handle an error condition?
Loading history...
1757
1758
				# rustine pour DECIMAL(10,2)
1759
				if (false !== strpos($k, ')')) {
1760
					$fields[$k_precedent] .= ',' . $k . ' ' . $def;
1761
					continue;
1762
				}
1763
1764
				// la primary key peut etre dans une des descriptions de champs
1765
				// et non en fin de table, cas encore decouvert avec Sqlite Manager
1766
				if (stripos($r[2], 'PRIMARY KEY') !== false) {
1767
					$keys['PRIMARY KEY'] = $k;
1768
				}
1769
1770
				$fields[$k] = $def;
1771
				$k_precedent = $k;
1772
			}
1773
			// key inclues dans la requete
1774 View Code Duplication
			foreach (preg_split('/\)\s*(,|$)/', $namedkeys) as $v) {
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...
1775
				if (preg_match("/^\s*([^(]*)\(([^(]*(\(\d+\))?)$/", $v, $r)) {
1776
					$k = str_replace("`", '', trim($r[1]));
1777
					$t = trim(strtolower(str_replace("`", '', $r[2])), '"');
1778
					if ($k && !isset($keys[$k])) {
1779
						$keys[$k] = $t;
1780
					} else {
1781
						$keys[] = $t;
1782
					}
1783
				}
1784
			}
1785
			// sinon ajouter les key index
1786
			$query =
1787
				'SELECT name,sql FROM'
1788
				. ' (SELECT * FROM sqlite_master UNION ALL'
1789
				. ' SELECT * FROM sqlite_temp_master)'
1790
				. " WHERE tbl_name LIKE '$nom_table'"
1791
				. " AND type='index' AND name NOT LIKE 'sqlite_%'"
1792
				. 'ORDER BY substr(type,2,1), name';
1793
			$a = spip_sqlite_query($query, $serveur, $requeter);
1794
			while ($r = spip_sqlite_fetch($a, null, $serveur)) {
1795
				$key = str_replace($nom_table . '_', '', $r['name']); // enlever le nom de la table ajoute a l'index
1796
				$keytype = "KEY";
1797
				if (strpos($r['sql'], "UNIQUE INDEX") !== false) {
1798
					$keytype = "UNIQUE KEY";
1799
				}
1800
				$colonnes = preg_replace(',.*\((.*)\).*,', '$1', $r['sql']);
1801
				$keys[$keytype . ' ' . $key] = $colonnes;
1802
			}
1803
		}
1804
1805
	} // c'est une vue, on liste les champs disponibles simplement
1806
	else {
1807
		if ($res = sql_fetsel('*', $nom_table, '', '', '', '1', '', $serveur)) { // limit 1
1808
			$fields = array();
1809
			foreach ($res as $c => $v) {
1810
				$fields[$c] = '';
1811
			}
1812
			$keys = array();
1813
		} else {
1814
			return "";
1815
		}
1816
	}
1817
1818
	return array('field' => $fields, 'key' => $keys);
1819
1820
}
1821
1822
1823
/**
1824
 * Met à jour des enregistrements d'une table SQL
1825
 *
1826
 * @param string $table
1827
 *     Nom de la table
1828
 * @param array $champs
1829
 *     Couples (colonne => valeur)
1830
 * @param string|array $where
1831
 *     Conditions a remplir (Where)
1832
 * @param array $desc
0 ignored issues
show
Documentation introduced by
Should the type for parameter $desc not be string|array? Also, consider making the array more specific, something like array<String>, or String[].

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
1833
 *     Tableau de description des colonnes de la table SQL utilisée
1834
 *     (il sera calculé si nécessaire s'il n'est pas transmis).
1835
 * @param string $serveur
1836
 *     Nom de la connexion
1837
 * @param bool $requeter
1838
 *     Exécuter la requête, sinon la retourner
1839
 * @return array|bool|string
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|SQLiteResult|string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1840
 *     - string : texte de la requête si demandé
1841
 *     - true si la requête a réussie, false sinon
1842
 *     - array Tableau décrivant la requête et son temps d'exécution si var_profile est actif
1843
 */
1844
function spip_sqlite_update($table, $champs, $where = '', $desc = '', $serveur = '', $requeter = true) {
1845
	// recherche de champs 'timestamp' pour mise a jour auto de ceux-ci
1846
	$champs = _sqlite_ajouter_champs_timestamp($table, $champs, $desc, $serveur);
0 ignored issues
show
Bug introduced by
It seems like $desc defined by parameter $desc on line 1844 can also be of type array; however, _sqlite_ajouter_champs_timestamp() does only seem to accept string, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1847
1848
	$set = array();
1849
	foreach ($champs as $champ => $val) {
1850
		$set[] = $champ . "=$val";
1851
	}
1852 View Code Duplication
	if (!empty($set)) {
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...
1853
		return spip_sqlite_query(
1854
			_sqlite_calculer_expression('UPDATE', $table, ',')
1855
			. _sqlite_calculer_expression('SET', $set, ',')
1856
			. _sqlite_calculer_expression('WHERE', $where),
1857
			$serveur, $requeter);
1858
	}
1859
}
1860
1861
1862
/**
1863
 * Met à jour des enregistrements d'une table SQL et protège chaque valeur
1864
 *
1865
 * Protège chaque valeur transmise avec sql_quote(), adapté au type
1866
 * de champ attendu par la table SQL
1867
 *
1868
 * @param string $table
1869
 *     Nom de la table
1870
 * @param array $champs
1871
 *     Couples (colonne => valeur)
1872
 * @param string|array $where
1873
 *     Conditions a remplir (Where)
1874
 * @param array $desc
1875
 *     Tableau de description des colonnes de la table SQL utilisée
1876
 *     (il sera calculé si nécessaire s'il n'est pas transmis).
1877
 * @param string $serveur
1878
 *     Nom de la connexion
1879
 * @param bool $requeter
1880
 *     Exécuter la requête, sinon la retourner
1881
 * @return array|bool|string
0 ignored issues
show
Documentation introduced by
Should the return type not be null|boolean|SQLiteResult|string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
1882
 *     - string : texte de la requête si demandé
1883
 *     - true si la requête a réussie, false sinon
1884
 *     - array Tableau décrivant la requête et son temps d'exécution si var_profile est actif
1885
 */
1886
function spip_sqlite_updateq($table, $champs, $where = '', $desc = array(), $serveur = '', $requeter = true) {
1887
1888
	if (!$champs) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $champs of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1889
		return;
1890
	}
1891
	if (!$desc) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $desc of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
1892
		$desc = description_table($table, $serveur);
1893
	}
1894
	if (!$desc) {
1895
		die("$table insertion sans description");
1896
	}
1897
	$fields = $desc['field'];
1898
1899
	// recherche de champs 'timestamp' pour mise a jour auto de ceux-ci
1900
	$champs = _sqlite_ajouter_champs_timestamp($table, $champs, $desc, $serveur);
1901
1902
	$set = array();
1903
	foreach ($champs as $champ => $val) {
1904
		$set[] = $champ . '=' . _sqlite_calculer_cite($val, isset($fields[$champ]) ? $fields[$champ] : '');
1905
	}
1906
1907
	return spip_sqlite_query(
1908
		_sqlite_calculer_expression('UPDATE', $table, ',')
1909
		. _sqlite_calculer_expression('SET', $set, ',')
1910
		. _sqlite_calculer_expression('WHERE', $where),
1911
		$serveur, $requeter);
1912
}
1913
1914
1915
/*
1916
 * 
1917
 * Ensuite les fonctions non abstraites
1918
 * crees pour l'occasion de sqlite
1919
 * 
1920
 */
1921
1922
1923
/**
1924
 * Initialise la première connexion à un serveur SQLite
1925
 *
1926
 * @return void
1927
 */
1928
function _sqlite_init() {
1929
	if (!defined('_DIR_DB')) {
1930
		define('_DIR_DB', _DIR_ETC . 'bases/');
1931
	}
1932
	if (!defined('_SQLITE_CHMOD')) {
1933
		define('_SQLITE_CHMOD', _SPIP_CHMOD);
1934
	}
1935
1936
	if (!is_dir($d = _DIR_DB)) {
1937
		include_spip('inc/flock');
1938
		sous_repertoire($d);
1939
	}
1940
}
1941
1942
1943
/**
1944
 * Teste la version sqlite du link en cours
1945
 *
1946
 * @param string $version
1947
 * @param string $link
1948
 * @param string $serveur
1949
 * @param bool $requeter
1950
 * @return bool|int
1951
 */
1952
function _sqlite_is_version($version = '', $link = '', $serveur = '', $requeter = true) {
0 ignored issues
show
Unused Code introduced by
The parameter $requeter is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1953
	if ($link === '') {
1954
		$link = _sqlite_link($serveur);
1955
	}
1956
	if (!$link) {
1957
		return false;
1958
	}
1959
1960
	$v = 3;
1961
1962
	if (!$version) {
1963
		return $v;
1964
	}
1965
1966
	return ($version == $v);
1967
}
1968
1969
1970
/**
1971
 * Retrouver un link d'une connexion SQLite
1972
 *
1973
 * @param string $serveur Nom du serveur
1974
 * @return Object Information de connexion pour SQLite
1975
 */
1976
function _sqlite_link($serveur = '') {
1977
	$link = &$GLOBALS['connexions'][$serveur ? $serveur : 0]['link'];
1978
1979
	return $link;
1980
}
1981
1982
1983
/* ordre alphabetique pour les autres */
1984
1985
1986
/**
1987
 * Renvoie les bons echappements (mais pas sur les fonctions comme NOW())
1988
 *
1989
 * @param string|number $v Texte ou nombre à échapper
1990
 * @param string $type Type de donnée attendue, description SQL de la colonne de destination
1991
 * @return string|number     Texte ou nombre échappé
1992
 */
1993
function _sqlite_calculer_cite($v, $type) {
1994
	if ($type) {
1995
		if (is_null($v)
1996
			and stripos($type, "NOT NULL") === false
1997
		) {
1998
			return 'NULL';
1999
		} // null php se traduit en NULL SQL
2000
2001
		if (sql_test_date($type) and preg_match('/^\w+\(/', $v)) {
2002
			return $v;
2003
		}
2004
		if (sql_test_int($type)) {
2005
			if (is_numeric($v)) {
2006
				return $v;
2007
			} elseif (ctype_xdigit(substr($v, 2)) and strncmp($v, '0x', 2) == 0) {
2008
				return hexdec(substr($v, 2));
2009
			} else {
2010
				return intval($v);
2011
			}
2012
		}
2013
	} else {
2014
		// si on ne connait pas le type on le deduit de $v autant que possible
2015
		if (is_numeric($v)) {
2016
			return strval($v);
2017
		}
2018
	}
2019
2020
	// trouver un link sqlite3 pour faire l'echappement
2021
	foreach ($GLOBALS['connexions'] as $s) {
2022
		if ($l = $s['link']) {
2023
			return $l->quote($v);
2024
		}
2025
	}
2026
2027
	// echapper les ' en ''
2028
	spip_log("Pas de methode ->quote pour echapper", "sqlite." . _LOG_INFO_IMPORTANTE);
2029
2030
	return ("'" . str_replace("'", "''", $v) . "'");
2031
}
2032
2033
2034
/**
2035
 * Calcule un expression pour une requête, en cumulant chaque élément
2036
 * avec l'opérateur de liaison ($join) indiqué
2037
 *
2038
 * Renvoie grosso modo "$expression join($join, $v)"
2039
 *
2040
 * @param string $expression Mot clé de l'expression, tel que "WHERE" ou "ORDER BY"
2041
 * @param array|string $v Données de l'expression
2042
 * @param string $join Si les données sont un tableau, elles seront groupées par cette jointure
2043
 * @return string            Texte de l'expression, une partie donc, du texte la requête.
2044
 */
2045 View Code Duplication
function _sqlite_calculer_expression($expression, $v, $join = 'AND') {
0 ignored issues
show
Duplication introduced by
This function 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...
2046
	if (empty($v)) {
2047
		return '';
2048
	}
2049
2050
	$exp = "\n$expression ";
2051
2052
	if (!is_array($v)) {
2053
		return $exp . $v;
2054
	} else {
2055
		if (strtoupper($join) === 'AND') {
2056
			return $exp . join("\n\t$join ", array_map('_sqlite_calculer_where', $v));
2057
		} else {
2058
			return $exp . join($join, $v);
2059
		}
2060
	}
2061
}
2062
2063
2064
/**
2065
 * Prépare une clause order by
2066
 *
2067
 * Regroupe en texte les éléments si un tableau est donné
2068
 *
2069
 * @note
2070
 *   Pas besoin de conversion pour 0+x comme il faudrait pour mysql.
2071
 *
2072
 * @param string|array $orderby Texte du orderby à préparer
2073
 * @return string Texte du orderby préparé
2074
 */
2075
function _sqlite_calculer_order($orderby) {
2076
	return (is_array($orderby)) ? join(", ", $orderby) : $orderby;
2077
}
2078
2079
2080
/**
2081
 * Renvoie des `nom AS alias`
2082
 *
2083
 * @param array $args
2084
 * @return string Sélection de colonnes pour une clause SELECT
2085
 */
2086 View Code Duplication
function _sqlite_calculer_select_as($args) {
0 ignored issues
show
Duplication introduced by
This function 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...
2087
	$res = '';
2088
	foreach ($args as $k => $v) {
2089
		if (substr($k, -1) == '@') {
2090
			// c'est une jointure qui se refere au from precedent
2091
			// pas de virgule
2092
			$res .= '  ' . $v;
2093
		} else {
2094
			if (!is_numeric($k)) {
2095
				$p = strpos($v, " ");
2096
				if ($p) {
2097
					$v = substr($v, 0, $p) . " AS '$k'" . substr($v, $p);
2098
				} else {
2099
					$v .= " AS '$k'";
2100
				}
2101
			}
2102
			$res .= ', ' . $v;
2103
		}
2104
	}
2105
2106
	return substr($res, 2);
2107
}
2108
2109
2110
/**
2111
 * Prépare une clause WHERE pour SQLite
2112
 *
2113
 * Retourne une chaîne avec les bonnes parenthèses pour la
2114
 * contrainte indiquée, au format donnée par le compilateur
2115
 *
2116
 * @param array|string $v
2117
 *     Description des contraintes
2118
 *     - string : Texte du where
2119
 *     - sinon tableau : A et B peuvent être de type string ou array,
2120
 *       OP et C sont de type string :
2121
 *       - array(A) : A est le texte du where
2122
 *       - array(OP, A) : contrainte OP( A )
2123
 *       - array(OP, A, B) : contrainte (A OP B)
2124
 *       - array(OP, A, B, C) : contrainte (A OP (B) : C)
2125
 * @return string
2126
 *     Contrainte pour clause WHERE
2127
 */
2128 View Code Duplication
function _sqlite_calculer_where($v) {
0 ignored issues
show
Duplication introduced by
This function 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...
2129
	if (!is_array($v)) {
2130
		return $v;
2131
	}
2132
2133
	$op = array_shift($v);
2134
	if (!($n = count($v))) {
2135
		return $op;
2136
	} else {
2137
		$arg = _sqlite_calculer_where(array_shift($v));
2138
		if ($n == 1) {
2139
			return "$op($arg)";
2140
		} else {
2141
			$arg2 = _sqlite_calculer_where(array_shift($v));
2142
			if ($n == 2) {
2143
				return "($arg $op $arg2)";
2144
			} else {
2145
				return "($arg $op ($arg2) : $v[0])";
2146
			}
2147
		}
2148
	}
2149
}
2150
2151
2152
/**
2153
 * Charger les modules SQLite
2154
 *
2155
 * Si possible et juste la version demandée,
2156
 * ou, si aucune version, on renvoie les versions sqlite disponibles
2157
 * sur ce serveur dans un tableau
2158
 *
2159
 * @param string $version
2160
 * @return array|bool
2161
 */
2162
function _sqlite_charger_version($version = '') {
2163
	$versions = array();
2164
2165
	// version 3
2166
	if (!$version || $version == 3) {
2167
		if (extension_loaded('pdo') && extension_loaded('pdo_sqlite')) {
2168
			$versions[] = 3;
2169
		}
2170
	}
2171
	if ($version) {
2172
		return in_array($version, $versions);
2173
	}
2174
2175
	return $versions;
2176
}
2177
2178
2179
/**
2180
 * Gestion des requêtes ALTER non reconnues de SQLite
2181
 *
2182
 * Requêtes non reconnues :
2183
 *
2184
 *     ALTER TABLE table DROP column
2185
 *     ALTER TABLE table CHANGE [COLUMN] columnA columnB definition
2186
 *     ALTER TABLE table MODIFY column definition
2187
 *     ALTER TABLE table ADD|DROP PRIMARY KEY
2188
 *
2189
 * `MODIFY` est transformé en `CHANGE columnA columnA` par spip_sqlite_alter()
2190
 *
2191
 * 1) Créer une table B avec le nouveau format souhaité
2192
 * 2) Copier la table d'origine A vers B
2193
 * 3) Supprimer la table A
2194
 * 4) Renommer la table B en A
2195
 * 5) Remettre les index (qui sont supprimés avec la table A)
2196
 *
2197
 * @param string|array $table
2198
 *     - string : Nom de la table table,
2199
 *     - array : couple (nom de la table => nom futur)
2200
 * @param string|array $colonne
2201
 *     - string : nom de la colonne,
2202
 *     - array : couple (nom de la colonne => nom futur)
2203
 * @param array $opt
2204
 *     options comme les tables SPIP, qui sera mergé à la table créee :
2205
 *     `array('field'=>array('nom'=>'syntaxe', ...), 'key'=>array('KEY nom'=>'colonne', ...))`
2206
 * @param string $serveur
2207
 *     Nom de la connexion SQL en cours
2208
 * @return bool
2209
 *     true si OK, false sinon.
2210
 */
2211
function _sqlite_modifier_table($table, $colonne, $opt = array(), $serveur = '') {
2212
2213
	if (is_array($table)) {
2214
		list($table_origine, $table_destination) = reset($table);
2215
	} else {
2216
		$table_origine = $table_destination = $table;
2217
	}
2218
	// ne prend actuellement qu'un changement
2219
	// mais pourra etre adapte pour changer plus qu'une colonne a la fois
2220
	if (is_array($colonne)) {
2221
		list($colonne_origine, $colonne_destination) = reset($colonne);
2222
	} else {
2223
		$colonne_origine = $colonne_destination = $colonne;
2224
	}
2225
	if (!isset($opt['field'])) {
2226
		$opt['field'] = array();
2227
	}
2228
	if (!isset($opt['key'])) {
2229
		$opt['key'] = array();
2230
	}
2231
2232
	// si les noms de tables sont differents, pas besoin de table temporaire
2233
	// on prendra directement le nom de la future table
2234
	$meme_table = ($table_origine == $table_destination);
2235
2236
	$def_origine = sql_showtable($table_origine, false, $serveur);
2237 View Code Duplication
	if (!$def_origine or !isset($def_origine['field'])) {
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...
2238
		spip_log("Alter table impossible sur $table_origine : table non trouvee", 'sqlite' . _LOG_ERREUR);
2239
2240
		return false;
2241
	}
2242
2243
2244
	$table_tmp = $table_origine . '_tmp';
2245
2246
	// 1) creer une table temporaire avec les modifications	
2247
	// - DROP : suppression de la colonne
2248
	// - CHANGE : modification de la colonne
2249
	// (foreach pour conserver l'ordre des champs)
2250
2251
	// field 
2252
	$fields = array();
2253
	// pour le INSERT INTO plus loin
2254
	// stocker la correspondance nouvelles->anciennes colonnes
2255
	$fields_correspondances = array();
2256
	foreach ($def_origine['field'] as $c => $d) {
2257
2258
		if ($colonne_origine && ($c == $colonne_origine)) {
2259
			// si pas DROP
2260
			if ($colonne_destination) {
2261
				$fields[$colonne_destination] = $opt['field'][$colonne_destination];
2262
				$fields_correspondances[$colonne_destination] = $c;
2263
			}
2264
		} else {
2265
			$fields[$c] = $d;
2266
			$fields_correspondances[$c] = $c;
2267
		}
2268
	}
2269
	// cas de ADD sqlite2 (ajout du champ en fin de table):
2270
	if (!$colonne_origine && $colonne_destination) {
2271
		$fields[$colonne_destination] = $opt['field'][$colonne_destination];
2272
	}
2273
2274
	// key...
2275
	$keys = array();
2276
	foreach ($def_origine['key'] as $c => $d) {
2277
		$c = str_replace($colonne_origine, $colonne_destination, $c);
2278
		$d = str_replace($colonne_origine, $colonne_destination, $d);
2279
		// seulement si on ne supprime pas la colonne !
2280
		if ($d) {
2281
			$keys[$c] = $d;
2282
		}
2283
	}
2284
2285
	// autres keys, on merge
2286
	$keys = array_merge($keys, $opt['key']);
2287
	$queries = array();
2288
2289
	// copier dans destination (si differente de origine), sinon tmp
2290
	$table_copie = ($meme_table) ? $table_tmp : $table_destination;
2291
	$autoinc = (isset($keys['PRIMARY KEY'])
2292
		and $keys['PRIMARY KEY']
2293
		and stripos($keys['PRIMARY KEY'], ',') === false
2294
		and stripos($fields[$keys['PRIMARY KEY']], 'default') === false);
2295
2296
	if ($q = _sqlite_requete_create(
2297
		$table_copie,
2298
		$fields,
2299
		$keys,
2300
		$autoinc,
2301
		$temporary = false,
2302
		$ifnotexists = true,
2303
		$serveur)
2304
	) {
2305
		$queries[] = $q;
2306
	}
2307
2308
2309
	// 2) y copier les champs qui vont bien
2310
	$champs_dest = join(', ', array_keys($fields_correspondances));
2311
	$champs_ori = join(', ', $fields_correspondances);
2312
	$queries[] = "INSERT INTO $table_copie ($champs_dest) SELECT $champs_ori FROM $table_origine";
2313
2314
	// 3) supprimer la table d'origine
2315
	$queries[] = "DROP TABLE $table_origine";
2316
2317
	// 4) renommer la table temporaire 
2318
	// avec le nom de la table destination
2319
	// si necessaire
2320
	if ($meme_table) {
2321
		$queries[] = "ALTER TABLE $table_copie RENAME TO $table_destination";
2322
	}
2323
2324
	// 5) remettre les index !
2325
	foreach ($keys as $k => $v) {
2326
		if ($k == 'PRIMARY KEY') {
2327
		} else {
2328
			// enlever KEY
2329
			$k = substr($k, 4);
2330
			$queries[] = "CREATE INDEX $table_destination" . "_$k ON $table_destination ($v)";
2331
		}
2332
	}
2333
2334
2335
	if (count($queries)) {
2336
		spip_sqlite::demarrer_transaction($serveur);
2337
		// il faut les faire une par une car $query = join('; ', $queries).";"; ne fonctionne pas
2338
		foreach ($queries as $q) {
2339
			if (!spip_sqlite::executer_requete($q, $serveur)) {
2340
				spip_log(_LOG_GRAVITE_ERREUR, "SQLite : ALTER TABLE table :"
2341
					. " Erreur a l'execution de la requete : $q", 'sqlite');
0 ignored issues
show
Unused Code introduced by
The call to spip_log() has too many arguments starting with 'sqlite'.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
2342
				spip_sqlite::annuler_transaction($serveur);
2343
2344
				return false;
2345
			}
2346
		}
2347
		spip_sqlite::finir_transaction($serveur);
2348
	}
2349
2350
	return true;
2351
}
2352
2353
2354
/**
2355
 * Nom des fonctions
2356
 *
2357
 * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,string|arra...,array<string,string>>>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
2358
 */
2359
function _sqlite_ref_fonctions() {
2360
	$fonctions = array(
2361
		'alter' => 'spip_sqlite_alter',
2362
		'count' => 'spip_sqlite_count',
2363
		'countsel' => 'spip_sqlite_countsel',
2364
		'create' => 'spip_sqlite_create',
2365
		'create_base' => 'spip_sqlite_create_base',
2366
		'create_view' => 'spip_sqlite_create_view',
2367
		'date_proche' => 'spip_sqlite_date_proche',
2368
		'delete' => 'spip_sqlite_delete',
2369
		'drop_table' => 'spip_sqlite_drop_table',
2370
		'drop_view' => 'spip_sqlite_drop_view',
2371
		'errno' => 'spip_sqlite_errno',
2372
		'error' => 'spip_sqlite_error',
2373
		'explain' => 'spip_sqlite_explain',
2374
		'fetch' => 'spip_sqlite_fetch',
2375
		'seek' => 'spip_sqlite_seek',
2376
		'free' => 'spip_sqlite_free',
2377
		'hex' => 'spip_sqlite_hex',
2378
		'in' => 'spip_sqlite_in',
2379
		'insert' => 'spip_sqlite_insert',
2380
		'insertq' => 'spip_sqlite_insertq',
2381
		'insertq_multi' => 'spip_sqlite_insertq_multi',
2382
		'listdbs' => 'spip_sqlite_listdbs',
2383
		'multi' => 'spip_sqlite_multi',
2384
		'optimize' => 'spip_sqlite_optimize',
2385
		'query' => 'spip_sqlite_query',
2386
		'quote' => 'spip_sqlite_quote',
2387
		'repair' => 'spip_sqlite_repair',
2388
		'replace' => 'spip_sqlite_replace',
2389
		'replace_multi' => 'spip_sqlite_replace_multi',
2390
		'select' => 'spip_sqlite_select',
2391
		'selectdb' => 'spip_sqlite_selectdb',
2392
		'set_charset' => 'spip_sqlite_set_charset',
2393
		'get_charset' => 'spip_sqlite_get_charset',
2394
		'showbase' => 'spip_sqlite_showbase',
2395
		'showtable' => 'spip_sqlite_showtable',
2396
		'update' => 'spip_sqlite_update',
2397
		'updateq' => 'spip_sqlite_updateq',
2398
		'preferer_transaction' => 'spip_sqlite_preferer_transaction',
2399
		'demarrer_transaction' => 'spip_sqlite_demarrer_transaction',
2400
		'terminer_transaction' => 'spip_sqlite_terminer_transaction',
2401
	);
2402
2403
	// association de chaque nom http d'un charset aux couples sqlite 
2404
	// SQLite supporte utf-8 et utf-16 uniquement.
2405
	$charsets = array(
2406
		'utf-8' => array('charset' => 'utf8', 'collation' => 'utf8_general_ci'),
2407
		//'utf-16be'=>array('charset'=>'utf16be','collation'=>'UTF-16BE'),// aucune idee de quoi il faut remplir dans es champs la
2408
		//'utf-16le'=>array('charset'=>'utf16le','collation'=>'UTF-16LE')
2409
	);
2410
2411
	$fonctions['charsets'] = $charsets;
2412
2413
	return $fonctions;
2414
}
2415
2416
2417
/**
2418
 * $query est une requete ou une liste de champs
2419
 *
2420
 * @param  $query
2421
 * @param bool $autoinc
2422
 * @return mixed
2423
 */
2424
function _sqlite_remplacements_definitions_table($query, $autoinc = false) {
2425
	// quelques remplacements
2426
	$num = "(\s*\([0-9]*\))?";
2427
	$enum = "(\s*\([^\)]*\))?";
2428
2429
	$remplace = array(
2430
		'/enum' . $enum . '/is' => 'VARCHAR(255)',
2431
		'/COLLATE \w+_bin/is' => 'COLLATE BINARY',
2432
		'/COLLATE \w+_ci/is' => 'COLLATE NOCASE',
2433
		'/auto_increment/is' => '',
2434
		'/(timestamp .* )ON .*$/is' => '\\1',
2435
		'/character set \w+/is' => '',
2436
		'/((big|small|medium|tiny)?int(eger)?)' . $num . '\s*unsigned/is' => '\\1 UNSIGNED',
2437
		'/(text\s+not\s+null(\s+collate\s+\w+)?)\s*$/is' => "\\1 DEFAULT ''",
2438
		'/((char|varchar)' . $num . '\s+not\s+null(\s+collate\s+\w+)?)\s*$/is' => "\\1 DEFAULT ''",
2439
		'/(datetime\s+not\s+null)\s*$/is' => "\\1 DEFAULT '0000-00-00 00:00:00'",
2440
		'/(date\s+not\s+null)\s*$/is' => "\\1 DEFAULT '0000-00-00'",
2441
	);
2442
2443
	// pour l'autoincrement, il faut des INTEGER NOT NULL PRIMARY KEY
2444
	$remplace_autocinc = array(
2445
		'/(big|small|medium|tiny)?int(eger)?' . $num . '/is' => 'INTEGER'
2446
	);
2447
	// pour les int non autoincrement, il faut un DEFAULT
2448
	$remplace_nonautocinc = array(
2449
		'/((big|small|medium|tiny)?int(eger)?' . $num . '\s+not\s+null)\s*$/is' => "\\1 DEFAULT 0",
2450
	);
2451
2452
	if (is_string($query)) {
2453
		$query = preg_replace(array_keys($remplace), $remplace, $query);
2454
		if ($autoinc or preg_match(',AUTO_INCREMENT,is', $query)) {
2455
			$query = preg_replace(array_keys($remplace_autocinc), $remplace_autocinc, $query);
2456
		} else {
2457
			$query = preg_replace(array_keys($remplace_nonautocinc), $remplace_nonautocinc, $query);
2458
			$query = _sqlite_collate_ci($query);
2459
		}
2460
	} elseif (is_array($query)) {
2461
		foreach ($query as $k => $q) {
2462
			$ai = ($autoinc ? $k == $autoinc : preg_match(',AUTO_INCREMENT,is', $q));
2463
			$query[$k] = preg_replace(array_keys($remplace), $remplace, $query[$k]);
2464
			if ($ai) {
2465
				$query[$k] = preg_replace(array_keys($remplace_autocinc), $remplace_autocinc, $query[$k]);
2466
			} else {
2467
				$query[$k] = preg_replace(array_keys($remplace_nonautocinc), $remplace_nonautocinc, $query[$k]);
2468
				$query[$k] = _sqlite_collate_ci($query[$k]);
2469
			}
2470
		}
2471
	}
2472
2473
	return $query;
2474
}
2475
2476
/**
2477
 * Definir la collation d'un champ en fonction de si une collation est deja explicite
2478
 * et du par defaut que l'on veut NOCASE
2479
 *
2480
 * @param string $champ
2481
 * @return string
2482
 */
2483
function _sqlite_collate_ci($champ) {
2484
	if (stripos($champ, "COLLATE") !== false) {
2485
		return $champ;
2486
	}
2487
	if (stripos($champ, "BINARY") !== false) {
2488
		return str_ireplace("BINARY", "COLLATE BINARY", $champ);
2489
	}
2490
	if (preg_match(",^(char|varchar|(long|small|medium|tiny)?text),i", $champ)) {
2491
		return $champ . " COLLATE NOCASE";
2492
	}
2493
2494
	return $champ;
2495
}
2496
2497
2498
/**
2499
 * Creer la requete pour la creation d'une table
2500
 * retourne la requete pour utilisation par sql_create() et sql_alter()
2501
 *
2502
 * @param  $nom
2503
 * @param  $champs
2504
 * @param  $cles
2505
 * @param bool $autoinc
2506
 * @param bool $temporary
2507
 * @param bool $_ifnotexists
2508
 * @param string $serveur
2509
 * @param bool $requeter
2510
 * @return bool|string
2511
 */
2512
function _sqlite_requete_create(
2513
	$nom,
2514
	$champs,
2515
	$cles,
2516
	$autoinc = false,
2517
	$temporary = false,
2518
	$_ifnotexists = true,
2519
	$serveur = '',
2520
	$requeter = true
0 ignored issues
show
Unused Code introduced by
The parameter $requeter is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
2521
) {
2522
	$query = $keys = $s = $p = '';
0 ignored issues
show
Unused Code introduced by
$p is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
2523
2524
	// certains plugins declarent les tables  (permet leur inclusion dans le dump)
2525
	// sans les renseigner (laisse le compilo recuperer la description)
2526
	if (!is_array($champs) || !is_array($cles)) {
2527
		return;
2528
	}
2529
2530
	// sqlite ne gere pas KEY tout court dans une requete CREATE TABLE
2531
	// il faut passer par des create index
2532
	// Il gere par contre primary key !
2533
	// Soit la PK est definie dans les cles, soit dans un champs
2534
	// soit faussement dans les 2 (et dans ce cas, il faut l’enlever à un des 2 endroits !)
2535
	$pk = "PRIMARY KEY";
2536
	// le champ de cle primaire
2537
	$champ_pk = !empty($cles[$pk]) ? $cles[$pk] : '';
2538
2539
	foreach ($champs as $k => $v) {
2540
		if (false !== stripos($v, $pk)) {
2541
			$champ_pk = $k;
2542
			// on n'en a plus besoin dans field, vu que defini dans key
2543
			$champs[$k] = preg_replace("/$pk/is", '', $champs[$k]);
2544
			break;
2545
		}
2546
	}
2547
2548
	if ($champ_pk) {
2549
		$keys = "\n\t\t$pk ($champ_pk)";
2550
	}
2551
	// Pas de DEFAULT 0 sur les cles primaires en auto-increment
2552
	if (isset($champs[$champ_pk])
2553
		and stripos($champs[$champ_pk], "default 0") !== false
2554
	) {
2555
		$champs[$champ_pk] = trim(str_ireplace("default 0", "", $champs[$champ_pk]));
2556
	}
2557
2558
	$champs = _sqlite_remplacements_definitions_table($champs, $autoinc ? $champ_pk : false);
2559
	foreach ($champs as $k => $v) {
2560
		$query .= "$s\n\t\t$k $v";
2561
		$s = ",";
2562
	}
2563
2564
	$ifnotexists = "";
2565
	if ($_ifnotexists) {
2566
2567
		$version = spip_sqlite_fetch(spip_sqlite_query("select sqlite_version() AS sqlite_version", $serveur), '',
2568
			$serveur);
2569
		if (!function_exists('spip_version_compare')) {
2570
			include_spip('plugins/installer');
2571
		}
2572
2573 View Code Duplication
		if ($version and spip_version_compare($version['sqlite_version'], '3.3.0', '>=')) {
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...
2574
			$ifnotexists = ' IF NOT EXISTS';
2575
		} else {
2576
			/* simuler le IF EXISTS - version 2 et sqlite < 3.3a */
2577
			$a = spip_sqlite_showtable($nom, $serveur);
2578
			if (isset($a['key']['KEY ' . $nom])) {
2579
				return true;
2580
			}
2581
		}
2582
2583
	}
2584
2585
	$temporary = $temporary ? ' TEMPORARY' : '';
2586
	$q = "CREATE$temporary TABLE$ifnotexists $nom ($query" . ($keys ? ",$keys" : '') . ")\n";
2587
2588
	return $q;
2589
}
2590
2591
2592
/**
2593
 * Retrouver les champs 'timestamp'
2594
 * pour les ajouter aux 'insert' ou 'replace'
2595
 * afin de simuler le fonctionnement de mysql
2596
 *
2597
 * stocke le resultat pour ne pas faire
2598
 * de requetes showtable intempestives
2599
 *
2600
 * @param  $table
2601
 * @param  $couples
2602
 * @param string $desc
2603
 * @param string $serveur
2604
 * @return
2605
 */
2606
function _sqlite_ajouter_champs_timestamp($table, $couples, $desc = '', $serveur = '') {
2607
	static $tables = array();
2608
2609
	if (!isset($tables[$table])) {
2610
2611 View Code Duplication
		if (!$desc) {
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...
2612
			$trouver_table = charger_fonction('trouver_table', 'base');
2613
			$desc = $trouver_table($table, $serveur);
2614
			// si pas de description, on ne fait rien, ou on die() ?
2615
			if (!$desc) {
2616
				return $couples;
2617
			}
2618
		}
2619
2620
		// recherche des champs avec simplement 'TIMESTAMP'
2621
		// cependant, il faudra peut etre etendre
2622
		// avec la gestion de DEFAULT et ON UPDATE
2623
		// mais ceux-ci ne sont pas utilises dans le core
2624
		$tables[$table] = array();
2625
2626
		foreach ($desc['field'] as $k => $v) {
2627 View Code Duplication
			if (strpos(strtolower(ltrim($v)), 'timestamp') === 0) {
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...
2628
				$tables[$table][$k] = "datetime('now')";
2629
			}
2630
		}
2631
	}
2632
2633
	// ajout des champs type 'timestamp' absents
2634
	return array_merge($tables[$table], $couples);
2635
}
2636
2637
2638
/**
2639
 * Renvoyer la liste des versions sqlite disponibles
2640
 * sur le serveur
2641
 *
2642
 * @return array|bool
2643
 */
2644
function spip_versions_sqlite() {
2645
	return _sqlite_charger_version();
2646
}
2647
2648
/**
2649
 * Gère l'envoi et la réception de requêtes à SQLite, qui peuvent être
2650
 * encadrées de transactions.
2651
 **/
2652
class spip_sqlite {
2653
	/** @var sqlite_requeteur[] Liste des instances de requêteurs créés */
2654
	public static $requeteurs = array();
2655
	/** @var bool[] Pour chaque connexion, flag pour savoir si une transaction est en cours */
2656
	public static $transaction_en_cours = array();
2657
2658
2659
	/**
2660
	 * Retourne une unique instance du requêteur
2661
	 *
2662
	 * Retourne une instance unique du requêteur pour une connexion SQLite
2663
	 * donnée
2664
	 *
2665
	 * @param string $serveur
2666
	 *    Nom du connecteur
2667
	 * @return sqlite_requeteur
2668
	 *    Instance unique du requêteur
2669
	 **/
2670
	public static function requeteur($serveur) {
2671
		if (!isset(spip_sqlite::$requeteurs[$serveur])) {
2672
			spip_sqlite::$requeteurs[$serveur] = new sqlite_requeteur($serveur);
2673
		}
2674
2675
		return spip_sqlite::$requeteurs[$serveur];
2676
	}
2677
2678
	/**
2679
	 * Prépare le texte d'une requête avant son exécution
2680
	 *
2681
	 * Adapte la requête au format plus ou moins MySQL par un format
2682
	 * compris de SQLite.
2683
	 *
2684
	 * Change les préfixes de tables SPIP par ceux véritables
2685
	 *
2686
	 * @param string $query Requête à préparer
2687
	 * @param string $serveur Nom de la connexion
2688
	 * @return string           Requête préparée
2689
	 */
2690
	public static function traduire_requete($query, $serveur) {
2691
		$requeteur = spip_sqlite::requeteur($serveur);
2692
		$traducteur = new sqlite_traducteur($query, $requeteur->prefixe, $requeteur->sqlite_version);
2693
2694
		return $traducteur->traduire_requete();
2695
	}
2696
2697
	/**
2698
	 * Démarre une transaction
2699
	 *
2700
	 * @param string $serveur Nom de la connexion
2701
	 **/
2702
	public static function demarrer_transaction($serveur) {
2703
		spip_sqlite::executer_requete("BEGIN TRANSACTION", $serveur);
2704
		spip_sqlite::$transaction_en_cours[$serveur] = true;
2705
	}
2706
2707
	/**
2708
	 * Exécute la requête donnée
2709
	 *
2710
	 * @param string $query Requête
2711
	 * @param string $serveur Nom de la connexion
2712
	 * @param null|bool $tracer Demander des statistiques (temps) ?
2713
	 **/
2714
	public static function executer_requete($query, $serveur, $tracer = null) {
2715
		$requeteur = spip_sqlite::requeteur($serveur);
2716
2717
		return $requeteur->executer_requete($query, $tracer);
2718
	}
2719
2720
	/**
2721
	 * Obtient l'identifiant de la dernière ligne insérée ou modifiée
2722
	 *
2723
	 * @param string $serveur Nom de la connexion
2724
	 * return int                Identifiant
2725
	 **/
2726
	public static function last_insert_id($serveur) {
2727
		$requeteur = spip_sqlite::requeteur($serveur);
2728
2729
		return $requeteur->last_insert_id($serveur);
0 ignored issues
show
Unused Code introduced by
The call to sqlite_requeteur::last_insert_id() has too many arguments starting with $serveur.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
2730
	}
2731
2732
	/**
2733
	 * Annule une transaction
2734
	 *
2735
	 * @param string $serveur Nom de la connexion
2736
	 **/
2737
	public static function annuler_transaction($serveur) {
2738
		spip_sqlite::executer_requete("ROLLBACK", $serveur);
2739
		spip_sqlite::$transaction_en_cours[$serveur] = false;
2740
	}
2741
2742
	/**
2743
	 * Termine une transaction
2744
	 *
2745
	 * @param string $serveur Nom de la connexion
2746
	 **/
2747
	public static function finir_transaction($serveur) {
2748
		// si pas de transaction en cours, ne rien faire et le dire
2749
		if (!isset(spip_sqlite::$transaction_en_cours[$serveur])
2750
			or spip_sqlite::$transaction_en_cours[$serveur] == false
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
2751
		) {
2752
			return false;
2753
		}
2754
		// sinon fermer la transaction et retourner true
2755
		spip_sqlite::executer_requete("COMMIT", $serveur);
2756
		spip_sqlite::$transaction_en_cours[$serveur] = false;
2757
2758
		return true;
2759
	}
2760
}
2761
2762
/*
2763
 * Classe pour partager les lancements de requête
2764
 * 
2765
 * Instanciée une fois par `$serveur` :
2766
 * 
2767
 * - peut corriger la syntaxe des requêtes pour la conformité à SQLite
2768
 * - peut tracer les requêtes
2769
 */
2770
2771
class sqlite_requeteur {
2772
	/** @var string Texte de la requête */
2773
	public $query = ''; // la requete
2774
	/** @var string Nom de la connexion */
2775
	public $serveur = '';
2776
	/** @var Ressource Identifiant de la connexion SQLite */
2777
	public $link = '';
2778
	/** @var string Prefixe des tables SPIP */
2779
	public $prefixe = '';
2780
	/** @var string Nom de la base de donnée */
2781
	public $db = '';
2782
	/** @var bool Doit-on tracer les requetes (var_profile) ? */
2783
	public $tracer = false; // doit-on tracer les requetes (var_profile)
2784
2785
	/** @var string Version de SQLite (2 ou 3) */
2786
	public $sqlite_version = '';
2787
2788
	/**
2789
	 * Constructeur
2790
	 *
2791
	 * @param string $serveur
2792
	 * @return bool
0 ignored issues
show
Comprehensibility Best Practice introduced by
Adding a @return annotation to constructors is generally not recommended as a constructor does not have a meaningful return value.

Adding a @return annotation to a constructor is not recommended, since a constructor does not have a meaningful return value.

Please refer to the PHP core documentation on constructors.

Loading history...
2793
	 */
2794
	public function __construct($serveur = '') {
2795
		_sqlite_init();
2796
		$this->serveur = strtolower($serveur);
2797
2798
		if (!($this->link = _sqlite_link($this->serveur)) && (!defined('_ECRIRE_INSTALL') || !_ECRIRE_INSTALL)) {
2799
			spip_log("Aucune connexion sqlite (link)", 'sqlite.' . _LOG_ERREUR);
2800
2801
			return false;
0 ignored issues
show
Bug introduced by
Constructors do not have meaningful return values, anything that is returned from here is discarded. Are you sure this is correct?
Loading history...
2802
		}
2803
2804
		$this->sqlite_version = _sqlite_is_version('', $this->link);
0 ignored issues
show
Documentation Bug introduced by
It seems like _sqlite_is_version('', $this->link) of type integer or boolean is incompatible with the declared type string of property $sqlite_version.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
2805
2806
		$this->prefixe = $GLOBALS['connexions'][$this->serveur ? $this->serveur : 0]['prefixe'];
2807
		$this->db = $GLOBALS['connexions'][$this->serveur ? $this->serveur : 0]['db'];
2808
2809
		// tracage des requetes ?
2810
		$this->tracer = (isset($_GET['var_profile']) && $_GET['var_profile']);
2811
	}
2812
2813
	/**
2814
	 * Lancer la requête transmise et faire le tracage si demandé
2815
	 *
2816
	 * @param string $query
2817
	 *     Requête à exécuter
2818
	 * @param bool|null $tracer
2819
	 *     true pour tracer la requête
2820
	 * @return bool|SQLiteResult
2821
	 */
2822
	public function executer_requete($query, $tracer = null) {
2823
		if (is_null($tracer)) {
2824
			$tracer = $this->tracer;
2825
		}
2826
		$err = "";
2827
		$t = 0;
2828
		if ($tracer) {
2829
			include_spip('public/tracer');
2830
			$t = trace_query_start();
2831
		}
2832
2833
		# spip_log("requete: $this->serveur >> $query",'sqlite.'._LOG_DEBUG); // boum ? pourquoi ?
2834
		if ($this->link) {
2835
			// memoriser la derniere erreur PHP vue
2836
			$e = (function_exists('error_get_last') ? error_get_last() : "");
2837
			// sauver la derniere requete
2838
			$GLOBALS['connexions'][$this->serveur ? $this->serveur : 0]['last'] = $query;
2839
			$GLOBALS['connexions'][$this->serveur ? $this->serveur : 0]['total_requetes']++;
2840
2841
			$r = $this->link->query($query);
2842
			// sauvegarde de la requete (elle y est deja dans $r->queryString)
2843
			# $r->spipQueryString = $query;
2844
2845
			// comptage : oblige de compter le nombre d'entrees retournees
2846
			// par une requete SELECT
2847
			// aucune autre solution ne donne le nombre attendu :( !
2848
			// particulierement s'il y a des LIMIT dans la requete.
2849
			if (strtoupper(substr(ltrim($query), 0, 6)) == 'SELECT') {
2850
				if ($r) {
2851
					// noter le link et la query pour faire le comptage *si* on en a besoin
2852
					$r->spipSqliteRowCount = array($this->link, $query);
2853
				} elseif ($r instanceof PDOStatement) {
2854
					$r->spipSqliteRowCount = 0;
0 ignored issues
show
Bug introduced by
The property spipSqliteRowCount does not seem to exist in PDOStatement.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
2855
				}
2856
			}
2857
2858
			// loger les warnings/erreurs eventuels de sqlite remontant dans PHP
2859
			if ($err = (function_exists('error_get_last') ? error_get_last() : "") and $err != $e) {
2860
				$err = strip_tags($err['message']) . " in " . $err['file'] . " line " . $err['line'];
2861
				spip_log("$err - " . $query, 'sqlite.' . _LOG_ERREUR);
2862
			} else {
2863
				$err = "";
2864
			}
2865
2866
		} else {
2867
			$r = false;
2868
		}
2869
2870
		if (spip_sqlite_errno($this->serveur)) {
2871
			$err .= spip_sqlite_error($query, $this->serveur);
2872
		}
2873
2874
		return $t ? trace_query_end($query, $t, $r, $err, $this->serveur) : $r;
2875
	}
2876
2877
	/**
2878
	 * Obtient l'identifiant de la dernière ligne insérée ou modifiée
2879
	 *
2880
	 * @return int
2881
	 **/
2882
	public function last_insert_id() {
2883
		return $this->link->lastInsertId();
2884
	}
2885
}
2886
2887
2888
/**
2889
 * Cette classe est presente essentiellement pour un preg_replace_callback
2890
 * avec des parametres dans la fonction appelee que l'on souhaite incrementer
2891
 * (fonction pour proteger les textes)
2892
 */
2893
class sqlite_traducteur {
2894
	/** @var string $query Texte de la requête */
2895
	public $query = '';
2896
	/** @var string $prefixe Préfixe des tables */
2897
	public $prefixe = '';
2898
	/** @var string $sqlite_version Version de sqlite (2 ou 3) */
2899
	public $sqlite_version = '';
2900
2901
	/** Pour les corrections à effectuer sur les requêtes : array(code=>'texte') trouvé
2902
	 *
2903
	 * @var array
2904
	 */
2905
	public $textes = array();
2906
2907
	/**
2908
	 * Constructeur
2909
	 *
2910
	 * @param string $query Requête à préparer
2911
	 * @param string $prefixe Prefixe des tables à utiliser
2912
	 * @param string $sqlite_version Version SQLite (2 ou 3)
2913
	 */
2914
	public function __construct($query, $prefixe, $sqlite_version) {
2915
		$this->query = $query;
2916
		$this->prefixe = $prefixe;
2917
		$this->sqlite_version = $sqlite_version;
2918
	}
2919
2920
	/**
2921
	 * Transformer la requete pour SQLite
2922
	 *
2923
	 * Enlève les textes, transforme la requête pour quelle soit
2924
	 * bien interprétée par SQLite, puis remet les textes
2925
	 * la fonction affecte `$this->query`
2926
	 */
2927
	public function traduire_requete() {
2928
		//
2929
		// 1) Protection des textes en les remplacant par des codes
2930
		//
2931
		// enlever les 'textes' et initialiser avec
2932
		list($this->query, $textes) = query_echappe_textes($this->query);
2933
2934
		//
2935
		// 2) Corrections de la requete
2936
		//
2937
		// Correction Create Database
2938
		// Create Database -> requete ignoree
2939 View Code Duplication
		if (strpos($this->query, 'CREATE DATABASE') === 0) {
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...
2940
			spip_log("Sqlite : requete non executee -> $this->query", 'sqlite.' . _LOG_AVERTISSEMENT);
2941
			$this->query = "SELECT 1";
2942
		}
2943
2944
		// Correction Insert Ignore
2945
		// INSERT IGNORE -> insert (tout court et pas 'insert or replace')
2946 View Code Duplication
		if (strpos($this->query, 'INSERT IGNORE') === 0) {
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...
2947
			spip_log("Sqlite : requete transformee -> $this->query", 'sqlite.' . _LOG_DEBUG);
2948
			$this->query = 'INSERT ' . substr($this->query, '13');
2949
		}
2950
2951
		// Correction des dates avec INTERVAL
2952
		// utiliser sql_date_proche() de preference
2953 View Code Duplication
		if (strpos($this->query, 'INTERVAL') !== false) {
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...
2954
			$this->query = preg_replace_callback("/DATE_(ADD|SUB)(.*)INTERVAL\s+(\d+)\s+([a-zA-Z]+)\)/U",
2955
				array(&$this, '_remplacerDateParTime'),
2956
				$this->query);
2957
		}
2958
2959 View Code Duplication
		if (strpos($this->query, 'LEFT(') !== false) {
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...
2960
			$this->query = str_replace('LEFT(', '_LEFT(', $this->query);
2961
		}
2962
2963
		if (strpos($this->query, 'TIMESTAMPDIFF(') !== false) {
2964
			$this->query = preg_replace('/TIMESTAMPDIFF\(\s*([^,]*)\s*,/Uims', "TIMESTAMPDIFF('\\1',", $this->query);
2965
		}
2966
2967
2968
		// Correction Using
2969
		// USING (non reconnu en sqlite2)
2970
		// problematique car la jointure ne se fait pas du coup.
2971
		if (($this->sqlite_version == 2) && (strpos($this->query, "USING") !== false)) {
2972
			spip_log("'USING (champ)' n'est pas reconnu en SQLite 2. Utilisez 'ON table1.champ = table2.champ'",
2973
				'sqlite.' . _LOG_ERREUR);
2974
			$this->query = preg_replace('/USING\s*\([^\)]*\)/', '', $this->query);
2975
		}
2976
2977
		// Correction Field
2978
		// remplace FIELD(table,i,j,k...) par CASE WHEN table=i THEN n ... ELSE 0 END
2979 View Code Duplication
		if (strpos($this->query, 'FIELD') !== false) {
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...
2980
			$this->query = preg_replace_callback('/FIELD\s*\(([^\)]*)\)/',
2981
				array(&$this, '_remplacerFieldParCase'),
2982
				$this->query);
2983
		}
2984
2985
		// Correction des noms de tables FROM
2986
		// mettre les bons noms de table dans from, update, insert, replace...
2987
		if (preg_match('/\s(SET|VALUES|WHERE|DATABASE)\s/iS', $this->query, $regs)) {
2988
			$suite = strstr($this->query, $regs[0]);
2989
			$this->query = substr($this->query, 0, -strlen($suite));
2990
		} else {
2991
			$suite = '';
2992
		}
2993
		$pref = ($this->prefixe) ? $this->prefixe . "_" : "";
2994
		$this->query = preg_replace('/([,\s])spip_/S', '\1' . $pref, $this->query) . $suite;
2995
2996
		// Correction zero AS x
2997
		// pg n'aime pas 0+x AS alias, sqlite, dans le meme style, 
2998
		// n'apprecie pas du tout SELECT 0 as x ... ORDER BY x
2999
		// il dit que x ne doit pas être un integer dans le order by !
3000
		// on remplace du coup x par vide() dans ce cas uniquement
3001
		//
3002
		// rien que pour public/vertebrer.php ?
3003
		if ((strpos($this->query, "0 AS") !== false)) {
3004
			// on ne remplace que dans ORDER BY ou GROUP BY
3005
			if (preg_match('/\s(ORDER|GROUP) BY\s/i', $this->query, $regs)) {
3006
				$suite = strstr($this->query, $regs[0]);
3007
				$this->query = substr($this->query, 0, -strlen($suite));
3008
3009
				// on cherche les noms des x dans 0 AS x
3010
				// on remplace dans $suite le nom par vide()
3011
				preg_match_all('/\b0 AS\s*([^\s,]+)/', $this->query, $matches, PREG_PATTERN_ORDER);
3012
				foreach ($matches[1] as $m) {
3013
					$suite = str_replace($m, 'VIDE()', $suite);
3014
				}
3015
				$this->query .= $suite;
3016
			}
3017
		}
3018
3019
		// Correction possible des divisions entieres
3020
		// Le standard SQL (lequel? ou?) semble indiquer que
3021
		// a/b=c doit donner c entier si a et b sont entiers 4/3=1.
3022
		// C'est ce que retournent effectivement SQL Server et SQLite
3023
		// Ce n'est pas ce qu'applique MySQL qui retourne un reel : 4/3=1.333...
3024
		// 
3025
		// On peut forcer la conversion en multipliant par 1.0 avant la division
3026
		// /!\ SQLite 3.5.9 Debian/Ubuntu est victime d'un bug en plus ! 
3027
		// cf. https://bugs.launchpad.net/ubuntu/+source/sqlite3/+bug/254228
3028
		//     http://www.sqlite.org/cvstrac/tktview?tn=3202
3029
		// (4*1.0/3) n'est pas rendu dans ce cas !
3030
		# $this->query = str_replace('/','* 1.00 / ',$this->query);
3031
3032
3033
		// Correction critere REGEXP, non reconnu en sqlite2
3034
		if (($this->sqlite_version == 2) && (strpos($this->query, 'REGEXP') !== false)) {
3035
			$this->query = preg_replace('/([^\s\(]*)(\s*)REGEXP(\s*)([^\s\)]*)/', 'REGEXP($4, $1)', $this->query);
3036
		}
3037
3038
		//
3039
		// 3) Remise en place des textes d'origine
3040
		//
3041
		// Correction Antiquotes et echappements
3042
		// ` => rien
3043 View Code Duplication
		if (strpos($this->query, '`') !== false) {
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...
3044
			$this->query = str_replace('`', '', $this->query);
3045
		}
3046
3047
		$this->query = query_reinjecte_textes($this->query, $textes);
0 ignored issues
show
Security Bug introduced by
It seems like $textes can also be of type false; however, query_reinjecte_textes() does only seem to accept array, did you maybe forget to handle an error condition?
Loading history...
3048
3049
		return $this->query;
3050
	}
3051
3052
3053
	/**
3054
	 * Callback pour remplacer `DATE_` / `INTERVAL`
3055
	 * par `DATE ... strtotime`
3056
	 *
3057
	 * @param array $matches Captures
3058
	 * @return string Texte de date compris par SQLite
3059
	 */
3060
	public function _remplacerDateParTime($matches) {
3061
		$op = strtoupper($matches[1] == 'ADD') ? '+' : '-';
3062
3063
		return "datetime$matches[2] '$op$matches[3] $matches[4]')";
3064
	}
3065
3066
	/**
3067
	 * Callback pour remplacer `FIELD(table,i,j,k...)`
3068
	 * par `CASE WHEN table=i THEN n ... ELSE 0 END`
3069
	 *
3070
	 * @param array $matches Captures
3071
	 * @return string Texte de liste ordonnée compris par SQLite
3072
	 */
3073
	public function _remplacerFieldParCase($matches) {
3074
		$fields = substr($matches[0], 6, -1); // ne recuperer que l'interieur X de field(X)
3075
		$t = explode(',', $fields);
3076
		$index = array_shift($t);
3077
3078
		$res = '';
3079
		$n = 0;
3080
		foreach ($t as $v) {
3081
			$n++;
3082
			$res .= "\nWHEN $index=$v THEN $n";
3083
		}
3084
3085
		return "CASE $res ELSE 0 END ";
3086
	}
3087
3088
}
3089