Completed
Push — master ( 1a3b2f...b50fb4 )
by cam
09:59
created

sqlite_generique.php ➔ spip_sqlite_drop_table()   A

Complexity

Conditions 6
Paths 10

Size

Total Lines 19

Duplication

Lines 5
Ratio 26.32 %

Importance

Changes 0
Metric Value
cc 6
nc 10
nop 4
dl 5
loc 19
rs 9.0111
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 View Code Duplication
		if ($sqlite_version == 3) {
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...
98
			$ok = $link = new PDO("sqlite:$tmp");
99
		} else {
100
			$ok = $link = sqlite_open($tmp, _SQLITE_CHMOD, $err);
101
		}
102 View Code Duplication
	} else {
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...
103
		// Ouvrir (eventuellement creer la base)
104
		// si pas de version fourni, on essaie la 3, sinon la 2
105
		if ($sqlite_version == 3) {
106
			$ok = $link = new PDO("sqlite:$f");
107
		} else {
108
			$ok = $link = sqlite_open($f, _SQLITE_CHMOD, $err);
109
		}
110
	}
111
112
	if (!$ok) {
113
		$e = sqlite_last_error($db);
114
		spip_log("Impossible d'ouvrir la base SQLite($sqlite_version) $f : $e", 'sqlite.' . _LOG_HS);
115
116
		return false;
117
	}
118
119
	if ($link) {
120
		$last_connect = array(
121
			'addr' => $addr,
122
			'port' => $port,
123
			'login' => $login,
124
			'pass' => $pass,
125
			'db' => $db,
126
			'prefixe' => $prefixe,
127
		);
128
		// etre sur qu'on definit bien les fonctions a chaque nouvelle connexion
129
		include_spip('req/sqlite_fonctions');
130
		_sqlite_init_functions($link);
131
	}
132
133
	return array(
134
		'db' => $db,
135
		'prefixe' => $prefixe ? $prefixe : $db,
136
		'link' => $link,
137
		'total_requetes' => 0,
138
	);
139
}
140
141
142
/**
143
 * Fonction de requete generale, munie d'une trace a la demande
144
 *
145
 * @param string $query
146
 *    Requete a executer
147
 * @param string $serveur
148
 *    Nom du connecteur
149
 * @param bool $requeter
150
 *    Effectuer la requete ?
151
 *    - true pour executer
152
 *    - false pour retourner le texte de la requete
153
 * @return bool|SQLiteResult|string
154
 *    Resultat de la requete
155
 */
156
function spip_sqlite_query($query, $serveur = '', $requeter = true) {
157
	#spip_log("spip_sqlite_query() > $query",'sqlite.'._LOG_DEBUG);
158
	#_sqlite_init(); // fait la premiere fois dans spip_sqlite
159
	$query = spip_sqlite::traduire_requete($query, $serveur);
160
	if (!$requeter) {
161
		return $query;
162
	}
163
164
	return spip_sqlite::executer_requete($query, $serveur);
165
}
166
167
168
/* ordre alphabetique pour les autres */
169
170
/**
171
 * Modifie une structure de table SQLite
172
 *
173
 * @param string $query Requête SQL (sans 'ALTER ')
174
 * @param string $serveur Nom de la connexion
175
 * @param bool $requeter inutilisé
176
 * @return bool
177
 *     False si erreur dans l'exécution, true sinon
178
 */
179
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...
180
181
	$query = spip_sqlite_query("ALTER $query", $serveur, false);
182
	// traduire la requete pour recuperer les bons noms de table
183
	$query = spip_sqlite::traduire_requete($query, $serveur);
184
185
	/*
186
		 * la il faut faire les transformations
187
		 * si ALTER TABLE x (DROP|CHANGE) y
188
		 *
189
		 * 1) recuperer "ALTER TABLE table "
190
		 * 2) spliter les sous requetes (,)
191
		 * 3) faire chaque requete independemment
192
		 */
193
194
	// 1
195
	if (preg_match("/\s*(ALTER(\s*IGNORE)?\s*TABLE\s*([^\s]*))\s*(.*)?/is", $query, $regs)) {
196
		$debut = $regs[1];
197
		$table = $regs[3];
198
		$suite = $regs[4];
199
	} else {
200
		spip_log("SQLite : Probleme de ALTER TABLE mal forme dans $query", 'sqlite.' . _LOG_ERREUR);
201
202
		return false;
203
	}
204
205
	// 2
206
	// il faudrait une regexp pour eviter de spliter ADD PRIMARY KEY (colA, colB)
207
	// tout en cassant "ADD PRIMARY KEY (colA, colB), ADD INDEX (chose)"... en deux
208
	// ou revoir l'api de sql_alter en creant un 
209
	// sql_alter_table($table,array($actions));
210
	$todo = explode(',', $suite);
211
212
	// on remet les morceaux dechires ensembles... que c'est laid !
213
	$todo2 = array();
214
	$i = 0;
215
	$ouverte = false;
216 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...
217
		$todo2[$i] = isset($todo2[$i]) ? $todo2[$i] . "," . $do : $do;
218
		$o = (false !== strpos($do, "("));
219
		$f = (false !== strpos($do, ")"));
220
		if ($o and !$f) {
221
			$ouverte = true;
222
		} elseif ($f) {
223
			$ouverte = false;
224
		}
225
		if (!$ouverte) {
226
			$i++;
227
		}
228
	}
229
230
	// 3
231
	$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...
232
	foreach ($todo2 as $do) {
233
		$do = trim($do);
234
		if (!preg_match('/(DROP PRIMARY KEY|DROP KEY|DROP INDEX|DROP COLUMN|DROP'
235
			. '|CHANGE COLUMN|CHANGE|MODIFY|RENAME TO|RENAME'
236
			. '|ADD PRIMARY KEY|ADD KEY|ADD INDEX|ADD UNIQUE KEY|ADD UNIQUE'
237
			. '|ADD COLUMN|ADD'
238
			. ')\s*([^\s]*)\s*(.*)?/i', $do, $matches)
239
		) {
240
			spip_log("SQLite : Probleme de ALTER TABLE, utilisation non reconnue dans : $do \n(requete d'origine : $query)",
241
				'sqlite.' . _LOG_ERREUR);
242
243
			return false;
244
		}
245
246
		$cle = strtoupper($matches[1]);
247
		$colonne_origine = $matches[2];
248
		$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...
249
250
		$def = $matches[3];
251
252
		// eluder une eventuelle clause before|after|first inutilisable
253
		$defr = rtrim(preg_replace('/(BEFORE|AFTER|FIRST)(.*)$/is', '', $def));
254
		$defo = $defr; // garder la def d'origine pour certains cas
255
		// remplacer les definitions venant de mysql
256
		$defr = _sqlite_remplacements_definitions_table($defr);
257
258
		// reinjecter dans le do
259
		$do = str_replace($def, $defr, $do);
260
		$def = $defr;
261
262
		switch ($cle) {
263
			// suppression d'un index
264
			case 'DROP KEY':
265
			case 'DROP INDEX':
266
				$nom_index = $colonne_origine;
267
				spip_sqlite_drop_index($nom_index, $table, $serveur);
268
				break;
269
270
			// suppression d'une pk
271 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...
272
				if (!_sqlite_modifier_table(
273
					$table,
274
					$colonne_origine,
275
					array('key' => array('PRIMARY KEY' => '')),
276
					$serveur)
277
				) {
278
					return false;
279
				}
280
				break;
281
			// suppression d'une colonne
282
			case 'DROP COLUMN':
283 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...
284
				if (!_sqlite_modifier_table(
285
					$table,
286
					array($colonne_origine => ""),
287
					array(),
288
					$serveur)
289
				) {
290
					return false;
291
				}
292
				break;
293
294
			case 'CHANGE COLUMN':
295
			case 'CHANGE':
296
				// recuperer le nom de la future colonne
297
				// on reprend la def d'origine car _sqlite_modifier_table va refaire la translation
298
				// en tenant compte de la cle primaire (ce qui est mieux)
299
				$def = trim($defo);
300
				$colonne_destination = substr($def, 0, strpos($def, ' '));
301
				$def = substr($def, strlen($colonne_destination) + 1);
302
303
				if (!_sqlite_modifier_table(
304
					$table,
305
					array($colonne_origine => $colonne_destination),
306
					array('field' => array($colonne_destination => $def)),
307
					$serveur)
308
				) {
309
					return false;
310
				}
311
				break;
312
313 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...
314
				// on reprend la def d'origine car _sqlite_modifier_table va refaire la translation
315
				// en tenant compte de la cle primaire (ce qui est mieux)
316
				if (!_sqlite_modifier_table(
317
					$table,
318
					$colonne_origine,
319
					array('field' => array($colonne_origine => $defo)),
320
					$serveur)
321
				) {
322
					return false;
323
				}
324
				break;
325
326
			// pas geres en sqlite2
327
			case 'RENAME':
328
				$do = "RENAME TO" . substr($do, 6);
329
			case 'RENAME TO':
330
				if (_sqlite_is_version(3, '', $serveur)) {
331 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...
332
						spip_log("SQLite : Erreur ALTER TABLE / RENAME : $query", 'sqlite.' . _LOG_ERREUR);
333
334
						return false;
335
					}
336
					// artillerie lourde pour sqlite2 !
337
				} else {
338
					$table_dest = trim(substr($do, 9));
339 View Code Duplication
					if (!_sqlite_modifier_table(array($table => $table_dest), '', array(), $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...
340
						spip_log("SQLite : Erreur ALTER TABLE / RENAME : $query", 'sqlite.' . _LOG_ERREUR);
341
342
						return false;
343
					}
344
				}
345
				break;
346
347
			// ajout d'une pk
348
			case 'ADD PRIMARY KEY':
349
				$pk = trim(substr($do, 16));
350
				$pk = ($pk[0] == '(') ? substr($pk, 1, -1) : $pk;
351
				if (!_sqlite_modifier_table(
352
					$table,
353
					$colonne_origine,
354
					array('key' => array('PRIMARY KEY' => $pk)),
355
					$serveur)
356
				) {
357
					return false;
358
				}
359
				break;
360
			// ajout d'un index
361
			case 'ADD UNIQUE KEY':
362
			case 'ADD UNIQUE':
363
				$unique = true;
364
			case 'ADD INDEX':
365
			case 'ADD KEY':
366
				if (!isset($unique)) {
367
					$unique = false;
368
				}
369
				// peut etre "(colonne)" ou "nom_index (colonnes)"
370
				// bug potentiel si qqn met "(colonne, colonne)"
371
				//
372
				// nom_index (colonnes)
373
				if ($def) {
374
					$colonnes = substr($def, 1, -1);
375
					$nom_index = $colonne_origine;
376
				} else {
377
					// (colonne)
378
					if ($colonne_origine[0] == "(") {
379
						$colonnes = substr($colonne_origine, 1, -1);
380 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...
381
							spip_log(_LOG_GRAVITE_ERREUR, "SQLite : Erreur, impossible de creer un index sur plusieurs colonnes"
382
								. " 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...
383
							break;
384
						} else {
385
							$nom_index = $colonnes;
386
						}
387
					} // nom_index
388
					else {
389
						$nom_index = $colonnes = $colonne_origine;
390
					}
391
				}
392
				spip_sqlite_create_index($nom_index, $table, $colonnes, $unique, $serveur);
393
				break;
394
395
			// pas geres en sqlite2
396
			case 'ADD COLUMN':
397
				$do = "ADD" . substr($do, 10);
398
			case 'ADD':
399
			default:
400
				if (_sqlite_is_version(3, '', $serveur) and !preg_match(',primary\s+key,i', $do)) {
401 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...
402
						spip_log("SQLite : Erreur ALTER TABLE / ADD : $query", 'sqlite.' . _LOG_ERREUR);
403
404
						return false;
405
					}
406
					break;
407
408
				}
409
				// artillerie lourde pour sqlite2 !
410
				// ou si la colonne est aussi primary key
411
				// cas du add id_truc int primary key
412
				// ajout d'une colonne qui passe en primary key directe
413
				else {
414
					$def = trim(substr($do, 3));
415
					$colonne_ajoutee = substr($def, 0, strpos($def, ' '));
416
					$def = substr($def, strlen($colonne_ajoutee) + 1);
417
					$opts = array();
418
					if (preg_match(',primary\s+key,i', $def)) {
419
						$opts['key'] = array('PRIMARY KEY' => $colonne_ajoutee);
420
						$def = preg_replace(',primary\s+key,i', '', $def);
421
					}
422
					$opts['field'] = array($colonne_ajoutee => $def);
423 View Code Duplication
					if (!_sqlite_modifier_table($table, array($colonne_ajoutee), $opts, $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...
424
						spip_log("SQLite : Erreur ALTER TABLE / ADD : $query", 'sqlite.' . _LOG_ERREUR);
425
426
						return false;
427
					}
428
				}
429
				break;
430
		}
431
		// tout est bon, ouf !
432
		spip_log("SQLite ($serveur) : Changements OK : $debut $do", 'sqlite.' . _LOG_INFO);
433
	}
434
435
	spip_log("SQLite ($serveur) : fin ALTER TABLE OK !", 'sqlite.' . _LOG_INFO);
436
437
	return true;
438
}
439
440
441
/**
442
 * Crée une table SQL
443
 *
444
 * Crée une table SQL nommee `$nom` à partir des 2 tableaux `$champs` et `$cles`
445
 *
446
 * @note Le nom des caches doit être inferieur à 64 caractères
447
 *
448
 * @param string $nom Nom de la table SQL
449
 * @param array $champs Couples (champ => description SQL)
450
 * @param array $cles Couples (type de clé => champ(s) de la clé)
451
 * @param bool $autoinc True pour ajouter un auto-incrément sur la Primary Key
452
 * @param bool $temporary True pour créer une table temporaire
453
 * @param string $serveur Nom de la connexion
454
 * @param bool $requeter Exécuter la requête, sinon la retourner
455
 * @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...
456
 *     - string Texte de la requête si demandée
457
 *     - true si la requête réussie, false sinon.
458
 */
459
function spip_sqlite_create(
460
	$nom,
461
	$champs,
462
	$cles,
463
	$autoinc = false,
464
	$temporary = false,
465
	$serveur = '',
466
	$requeter = true
467
) {
468
	$query = _sqlite_requete_create($nom, $champs, $cles, $autoinc, $temporary, $ifnotexists = true, $serveur, $requeter);
469
	if (!$query) {
470
		return false;
471
	}
472
	$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 468 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...
473
474
	// SQLite ne cree pas les KEY sur les requetes CREATE TABLE
475
	// il faut donc les faire creer ensuite
476
	if (!$requeter) {
477
		return $res;
478
	}
479
480
	$ok = $res ? true : false;
481
	if ($ok) {
482
		foreach ($cles as $k => $v) {
483
			if (preg_match(',^(KEY|UNIQUE)\s,i', $k, $m)) {
484
				$index = trim(substr($k, strlen($m[1])));
485
				$unique = (strlen($m[1]) > 3);
486
				$ok &= spip_sqlite_create_index($index, $nom, $v, $unique, $serveur);
487
			}
488
		}
489
	}
490
491
	return $ok ? true : false;
492
}
493
494
/**
495
 * Crée une base de données SQLite
496
 *
497
 * @param string $nom Nom de la base (sans l'extension de fichier)
498
 * @param string $serveur Nom de la connexion
499
 * @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...
500
 *
501
 * @return bool true si la base est créee.
502
 **/
503
function spip_sqlite_create_base($nom, $serveur = '', $option = true) {
0 ignored issues
show
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...
504
	$f = $nom . '.sqlite';
505
	if (strpos($nom, "/") === false) {
506
		$f = _DIR_DB . $f;
507
	}
508
	if (_sqlite_is_version(2, '', $serveur)) {
509
		$ok = sqlite_open($f, _SQLITE_CHMOD, $err);
510
	} else {
511
		$ok = new PDO("sqlite:$f");
512
	}
513
	if ($ok) {
514
		unset($ok);
515
516
		return true;
517
	}
518
	unset($ok);
519
520
	return false;
521
}
522
523
524
/**
525
 * Crée une vue SQL nommée `$nom`
526
 *
527
 * @param string $nom
528
 *    Nom de la vue a creer
529
 * @param string $query_select
530
 *     Texte de la requête de sélection servant de base à la vue
531
 * @param string $serveur
532
 *     Nom du connecteur
533
 * @param bool $requeter
534
 *     Effectuer la requete, sinon la retourner
535
 * @return bool|SQLiteResult|string
536
 *     - true si la vue est créée
537
 *     - false si erreur ou si la vue existe déja
538
 *     - string texte de la requête si $requeter vaut false
539
 */
540 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...
541
	if (!$query_select) {
542
		return false;
543
	}
544
	// vue deja presente
545
	if (sql_showtable($nom, false, $serveur)) {
546
		spip_log("Echec creation d'une vue sql ($nom) car celle-ci existe deja (serveur:$serveur)",
547
			'sqlite.' . _LOG_ERREUR);
548
549
		return false;
550
	}
551
552
	$query = "CREATE VIEW $nom AS " . $query_select;
553
554
	return spip_sqlite_query($query, $serveur, $requeter);
555
}
556
557
/**
558
 * Fonction de création d'un INDEX
559
 *
560
 * @param string $nom
561
 *     Nom de l'index
562
 * @param string $table
563
 *     Table SQL de l'index
564
 * @param string|array $champs
565
 *     Liste de champs sur lesquels s'applique l'index
566
 * @param string|bool $unique
567
 *     Créer un index UNIQUE ?
568
 * @param string $serveur
569
 *     Nom de la connexion sql utilisee
570
 * @param bool $requeter
571
 *     true pour executer la requête ou false pour retourner le texte de la requête
572
 * @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...
573
 *    string : requête, false si erreur, true sinon.
574
 */
575
function spip_sqlite_create_index($nom, $table, $champs, $unique = '', $serveur = '', $requeter = true) {
576 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...
577
		spip_log("Champ manquant pour creer un index sqlite ($nom, $table, (" . join(',', $champs) . "))",
578
			'sqlite.' . _LOG_ERREUR);
579
580
		return false;
581
	}
582
583
	// SQLite ne differentie pas noms des index en fonction des tables
584
	// il faut donc creer des noms uniques d'index pour une base sqlite
585
	$nom = $table . '_' . $nom;
586
	// enlever d'eventuelles parentheses deja presentes sur champs
587
	if (!is_array($champs)) {
588
		if ($champs[0] == "(") {
589
			$champs = substr($champs, 1, -1);
590
		}
591
		$champs = array($champs);
592
		// supprimer l'info de longueur d'index mysql en fin de champ
593
		$champs = preg_replace(",\(\d+\)$,", "", $champs);
594
	}
595
596
	$ifnotexists = "";
597
	$version = spip_sqlite_fetch(spip_sqlite_query("select sqlite_version() AS sqlite_version", $serveur), '', $serveur);
598
	if (!function_exists('spip_version_compare')) {
599
		include_spip('plugins/installer');
600
	}
601
602 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...
603
		$ifnotexists = ' IF NOT EXISTS';
604
	} else {
605
		/* simuler le IF EXISTS - version 2 et sqlite < 3.3a */
606
		$a = spip_sqlite_showtable($table, $serveur);
607
		if (isset($a['key']['KEY ' . $nom])) {
608
			return true;
609
		}
610
	}
611
612
	$query = "CREATE " . ($unique ? "UNIQUE " : "") . "INDEX$ifnotexists $nom ON $table (" . join(',', $champs) . ")";
613
	$res = spip_sqlite_query($query, $serveur, $requeter);
614
	if (!$requeter) {
615
		return $res;
616
	}
617
	if ($res) {
0 ignored issues
show
Coding Style introduced by
The if-else statement can be simplified to return (bool) $res;.
Loading history...
618
		return true;
619
	} else {
620
		return false;
621
	}
622
}
623
624
/**
625
 * Retourne le nombre de lignes d’une ressource de sélection obtenue
626
 * avec `sql_select()`
627
 *
628
 * En PDO/sqlite3, il faut calculer le count par une requete count(*)
629
 * pour les resultats de SELECT
630
 * cela est fait sans spip_sqlite_query()
631
 *
632
 * @param Ressource|Object $r Ressource de résultat
633
 * @param string $serveur Nom de la connexion
634
 * @param bool $requeter Inutilisé
635
 * @return int                 Nombre de lignes
636
 */
637
function spip_sqlite_count($r, $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...
638
	if (!$r) {
639
		return 0;
640
	}
641
642
	if (_sqlite_is_version(3, '', $serveur)) {
643
		// select ou autre (insert, update,...) ?
644
645
		// (link,requete) a compter
646
		if (is_array($r->spipSqliteRowCount)) {
647
			list($link, $query) = $r->spipSqliteRowCount;
648
			// amelioration possible a tester intensivement : pas de order by pour compter !
649
			// $query = preg_replace(",ORDER BY .+(LIMIT\s|HAVING\s|GROUP BY\s|$),Uims","\\1",$query);
650
			$query = "SELECT count(*) as zzzzsqlitecount FROM ($query)";
651
			$l = $link->query($query);
652
			$i = 0;
653
			if ($l and $z = $l->fetch()) {
654
				$i = $z['zzzzsqlitecount'];
655
			}
656
			$r->spipSqliteRowCount = $i;
657
		}
658
		if (isset($r->spipSqliteRowCount)) {
659
			// Ce compte est faux s'il y a des limit dans la requete :(
660
			// il retourne le nombre d'enregistrements sans le limit
661
			return $r->spipSqliteRowCount;
662
		} else {
663
			return $r->rowCount();
664
		}
665
	} else {
666
		return sqlite_num_rows($r);
667
	}
668
}
669
670
671
/**
672
 * Retourne le nombre de lignes d'une sélection
673
 *
674
 * @param array|string $from Tables à consulter (From)
675
 * @param array|string $where Conditions a remplir (Where)
676
 * @param array|string $groupby Critère de regroupement (Group by)
677
 * @param array $having Tableau des des post-conditions à remplir (Having)
678
 * @param string $serveur Nom de la connexion
679
 * @param bool $requeter Exécuter la requête, sinon la retourner
680
 * @return int|bool|string
681
 *     - String Texte de la requête si demandé
682
 *     - int Nombre de lignes
683
 *     - false si la requête a échouée
684
 **/
685
function spip_sqlite_countsel(
686
	$from = array(),
687
	$where = array(),
688
	$groupby = '',
689
	$having = array(),
690
	$serveur = '',
691
	$requeter = true
692
) {
693
	$c = !$groupby ? '*' : ('DISTINCT ' . (is_string($groupby) ? $groupby : join(',', $groupby)));
694
	$r = spip_sqlite_select("COUNT($c)", $from, $where, '', '', '',
695
		$having, $serveur, $requeter);
696
	if ((is_resource($r) or is_object($r)) && $requeter) { // ressource : sqlite2, object : sqlite3
697
		if (_sqlite_is_version(3, '', $serveur)) {
698
			list($r) = spip_sqlite_fetch($r, SPIP_SQLITE3_NUM, $serveur);
699
		} else {
700
			list($r) = spip_sqlite_fetch($r, SPIP_SQLITE2_NUM, $serveur);
701
		}
702
703
	}
704
705
	return $r;
706
}
707
708
709
/**
710
 * Supprime des enregistrements d'une table
711
 *
712
 * @param string $table Nom de la table SQL
713
 * @param string|array $where Conditions à vérifier
714
 * @param string $serveur Nom du connecteur
715
 * @param bool $requeter Exécuter la requête, sinon la retourner
716
 * @return bool|string
717
 *     - int : nombre de suppressions réalisées,
718
 *     - Texte de la requête si demandé,
719
 *     - False en cas d'erreur.
720
 **/
721
function spip_sqlite_delete($table, $where = '', $serveur = '', $requeter = true) {
722
	$res = spip_sqlite_query(
723
		_sqlite_calculer_expression('DELETE FROM', $table, ',')
724
		. _sqlite_calculer_expression('WHERE', $where),
725
		$serveur, $requeter);
726
727
	// renvoyer la requete inerte si demandee
728
	if (!$requeter) {
729
		return $res;
730
	}
731
732
	if ($res) {
733
		$link = _sqlite_link($serveur);
734
		if (_sqlite_is_version(3, $link)) {
735
			return $res->rowCount();
736
		} else {
737
			return sqlite_changes($link);
738
		}
739
	} else {
740
		return false;
741
	}
742
}
743
744
745
/**
746
 * Supprime une table SQL
747
 *
748
 * @param string $table Nom de la table SQL
749
 * @param string $exist True pour ajouter un test d'existence avant de supprimer
750
 * @param string $serveur Nom de la connexion
751
 * @param bool $requeter Exécuter la requête, sinon la retourner
752
 * @return bool|string
753
 *     - string Texte de la requête si demandé
754
 *     - true si la requête a réussie, false sinon
755
 */
756
function spip_sqlite_drop_table($table, $exist = '', $serveur = '', $requeter = true) {
757
	if ($exist) {
758
		$exist = " IF EXISTS";
759
	}
760
761
	/* simuler le IF EXISTS - version 2 */
762 View Code Duplication
	if ($exist && _sqlite_is_version(2, '', $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...
763
		$a = spip_sqlite_showtable($table, $serveur);
764
		if (!$a) {
765
			return true;
766
		}
767
		$exist = '';
768
	}
769
	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...
770
		return true;
771
	} else {
772
		return false;
773
	}
774
}
775
776
777
/**
778
 * Supprime une vue SQL
779
 *
780
 * @param string $view Nom de la vue SQL
781
 * @param string $exist True pour ajouter un test d'existence avant de supprimer
782
 * @param string $serveur Nom de la connexion
783
 * @param bool $requeter Exécuter la requête, sinon la retourner
784
 * @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...
785
 *     - string Texte de la requête si demandé
786
 *     - true si la requête a réussie, false sinon
787
 */
788
function spip_sqlite_drop_view($view, $exist = '', $serveur = '', $requeter = true) {
789
	if ($exist) {
790
		$exist = " IF EXISTS";
791
	}
792
793
	/* simuler le IF EXISTS - version 2 */
794 View Code Duplication
	if ($exist && _sqlite_is_version(2, '', $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...
795
		$a = spip_sqlite_showtable($view, $serveur);
796
		if (!$a) {
797
			return true;
798
		}
799
		$exist = '';
800
	}
801
802
	return spip_sqlite_query("DROP VIEW$exist $view", $serveur, $requeter);
803
}
804
805
/**
806
 * Fonction de suppression d'un INDEX
807
 *
808
 * @param string $nom : nom de l'index
809
 * @param string $table : table sql de l'index
810
 * @param string $serveur : nom de la connexion sql utilisee
811
 * @param bool $requeter : true pour executer la requête ou false pour retourner le texte de la requête
812
 *
813
 * @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...
814
 */
815
function spip_sqlite_drop_index($nom, $table, $serveur = '', $requeter = true) {
816
	if (!($nom or $table)) {
817
		spip_log("Champ manquant pour supprimer un index sqlite ($nom, $table)", 'sqlite.' . _LOG_ERREUR);
818
819
		return false;
820
	}
821
822
	// SQLite ne differentie pas noms des index en fonction des tables
823
	// il faut donc creer des noms uniques d'index pour une base sqlite
824
	$index = $table . '_' . $nom;
825
	$exist = " IF EXISTS";
826
827
	/* simuler le IF EXISTS - version 2 */
828
	if (_sqlite_is_version(2, '', $serveur)) {
829
		$a = spip_sqlite_showtable($table, $serveur);
830
		if (!isset($a['key']['KEY ' . $nom])) {
831
			return true;
832
		}
833
		$exist = '';
834
	}
835
836
	$query = "DROP INDEX$exist $index";
837
838
	return spip_sqlite_query($query, $serveur, $requeter);
839
}
840
841
/**
842
 * Retourne la dernière erreur generée
843
 *
844
 * @uses sql_error_backtrace()
845
 *
846
 * @param string $query
847
 *     Requête qui était exécutée
848
 * @param string $serveur
849
 *     Nom de la connexion
850
 * @return string
851
 *     Erreur eventuelle
852
 **/
853
function spip_sqlite_error($query = '', $serveur = '') {
854
	$link = _sqlite_link($serveur);
855
856 View Code Duplication
	if (_sqlite_is_version(3, $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...
857
		$errs = $link->errorInfo();
858
		/*
859
			$errs[0]
860
				numero SQLState ('HY000' souvent lors d'une erreur)
861
				http://www.easysoft.com/developer/interfaces/odbc/sqlstate_status_return_codes.html
862
			$errs[1]
863
				numéro d'erreur SQLite (souvent 1 lors d'une erreur)
864
				http://www.sqlite.org/c3ref/c_abort.html
865
			$errs[2]
866
				Le texte du message d'erreur
867
		*/
868
		$s = '';
869
		if (ltrim($errs[0], '0')) { // 00000 si pas d'erreur
870
			$s = "$errs[2]";
871
		}
872
	} elseif ($link) {
873
		$s = sqlite_error_string(sqlite_last_error($link));
874
	} else {
875
		$s = ": aucune ressource sqlite (link)";
876
	}
877 View Code Duplication
	if ($s) {
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...
878
		$trace = debug_backtrace();
879
		if ($trace[0]['function'] != "spip_mysql_error") {
880
			spip_log("$s - $query - " . sql_error_backtrace(), 'sqlite.' . _LOG_ERREUR);
881
		}
882
	}
883
884
	return $s;
885
}
886
887
/**
888
 * Retourne le numero de la dernière erreur SQL
889
 *
890
 * Le numéro (en sqlite3/pdo) est un retour ODBC tel que (très souvent) HY000
891
 * http://www.easysoft.com/developer/interfaces/odbc/sqlstate_status_return_codes.html
892
 *
893
 * @param string $serveur
894
 *    nom de la connexion
895
 * @return int|string
896
 *    0 pas d'erreur
897
 *    1 ou autre erreur (en sqlite 2)
898
 *    'HY000/1' : numéro de l'erreur SQLState / numéro d'erreur interne SQLite (en sqlite 3)
899
 **/
900
function spip_sqlite_errno($serveur = '') {
901
	$link = _sqlite_link($serveur);
902
903 View Code Duplication
	if (_sqlite_is_version(3, $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...
904
		$t = $link->errorInfo();
905
		$s = ltrim($t[0], '0'); // 00000 si pas d'erreur
906
		if ($s) {
907
			$s .= ' / ' . $t[1];
908
		} // ajoute l'erreur du moteur SQLite
909
	} elseif ($link) {
910
		$s = sqlite_last_error($link);
911
	} else {
912
		$s = ": aucune ressource sqlite (link)";
913
	}
914
915
	if ($s) {
916
		spip_log("Erreur sqlite $s", 'sqlite.' . _LOG_ERREUR);
917
	}
918
919
	return $s ? $s : 0;
920
}
921
922
923
/**
924
 * Retourne une explication de requête (Explain) SQLite
925
 *
926
 * @param string $query Texte de la requête
927
 * @param string $serveur Nom de la connexion
928
 * @param bool $requeter Exécuter la requête, sinon la retourner
929
 * @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...
930
 *     - array : Tableau de l'explication
931
 *     - string si on retourne le texte de la requête
932
 *     - false si on a pas pu avoir d'explication
933
 */
934
function spip_sqlite_explain($query, $serveur = '', $requeter = true) {
935
	if (strpos(ltrim($query), 'SELECT') !== 0) {
936
		return array();
937
	}
938
939
	$query = spip_sqlite::traduire_requete($query, $serveur);
940
	$query = 'EXPLAIN ' . $query;
941
	if (!$requeter) {
942
		return $query;
943
	}
944
	// on ne trace pas ces requetes, sinon on obtient un tracage sans fin...
945
	$r = spip_sqlite::executer_requete($query, $serveur, false);
946
947
	return $r ? spip_sqlite_fetch($r, null, $serveur) : false; // hum ? etrange ca... a verifier
948
}
949
950
951
/**
952
 * Rècupère une ligne de résultat
953
 *
954
 * Récupère la ligne suivante d'une ressource de résultat
955
 *
956
 * @param Ressource $r Ressource de résultat (issu de sql_select)
957
 * @param string $t Structure de résultat attendu (défaut ASSOC)
958
 * @param string $serveur Nom de la connexion
959
 * @param bool $requeter Inutilisé
960
 * @return array           Ligne de résultat
961
 */
962
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...
963
964
	$link = _sqlite_link($serveur);
965
	$is_v3 = _sqlite_is_version(3, $link);
966
	if (!$t) {
967
		$t = ($is_v3 ? SPIP_SQLITE3_ASSOC : SPIP_SQLITE2_ASSOC);
968
	}
969
970
	$retour = false;
971
	if ($r) {
972
		$retour = ($is_v3 ? $r->fetch($t) : sqlite_fetch_array($r, $t));
973
	}
974
975
	// les version 2 et 3 parfois renvoie des 'table.titre' au lieu de 'titre' tout court ! pff !
976
	// suppression de 'table.' pour toutes les cles (c'est un peu violent !)
977
	// c'est couteux : on ne verifie que la premiere ligne pour voir si on le fait ou non
978
	if ($retour
979
		and strpos(implode('', array_keys($retour)), '.') !== false
980
	) {
981
		foreach ($retour as $cle => $val) {
982
			if (($pos = strpos($cle, '.')) !== false) {
983
				$retour[substr($cle, $pos + 1)] = &$retour[$cle];
984
				unset($retour[$cle]);
985
			}
986
		}
987
	}
988
989
	return $retour;
990
}
991
992
/**
993
 * Place le pointeur de résultat sur la position indiquée
994
 *
995
 * @param Ressource $r Ressource de résultat
996
 * @param int $row_number Position. Déplacer le pointeur à cette ligne
997
 * @param string $serveur Nom de la connexion
998
 * @param bool $requeter Inutilisé
999
 * @return bool True si déplacement réussi, false sinon.
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|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...
1000
 **/
1001
function spip_sqlite_seek($r, $row_number, $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...
1002
	if ($r) {
1003
		$link = _sqlite_link($serveur);
1004
		if (_sqlite_is_version(3, $link)) {
1005
			// encore un truc de bien fichu : PDO ne PEUT PAS faire de seek ou de rewind...
1006
			// je me demande si pour sqlite 3 il ne faudrait pas mieux utiliser
1007
			// les nouvelles fonctions sqlite3_xx (mais encore moins presentes...)
1008
			return false;
1009
		} else {
1010
			return sqlite_seek($r, $row_number);
1011
		}
1012
	}
1013
}
1014
1015
1016
/**
1017
 * Libère une ressource de résultat
1018
 *
1019
 * Indique à SQLite de libérer de sa mémoire la ressoucre de résultat indiquée
1020
 * car on n'a plus besoin de l'utiliser.
1021
 *
1022
 * @param Ressource|Object $r Ressource de résultat
1023
 * @param string $serveur Nom de la connexion
1024
 * @param bool $requeter Inutilisé
1025
 * @return bool                True si réussi
1026
 */
1027
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...
1028
	unset($r);
1029
1030
	return true;
1031
	//return sqlite_free_result($r);
1032
}
1033
1034
1035
/**
1036
 * Teste si le charset indiqué est disponible sur le serveur SQL (aucune action ici)
1037
 *
1038
 * Cette fonction n'a aucune action actuellement
1039
 *
1040
 * @param array|string $charset Nom du charset à tester.
1041
 * @param string $serveur Nom de la connexion
1042
 * @param bool $requeter inutilisé
1043
 * @return void
1044
 */
1045
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...
1046
	//$c = !$charset ? '' : (" LIKE "._q($charset['charset']));
1047
	//return spip_sqlite_fetch(sqlite_query(_sqlite_link($serveur), "SHOW CHARACTER SET$c"), NULL, $serveur);
1048
}
1049
1050
1051
/**
1052
 * Prépare une chaîne hexadécimale
1053
 *
1054
 * Par exemple : FF ==> 255 en SQLite
1055
 *
1056
 * @param string $v
1057
 *     Chaine hexadecimale
1058
 * @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...
1059
 *     Valeur hexadécimale pour SQLite
1060
 **/
1061
function spip_sqlite_hex($v) {
1062
	return hexdec($v);
1063
}
1064
1065
1066
/**
1067
 * Retourne une expression IN pour le gestionnaire de base de données
1068
 *
1069
 * IN (...) est limité à 255 éléments, d'où cette fonction assistante
1070
 *
1071
 * @param string $val
1072
 *     Colonne SQL sur laquelle appliquer le test
1073
 * @param string|array $valeurs
1074
 *     Liste des valeurs possibles (séparés par des virgules si string)
1075
 * @param string $not
1076
 *     - '' sélectionne les éléments correspondant aux valeurs
1077
 *     - 'NOT' inverse en sélectionnant les éléments ne correspondant pas aux valeurs
1078
 * @param string $serveur
1079
 *     Nom du connecteur
1080
 * @param bool $requeter
1081
 *     Inutilisé
1082
 * @return string
1083
 *     Expression de requête SQL
1084
 **/
1085 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...
1086
	$n = $i = 0;
1087
	$in_sql = "";
1088
	while ($n = strpos($valeurs, ',', $n + 1)) {
1089
		if ((++$i) >= 255) {
1090
			$in_sql .= "($val $not IN (" .
1091
				substr($valeurs, 0, $n) .
1092
				"))\n" .
1093
				($not ? "AND\t" : "OR\t");
1094
			$valeurs = substr($valeurs, $n + 1);
1095
			$i = $n = 0;
1096
		}
1097
	}
1098
	$in_sql .= "($val $not IN ($valeurs))";
1099
1100
	return "($in_sql)";
1101
}
1102
1103
1104
/**
1105
 * Insère une ligne dans une table
1106
 *
1107
 * @param string $table
1108
 *     Nom de la table SQL
1109
 * @param string $champs
1110
 *     Liste des colonnes impactées,
1111
 * @param string $valeurs
1112
 *     Liste des valeurs,
1113
 * @param array $desc
1114
 *     Tableau de description des colonnes de la table SQL utilisée
1115
 *     (il sera calculé si nécessaire s'il n'est pas transmis).
1116
 * @param string $serveur
1117
 *     Nom du connecteur
1118
 * @param bool $requeter
1119
 *     Exécuter la requête, sinon la retourner
1120
 * @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...
1121
 *     - int|true identifiant de l'élément inséré (si possible), ou true, si réussite
1122
 *     - Texte de la requête si demandé,
1123
 *     - False en cas d'erreur,
1124
 *     - Tableau de description de la requête et du temps d'exécution, si var_profile activé
1125
 **/
1126
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...
1127
1128
	$query = "INSERT INTO $table " . ($champs ? "$champs VALUES $valeurs" : "DEFAULT VALUES");
1129
	if ($r = spip_sqlite_query($query, $serveur, $requeter)) {
1130
		if (!$requeter) {
1131
			return $r;
1132
		}
1133
		$nb = spip_sqlite::last_insert_id($serveur);
1134
	} else {
1135
		$nb = false;
1136
	}
1137
1138
	$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...
1139
1140
	// cas particulier : ne pas substituer la reponse spip_sqlite_query si on est en profilage
1141
	return isset($_GET['var_profile']) ? $r : $nb;
1142
1143
}
1144
1145
1146
/**
1147
 * Insère une ligne dans une table, en protégeant chaque valeur
1148
 *
1149
 * @param string $table
1150
 *     Nom de la table SQL
1151
 * @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...
1152
 *    Couples (colonne => valeur)
1153
 * @param array $desc
1154
 *     Tableau de description des colonnes de la table SQL utilisée
1155
 *     (il sera calculé si nécessaire s'il n'est pas transmis).
1156
 * @param string $serveur
1157
 *     Nom du connecteur
1158
 * @param bool $requeter
1159
 *     Exécuter la requête, sinon la retourner
1160
 * @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...
1161
 *     - int|true identifiant de l'élément inséré (si possible), ou true, si réussite
1162
 *     - Texte de la requête si demandé,
1163
 *     - False en cas d'erreur,
1164
 *     - Tableau de description de la requête et du temps d'exécution, si var_profile activé
1165
 **/
1166
function spip_sqlite_insertq($table, $couples = array(), $desc = array(), $serveur = '', $requeter = true) {
1167
	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...
1168
		$desc = description_table($table, $serveur);
1169
	}
1170
	if (!$desc) {
1171
		die("$table insertion sans description");
0 ignored issues
show
Coding Style Compatibility introduced by
The function spip_sqlite_insertq() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
1172
	}
1173
	$fields = isset($desc['field']) ? $desc['field'] : array();
1174
1175
	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...
1176
		$couples[$champ] = _sqlite_calculer_cite($val, $fields[$champ]);
1177
	}
1178
1179
	// recherche de champs 'timestamp' pour mise a jour auto de ceux-ci
1180
	$couples = _sqlite_ajouter_champs_timestamp($table, $couples, $desc, $serveur);
1181
1182
	$cles = $valeurs = "";
1183
	if (count($couples)) {
1184
		$cles = "(" . join(',', array_keys($couples)) . ")";
1185
		$valeurs = "(" . join(',', $couples) . ")";
1186
	}
1187
1188
	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 1168 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...
1189
}
1190
1191
1192
/**
1193
 * Insère plusieurs lignes d'un coup dans une table
1194
 *
1195
 * @param string $table
1196
 *     Nom de la table SQL
1197
 * @param array $tab_couples
1198
 *     Tableau de tableaux associatifs (colonne => valeur)
1199
 * @param array $desc
1200
 *     Tableau de description des colonnes de la table SQL utilisée
1201
 *     (il sera calculé si nécessaire s'il n'est pas transmis).
1202
 * @param string $serveur
1203
 *     Nom du connecteur
1204
 * @param bool $requeter
1205
 *     Exécuter la requête, sinon la retourner
1206
 * @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...
1207
 *     - True en cas de succès,
1208
 *     - Texte de la requête si demandé,
1209
 *     - False en cas d'erreur.
1210
 **/
1211
function spip_sqlite_insertq_multi($table, $tab_couples = array(), $desc = array(), $serveur = '', $requeter = true) {
1212
	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...
1213
		$desc = description_table($table, $serveur);
1214
	}
1215
	if (!$desc) {
1216
		die("$table insertion sans description");
0 ignored issues
show
Coding Style Compatibility introduced by
The function spip_sqlite_insertq_multi() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
1217
	}
1218
	if (!isset($desc['field'])) {
1219
		$desc['field'] = array();
1220
	}
1221
1222
	// recuperer les champs 'timestamp' pour mise a jour auto de ceux-ci
1223
	$maj = _sqlite_ajouter_champs_timestamp($table, array(), $desc, $serveur);
1224
1225
	// seul le nom de la table est a traduire ici :
1226
	// le faire une seule fois au debut
1227
	$query_start = "INSERT INTO $table ";
1228
	$query_start = spip_sqlite::traduire_requete($query_start, $serveur);
1229
1230
	// ouvrir une transaction
1231
	if ($requeter) {
1232
		spip_sqlite::demarrer_transaction($serveur);
1233
	}
1234
1235
	while ($couples = array_shift($tab_couples)) {
1236
		foreach ($couples as $champ => $val) {
1237
			$couples[$champ] = _sqlite_calculer_cite($val, $desc['field'][$champ]);
1238
		}
1239
1240
		// inserer les champs timestamp par defaut
1241
		$couples = array_merge($maj, $couples);
1242
1243
		$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...
1244
		if (count($couples)) {
1245
			$champs = "(" . join(',', array_keys($couples)) . ")";
1246
			$valeurs = "(" . join(',', $couples) . ")";
1247
			$query = $query_start . "$champs VALUES $valeurs";
1248
		} else {
1249
			$query = $query_start . "DEFAULT VALUES";
1250
		}
1251
1252
		if ($requeter) {
1253
			$retour = spip_sqlite::executer_requete($query, $serveur);
1254
		}
1255
1256
		// sur le dernier couple uniquement
1257
		if (!count($tab_couples)) {
1258
			$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...
1259
			if ($requeter) {
1260
				$nb = spip_sqlite::last_insert_id($serveur);
1261
			} else {
1262
				return $query;
1263
			}
1264
		}
1265
1266
		$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...
1267
	}
1268
1269
	if ($requeter) {
1270
		spip_sqlite::finir_transaction($serveur);
1271
	}
1272
1273
	// renvoie le dernier id d'autoincrement ajoute
1274
	// cas particulier : ne pas substituer la reponse spip_sqlite_query si on est en profilage
1275
	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...
1276
}
1277
1278
1279
/**
1280
 * Retourne si le moteur SQL préfère utiliser des transactions.
1281
 *
1282
 * @param string $serveur
1283
 *     Nom du connecteur
1284
 * @param bool $requeter
1285
 *     Inutilisé
1286
 * @return bool
1287
 *     Toujours true.
1288
 **/
1289
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...
1290
	return true;
1291
}
1292
1293
/**
1294
 * Démarre une transaction
1295
 *
1296
 * Pratique pour des sql_updateq() dans un foreach,
1297
 * parfois 100* plus rapide s'ils sont nombreux en sqlite !
1298
 *
1299
 * @param string $serveur
1300
 *     Nom du connecteur
1301
 * @param bool $requeter
1302
 *     true pour exécuter la requête ou false pour retourner le texte de la requête
1303
 * @return bool|string
1304
 *     string si texte de la requête demandé, true sinon
1305
 **/
1306
function spip_sqlite_demarrer_transaction($serveur = '', $requeter = true) {
1307
	if (!$requeter) {
1308
		return "BEGIN TRANSACTION";
1309
	}
1310
	spip_sqlite::demarrer_transaction($serveur);
1311
1312
	return true;
1313
}
1314
1315
/**
1316
 * Clôture une transaction
1317
 *
1318
 * @param string $serveur
1319
 *     Nom du connecteur
1320
 * @param bool $requeter
1321
 *     true pour exécuter la requête ou false pour retourner le texte de la requête
1322
 * @return bool|string
1323
 *     string si texte de la requête demandé, true sinon
1324
 **/
1325
function spip_sqlite_terminer_transaction($serveur = '', $requeter = true) {
1326
	if (!$requeter) {
1327
		return "COMMIT";
1328
	}
1329
	spip_sqlite::finir_transaction($serveur);
1330
1331
	return true;
1332
}
1333
1334
1335
/**
1336
 * Liste les bases de données disponibles
1337
 *
1338
 * @param string $serveur
1339
 *     Nom du connecteur
1340
 * @param bool $requeter
1341
 *     Inutilisé
1342
 * @return array
1343
 *     Liste des noms de bases
1344
 **/
1345
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...
1346
	_sqlite_init();
1347
1348
	if (!is_dir($d = substr(_DIR_DB, 0, -1))) {
1349
		return array();
1350
	}
1351
1352
	include_spip('inc/flock');
1353
	$bases = preg_files($d, $pattern = '(.*)\.sqlite$');
1354
	$bds = array();
1355
1356
	foreach ($bases as $b) {
1357
		// pas de bases commencant pas sqlite 
1358
		// (on s'en sert pour l'installation pour simuler la presence d'un serveur)
1359
		// les bases sont de la forme _sqliteX_tmp_spip_install.sqlite
1360
		if (strpos($b, '_sqlite')) {
1361
			continue;
1362
		}
1363
		$bds[] = preg_replace(";.*/$pattern;iS", '$1', $b);
1364
	}
1365
1366
	return $bds;
1367
}
1368
1369
1370
/**
1371
 * Retourne l'instruction SQL pour obtenir le texte d'un champ contenant
1372
 * une balise `<multi>` dans la langue indiquée
1373
 *
1374
 * Cette sélection est mise dans l'alias `multi` (instruction AS multi).
1375
 *
1376
 * @param string $objet Colonne ayant le texte
1377
 * @param string $lang Langue à extraire
1378
 * @return string       Texte de sélection pour la requête
1379
 */
1380
function spip_sqlite_multi($objet, $lang) {
1381
	$r = "EXTRAIRE_MULTI(" . $objet . ", '" . $lang . "') AS multi";
1382
1383
	return $r;
1384
}
1385
1386
1387
/**
1388
 * Optimise une table SQL
1389
 *
1390
 * @note
1391
 *   Sqlite optimise TOUT un fichier sinon rien.
1392
 *   On évite donc 2 traitements sur la même base dans un hit.
1393
 *
1394
 * @param $table nom de la table a optimiser
1395
 * @param $serveur nom de la connexion
1396
 * @param $requeter effectuer la requete ? sinon retourner son code
1397
 * @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...
1398
 **/
1399
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...
1400
	static $do = false;
1401
	if ($requeter and $do) {
1402
		return true;
1403
	}
1404
	if ($requeter) {
1405
		$do = true;
1406
	}
1407
1408
	return spip_sqlite_query("VACUUM", $serveur, $requeter);
1409
}
1410
1411
1412
/**
1413
 * Échapper une valeur selon son type
1414
 * mais pour SQLite avec ses spécificités
1415
 *
1416
 * @param string|array|number $v
1417
 *     Texte, nombre ou tableau à échapper
1418
 * @param string $type
1419
 *     Description du type attendu
1420
 *    (par exemple description SQL de la colonne recevant la donnée)
1421
 * @return string|number
1422
 *    Donnée prête à être utilisée par le gestionnaire SQL
1423
 */
1424 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...
1425
	if (!is_array($v)) {
1426
		return _sqlite_calculer_cite($v, $type);
1427
	}
1428
	// si c'est un tableau, le parcourir en propageant le type
1429
	foreach ($v as $k => $r) {
1430
		$v[$k] = spip_sqlite_quote($r, $type);
1431
	}
1432
1433
	return join(",", $v);
1434
}
1435
1436
1437
/**
1438
 * Tester si une date est proche de la valeur d'un champ
1439
 *
1440
 * @param string $champ
1441
 *     Nom du champ a tester
1442
 * @param int $interval
1443
 *     Valeur de l'intervalle : -1, 4, ...
1444
 * @param string $unite
1445
 *     Utité utilisée (DAY, MONTH, YEAR, ...)
1446
 * @return string
1447
 *     Expression SQL
1448
 **/
1449
function spip_sqlite_date_proche($champ, $interval, $unite) {
1450
	$op = (($interval <= 0) ? '>' : '<');
1451
1452
	return "($champ $op datetime('" . date("Y-m-d H:i:s") . "', '$interval $unite'))";
1453
}
1454
1455
1456
/**
1457
 * Répare une table SQL
1458
 *
1459
 * Il n'y a pas de fonction native repair dans sqlite, mais on profite
1460
 * pour vérifier que tous les champs (text|char) ont bien une clause DEFAULT
1461
 *
1462
 * @param string $table Nom de la table SQL
1463
 * @param string $serveur Nom de la connexion
1464
 * @param bool $requeter Exécuter la requête, sinon la retourner
1465
 * @return string[]
1466
 *     Tableau avec clé 0 pouvant avoir " OK " ou " ERROR " indiquant
1467
 *     l'état de la table après la réparation
1468
 */
1469
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...
1470
	if ($desc = spip_sqlite_showtable($table, $serveur)
1471
		and isset($desc['field'])
1472
		and is_array($desc['field'])
1473
	) {
1474
		foreach ($desc['field'] as $c => $d) {
1475 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...
1476
				and stripos($d, "NOT NULL") !== false
1477
				and stripos($d, "DEFAULT") === false
1478
				/* pas touche aux cles primaires */
1479
				and (!isset($desc['key']['PRIMARY KEY']) or $desc['key']['PRIMARY KEY'] !== $c)
1480
			) {
1481
				spip_sqlite_alter($q = "TABLE $table CHANGE $c $c $d DEFAULT ''", $serveur);
1482
				spip_log("ALTER $q", "repair" . _LOG_INFO_IMPORTANTE);
1483
			}
1484 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...
1485
				and stripos($d, "NOT NULL") !== false
1486
				and stripos($d, "DEFAULT") === false
1487
				/* pas touche aux cles primaires */
1488
				and (!isset($desc['key']['PRIMARY KEY']) or $desc['key']['PRIMARY KEY'] !== $c)
1489
			) {
1490
				spip_sqlite_alter($q = "TABLE $table CHANGE $c $c $d DEFAULT '0'", $serveur);
1491
				spip_log("ALTER $q", "repair" . _LOG_INFO_IMPORTANTE);
1492
			}
1493 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...
1494
				and stripos($d, "NOT NULL") !== false
1495
				and stripos($d, "DEFAULT") === false
1496
				/* pas touche aux cles primaires */
1497
				and (!isset($desc['key']['PRIMARY KEY']) or $desc['key']['PRIMARY KEY'] !== $c)
1498
			) {
1499
				spip_sqlite_alter($q = "TABLE $table CHANGE $c $c $d DEFAULT '0000-00-00 00:00:00'", $serveur);
1500
				spip_log("ALTER $q", "repair" . _LOG_INFO_IMPORTANTE);
1501
			}
1502
		}
1503
1504
		return array(" OK ");
1505
	}
1506
1507
	return array(" ERROR ");
1508
}
1509
1510
1511
/**
1512
 * Insère où met à jour une entrée d’une table SQL
1513
 *
1514
 * La clé ou les cles primaires doivent être présentes dans les données insérés.
1515
 * La fonction effectue une protection automatique des données.
1516
 *
1517
 * Préférer à cette fonction updateq ou insertq.
1518
 *
1519
 * @param string $table
1520
 *     Nom de la table SQL
1521
 * @param array $couples
1522
 *     Couples colonne / valeur à modifier,
1523
 * @param array $desc
1524
 *     Tableau de description des colonnes de la table SQL utilisée
1525
 *     (il sera calculé si nécessaire s'il n'est pas transmis).
1526
 * @param string $serveur
1527
 *     Nom du connecteur
1528
 * @param bool $requeter
1529
 *     Exécuter la requête, sinon la retourner
1530
 * @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...
1531
 *     - true si réussite
1532
 *     - Texte de la requête si demandé,
1533
 *     - False en cas d'erreur.
1534
 **/
1535
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...
1536
	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...
1537
		$desc = description_table($table, $serveur);
1538
	}
1539
	if (!$desc) {
1540
		die("$table insertion sans description");
0 ignored issues
show
Coding Style Compatibility introduced by
The function spip_sqlite_replace() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
1541
	}
1542
	$fields = isset($desc['field']) ? $desc['field'] : array();
1543
1544
	foreach ($couples as $champ => $val) {
1545
		$couples[$champ] = _sqlite_calculer_cite($val, $fields[$champ]);
1546
	}
1547
1548
	// recherche de champs 'timestamp' pour mise a jour auto de ceux-ci
1549
	$couples = _sqlite_ajouter_champs_timestamp($table, $couples, $desc, $serveur);
1550
1551
	return spip_sqlite_query("REPLACE INTO $table (" . join(',', array_keys($couples)) . ') VALUES (' . join(',',
1552
			$couples) . ')', $serveur);
1553
}
1554
1555
1556
/**
1557
 * Insère où met à jour des entrées d’une table SQL
1558
 *
1559
 * La clé ou les cles primaires doivent être présentes dans les données insérés.
1560
 * La fonction effectue une protection automatique des données.
1561
 *
1562
 * Préférez insertq_multi et sql_updateq
1563
 *
1564
 * @param string $table
1565
 *     Nom de la table SQL
1566
 * @param array $tab_couples
1567
 *     Tableau de tableau (colonne / valeur à modifier),
1568
 * @param array $desc
1569
 *     Tableau de description des colonnes de la table SQL utilisée
1570
 *     (il sera calculé si nécessaire s'il n'est pas transmis).
1571
 * @param string $serveur
1572
 *     Nom du connecteur
1573
 * @param bool $requeter
1574
 *     Exécuter la requête, sinon la retourner
1575
 * @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...
1576
 *     - true si réussite
1577
 *     - Texte de la requête si demandé,
1578
 *     - False en cas d'erreur.
1579
 **/
1580
function spip_sqlite_replace_multi($table, $tab_couples, $desc = array(), $serveur = '', $requeter = true) {
1581
1582
	// boucler pour trainter chaque requete independemment
1583
	foreach ($tab_couples as $couples) {
1584
		$retour = spip_sqlite_replace($table, $couples, $desc, $serveur, $requeter);
1585
	}
1586
1587
	// renvoie le dernier id
1588
	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...
1589
}
1590
1591
1592
/**
1593
 * Exécute une requête de sélection avec SQLite
1594
 *
1595
 * Instance de sql_select (voir ses specs).
1596
 *
1597
 * @see sql_select()
1598
 *
1599
 * @param string|array $select Champs sélectionnés
1600
 * @param string|array $from Tables sélectionnées
1601
 * @param string|array $where Contraintes
1602
 * @param string|array $groupby Regroupements
1603
 * @param string|array $orderby Tris
1604
 * @param string $limit Limites de résultats
1605
 * @param string|array $having Contraintes posts sélections
1606
 * @param string $serveur Nom de la connexion
1607
 * @param bool $requeter Exécuter la requête, sinon la retourner
1608
 * @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...
1609
 *     - string : Texte de la requête si on ne l'exécute pas
1610
 *     - ressource si requête exécutée, ressource pour fetch()
1611
 *     - false si la requête exécutée a ratée
1612
 *     - array  : Tableau décrivant requête et temps d'exécution si var_profile actif pour tracer.
1613
 */
1614
function spip_sqlite_select(
1615
	$select,
1616
	$from,
1617
	$where = '',
1618
	$groupby = '',
1619
	$orderby = '',
1620
	$limit = '',
1621
	$having = '',
1622
	$serveur = '',
1623
	$requeter = true
1624
) {
1625
1626
	// version() n'est pas connu de sqlite
1627
	$select = str_replace('version()', 'sqlite_version()', $select);
1628
1629
	// recomposer from
1630
	$from = (!is_array($from) ? $from : _sqlite_calculer_select_as($from));
1631
1632
	$query =
1633
		_sqlite_calculer_expression('SELECT', $select, ', ')
1634
		. _sqlite_calculer_expression('FROM', $from, ', ')
1635
		. _sqlite_calculer_expression('WHERE', $where)
1636
		. _sqlite_calculer_expression('GROUP BY', $groupby, ',')
1637
		. _sqlite_calculer_expression('HAVING', $having)
1638
		. ($orderby ? ("\nORDER BY " . _sqlite_calculer_order($orderby)) : '')
1639
		. ($limit ? "\nLIMIT $limit" : '');
1640
1641
	// dans un select, on doit renvoyer la requête en cas d'erreur
1642
	$res = spip_sqlite_query($query, $serveur, $requeter);
1643
	// texte de la requete demande ?
1644
	if (!$requeter) {
1645
		return $res;
1646
	}
1647
	// erreur survenue ?
1648
	if ($res === false) {
1649
		return spip_sqlite::traduire_requete($query, $serveur);
1650
	}
1651
1652
	return $res;
1653
}
1654
1655
1656
/**
1657
 * Sélectionne un fichier de base de données
1658
 *
1659
 * @param string $db
1660
 *     Nom de la base à utiliser
1661
 * @param string $serveur
1662
 *     Nom du connecteur
1663
 * @param bool $requeter
1664
 *     Inutilisé
1665
 *
1666
 * @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...
1667
 *     - Nom de la base en cas de success.
1668
 *     - False en cas d'erreur.
1669
 **/
1670
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...
1671
	_sqlite_init();
1672
1673
	// interdire la creation d'une nouvelle base, 
1674
	// sauf si on est dans l'installation
1675
	if (!is_file($f = _DIR_DB . $db . '.sqlite')
1676
		&& (!defined('_ECRIRE_INSTALL') || !_ECRIRE_INSTALL)
1677
	) {
1678
		spip_log("Il est interdit de creer la base $db", 'sqlite.' . _LOG_HS);
1679
1680
		return false;
1681
	}
1682
1683
	// se connecter a la base indiquee
1684
	// avec les identifiants connus
1685
	$index = $serveur ? $serveur : 0;
1686
1687
	if ($link = spip_connect_db('', '', '', '', '@selectdb@' . $db, $serveur, '', '')) {
1688 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...
1689
			return $db;
1690
		}
1691
	} else {
1692
		spip_log("Impossible de selectionner la base $db", 'sqlite.' . _LOG_HS);
1693
1694
		return false;
1695
	}
1696
1697
}
1698
1699
1700
/**
1701
 * Définit un charset pour la connexion avec SQLite (aucune action ici)
1702
 *
1703
 * Cette fonction n'a aucune action actuellement.
1704
 *
1705
 * @param string $charset Charset à appliquer
1706
 * @param string $serveur Nom de la connexion
1707
 * @param bool $requeter inutilisé
1708
 * @return void
1709
 */
1710
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...
1711
	# spip_log("Gestion charset sql a ecrire : "."SET NAMES "._q($charset), 'sqlite.'._LOG_ERREUR);
1712
	# return spip_sqlite_query("SET NAMES ". spip_sqlite_quote($charset), $serveur); //<-- Passe pas !
1713
}
1714
1715
1716
/**
1717
 * Retourne une ressource de la liste des tables de la base de données
1718
 *
1719
 * @param string $match
1720
 *     Filtre sur tables à récupérer
1721
 * @param string $serveur
1722
 *     Connecteur de la base
1723
 * @param bool $requeter
1724
 *     true pour éxecuter la requête
1725
 *     false pour retourner le texte de la requête.
1726
 * @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...
1727
 *     Ressource à utiliser avec sql_fetch()
1728
 **/
1729
function spip_sqlite_showbase($match, $serveur = '', $requeter = true) {
1730
	// type est le type d'entrée : table / index / view
1731
	// on ne retourne que les tables (?) et non les vues...
1732
	# ESCAPE non supporte par les versions sqlite <3
1733
	#	return spip_sqlite_query("SELECT name FROM sqlite_master WHERE type='table' AND tbl_name LIKE "._q($match)." ESCAPE '\'", $serveur, $requeter);
1734
	$match = preg_quote($match);
1735
	$match = str_replace("\\\_", "[[TIRETBAS]]", $match);
1736
	$match = str_replace("\\\%", "[[POURCENT]]", $match);
1737
	$match = str_replace("_", ".", $match);
1738
	$match = str_replace("%", ".*", $match);
1739
	$match = str_replace("[[TIRETBAS]]", "_", $match);
1740
	$match = str_replace("[[POURCENT]]", "%", $match);
1741
	$match = "^$match$";
1742
1743
	return spip_sqlite_query("SELECT name FROM sqlite_master WHERE type='table' AND tbl_name REGEXP " . _q($match),
1744
		$serveur, $requeter);
1745
}
1746
1747
define('_SQLITE_RE_SHOW_TABLE', '/^[^(),]*\(((?:[^()]*\((?:[^()]*\([^()]*\))?[^()]*\)[^()]*)*[^()]*)\)[^()]*$/');
1748
/**
1749
 * Obtient la description d'une table ou vue SQLite
1750
 *
1751
 * Récupère la définition d'une table ou d'une vue avec colonnes, indexes, etc.
1752
 * au même format que la définition des tables SPIP, c'est à dire
1753
 * un tableau avec les clés
1754
 *
1755
 * - `field` (tableau colonne => description SQL) et
1756
 * - `key` (tableau type de clé => colonnes)
1757
 *
1758
 * @param string $nom_table Nom de la table SQL
1759
 * @param string $serveur Nom de la connexion
1760
 * @param bool $requeter Exécuter la requête, sinon la retourner
1761
 * @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...
1762
 *     - chaîne vide si pas de description obtenue
1763
 *     - string Texte de la requête si demandé
1764
 *     - array description de la table sinon
1765
 */
1766
function spip_sqlite_showtable($nom_table, $serveur = '', $requeter = true) {
1767
	$query =
1768
		'SELECT sql, type FROM'
1769
		. ' (SELECT * FROM sqlite_master UNION ALL'
1770
		. ' SELECT * FROM sqlite_temp_master)'
1771
		. " WHERE tbl_name LIKE '$nom_table'"
1772
		. " AND type!='meta' AND sql NOT NULL AND name NOT LIKE 'sqlite_%'"
1773
		. ' ORDER BY substr(type,2,1), name';
1774
1775
	$a = spip_sqlite_query($query, $serveur, $requeter);
1776
	if (!$a) {
1777
		return "";
1778
	}
1779
	if (!$requeter) {
1780
		return $a;
1781
	}
1782
	if (!($a = spip_sqlite_fetch($a, null, $serveur))) {
1783
		return "";
1784
	}
1785
	$vue = ($a['type'] == 'view'); // table | vue
1786
1787
	// c'est une table
1788
	// il faut parser le create
1789
	if (!$vue) {
1790
		if (!preg_match(_SQLITE_RE_SHOW_TABLE, array_shift($a), $r)) {
1791
			return "";
1792
		} else {
1793
			$desc = $r[1];
1794
			// extraction d'une KEY éventuelle en prenant garde de ne pas
1795
			// relever un champ dont le nom contient KEY (ex. ID_WHISKEY)
1796 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...
1797
				$namedkeys = $r[2];
1798
				$desc = $r[1];
1799
			} else {
1800
				$namedkeys = "";
1801
			}
1802
1803
			$fields = array();
1804
			$keys = array();
1805
1806
			// enlever les contenus des valeurs DEFAULT 'xxx' qui pourraient perturber
1807
			// par exemple s'il contiennent une virgule.
1808
			// /!\ cela peut aussi echapper le nom des champs si la table a eu des operations avec SQLite Manager !
1809
			list($desc, $echaps) = query_echappe_textes($desc);
1810
1811
			// separer toutes les descriptions de champs, separes par des virgules
1812
			# /!\ explode peut exploser aussi DECIMAL(10,2) !
1813
			$k_precedent = null;
1814
			foreach (explode(",", $desc) as $v) {
1815
1816
				preg_match("/^\s*([^\s]+)\s+(.*)/", $v, $r);
1817
				// Les cles de champs peuvent etre entourees
1818
				// de guillements doubles " , simples ', graves ` ou de crochets [ ],  ou rien.
1819
				// http://www.sqlite.org/lang_keywords.html
1820
				$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...
1821
				if ($char = strpbrk($k[0], '\'"[`')) {
1822
					$k = trim($k, $char);
1823
					if ($char == '[') {
1824
						$k = rtrim($k, ']');
1825
					}
1826
				}
1827
				$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...
1828
1829
				# rustine pour DECIMAL(10,2)
1830
				if (false !== strpos($k, ')')) {
1831
					$fields[$k_precedent] .= ',' . $k . ' ' . $def;
1832
					continue;
1833
				}
1834
1835
				// la primary key peut etre dans une des descriptions de champs
1836
				// et non en fin de table, cas encore decouvert avec Sqlite Manager
1837
				if (stripos($r[2], 'PRIMARY KEY') !== false) {
1838
					$keys['PRIMARY KEY'] = $k;
1839
				}
1840
1841
				$fields[$k] = $def;
1842
				$k_precedent = $k;
1843
			}
1844
			// key inclues dans la requete
1845 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...
1846
				if (preg_match("/^\s*([^(]*)\(([^(]*(\(\d+\))?)$/", $v, $r)) {
1847
					$k = str_replace("`", '', trim($r[1]));
1848
					$t = trim(strtolower(str_replace("`", '', $r[2])), '"');
1849
					if ($k && !isset($keys[$k])) {
1850
						$keys[$k] = $t;
1851
					} else {
1852
						$keys[] = $t;
1853
					}
1854
				}
1855
			}
1856
			// sinon ajouter les key index
1857
			$query =
1858
				'SELECT name,sql FROM'
1859
				. ' (SELECT * FROM sqlite_master UNION ALL'
1860
				. ' SELECT * FROM sqlite_temp_master)'
1861
				. " WHERE tbl_name LIKE '$nom_table'"
1862
				. " AND type='index' AND name NOT LIKE 'sqlite_%'"
1863
				. 'ORDER BY substr(type,2,1), name';
1864
			$a = spip_sqlite_query($query, $serveur, $requeter);
1865
			while ($r = spip_sqlite_fetch($a, null, $serveur)) {
1866
				$key = str_replace($nom_table . '_', '', $r['name']); // enlever le nom de la table ajoute a l'index
1867
				$keytype = "KEY";
1868
				if (strpos($r['sql'], "UNIQUE INDEX") !== false) {
1869
					$keytype = "UNIQUE KEY";
1870
				}
1871
				$colonnes = preg_replace(',.*\((.*)\).*,', '$1', $r['sql']);
1872
				$keys[$keytype . ' ' . $key] = $colonnes;
1873
			}
1874
		}
1875
1876
	} // c'est une vue, on liste les champs disponibles simplement
1877
	else {
1878
		if ($res = sql_fetsel('*', $nom_table, '', '', '', '1', '', $serveur)) { // limit 1
1879
			$fields = array();
1880
			foreach ($res as $c => $v) {
1881
				$fields[$c] = '';
1882
			}
1883
			$keys = array();
1884
		} else {
1885
			return "";
1886
		}
1887
	}
1888
1889
	return array('field' => $fields, 'key' => $keys);
1890
1891
}
1892
1893
1894
/**
1895
 * Met à jour des enregistrements d'une table SQL
1896
 *
1897
 * @param string $table
1898
 *     Nom de la table
1899
 * @param array $champs
1900
 *     Couples (colonne => valeur)
1901
 * @param string|array $where
1902
 *     Conditions a remplir (Where)
1903
 * @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...
1904
 *     Tableau de description des colonnes de la table SQL utilisée
1905
 *     (il sera calculé si nécessaire s'il n'est pas transmis).
1906
 * @param string $serveur
1907
 *     Nom de la connexion
1908
 * @param bool $requeter
1909
 *     Exécuter la requête, sinon la retourner
1910
 * @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...
1911
 *     - string : texte de la requête si demandé
1912
 *     - true si la requête a réussie, false sinon
1913
 *     - array Tableau décrivant la requête et son temps d'exécution si var_profile est actif
1914
 */
1915
function spip_sqlite_update($table, $champs, $where = '', $desc = '', $serveur = '', $requeter = true) {
1916
	// recherche de champs 'timestamp' pour mise a jour auto de ceux-ci
1917
	$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 1915 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...
1918
1919
	$set = array();
1920
	foreach ($champs as $champ => $val) {
1921
		$set[] = $champ . "=$val";
1922
	}
1923 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...
1924
		return spip_sqlite_query(
1925
			_sqlite_calculer_expression('UPDATE', $table, ',')
1926
			. _sqlite_calculer_expression('SET', $set, ',')
1927
			. _sqlite_calculer_expression('WHERE', $where),
1928
			$serveur, $requeter);
1929
	}
1930
}
1931
1932
1933
/**
1934
 * Met à jour des enregistrements d'une table SQL et protège chaque valeur
1935
 *
1936
 * Protège chaque valeur transmise avec sql_quote(), adapté au type
1937
 * de champ attendu par la table SQL
1938
 *
1939
 * @param string $table
1940
 *     Nom de la table
1941
 * @param array $champs
1942
 *     Couples (colonne => valeur)
1943
 * @param string|array $where
1944
 *     Conditions a remplir (Where)
1945
 * @param array $desc
1946
 *     Tableau de description des colonnes de la table SQL utilisée
1947
 *     (il sera calculé si nécessaire s'il n'est pas transmis).
1948
 * @param string $serveur
1949
 *     Nom de la connexion
1950
 * @param bool $requeter
1951
 *     Exécuter la requête, sinon la retourner
1952
 * @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...
1953
 *     - string : texte de la requête si demandé
1954
 *     - true si la requête a réussie, false sinon
1955
 *     - array Tableau décrivant la requête et son temps d'exécution si var_profile est actif
1956
 */
1957
function spip_sqlite_updateq($table, $champs, $where = '', $desc = array(), $serveur = '', $requeter = true) {
1958
1959
	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...
1960
		return;
1961
	}
1962
	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...
1963
		$desc = description_table($table, $serveur);
1964
	}
1965
	if (!$desc) {
1966
		die("$table insertion sans description");
0 ignored issues
show
Coding Style Compatibility introduced by
The function spip_sqlite_updateq() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
1967
	}
1968
	$fields = $desc['field'];
1969
1970
	// recherche de champs 'timestamp' pour mise a jour auto de ceux-ci
1971
	$champs = _sqlite_ajouter_champs_timestamp($table, $champs, $desc, $serveur);
1972
1973
	$set = array();
1974
	foreach ($champs as $champ => $val) {
1975
		$set[] = $champ . '=' . _sqlite_calculer_cite($val, isset($fields[$champ]) ? $fields[$champ] : '');
1976
	}
1977
1978
	return spip_sqlite_query(
1979
		_sqlite_calculer_expression('UPDATE', $table, ',')
1980
		. _sqlite_calculer_expression('SET', $set, ',')
1981
		. _sqlite_calculer_expression('WHERE', $where),
1982
		$serveur, $requeter);
1983
}
1984
1985
1986
/*
1987
 * 
1988
 * Ensuite les fonctions non abstraites
1989
 * crees pour l'occasion de sqlite
1990
 * 
1991
 */
1992
1993
1994
/**
1995
 * Initialise la première connexion à un serveur SQLite
1996
 *
1997
 * @return void
1998
 */
1999
function _sqlite_init() {
2000
	if (!defined('_DIR_DB')) {
2001
		define('_DIR_DB', _DIR_ETC . 'bases/');
2002
	}
2003
	if (!defined('_SQLITE_CHMOD')) {
2004
		define('_SQLITE_CHMOD', _SPIP_CHMOD);
2005
	}
2006
2007
	if (!is_dir($d = _DIR_DB)) {
2008
		include_spip('inc/flock');
2009
		sous_repertoire($d);
2010
	}
2011
}
2012
2013
2014
/**
2015
 * Teste la version sqlite du link en cours
2016
 *
2017
 * @param string $version
2018
 * @param string $link
2019
 * @param string $serveur
2020
 * @param bool $requeter
2021
 * @return bool|int
2022
 */
2023
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...
2024
	if ($link === '') {
2025
		$link = _sqlite_link($serveur);
2026
	}
2027
	if (!$link) {
2028
		return false;
2029
	}
2030
	if ($link instanceof PDO) {
2031
		$v = 3;
2032
	} else {
2033
		$v = 2;
2034
	}
2035
2036
	if (!$version) {
2037
		return $v;
2038
	}
2039
2040
	return ($version == $v);
2041
}
2042
2043
2044
/**
2045
 * Retrouver un link d'une connexion SQLite
2046
 *
2047
 * @param string $serveur Nom du serveur
2048
 * @return Object Information de connexion pour SQLite
2049
 */
2050
function _sqlite_link($serveur = '') {
2051
	$link = &$GLOBALS['connexions'][$serveur ? $serveur : 0]['link'];
2052
2053
	return $link;
2054
}
2055
2056
2057
/* ordre alphabetique pour les autres */
2058
2059
2060
/**
2061
 * Renvoie les bons echappements (mais pas sur les fonctions comme NOW())
2062
 *
2063
 * @param string|number $v Texte ou nombre à échapper
2064
 * @param string $type Type de donnée attendue, description SQL de la colonne de destination
2065
 * @return string|number     Texte ou nombre échappé
2066
 */
2067
function _sqlite_calculer_cite($v, $type) {
2068
	if ($type) {
2069
		if (is_null($v)
2070
			and stripos($type, "NOT NULL") === false
2071
		) {
2072
			return 'NULL';
2073
		} // null php se traduit en NULL SQL
2074
2075
		if (sql_test_date($type) and preg_match('/^\w+\(/', $v)) {
2076
			return $v;
2077
		}
2078
		if (sql_test_int($type)) {
2079
			if (is_numeric($v)) {
2080
				return $v;
2081
			} elseif (ctype_xdigit(substr($v, 2)) and strncmp($v, '0x', 2) == 0) {
2082
				return hexdec(substr($v, 2));
2083
			} else {
2084
				return intval($v);
2085
			}
2086
		}
2087
	} else {
2088
		// si on ne connait pas le type on le deduit de $v autant que possible
2089
		if (is_numeric($v)) {
2090
			return strval($v);
2091
		}
2092
	}
2093
2094
	if (function_exists('sqlite_escape_string')) {
2095
		return "'" . sqlite_escape_string($v) . "'";
2096
	}
2097
2098
	// trouver un link sqlite3 pour faire l'echappement
2099
	foreach ($GLOBALS['connexions'] as $s) {
2100
		if (_sqlite_is_version(3, $l = $s['link'])) {
2101
			return $l->quote($v);
2102
		}
2103
	}
2104
2105
	// echapper les ' en ''
2106
	spip_log("Pas de methode sqlite_escape_string ni ->quote pour echapper", "sqlite." . _LOG_INFO_IMPORTANTE);
2107
2108
	return ("'" . str_replace("'", "''", $v) . "'");
2109
}
2110
2111
2112
/**
2113
 * Calcule un expression pour une requête, en cumulant chaque élément
2114
 * avec l'opérateur de liaison ($join) indiqué
2115
 *
2116
 * Renvoie grosso modo "$expression join($join, $v)"
2117
 *
2118
 * @param string $expression Mot clé de l'expression, tel que "WHERE" ou "ORDER BY"
2119
 * @param array|string $v Données de l'expression
2120
 * @param string $join Si les données sont un tableau, elles seront groupées par cette jointure
2121
 * @return string            Texte de l'expression, une partie donc, du texte la requête.
2122
 */
2123 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...
2124
	if (empty($v)) {
2125
		return '';
2126
	}
2127
2128
	$exp = "\n$expression ";
2129
2130
	if (!is_array($v)) {
2131
		return $exp . $v;
2132
	} else {
2133
		if (strtoupper($join) === 'AND') {
2134
			return $exp . join("\n\t$join ", array_map('_sqlite_calculer_where', $v));
2135
		} else {
2136
			return $exp . join($join, $v);
2137
		}
2138
	}
2139
}
2140
2141
2142
/**
2143
 * Prépare une clause order by
2144
 *
2145
 * Regroupe en texte les éléments si un tableau est donné
2146
 *
2147
 * @note
2148
 *   Pas besoin de conversion pour 0+x comme il faudrait pour mysql.
2149
 *
2150
 * @param string|array $orderby Texte du orderby à préparer
2151
 * @return string Texte du orderby préparé
2152
 */
2153
function _sqlite_calculer_order($orderby) {
2154
	return (is_array($orderby)) ? join(", ", $orderby) : $orderby;
2155
}
2156
2157
2158
/**
2159
 * Renvoie des `nom AS alias`
2160
 *
2161
 * @param array $args
2162
 * @return string Sélection de colonnes pour une clause SELECT
2163
 */
2164 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...
2165
	$res = '';
2166
	foreach ($args as $k => $v) {
2167
		if (substr($k, -1) == '@') {
2168
			// c'est une jointure qui se refere au from precedent
2169
			// pas de virgule
2170
			$res .= '  ' . $v;
2171
		} else {
2172
			if (!is_numeric($k)) {
2173
				$p = strpos($v, " ");
2174
				if ($p) {
2175
					$v = substr($v, 0, $p) . " AS '$k'" . substr($v, $p);
2176
				} else {
2177
					$v .= " AS '$k'";
2178
				}
2179
			}
2180
			$res .= ', ' . $v;
2181
		}
2182
	}
2183
2184
	return substr($res, 2);
2185
}
2186
2187
2188
/**
2189
 * Prépare une clause WHERE pour SQLite
2190
 *
2191
 * Retourne une chaîne avec les bonnes parenthèses pour la
2192
 * contrainte indiquée, au format donnée par le compilateur
2193
 *
2194
 * @param array|string $v
2195
 *     Description des contraintes
2196
 *     - string : Texte du where
2197
 *     - sinon tableau : A et B peuvent être de type string ou array,
2198
 *       OP et C sont de type string :
2199
 *       - array(A) : A est le texte du where
2200
 *       - array(OP, A) : contrainte OP( A )
2201
 *       - array(OP, A, B) : contrainte (A OP B)
2202
 *       - array(OP, A, B, C) : contrainte (A OP (B) : C)
2203
 * @return string
2204
 *     Contrainte pour clause WHERE
2205
 */
2206 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...
2207
	if (!is_array($v)) {
2208
		return $v;
2209
	}
2210
2211
	$op = array_shift($v);
2212
	if (!($n = count($v))) {
2213
		return $op;
2214
	} else {
2215
		$arg = _sqlite_calculer_where(array_shift($v));
2216
		if ($n == 1) {
2217
			return "$op($arg)";
2218
		} else {
2219
			$arg2 = _sqlite_calculer_where(array_shift($v));
2220
			if ($n == 2) {
2221
				return "($arg $op $arg2)";
2222
			} else {
2223
				return "($arg $op ($arg2) : $v[0])";
2224
			}
2225
		}
2226
	}
2227
}
2228
2229
2230
/**
2231
 * Charger les modules SQLite
2232
 *
2233
 * Si possible et juste la version demandée,
2234
 * ou, si aucune version, on renvoie les versions sqlite disponibles
2235
 * sur ce serveur dans un tableau
2236
 *
2237
 * @param string $version
2238
 * @return array|bool
2239
 */
2240
function _sqlite_charger_version($version = '') {
2241
	$versions = array();
2242
2243
	// version 2
2244
	if (!$version || $version == 2) {
2245
		if (extension_loaded('sqlite')) {
2246
			$versions[] = 2;
2247
		}
2248
	}
2249
2250
	// version 3
2251
	if (!$version || $version == 3) {
2252
		if (extension_loaded('pdo') && extension_loaded('pdo_sqlite')) {
2253
			$versions[] = 3;
2254
		}
2255
	}
2256
	if ($version) {
2257
		return in_array($version, $versions);
2258
	}
2259
2260
	return $versions;
2261
}
2262
2263
2264
/**
2265
 * Gestion des requêtes ALTER non reconnues de SQLite
2266
 *
2267
 * Requêtes non reconnues :
2268
 *
2269
 *     ALTER TABLE table DROP column
2270
 *     ALTER TABLE table CHANGE [COLUMN] columnA columnB definition
2271
 *     ALTER TABLE table MODIFY column definition
2272
 *     ALTER TABLE table ADD|DROP PRIMARY KEY
2273
 *
2274
 * `MODIFY` est transformé en `CHANGE columnA columnA` par spip_sqlite_alter()
2275
 *
2276
 * 1) Créer une table B avec le nouveau format souhaité
2277
 * 2) Copier la table d'origine A vers B
2278
 * 3) Supprimer la table A
2279
 * 4) Renommer la table B en A
2280
 * 5) Remettre les index (qui sont supprimés avec la table A)
2281
 *
2282
 * @param string|array $table
2283
 *     - string : Nom de la table table,
2284
 *     - array : couple (nom de la table => nom futur)
2285
 * @param string|array $colonne
2286
 *     - string : nom de la colonne,
2287
 *     - array : couple (nom de la colonne => nom futur)
2288
 * @param array $opt
2289
 *     options comme les tables SPIP, qui sera mergé à la table créee :
2290
 *     `array('field'=>array('nom'=>'syntaxe', ...), 'key'=>array('KEY nom'=>'colonne', ...))`
2291
 * @param string $serveur
2292
 *     Nom de la connexion SQL en cours
2293
 * @return bool
2294
 *     true si OK, false sinon.
2295
 */
2296
function _sqlite_modifier_table($table, $colonne, $opt = array(), $serveur = '') {
2297
2298
	if (is_array($table)) {
2299
		reset($table);
2300
		list($table_origine, $table_destination) = each($table);
2301
	} else {
2302
		$table_origine = $table_destination = $table;
2303
	}
2304
	// ne prend actuellement qu'un changement
2305
	// mais pourra etre adapte pour changer plus qu'une colonne a la fois
2306
	if (is_array($colonne)) {
2307
		reset($colonne);
2308
		list($colonne_origine, $colonne_destination) = each($colonne);
2309
	} else {
2310
		$colonne_origine = $colonne_destination = $colonne;
2311
	}
2312
	if (!isset($opt['field'])) {
2313
		$opt['field'] = array();
2314
	}
2315
	if (!isset($opt['key'])) {
2316
		$opt['key'] = array();
2317
	}
2318
2319
	// si les noms de tables sont differents, pas besoin de table temporaire
2320
	// on prendra directement le nom de la future table
2321
	$meme_table = ($table_origine == $table_destination);
2322
2323
	$def_origine = sql_showtable($table_origine, false, $serveur);
2324 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...
2325
		spip_log("Alter table impossible sur $table_origine : table non trouvee", 'sqlite' . _LOG_ERREUR);
2326
2327
		return false;
2328
	}
2329
2330
2331
	$table_tmp = $table_origine . '_tmp';
2332
2333
	// 1) creer une table temporaire avec les modifications	
2334
	// - DROP : suppression de la colonne
2335
	// - CHANGE : modification de la colonne
2336
	// (foreach pour conserver l'ordre des champs)
2337
2338
	// field 
2339
	$fields = array();
2340
	// pour le INSERT INTO plus loin
2341
	// stocker la correspondance nouvelles->anciennes colonnes
2342
	$fields_correspondances = array();
2343
	foreach ($def_origine['field'] as $c => $d) {
2344
2345
		if ($colonne_origine && ($c == $colonne_origine)) {
2346
			// si pas DROP
2347
			if ($colonne_destination) {
2348
				$fields[$colonne_destination] = $opt['field'][$colonne_destination];
2349
				$fields_correspondances[$colonne_destination] = $c;
2350
			}
2351
		} else {
2352
			$fields[$c] = $d;
2353
			$fields_correspondances[$c] = $c;
2354
		}
2355
	}
2356
	// cas de ADD sqlite2 (ajout du champ en fin de table):
2357
	if (!$colonne_origine && $colonne_destination) {
2358
		$fields[$colonne_destination] = $opt['field'][$colonne_destination];
2359
	}
2360
2361
	// key...
2362
	$keys = array();
2363
	foreach ($def_origine['key'] as $c => $d) {
2364
		$c = str_replace($colonne_origine, $colonne_destination, $c);
2365
		$d = str_replace($colonne_origine, $colonne_destination, $d);
2366
		// seulement si on ne supprime pas la colonne !
2367
		if ($d) {
2368
			$keys[$c] = $d;
2369
		}
2370
	}
2371
2372
	// autres keys, on merge
2373
	$keys = array_merge($keys, $opt['key']);
2374
	$queries = array();
2375
2376
	// copier dans destination (si differente de origine), sinon tmp
2377
	$table_copie = ($meme_table) ? $table_tmp : $table_destination;
2378
	$autoinc = (isset($keys['PRIMARY KEY'])
2379
		and $keys['PRIMARY KEY']
2380
		and stripos($keys['PRIMARY KEY'], ',') === false
2381
		and stripos($fields[$keys['PRIMARY KEY']], 'default') === false);
2382
2383
	if ($q = _sqlite_requete_create(
2384
		$table_copie,
2385
		$fields,
2386
		$keys,
2387
		$autoinc,
2388
		$temporary = false,
2389
		$ifnotexists = true,
2390
		$serveur)
2391
	) {
2392
		$queries[] = $q;
2393
	}
2394
2395
2396
	// 2) y copier les champs qui vont bien
2397
	$champs_dest = join(', ', array_keys($fields_correspondances));
2398
	$champs_ori = join(', ', $fields_correspondances);
2399
	$queries[] = "INSERT INTO $table_copie ($champs_dest) SELECT $champs_ori FROM $table_origine";
2400
2401
	// 3) supprimer la table d'origine
2402
	$queries[] = "DROP TABLE $table_origine";
2403
2404
	// 4) renommer la table temporaire 
2405
	// avec le nom de la table destination
2406
	// si necessaire
2407
	if ($meme_table) {
2408
		if (_sqlite_is_version(3, '', $serveur)) {
2409
			$queries[] = "ALTER TABLE $table_copie RENAME TO $table_destination";
2410
		} else {
2411
			$queries[] = _sqlite_requete_create(
2412
				$table_destination,
2413
				$fields,
2414
				$keys,
2415
				$autoinc,
2416
				$temporary = false,
2417
				$ifnotexists = false, // la table existe puisqu'on est dans une transaction
2418
				$serveur);
2419
			$queries[] = "INSERT INTO $table_destination SELECT * FROM $table_copie";
2420
			$queries[] = "DROP TABLE $table_copie";
2421
		}
2422
	}
2423
2424
	// 5) remettre les index !
2425
	foreach ($keys as $k => $v) {
2426
		if ($k == 'PRIMARY KEY') {
2427
		} else {
2428
			// enlever KEY
2429
			$k = substr($k, 4);
2430
			$queries[] = "CREATE INDEX $table_destination" . "_$k ON $table_destination ($v)";
2431
		}
2432
	}
2433
2434
2435
	if (count($queries)) {
2436
		spip_sqlite::demarrer_transaction($serveur);
2437
		// il faut les faire une par une car $query = join('; ', $queries).";"; ne fonctionne pas
2438
		foreach ($queries as $q) {
2439
			if (!spip_sqlite::executer_requete($q, $serveur)) {
2440
				spip_log(_LOG_GRAVITE_ERREUR, "SQLite : ALTER TABLE table :"
2441
					. " 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...
2442
				spip_sqlite::annuler_transaction($serveur);
2443
2444
				return false;
2445
			}
2446
		}
2447
		spip_sqlite::finir_transaction($serveur);
2448
	}
2449
2450
	return true;
2451
}
2452
2453
2454
/**
2455
 * Nom des fonctions
2456
 *
2457
 * @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...
2458
 */
2459
function _sqlite_ref_fonctions() {
2460
	$fonctions = array(
2461
		'alter' => 'spip_sqlite_alter',
2462
		'count' => 'spip_sqlite_count',
2463
		'countsel' => 'spip_sqlite_countsel',
2464
		'create' => 'spip_sqlite_create',
2465
		'create_base' => 'spip_sqlite_create_base',
2466
		'create_view' => 'spip_sqlite_create_view',
2467
		'date_proche' => 'spip_sqlite_date_proche',
2468
		'delete' => 'spip_sqlite_delete',
2469
		'drop_table' => 'spip_sqlite_drop_table',
2470
		'drop_view' => 'spip_sqlite_drop_view',
2471
		'errno' => 'spip_sqlite_errno',
2472
		'error' => 'spip_sqlite_error',
2473
		'explain' => 'spip_sqlite_explain',
2474
		'fetch' => 'spip_sqlite_fetch',
2475
		'seek' => 'spip_sqlite_seek',
2476
		'free' => 'spip_sqlite_free',
2477
		'hex' => 'spip_sqlite_hex',
2478
		'in' => 'spip_sqlite_in',
2479
		'insert' => 'spip_sqlite_insert',
2480
		'insertq' => 'spip_sqlite_insertq',
2481
		'insertq_multi' => 'spip_sqlite_insertq_multi',
2482
		'listdbs' => 'spip_sqlite_listdbs',
2483
		'multi' => 'spip_sqlite_multi',
2484
		'optimize' => 'spip_sqlite_optimize',
2485
		'query' => 'spip_sqlite_query',
2486
		'quote' => 'spip_sqlite_quote',
2487
		'repair' => 'spip_sqlite_repair',
2488
		'replace' => 'spip_sqlite_replace',
2489
		'replace_multi' => 'spip_sqlite_replace_multi',
2490
		'select' => 'spip_sqlite_select',
2491
		'selectdb' => 'spip_sqlite_selectdb',
2492
		'set_charset' => 'spip_sqlite_set_charset',
2493
		'get_charset' => 'spip_sqlite_get_charset',
2494
		'showbase' => 'spip_sqlite_showbase',
2495
		'showtable' => 'spip_sqlite_showtable',
2496
		'update' => 'spip_sqlite_update',
2497
		'updateq' => 'spip_sqlite_updateq',
2498
		'preferer_transaction' => 'spip_sqlite_preferer_transaction',
2499
		'demarrer_transaction' => 'spip_sqlite_demarrer_transaction',
2500
		'terminer_transaction' => 'spip_sqlite_terminer_transaction',
2501
	);
2502
2503
	// association de chaque nom http d'un charset aux couples sqlite 
2504
	// SQLite supporte utf-8 et utf-16 uniquement.
2505
	$charsets = array(
2506
		'utf-8' => array('charset' => 'utf8', 'collation' => 'utf8_general_ci'),
2507
		//'utf-16be'=>array('charset'=>'utf16be','collation'=>'UTF-16BE'),// aucune idee de quoi il faut remplir dans es champs la
2508
		//'utf-16le'=>array('charset'=>'utf16le','collation'=>'UTF-16LE')
2509
	);
2510
2511
	$fonctions['charsets'] = $charsets;
2512
2513
	return $fonctions;
2514
}
2515
2516
2517
/**
2518
 * $query est une requete ou une liste de champs
2519
 *
2520
 * @param  $query
2521
 * @param bool $autoinc
2522
 * @return mixed
2523
 */
2524
function _sqlite_remplacements_definitions_table($query, $autoinc = false) {
2525
	// quelques remplacements
2526
	$num = "(\s*\([0-9]*\))?";
2527
	$enum = "(\s*\([^\)]*\))?";
2528
2529
	$remplace = array(
2530
		'/enum' . $enum . '/is' => 'VARCHAR(255)',
2531
		'/COLLATE \w+_bin/is' => 'COLLATE BINARY',
2532
		'/COLLATE \w+_ci/is' => 'COLLATE NOCASE',
2533
		'/auto_increment/is' => '',
2534
		'/(timestamp .* )ON .*$/is' => '\\1',
2535
		'/character set \w+/is' => '',
2536
		'/((big|small|medium|tiny)?int(eger)?)' . $num . '\s*unsigned/is' => '\\1 UNSIGNED',
2537
		'/(text\s+not\s+null(\s+collate\s+\w+)?)\s*$/is' => "\\1 DEFAULT ''",
2538
		'/((char|varchar)' . $num . '\s+not\s+null(\s+collate\s+\w+)?)\s*$/is' => "\\1 DEFAULT ''",
2539
		'/(datetime\s+not\s+null)\s*$/is' => "\\1 DEFAULT '0000-00-00 00:00:00'",
2540
		'/(date\s+not\s+null)\s*$/is' => "\\1 DEFAULT '0000-00-00'",
2541
	);
2542
2543
	// pour l'autoincrement, il faut des INTEGER NOT NULL PRIMARY KEY
2544
	$remplace_autocinc = array(
2545
		'/(big|small|medium|tiny)?int(eger)?' . $num . '/is' => 'INTEGER'
2546
	);
2547
	// pour les int non autoincrement, il faut un DEFAULT
2548
	$remplace_nonautocinc = array(
2549
		'/((big|small|medium|tiny)?int(eger)?' . $num . '\s+not\s+null)\s*$/is' => "\\1 DEFAULT 0",
2550
	);
2551
2552
	if (is_string($query)) {
2553
		$query = preg_replace(array_keys($remplace), $remplace, $query);
2554
		if ($autoinc or preg_match(',AUTO_INCREMENT,is', $query)) {
2555
			$query = preg_replace(array_keys($remplace_autocinc), $remplace_autocinc, $query);
2556
		} else {
2557
			$query = preg_replace(array_keys($remplace_nonautocinc), $remplace_nonautocinc, $query);
2558
			$query = _sqlite_collate_ci($query);
2559
		}
2560
	} elseif (is_array($query)) {
2561
		foreach ($query as $k => $q) {
2562
			$ai = ($autoinc ? $k == $autoinc : preg_match(',AUTO_INCREMENT,is', $q));
2563
			$query[$k] = preg_replace(array_keys($remplace), $remplace, $query[$k]);
2564
			if ($ai) {
2565
				$query[$k] = preg_replace(array_keys($remplace_autocinc), $remplace_autocinc, $query[$k]);
2566
			} else {
2567
				$query[$k] = preg_replace(array_keys($remplace_nonautocinc), $remplace_nonautocinc, $query[$k]);
2568
				$query[$k] = _sqlite_collate_ci($query[$k]);
2569
			}
2570
		}
2571
	}
2572
2573
	return $query;
2574
}
2575
2576
/**
2577
 * Definir la collation d'un champ en fonction de si une collation est deja explicite
2578
 * et du par defaut que l'on veut NOCASE
2579
 *
2580
 * @param string $champ
2581
 * @return string
2582
 */
2583
function _sqlite_collate_ci($champ) {
2584
	if (stripos($champ, "COLLATE") !== false) {
2585
		return $champ;
2586
	}
2587
	if (stripos($champ, "BINARY") !== false) {
2588
		return str_ireplace("BINARY", "COLLATE BINARY", $champ);
2589
	}
2590
	if (preg_match(",^(char|varchar|(long|small|medium|tiny)?text),i", $champ)) {
2591
		return $champ . " COLLATE NOCASE";
2592
	}
2593
2594
	return $champ;
2595
}
2596
2597
2598
/**
2599
 * Creer la requete pour la creation d'une table
2600
 * retourne la requete pour utilisation par sql_create() et sql_alter()
2601
 *
2602
 * @param  $nom
2603
 * @param  $champs
2604
 * @param  $cles
2605
 * @param bool $autoinc
2606
 * @param bool $temporary
2607
 * @param bool $_ifnotexists
2608
 * @param string $serveur
2609
 * @param bool $requeter
2610
 * @return bool|string
2611
 */
2612
function _sqlite_requete_create(
2613
	$nom,
2614
	$champs,
2615
	$cles,
2616
	$autoinc = false,
2617
	$temporary = false,
2618
	$_ifnotexists = true,
2619
	$serveur = '',
2620
	$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...
2621
) {
2622
	$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...
2623
2624
	// certains plugins declarent les tables  (permet leur inclusion dans le dump)
2625
	// sans les renseigner (laisse le compilo recuperer la description)
2626
	if (!is_array($champs) || !is_array($cles)) {
2627
		return;
2628
	}
2629
2630
	// sqlite ne gere pas KEY tout court dans une requete CREATE TABLE
2631
	// il faut passer par des create index
2632
	// Il gere par contre primary key !
2633
	// Soit la PK est definie dans les cles, soit dans un champs
2634
	// soit faussement dans les 2 (et dans ce cas, il faut l’enlever à un des 2 endroits !)
2635
	$pk = "PRIMARY KEY";
2636
	// le champ de cle primaire
2637
	$champ_pk = !empty($cles[$pk]) ? $cles[$pk] : '';
2638
2639
	foreach ($champs as $k => $v) {
2640
		if (false !== stripos($v, $pk)) {
2641
			$champ_pk = $k;
2642
			// on n'en a plus besoin dans field, vu que defini dans key
2643
			$champs[$k] = preg_replace("/$pk/is", '', $champs[$k]);
2644
			break;
2645
		}
2646
	}
2647
2648
	if ($champ_pk) {
2649
		$keys = "\n\t\t$pk ($champ_pk)";
2650
	}
2651
	// Pas de DEFAULT 0 sur les cles primaires en auto-increment
2652
	if (isset($champs[$champ_pk])
2653
		and stripos($champs[$champ_pk], "default 0") !== false
2654
	) {
2655
		$champs[$champ_pk] = trim(str_ireplace("default 0", "", $champs[$champ_pk]));
2656
	}
2657
2658
	$champs = _sqlite_remplacements_definitions_table($champs, $autoinc ? $champ_pk : false);
2659
	foreach ($champs as $k => $v) {
2660
		$query .= "$s\n\t\t$k $v";
2661
		$s = ",";
2662
	}
2663
2664
	$ifnotexists = "";
2665
	if ($_ifnotexists) {
2666
2667
		$version = spip_sqlite_fetch(spip_sqlite_query("select sqlite_version() AS sqlite_version", $serveur), '',
2668
			$serveur);
2669
		if (!function_exists('spip_version_compare')) {
2670
			include_spip('plugins/installer');
2671
		}
2672
2673 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...
2674
			$ifnotexists = ' IF NOT EXISTS';
2675
		} else {
2676
			/* simuler le IF EXISTS - version 2 et sqlite < 3.3a */
2677
			$a = spip_sqlite_showtable($nom, $serveur);
2678
			if (isset($a['key']['KEY ' . $nom])) {
2679
				return true;
2680
			}
2681
		}
2682
2683
	}
2684
2685
	$temporary = $temporary ? ' TEMPORARY' : '';
2686
	$q = "CREATE$temporary TABLE$ifnotexists $nom ($query" . ($keys ? ",$keys" : '') . ")\n";
2687
2688
	return $q;
2689
}
2690
2691
2692
/**
2693
 * Retrouver les champs 'timestamp'
2694
 * pour les ajouter aux 'insert' ou 'replace'
2695
 * afin de simuler le fonctionnement de mysql
2696
 *
2697
 * stocke le resultat pour ne pas faire
2698
 * de requetes showtable intempestives
2699
 *
2700
 * @param  $table
2701
 * @param  $couples
2702
 * @param string $desc
2703
 * @param string $serveur
2704
 * @return
2705
 */
2706
function _sqlite_ajouter_champs_timestamp($table, $couples, $desc = '', $serveur = '') {
2707
	static $tables = array();
2708
2709
	if (!isset($tables[$table])) {
2710
2711 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...
2712
			$trouver_table = charger_fonction('trouver_table', 'base');
2713
			$desc = $trouver_table($table, $serveur);
2714
			// si pas de description, on ne fait rien, ou on die() ?
2715
			if (!$desc) {
2716
				return $couples;
2717
			}
2718
		}
2719
2720
		// recherche des champs avec simplement 'TIMESTAMP'
2721
		// cependant, il faudra peut etre etendre
2722
		// avec la gestion de DEFAULT et ON UPDATE
2723
		// mais ceux-ci ne sont pas utilises dans le core
2724
		$tables[$table] = array();
2725
2726
		foreach ($desc['field'] as $k => $v) {
2727 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...
2728
				$tables[$table][$k] = "datetime('now')";
2729
			}
2730
		}
2731
	}
2732
2733
	// ajout des champs type 'timestamp' absents
2734
	return array_merge($tables[$table], $couples);
2735
}
2736
2737
2738
/**
2739
 * Renvoyer la liste des versions sqlite disponibles
2740
 * sur le serveur
2741
 *
2742
 * @return array|bool
2743
 */
2744
function spip_versions_sqlite() {
2745
	return _sqlite_charger_version();
2746
}
2747
2748
/**
2749
 * Gère l'envoi et la réception de requêtes à SQLite, qui peuvent être
2750
 * encadrées de transactions.
2751
 **/
2752
class spip_sqlite {
2753
	/** @var sqlite_requeteur[] Liste des instances de requêteurs créés */
2754
	public static $requeteurs = array();
2755
	/** @var bool[] Pour chaque connexion, flag pour savoir si une transaction est en cours */
2756
	public static $transaction_en_cours = array();
2757
2758
2759
	/**
2760
	 * Retourne une unique instance du requêteur
2761
	 *
2762
	 * Retourne une instance unique du requêteur pour une connexion SQLite
2763
	 * donnée
2764
	 *
2765
	 * @param string $serveur
2766
	 *    Nom du connecteur
2767
	 * @return sqlite_requeteur
2768
	 *    Instance unique du requêteur
2769
	 **/
2770
	public static function requeteur($serveur) {
2771
		if (!isset(spip_sqlite::$requeteurs[$serveur])) {
2772
			spip_sqlite::$requeteurs[$serveur] = new sqlite_requeteur($serveur);
2773
		}
2774
2775
		return spip_sqlite::$requeteurs[$serveur];
2776
	}
2777
2778
	/**
2779
	 * Prépare le texte d'une requête avant son exécution
2780
	 *
2781
	 * Adapte la requête au format plus ou moins MySQL par un format
2782
	 * compris de SQLite.
2783
	 *
2784
	 * Change les préfixes de tables SPIP par ceux véritables
2785
	 *
2786
	 * @param string $query Requête à préparer
2787
	 * @param string $serveur Nom de la connexion
2788
	 * @return string           Requête préparée
2789
	 */
2790
	public static function traduire_requete($query, $serveur) {
2791
		$requeteur = spip_sqlite::requeteur($serveur);
2792
		$traducteur = new sqlite_traducteur($query, $requeteur->prefixe, $requeteur->sqlite_version);
2793
2794
		return $traducteur->traduire_requete();
2795
	}
2796
2797
	/**
2798
	 * Démarre une transaction
2799
	 *
2800
	 * @param string $serveur Nom de la connexion
2801
	 **/
2802
	public static function demarrer_transaction($serveur) {
2803
		spip_sqlite::executer_requete("BEGIN TRANSACTION", $serveur);
2804
		spip_sqlite::$transaction_en_cours[$serveur] = true;
2805
	}
2806
2807
	/**
2808
	 * Exécute la requête donnée
2809
	 *
2810
	 * @param string $query Requête
2811
	 * @param string $serveur Nom de la connexion
2812
	 * @param null|bool $tracer Demander des statistiques (temps) ?
2813
	 **/
2814
	public static function executer_requete($query, $serveur, $tracer = null) {
2815
		$requeteur = spip_sqlite::requeteur($serveur);
2816
2817
		return $requeteur->executer_requete($query, $tracer);
2818
	}
2819
2820
	/**
2821
	 * Obtient l'identifiant de la dernière ligne insérée ou modifiée
2822
	 *
2823
	 * @param string $serveur Nom de la connexion
2824
	 * return int                Identifiant
2825
	 **/
2826
	public static function last_insert_id($serveur) {
2827
		$requeteur = spip_sqlite::requeteur($serveur);
2828
2829
		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...
2830
	}
2831
2832
	/**
2833
	 * Annule une transaction
2834
	 *
2835
	 * @param string $serveur Nom de la connexion
2836
	 **/
2837
	public static function annuler_transaction($serveur) {
2838
		spip_sqlite::executer_requete("ROLLBACK", $serveur);
2839
		spip_sqlite::$transaction_en_cours[$serveur] = false;
2840
	}
2841
2842
	/**
2843
	 * Termine une transaction
2844
	 *
2845
	 * @param string $serveur Nom de la connexion
2846
	 **/
2847
	public static function finir_transaction($serveur) {
2848
		// si pas de transaction en cours, ne rien faire et le dire
2849
		if (!isset(spip_sqlite::$transaction_en_cours[$serveur])
2850
			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...
2851
		) {
2852
			return false;
2853
		}
2854
		// sinon fermer la transaction et retourner true
2855
		spip_sqlite::executer_requete("COMMIT", $serveur);
2856
		spip_sqlite::$transaction_en_cours[$serveur] = false;
2857
2858
		return true;
2859
	}
2860
}
2861
2862
/*
2863
 * Classe pour partager les lancements de requête
2864
 * 
2865
 * Instanciée une fois par `$serveur` :
2866
 * 
2867
 * - peut corriger la syntaxe des requêtes pour la conformité à SQLite
2868
 * - peut tracer les requêtes
2869
 */
2870
2871
class sqlite_requeteur {
2872
	/** @var string Texte de la requête */
2873
	public $query = ''; // la requete
2874
	/** @var string Nom de la connexion */
2875
	public $serveur = '';
2876
	/** @var Ressource Identifiant de la connexion SQLite */
2877
	public $link = '';
2878
	/** @var string Prefixe des tables SPIP */
2879
	public $prefixe = '';
2880
	/** @var string Nom de la base de donnée */
2881
	public $db = '';
2882
	/** @var bool Doit-on tracer les requetes (var_profile) ? */
2883
	public $tracer = false; // doit-on tracer les requetes (var_profile)
2884
2885
	/** @var string Version de SQLite (2 ou 3) */
2886
	public $sqlite_version = '';
2887
2888
	/**
2889
	 * Constructeur
2890
	 *
2891
	 * @param string $serveur
2892
	 * @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...
2893
	 */
2894
	public function __construct($serveur = '') {
2895
		_sqlite_init();
2896
		$this->serveur = strtolower($serveur);
2897
2898
		if (!($this->link = _sqlite_link($this->serveur)) && (!defined('_ECRIRE_INSTALL') || !_ECRIRE_INSTALL)) {
2899
			spip_log("Aucune connexion sqlite (link)", 'sqlite.' . _LOG_ERREUR);
2900
2901
			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...
2902
		}
2903
2904
		$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...
2905
2906
		$this->prefixe = $GLOBALS['connexions'][$this->serveur ? $this->serveur : 0]['prefixe'];
2907
		$this->db = $GLOBALS['connexions'][$this->serveur ? $this->serveur : 0]['db'];
2908
2909
		// tracage des requetes ?
2910
		$this->tracer = (isset($_GET['var_profile']) && $_GET['var_profile']);
2911
	}
2912
2913
	/**
2914
	 * Lancer la requête transmise et faire le tracage si demandé
2915
	 *
2916
	 * @param string $query
2917
	 *     Requête à exécuter
2918
	 * @param bool|null $tracer
2919
	 *     true pour tracer la requête
2920
	 * @return bool|SQLiteResult
2921
	 */
2922
	public function executer_requete($query, $tracer = null) {
2923
		if (is_null($tracer)) {
2924
			$tracer = $this->tracer;
2925
		}
2926
		$err = "";
2927
		$t = 0;
2928
		if ($tracer) {
2929
			include_spip('public/tracer');
2930
			$t = trace_query_start();
2931
		}
2932
2933
		# spip_log("requete: $this->serveur >> $query",'sqlite.'._LOG_DEBUG); // boum ? pourquoi ?
2934
		if ($this->link) {
2935
			// memoriser la derniere erreur PHP vue
2936
			$e = (function_exists('error_get_last') ? error_get_last() : "");
2937
			// sauver la derniere requete
2938
			$GLOBALS['connexions'][$this->serveur ? $this->serveur : 0]['last'] = $query;
2939
			$GLOBALS['connexions'][$this->serveur ? $this->serveur : 0]['total_requetes']++;
2940
2941
			if ($this->sqlite_version == 3) {
2942
				$r = $this->link->query($query);
2943
				// sauvegarde de la requete (elle y est deja dans $r->queryString)
2944
				# $r->spipQueryString = $query;
2945
2946
				// comptage : oblige de compter le nombre d'entrees retournees 
2947
				// par une requete SELECT
2948
				// aucune autre solution ne donne le nombre attendu :( !
2949
				// particulierement s'il y a des LIMIT dans la requete.
2950
				if (strtoupper(substr(ltrim($query), 0, 6)) == 'SELECT') {
2951
					if ($r) {
2952
						// noter le link et la query pour faire le comptage *si* on en a besoin
2953
						$r->spipSqliteRowCount = array($this->link, $query);
2954
					} elseif ($r instanceof PDOStatement) {
2955
						$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...
2956
					}
2957
				}
2958
			} else {
2959
				$r = sqlite_query($this->link, $query);
2960
			}
2961
2962
			// loger les warnings/erreurs eventuels de sqlite remontant dans PHP
2963
			if ($err = (function_exists('error_get_last') ? error_get_last() : "") and $err != $e) {
2964
				$err = strip_tags($err['message']) . " in " . $err['file'] . " line " . $err['line'];
2965
				spip_log("$err - " . $query, 'sqlite.' . _LOG_ERREUR);
2966
			} else {
2967
				$err = "";
2968
			}
2969
2970
		} else {
2971
			$r = false;
2972
		}
2973
2974
		if (spip_sqlite_errno($this->serveur)) {
2975
			$err .= spip_sqlite_error($query, $this->serveur);
2976
		}
2977
2978
		return $t ? trace_query_end($query, $t, $r, $err, $this->serveur) : $r;
2979
	}
2980
2981
	/**
2982
	 * Obtient l'identifiant de la dernière ligne insérée ou modifiée
2983
	 *
2984
	 * @return int
2985
	 **/
2986
	public function last_insert_id() {
2987
		if ($this->sqlite_version == 3) {
2988
			return $this->link->lastInsertId();
2989
		} else {
2990
			return sqlite_last_insert_rowid($this->link);
2991
		}
2992
	}
2993
}
2994
2995
2996
/**
2997
 * Cette classe est presente essentiellement pour un preg_replace_callback
2998
 * avec des parametres dans la fonction appelee que l'on souhaite incrementer
2999
 * (fonction pour proteger les textes)
3000
 */
3001
class sqlite_traducteur {
3002
	/** @var string $query Texte de la requête */
3003
	public $query = '';
3004
	/** @var string $prefixe Préfixe des tables */
3005
	public $prefixe = '';
3006
	/** @var string $sqlite_version Version de sqlite (2 ou 3) */
3007
	public $sqlite_version = '';
3008
3009
	/** Pour les corrections à effectuer sur les requêtes : array(code=>'texte') trouvé
3010
	 *
3011
	 * @var array
3012
	 */
3013
	public $textes = array();
3014
3015
	/**
3016
	 * Constructeur
3017
	 *
3018
	 * @param string $query Requête à préparer
3019
	 * @param string $prefixe Prefixe des tables à utiliser
3020
	 * @param string $sqlite_version Version SQLite (2 ou 3)
3021
	 */
3022
	public function __construct($query, $prefixe, $sqlite_version) {
3023
		$this->query = $query;
3024
		$this->prefixe = $prefixe;
3025
		$this->sqlite_version = $sqlite_version;
3026
	}
3027
3028
	/**
3029
	 * Transformer la requete pour SQLite
3030
	 *
3031
	 * Enlève les textes, transforme la requête pour quelle soit
3032
	 * bien interprétée par SQLite, puis remet les textes
3033
	 * la fonction affecte `$this->query`
3034
	 */
3035
	public function traduire_requete() {
3036
		//
3037
		// 1) Protection des textes en les remplacant par des codes
3038
		//
3039
		// enlever les 'textes' et initialiser avec
3040
		list($this->query, $textes) = query_echappe_textes($this->query);
3041
3042
		//
3043
		// 2) Corrections de la requete
3044
		//
3045
		// Correction Create Database
3046
		// Create Database -> requete ignoree
3047 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...
3048
			spip_log("Sqlite : requete non executee -> $this->query", 'sqlite.' . _LOG_AVERTISSEMENT);
3049
			$this->query = "SELECT 1";
3050
		}
3051
3052
		// Correction Insert Ignore
3053
		// INSERT IGNORE -> insert (tout court et pas 'insert or replace')
3054 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...
3055
			spip_log("Sqlite : requete transformee -> $this->query", 'sqlite.' . _LOG_DEBUG);
3056
			$this->query = 'INSERT ' . substr($this->query, '13');
3057
		}
3058
3059
		// Correction des dates avec INTERVAL
3060
		// utiliser sql_date_proche() de preference
3061 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...
3062
			$this->query = preg_replace_callback("/DATE_(ADD|SUB)(.*)INTERVAL\s+(\d+)\s+([a-zA-Z]+)\)/U",
3063
				array(&$this, '_remplacerDateParTime'),
3064
				$this->query);
3065
		}
3066
3067 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...
3068
			$this->query = str_replace('LEFT(', '_LEFT(', $this->query);
3069
		}
3070
3071
		if (strpos($this->query, 'TIMESTAMPDIFF(') !== false) {
3072
			$this->query = preg_replace('/TIMESTAMPDIFF\(\s*([^,]*)\s*,/Uims', "TIMESTAMPDIFF('\\1',", $this->query);
3073
		}
3074
3075
3076
		// Correction Using
3077
		// USING (non reconnu en sqlite2)
3078
		// problematique car la jointure ne se fait pas du coup.
3079
		if (($this->sqlite_version == 2) && (strpos($this->query, "USING") !== false)) {
3080
			spip_log("'USING (champ)' n'est pas reconnu en SQLite 2. Utilisez 'ON table1.champ = table2.champ'",
3081
				'sqlite.' . _LOG_ERREUR);
3082
			$this->query = preg_replace('/USING\s*\([^\)]*\)/', '', $this->query);
3083
		}
3084
3085
		// Correction Field
3086
		// remplace FIELD(table,i,j,k...) par CASE WHEN table=i THEN n ... ELSE 0 END
3087 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...
3088
			$this->query = preg_replace_callback('/FIELD\s*\(([^\)]*)\)/',
3089
				array(&$this, '_remplacerFieldParCase'),
3090
				$this->query);
3091
		}
3092
3093
		// Correction des noms de tables FROM
3094
		// mettre les bons noms de table dans from, update, insert, replace...
3095
		if (preg_match('/\s(SET|VALUES|WHERE|DATABASE)\s/iS', $this->query, $regs)) {
3096
			$suite = strstr($this->query, $regs[0]);
3097
			$this->query = substr($this->query, 0, -strlen($suite));
3098
		} else {
3099
			$suite = '';
3100
		}
3101
		$pref = ($this->prefixe) ? $this->prefixe . "_" : "";
3102
		$this->query = preg_replace('/([,\s])spip_/S', '\1' . $pref, $this->query) . $suite;
3103
3104
		// Correction zero AS x
3105
		// pg n'aime pas 0+x AS alias, sqlite, dans le meme style, 
3106
		// n'apprecie pas du tout SELECT 0 as x ... ORDER BY x
3107
		// il dit que x ne doit pas être un integer dans le order by !
3108
		// on remplace du coup x par vide() dans ce cas uniquement
3109
		//
3110
		// rien que pour public/vertebrer.php ?
3111
		if ((strpos($this->query, "0 AS") !== false)) {
3112
			// on ne remplace que dans ORDER BY ou GROUP BY
3113
			if (preg_match('/\s(ORDER|GROUP) BY\s/i', $this->query, $regs)) {
3114
				$suite = strstr($this->query, $regs[0]);
3115
				$this->query = substr($this->query, 0, -strlen($suite));
3116
3117
				// on cherche les noms des x dans 0 AS x
3118
				// on remplace dans $suite le nom par vide()
3119
				preg_match_all('/\b0 AS\s*([^\s,]+)/', $this->query, $matches, PREG_PATTERN_ORDER);
3120
				foreach ($matches[1] as $m) {
3121
					$suite = str_replace($m, 'VIDE()', $suite);
3122
				}
3123
				$this->query .= $suite;
3124
			}
3125
		}
3126
3127
		// Correction possible des divisions entieres
3128
		// Le standard SQL (lequel? ou?) semble indiquer que
3129
		// a/b=c doit donner c entier si a et b sont entiers 4/3=1.
3130
		// C'est ce que retournent effectivement SQL Server et SQLite
3131
		// Ce n'est pas ce qu'applique MySQL qui retourne un reel : 4/3=1.333...
3132
		// 
3133
		// On peut forcer la conversion en multipliant par 1.0 avant la division
3134
		// /!\ SQLite 3.5.9 Debian/Ubuntu est victime d'un bug en plus ! 
3135
		// cf. https://bugs.launchpad.net/ubuntu/+source/sqlite3/+bug/254228
3136
		//     http://www.sqlite.org/cvstrac/tktview?tn=3202
3137
		// (4*1.0/3) n'est pas rendu dans ce cas !
3138
		# $this->query = str_replace('/','* 1.00 / ',$this->query);
3139
3140
3141
		// Correction critere REGEXP, non reconnu en sqlite2
3142
		if (($this->sqlite_version == 2) && (strpos($this->query, 'REGEXP') !== false)) {
3143
			$this->query = preg_replace('/([^\s\(]*)(\s*)REGEXP(\s*)([^\s\)]*)/', 'REGEXP($4, $1)', $this->query);
3144
		}
3145
3146
		//
3147
		// 3) Remise en place des textes d'origine
3148
		//
3149
		// Correction Antiquotes et echappements
3150
		// ` => rien
3151 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...
3152
			$this->query = str_replace('`', '', $this->query);
3153
		}
3154
3155
		$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...
3156
3157
		return $this->query;
3158
	}
3159
3160
3161
	/**
3162
	 * Callback pour remplacer `DATE_` / `INTERVAL`
3163
	 * par `DATE ... strtotime`
3164
	 *
3165
	 * @param array $matches Captures
3166
	 * @return string Texte de date compris par SQLite
3167
	 */
3168
	public function _remplacerDateParTime($matches) {
3169
		$op = strtoupper($matches[1] == 'ADD') ? '+' : '-';
3170
3171
		return "datetime$matches[2] '$op$matches[3] $matches[4]')";
3172
	}
3173
3174
	/**
3175
	 * Callback pour remplacer `FIELD(table,i,j,k...)`
3176
	 * par `CASE WHEN table=i THEN n ... ELSE 0 END`
3177
	 *
3178
	 * @param array $matches Captures
3179
	 * @return string Texte de liste ordonnée compris par SQLite
3180
	 */
3181
	public function _remplacerFieldParCase($matches) {
3182
		$fields = substr($matches[0], 6, -1); // ne recuperer que l'interieur X de field(X)
3183
		$t = explode(',', $fields);
3184
		$index = array_shift($t);
3185
3186
		$res = '';
3187
		$n = 0;
3188
		foreach ($t as $v) {
3189
			$n++;
3190
			$res .= "\nWHEN $index=$v THEN $n";
3191
		}
3192
3193
		return "CASE $res ELSE 0 END ";
3194
	}
3195
3196
}
3197