490dcf734a3f5e7aff15bacee8c456d098e09309
[lhc/web/www.git] / www / ecrire / req / mysql.php
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 MySQL
16 *
17 * Ces instructions utilisent la librairie PHP Mysqli
18 *
19 * @package SPIP\Core\SQL\MySQL
20 */
21
22 if (!defined('_ECRIRE_INC_VERSION')) {
23 return;
24 }
25
26 if (!defined('_MYSQL_NOPLANES')) {
27 define('_MYSQL_NOPLANES', true);
28 }
29
30 /**
31 * Crée la première connexion à un serveur MySQL via MySQLi
32 *
33 * @param string $host Chemin du serveur
34 * @param int $port Port de connexion
35 * @param string $login Nom d'utilisateur
36 * @param string $pass Mot de passe
37 * @param string $db Nom de la base
38 * @param string $prefixe Préfixe des tables SPIP
39 * @return array|bool
40 * - false si la connexion a échoué
41 * - tableau décrivant la connexion sinon
42 */
43 function req_mysql_dist($host, $port, $login, $pass, $db = '', $prefixe = '') {
44 if (!charger_php_extension('mysqli')) {
45 return false;
46 }
47
48 // si port est fourni mais pas host, c'est un socket -> compat avec vieille syntaxe de mysql_connect() et anciens fichiers connect.php
49 if (
50 $port and !is_numeric($socket = $port)
51 and (!$host or $host=='localhost')) {
52 $link = @mysqli_connect($host, $login, $pass, '', null, $socket);
53 }
54 elseif ($port) {
55 $link = @mysqli_connect($host, $login, $pass, '', $port);
56 }
57 else {
58 $link = @mysqli_connect($host, $login, $pass);
59 }
60
61 if (!$link) {
62 spip_log('Echec mysqli_connect. Erreur : ' . mysqli_connect_error(), 'mysql.' . _LOG_HS);
63
64 return false;
65 }
66 $last = '';
67 if (!$db) {
68 $ok = $link;
69 $db = 'spip';
70 } else {
71 $ok = mysqli_select_db($link, $db);
72 if (defined('_MYSQL_SET_SQL_MODE')
73 or defined('_MYSQL_SQL_MODE_TEXT_NOT_NULL') // compatibilite
74 ) {
75 mysqli_query($link, $last = "set sql_mode=''");
76 }
77 }
78
79 spip_log("Connexion MySQLi vers $host, base $db, prefixe $prefixe " . ($ok ? "operationnelle" : 'impossible'),
80 _LOG_DEBUG);
81
82 return !$ok ? false : array(
83 'db' => $db,
84 'last' => $last,
85 'prefixe' => $prefixe ? $prefixe : $db,
86 'link' => $link,
87 'total_requetes' => 0,
88 );
89 }
90
91
92 $GLOBALS['spip_mysql_functions_1'] = array(
93 'alter' => 'spip_mysql_alter',
94 'count' => 'spip_mysql_count',
95 'countsel' => 'spip_mysql_countsel',
96 'create' => 'spip_mysql_create',
97 'create_base' => 'spip_mysql_create_base',
98 'create_view' => 'spip_mysql_create_view',
99 'date_proche' => 'spip_mysql_date_proche',
100 'delete' => 'spip_mysql_delete',
101 'drop_table' => 'spip_mysql_drop_table',
102 'drop_view' => 'spip_mysql_drop_view',
103 'errno' => 'spip_mysql_errno',
104 'error' => 'spip_mysql_error',
105 'explain' => 'spip_mysql_explain',
106 'fetch' => 'spip_mysql_fetch',
107 'seek' => 'spip_mysql_seek',
108 'free' => 'spip_mysql_free',
109 'hex' => 'spip_mysql_hex',
110 'in' => 'spip_mysql_in',
111 'insert' => 'spip_mysql_insert',
112 'insertq' => 'spip_mysql_insertq',
113 'insertq_multi' => 'spip_mysql_insertq_multi',
114 'listdbs' => 'spip_mysql_listdbs',
115 'multi' => 'spip_mysql_multi',
116 'optimize' => 'spip_mysql_optimize',
117 'query' => 'spip_mysql_query',
118 'quote' => 'spip_mysql_quote',
119 'replace' => 'spip_mysql_replace',
120 'replace_multi' => 'spip_mysql_replace_multi',
121 'repair' => 'spip_mysql_repair',
122 'select' => 'spip_mysql_select',
123 'selectdb' => 'spip_mysql_selectdb',
124 'set_charset' => 'spip_mysql_set_charset',
125 'get_charset' => 'spip_mysql_get_charset',
126 'showbase' => 'spip_mysql_showbase',
127 'showtable' => 'spip_mysql_showtable',
128 'update' => 'spip_mysql_update',
129 'updateq' => 'spip_mysql_updateq',
130
131 // association de chaque nom http d'un charset aux couples MySQL
132 'charsets' => array(
133 'cp1250' => array('charset' => 'cp1250', 'collation' => 'cp1250_general_ci'),
134 'cp1251' => array('charset' => 'cp1251', 'collation' => 'cp1251_general_ci'),
135 'cp1256' => array('charset' => 'cp1256', 'collation' => 'cp1256_general_ci'),
136 'iso-8859-1' => array('charset' => 'latin1', 'collation' => 'latin1_swedish_ci'),
137 //'iso-8859-6'=>array('charset'=>'latin1','collation'=>'latin1_swedish_ci'),
138 'iso-8859-9' => array('charset' => 'latin5', 'collation' => 'latin5_turkish_ci'),
139 //'iso-8859-15'=>array('charset'=>'latin1','collation'=>'latin1_swedish_ci'),
140 'utf-8' => array('charset' => 'utf8', 'collation' => 'utf8_general_ci')
141 )
142 );
143
144
145 /**
146 * Retrouver un link d'une connexion MySQL via MySQLi
147 *
148 * @param string $serveur Nom du serveur
149 * @return Object Information de connexion pour mysqli
150 */
151 function _mysql_link($serveur = '') {
152 $link = &$GLOBALS['connexions'][$serveur ? $serveur : 0]['link'];
153
154 return $link;
155 }
156
157
158 /**
159 * Définit un charset pour la connexion avec Mysql
160 *
161 * @param string $charset Charset à appliquer
162 * @param string $serveur Nom de la connexion
163 * @param bool $requeter inutilisé
164 * @return resource Ressource de résultats pour fetch()
165 */
166 function spip_mysql_set_charset($charset, $serveur = '', $requeter = true) {
167 $connexion = &$GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0];
168 spip_log("changement de charset sql : " . "SET NAMES " . _q($charset), _LOG_DEBUG);
169
170 return mysqli_query($connexion['link'], $connexion['last'] = "SET NAMES " . _q($charset));
171 }
172
173
174 /**
175 * Teste si le charset indiqué est disponible sur le serveur SQL
176 *
177 * @param array|string $charset Nom du charset à tester.
178 * @param string $serveur Nom de la connexion
179 * @param bool $requeter inutilisé
180 * @return array Description du charset (son nom est dans 'charset')
181 */
182 function spip_mysql_get_charset($charset = array(), $serveur = '', $requeter = true) {
183 $connexion = &$GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0];
184 $connexion['last'] = $c = "SHOW CHARACTER SET"
185 . (!$charset ? '' : (" LIKE " . _q($charset['charset'])));
186
187 return spip_mysql_fetch(mysqli_query($connexion['link'], $c), null, $serveur);
188 }
189
190
191 /**
192 * Exécute une requête Mysql (obsolète, ne plus utiliser)
193 *
194 * @deprecated Utiliser sql_query() ou autres
195 *
196 * @param string $query Requête
197 * @param string $serveur Nom de la connexion
198 * @param bool $requeter Exécuter la requête, sinon la retourner
199 * @return Resource Ressource pour fetch()
200 **/
201 function spip_query_db($query, $serveur = '', $requeter = true) {
202 return spip_mysql_query($query, $serveur, $requeter);
203 }
204
205
206 /**
207 * Exécute une requête MySQL, munie d'une trace à la demande
208 *
209 * @param string $query Requête
210 * @param string $serveur Nom de la connexion
211 * @param bool $requeter Exécuter la requête, sinon la retourner
212 * @return array|resource|string|bool
213 * - string : Texte de la requête si on ne l'exécute pas
214 * - ressource|bool : Si requête exécutée
215 * - array : Tableau décrivant requête et temps d'exécution si var_profile actif pour tracer.
216 */
217 function spip_mysql_query($query, $serveur = '', $requeter = true) {
218
219 $connexion = &$GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0];
220 $prefixe = $connexion['prefixe'];
221 $link = $connexion['link'];
222 $db = $connexion['db'];
223
224 $query = _mysql_traite_query($query, $db, $prefixe);
225
226 // renvoyer la requete inerte si demandee
227 if (!$requeter) {
228 return $query;
229 }
230
231 if (isset($_GET['var_profile'])) {
232 include_spip('public/tracer');
233 $t = trace_query_start();
234 } else {
235 $t = 0;
236 }
237
238 $connexion['last'] = $query;
239 $connexion['total_requetes']++;
240
241 // ajouter un debug utile dans log/mysql-slow.log ?
242 $debug = '';
243 if (defined('_DEBUG_SLOW_QUERIES') and _DEBUG_SLOW_QUERIES) {
244 if (isset($GLOBALS['debug']['aucasou'])) {
245 list(, $id, , $infos) = $GLOBALS['debug']['aucasou'];
246 $debug .= "BOUCLE$id @ " . $infos[0] . " | ";
247 }
248 $debug .= $_SERVER['REQUEST_URI'] . ' + ' . $GLOBALS['ip'];
249 $debug = ' /* ' . mysqli_real_escape_string($link, str_replace('*/', '@/', $debug)) . ' */';
250 }
251
252 $r = mysqli_query($link, $query . $debug);
253
254 //Eviter de propager le GoneAway sur les autres requetes d'un même processus PHP
255 if ($e = spip_mysql_errno($serveur)) { // Log d'un Gone Away
256 if ($e == 2006) { //Si Gone Away on relance une connexion vierge
257 //Fermer la connexion defaillante
258 mysqli_close($connexion['link']);
259 unset($GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0]);
260 //Relancer une connexion vierge
261 spip_connect($serveur);
262 $connexion = &$GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0];
263 $link = $connexion['link'];
264 //On retente au cas où
265 $r = mysqli_query($link, $query . $debug);
266 }
267 }
268
269 // Log de l'erreur eventuelle
270 if ($e = spip_mysql_errno($serveur)) {
271 $e .= spip_mysql_error($query, $serveur);
272 } // et du fautif
273 return $t ? trace_query_end($query, $t, $r, $e, $serveur) : $r;
274 }
275
276 /**
277 * Modifie une structure de table MySQL
278 *
279 * @param string $query Requête SQL (sans 'ALTER ')
280 * @param string $serveur Nom de la connexion
281 * @param bool $requeter Exécuter la requête, sinon la retourner
282 * @return array|bool|string
283 * - string : Texte de la requête si on ne l'exécute pas
284 * - bool : Si requête exécutée
285 * - array : Tableau décrivant requête et temps d'exécution si var_profile actif pour tracer.
286 */
287 function spip_mysql_alter($query, $serveur = '', $requeter = true) {
288 // ici on supprime les ` entourant le nom de table pour permettre
289 // la transposition du prefixe, compte tenu que les plugins ont la mauvaise habitude
290 // d'utiliser ceux-ci, copie-colle de phpmyadmin
291 $query = preg_replace(",^TABLE\s*`([^`]*)`,i", "TABLE \\1", $query);
292
293 return spip_mysql_query("ALTER " . $query, $serveur, $requeter); # i.e. que PG se debrouille
294 }
295
296
297 /**
298 * Optimise une table MySQL
299 *
300 * @param string $table Nom de la table
301 * @param string $serveur Nom de la connexion
302 * @param bool $requeter inutilisé
303 * @return bool Toujours true
304 */
305 function spip_mysql_optimize($table, $serveur = '', $requeter = true) {
306 spip_mysql_query("OPTIMIZE TABLE " . $table);
307
308 return true;
309 }
310
311
312 /**
313 * Retourne une explication de requête (Explain) MySQL
314 *
315 * @param string $query Texte de la requête
316 * @param string $serveur Nom de la connexion
317 * @param bool $requeter inutilisé
318 * @return array Tableau de l'explication
319 */
320 function spip_mysql_explain($query, $serveur = '', $requeter = true) {
321 if (strpos(ltrim($query), 'SELECT') !== 0) {
322 return array();
323 }
324 $connexion = &$GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0];
325 $prefixe = $connexion['prefixe'];
326 $link = $connexion['link'];
327 $db = $connexion['db'];
328
329 $query = 'EXPLAIN ' . _mysql_traite_query($query, $db, $prefixe);
330 $r = mysqli_query($link, $query);
331
332 return spip_mysql_fetch($r, null, $serveur);
333 }
334
335
336 /**
337 * Exécute une requête de sélection avec MySQL
338 *
339 * Instance de sql_select (voir ses specs).
340 *
341 * @see sql_select()
342 * @note
343 * Les `\n` et `\t` sont utiles au debusqueur.
344 *
345 * @param string|array $select Champs sélectionnés
346 * @param string|array $from Tables sélectionnées
347 * @param string|array $where Contraintes
348 * @param string|array $groupby Regroupements
349 * @param string|array $orderby Tris
350 * @param string $limit Limites de résultats
351 * @param string|array $having Contraintes posts sélections
352 * @param string $serveur Nom de la connexion
353 * @param bool $requeter Exécuter la requête, sinon la retourner
354 * @return array|bool|resource|string
355 * - string : Texte de la requête si on ne l'exécute pas
356 * - ressource si requête exécutée, ressource pour fetch()
357 * - false si la requête exécutée a ratée
358 * - array : Tableau décrivant requête et temps d'exécution si var_profile actif pour tracer.
359 */
360 function spip_mysql_select(
361 $select,
362 $from,
363 $where = '',
364 $groupby = '',
365 $orderby = '',
366 $limit = '',
367 $having = '',
368 $serveur = '',
369 $requeter = true
370 ) {
371
372
373 $from = (!is_array($from) ? $from : spip_mysql_select_as($from));
374 $query =
375 calculer_mysql_expression('SELECT', $select, ', ')
376 . calculer_mysql_expression('FROM', $from, ', ')
377 . calculer_mysql_expression('WHERE', $where)
378 . calculer_mysql_expression('GROUP BY', $groupby, ',')
379 . calculer_mysql_expression('HAVING', $having)
380 . ($orderby ? ("\nORDER BY " . spip_mysql_order($orderby)) : '')
381 . ($limit ? "\nLIMIT $limit" : '');
382
383 // renvoyer la requete inerte si demandee
384 if ($requeter === false) {
385 return $query;
386 }
387 $r = spip_mysql_query($query, $serveur, $requeter);
388
389 return $r ? $r : $query;
390 }
391
392
393 /**
394 * Prépare une clause order by
395 *
396 * Regroupe en texte les éléments si un tableau est donné
397 *
398 * @note
399 * 0+x avec un champ x commencant par des chiffres est converti par MySQL
400 * en le nombre qui commence x. Pas portable malheureusement, on laisse pour le moment.
401 *
402 * @param string|array $orderby Texte du orderby à préparer
403 * @return string Texte du orderby préparé
404 */
405 function spip_mysql_order($orderby) {
406 return (is_array($orderby)) ? join(", ", $orderby) : $orderby;
407 }
408
409
410 /**
411 * Prépare une clause WHERE pour MySQL
412 *
413 * Retourne une chaîne avec les bonnes parenthèses pour la
414 * contrainte indiquée, au format donnée par le compilateur
415 *
416 * @param array|string $v
417 * Description des contraintes
418 * - string : Texte du where
419 * - sinon tableau : A et B peuvent être de type string ou array,
420 * OP et C sont de type string :
421 * - array(A) : A est le texte du where
422 * - array(OP, A) : contrainte OP( A )
423 * - array(OP, A, B) : contrainte (A OP B)
424 * - array(OP, A, B, C) : contrainte (A OP (B) : C)
425 * @return string
426 * Contrainte pour clause WHERE
427 */
428 function calculer_mysql_where($v) {
429 if (!is_array($v)) {
430 return $v;
431 }
432
433 $op = array_shift($v);
434 if (!($n = count($v))) {
435 return $op;
436 } else {
437 $arg = calculer_mysql_where(array_shift($v));
438 if ($n == 1) {
439 return "$op($arg)";
440 } else {
441 $arg2 = calculer_mysql_where(array_shift($v));
442 if ($n == 2) {
443 return "($arg $op $arg2)";
444 } else {
445 return "($arg $op ($arg2) : $v[0])";
446 }
447 }
448 }
449 }
450
451 /**
452 * Calcule un expression pour une requête, en cumulant chaque élément
453 * avec l'opérateur de liaison ($join) indiqué
454 *
455 * Renvoie grosso modo "$expression join($join, $v)"
456 *
457 * @param string $expression Mot clé de l'expression, tel que "WHERE" ou "ORDER BY"
458 * @param array|string $v Données de l'expression
459 * @param string $join Si les données sont un tableau, elles seront groupées par cette jointure
460 * @return string Texte de l'expression, une partie donc, du texte la requête.
461 */
462 function calculer_mysql_expression($expression, $v, $join = 'AND') {
463 if (empty($v)) {
464 return '';
465 }
466
467 $exp = "\n$expression ";
468
469 if (!is_array($v)) {
470 return $exp . $v;
471 } else {
472 if (strtoupper($join) === 'AND') {
473 return $exp . join("\n\t$join ", array_map('calculer_mysql_where', $v));
474 } else {
475 return $exp . join($join, $v);
476 }
477 }
478 }
479
480
481 /**
482 * Renvoie des `nom AS alias`
483 *
484 * @param array $args
485 * @return string Sélection de colonnes pour une clause SELECT
486 */
487 function spip_mysql_select_as($args) {
488 $res = '';
489 foreach ($args as $k => $v) {
490 if (substr($k, -1) == '@') {
491 // c'est une jointure qui se refere au from precedent
492 // pas de virgule
493 $res .= ' ' . $v;
494 } else {
495 if (!is_numeric($k)) {
496 $p = strpos($v, " ");
497 if ($p) {
498 $v = substr($v, 0, $p) . " AS `$k`" . substr($v, $p);
499 } else {
500 $v .= " AS `$k`";
501 }
502 }
503 $res .= ', ' . $v;
504 }
505 }
506
507 return substr($res, 2);
508 }
509
510
511 /**
512 * Changer les noms des tables ($table_prefix)
513 *
514 * TODO: Quand tous les appels SQL seront abstraits on pourra l'améliorer
515 */
516 define('_SQL_PREFIXE_TABLE_MYSQL', '/([,\s])spip_/S');
517
518
519 /**
520 * Prépare le texte d'une requête avant son exécution
521 *
522 * Change les préfixes de tables SPIP par ceux véritables
523 *
524 * @param string $query Requête à préparer
525 * @param string $db Nom de la base de donnée
526 * @param string $prefixe Préfixe de tables à appliquer
527 * @return string Requête préparée
528 */
529 function _mysql_traite_query($query, $db = '', $prefixe = '') {
530
531 if ($GLOBALS['mysql_rappel_nom_base'] and $db) {
532 $pref = '`' . $db . '`.';
533 } else {
534 $pref = '';
535 }
536
537 if ($prefixe) {
538 $pref .= $prefixe . "_";
539 }
540
541 if (!preg_match('/\s(SET|VALUES|WHERE|DATABASE)\s/i', $query, $regs)) {
542 $suite = '';
543 } else {
544 $suite = strstr($query, $regs[0]);
545 $query = substr($query, 0, -strlen($suite));
546 // propager le prefixe en cas de requete imbriquee
547 // il faut alors echapper les chaine avant de le faire, pour ne pas risquer de
548 // modifier une requete qui est en fait juste du texte dans un champ
549 if (stripos($suite, "SELECT") !== false) {
550 list($suite, $textes) = query_echappe_textes($suite);
551 if (preg_match('/^(.*?)([(]\s*SELECT\b.*)$/si', $suite, $r)) {
552 $suite = $r[1] . _mysql_traite_query($r[2], $db, $prefixe);
553 }
554 $suite = query_reinjecte_textes($suite, $textes);
555 }
556 }
557 $r = preg_replace(_SQL_PREFIXE_TABLE_MYSQL, '\1' . $pref, $query) . $suite;
558
559 // en option, remplacer les emoji (que mysql ne sait pas gérer) en &#128169;
560 if (defined('_MYSQL_NOPLANES') and _MYSQL_NOPLANES and lire_meta('charset_sql_connexion') == 'utf8') {
561 include_spip('inc/charsets');
562 $r = utf8_noplanes($r);
563 }
564
565 #spip_log("_mysql_traite_query: " . substr($r,0, 50) . ".... $db, $prefixe", _LOG_DEBUG);
566 return $r;
567 }
568
569 /**
570 * Sélectionne une base de données
571 *
572 * @param string $db
573 * Nom de la base à utiliser
574 * @param string $serveur
575 * Nom du connecteur
576 * @param bool $requeter
577 * Inutilisé
578 *
579 * @return bool
580 * - True cas de succès.
581 * - False en cas d'erreur.
582 **/
583 function spip_mysql_selectdb($db, $serveur = '', $requeter = true) {
584 $link = _mysql_link($serveur);
585 $ok = mysqli_select_db($link, $db);
586 if (!$ok) {
587 spip_log('Echec mysqli_selectdb. Erreur : ' . mysqli_error($link), 'mysql.' . _LOG_CRITIQUE);
588 }
589
590 return $ok;
591 }
592
593
594 /**
595 * Retourne les bases de données accessibles
596 *
597 * Retourne un tableau du nom de toutes les bases de données
598 * accessibles avec les permissions de l'utilisateur SQL
599 * de cette connexion.
600 *
601 * Attention on n'a pas toujours les droits !
602 *
603 * @param string $serveur
604 * Nom du connecteur
605 * @param bool $requeter
606 * Inutilisé
607 * @return array
608 * Liste de noms de bases de données
609 **/
610 function spip_mysql_listdbs($serveur = '', $requeter = true) {
611 $dbs = array();
612 if ($res = spip_mysql_query("SHOW DATABASES", $serveur)) {
613 while ($row = mysqli_fetch_assoc($res)) {
614 $dbs[] = $row['Database'];
615 }
616 }
617
618 return $dbs;
619 }
620
621
622 /**
623 * Crée une table SQL
624 *
625 * Crée une table SQL nommee `$nom` à partir des 2 tableaux `$champs` et `$cles`
626 *
627 * @note Le nom des caches doit être inferieur à 64 caractères
628 *
629 * @param string $nom Nom de la table SQL
630 * @param array $champs Couples (champ => description SQL)
631 * @param array $cles Couples (type de clé => champ(s) de la clé)
632 * @param bool $autoinc True pour ajouter un auto-incrément sur la Primary Key
633 * @param bool $temporary True pour créer une table temporaire
634 * @param string $serveur Nom de la connexion
635 * @param bool $requeter inutilisé
636 * @return array|null|resource|string
637 * - null si champs ou cles n'est pas un tableau
638 * - true si la requête réussie, false sinon.
639 */
640 function spip_mysql_create(
641 $nom,
642 $champs,
643 $cles,
644 $autoinc = false,
645 $temporary = false,
646 $serveur = '',
647 $requeter = true
648 ) {
649
650 $query = '';
651 $keys = '';
652 $s = '';
653 $p = '';
654
655 // certains plugins declarent les tables (permet leur inclusion dans le dump)
656 // sans les renseigner (laisse le compilo recuperer la description)
657 if (!is_array($champs) || !is_array($cles)) {
658 return;
659 }
660
661 $res = spip_mysql_query("SELECT version() as v", $serveur);
662 if (($row = mysqli_fetch_array($res)) && (version_compare($row['v'], '5.0', '>='))) {
663 spip_mysql_query("SET sql_mode=''", $serveur);
664 }
665
666 foreach ($cles as $k => $v) {
667 $keys .= "$s\n\t\t$k ($v)";
668 if ($k == "PRIMARY KEY") {
669 $p = $v;
670 }
671 $s = ",";
672 }
673 $s = '';
674
675 $character_set = "";
676 if (@$GLOBALS['meta']['charset_sql_base']) {
677 $character_set .= " CHARACTER SET " . $GLOBALS['meta']['charset_sql_base'];
678 }
679 if (@$GLOBALS['meta']['charset_collation_sql_base']) {
680 $character_set .= " COLLATE " . $GLOBALS['meta']['charset_collation_sql_base'];
681 }
682
683 foreach ($champs as $k => $v) {
684 $v = _mysql_remplacements_definitions_table($v);
685 if (preg_match(',([a-z]*\s*(\(\s*[0-9]*\s*\))?(\s*binary)?),i', $v, $defs)) {
686 if (preg_match(',(char|text),i', $defs[1])
687 and !preg_match(',(binary|CHARACTER|COLLATE),i', $v)
688 ) {
689 $v = $defs[1] . $character_set . ' ' . substr($v, strlen($defs[1]));
690 }
691 }
692
693 $query .= "$s\n\t\t$k $v"
694 . (($autoinc && ($p == $k) && preg_match(',\b(big|small|medium)?int\b,i', $v))
695 ? " auto_increment"
696 : ''
697 );
698 $s = ",";
699 }
700 $temporary = $temporary ? 'TEMPORARY' : '';
701 $q = "CREATE $temporary TABLE IF NOT EXISTS $nom ($query" . ($keys ? ",$keys" : '') . ")"
702 . " ENGINE=MyISAM"
703 . ($character_set ? " DEFAULT $character_set" : "")
704 . "\n";
705
706 return spip_mysql_query($q, $serveur);
707 }
708
709
710 /**
711 * Adapte pour Mysql la déclaration SQL d'une colonne d'une table
712 *
713 * @param string $query
714 * Définition SQL d'un champ de table
715 * @return string
716 * Définition SQL adaptée pour MySQL d'un champ de table
717 */
718 function _mysql_remplacements_definitions_table($query) {
719 // quelques remplacements
720 $num = "(\s*\([0-9]*\))?";
721 $enum = "(\s*\([^\)]*\))?";
722
723 $remplace = array(
724 '/VARCHAR(\s*[^\s\(])/is' => 'VARCHAR(255)\\1',
725 );
726
727 $query = preg_replace(array_keys($remplace), $remplace, $query);
728
729 return $query;
730 }
731
732
733 /**
734 * Crée une base de données MySQL
735 *
736 * @param string $nom Nom de la base
737 * @param string $serveur Nom de la connexion
738 * @param bool $requeter Exécuter la requête, sinon la retourner
739 * @return bool true si la base est créee.
740 **/
741 function spip_mysql_create_base($nom, $serveur = '', $requeter = true) {
742 return spip_mysql_query("CREATE DATABASE `$nom`", $serveur, $requeter);
743 }
744
745
746 /**
747 * Crée une vue SQL nommée `$nom`
748 *
749 * @param string $nom
750 * Nom de la vue à creer
751 * @param string $query_select
752 * Texte de la requête de sélection servant de base à la vue
753 * @param string $serveur
754 * Nom du connecteur
755 * @param bool $requeter
756 * Effectuer la requete, sinon la retourner
757 * @return bool|string
758 * - true si la vue est créée
759 * - false si erreur ou si la vue existe déja
760 * - string texte de la requête si $requeter vaut false
761 */
762 function spip_mysql_create_view($nom, $query_select, $serveur = '', $requeter = true) {
763 if (!$query_select) {
764 return false;
765 }
766 // vue deja presente
767 if (sql_showtable($nom, false, $serveur)) {
768 spip_log("Echec creation d'une vue sql ($nom) car celle-ci existe deja (serveur:$serveur)", _LOG_ERREUR);
769
770 return false;
771 }
772
773 $query = "CREATE VIEW $nom AS " . $query_select;
774
775 return spip_mysql_query($query, $serveur, $requeter);
776 }
777
778
779 /**
780 * Supprime une table SQL
781 *
782 * @param string $table Nom de la table SQL
783 * @param string $exist True pour ajouter un test d'existence avant de supprimer
784 * @param string $serveur Nom de la connexion
785 * @param bool $requeter Exécuter la requête, sinon la retourner
786 * @return bool|string
787 * - string Texte de la requête si demandé
788 * - true si la requête a réussie, false sinon
789 */
790 function spip_mysql_drop_table($table, $exist = '', $serveur = '', $requeter = true) {
791 if ($exist) {
792 $exist = " IF EXISTS";
793 }
794
795 return spip_mysql_query("DROP TABLE$exist $table", $serveur, $requeter);
796 }
797
798 /**
799 * Supprime une vue SQL
800 *
801 * @param string $view Nom de la vue SQL
802 * @param string $exist True pour ajouter un test d'existence avant de supprimer
803 * @param string $serveur Nom de la connexion
804 * @param bool $requeter Exécuter la requête, sinon la retourner
805 * @return bool|string
806 * - string Texte de la requête si demandé
807 * - true si la requête a réussie, false sinon
808 */
809 function spip_mysql_drop_view($view, $exist = '', $serveur = '', $requeter = true) {
810 if ($exist) {
811 $exist = " IF EXISTS";
812 }
813
814 return spip_mysql_query("DROP VIEW$exist $view", $serveur, $requeter);
815 }
816
817 /**
818 * Retourne une ressource de la liste des tables de la base de données
819 *
820 * @param string $match
821 * Filtre sur tables à récupérer
822 * @param string $serveur
823 * Connecteur de la base
824 * @param bool $requeter
825 * true pour éxecuter la requête
826 * false pour retourner le texte de la requête.
827 * @return ressource
828 * Ressource à utiliser avec sql_fetch()
829 **/
830 function spip_mysql_showbase($match, $serveur = '', $requeter = true) {
831 return spip_mysql_query("SHOW TABLES LIKE " . _q($match), $serveur, $requeter);
832 }
833
834 /**
835 * Répare une table SQL
836 *
837 * Utilise `REPAIR TABLE ...` de MySQL
838 *
839 * @param string $table Nom de la table SQL
840 * @param string $serveur Nom de la connexion
841 * @param bool $requeter Exécuter la requête, sinon la retourner
842 * @return bool|string
843 * - string Texte de la requête si demandée,
844 * - true si la requête a réussie, false sinon
845 */
846 function spip_mysql_repair($table, $serveur = '', $requeter = true) {
847 return spip_mysql_query("REPAIR TABLE `$table`", $serveur, $requeter);
848 }
849
850
851 define('_MYSQL_RE_SHOW_TABLE', '/^[^(),]*\(((?:[^()]*\((?:[^()]*\([^()]*\))?[^()]*\)[^()]*)*[^()]*)\)[^()]*$/');
852 /**
853 * Obtient la description d'une table ou vue MySQL
854 *
855 * Récupère la définition d'une table ou d'une vue avec colonnes, indexes, etc.
856 * au même format que la définition des tables SPIP, c'est à dire
857 * un tableau avec les clés
858 *
859 * - `field` (tableau colonne => description SQL) et
860 * - `key` (tableau type de clé => colonnes)
861 *
862 * @param string $nom_table Nom de la table SQL
863 * @param string $serveur Nom de la connexion
864 * @param bool $requeter Exécuter la requête, sinon la retourner
865 * @return array|string
866 * - chaîne vide si pas de description obtenue
867 * - string Texte de la requête si demandé
868 * - array description de la table sinon
869 */
870 function spip_mysql_showtable($nom_table, $serveur = '', $requeter = true) {
871 $s = spip_mysql_query("SHOW CREATE TABLE `$nom_table`", $serveur, $requeter);
872 if (!$s) {
873 return '';
874 }
875 if (!$requeter) {
876 return $s;
877 }
878
879 list(, $a) = mysqli_fetch_array($s, MYSQLI_NUM);
880 if (preg_match(_MYSQL_RE_SHOW_TABLE, $a, $r)) {
881 $desc = $r[1];
882 // extraction d'une KEY éventuelle en prenant garde de ne pas
883 // relever un champ dont le nom contient KEY (ex. ID_WHISKEY)
884 if (preg_match("/^(.*?),([^,]*\sKEY[ (].*)$/s", $desc, $r)) {
885 $namedkeys = $r[2];
886 $desc = $r[1];
887 } else {
888 $namedkeys = "";
889 }
890
891 $fields = array();
892 foreach (preg_split("/,\s*`/", $desc) as $v) {
893 preg_match("/^\s*`?([^`]*)`\s*(.*)/", $v, $r);
894 $fields[strtolower($r[1])] = $r[2];
895 }
896 $keys = array();
897
898 foreach (preg_split('/\)\s*(,|$)/', $namedkeys) as $v) {
899 if (preg_match("/^\s*([^(]*)\(([^(]*(\(\d+\))?)$/", $v, $r)) {
900 $k = str_replace("`", '', trim($r[1]));
901 $t = strtolower(str_replace("`", '', $r[2]));
902 if ($k && !isset($keys[$k])) {
903 $keys[$k] = $t;
904 } else {
905 $keys[] = $t;
906 }
907 }
908 }
909 spip_mysql_free($s);
910
911 return array('field' => $fields, 'key' => $keys);
912 }
913
914 $res = spip_mysql_query("SHOW COLUMNS FROM `$nom_table`", $serveur);
915 if ($res) {
916 $nfields = array();
917 $nkeys = array();
918 while ($val = spip_mysql_fetch($res)) {
919 $nfields[$val["Field"]] = $val['Type'];
920 if ($val['Null'] == 'NO') {
921 $nfields[$val["Field"]] .= ' NOT NULL';
922 }
923 if ($val['Default'] === '0' || $val['Default']) {
924 if (preg_match('/[A-Z_]/', $val['Default'])) {
925 $nfields[$val["Field"]] .= ' DEFAULT ' . $val['Default'];
926 } else {
927 $nfields[$val["Field"]] .= " DEFAULT '" . $val['Default'] . "'";
928 }
929 }
930 if ($val['Extra']) {
931 $nfields[$val["Field"]] .= ' ' . $val['Extra'];
932 }
933 if ($val['Key'] == 'PRI') {
934 $nkeys['PRIMARY KEY'] = $val["Field"];
935 } else {
936 if ($val['Key'] == 'MUL') {
937 $nkeys['KEY ' . $val["Field"]] = $val["Field"];
938 } else {
939 if ($val['Key'] == 'UNI') {
940 $nkeys['UNIQUE KEY ' . $val["Field"]] = $val["Field"];
941 }
942 }
943 }
944 }
945 spip_mysql_free($res);
946
947 return array('field' => $nfields, 'key' => $nkeys);
948 }
949
950 return "";
951 }
952
953
954 /**
955 * Rècupère une ligne de résultat
956 *
957 * Récupère la ligne suivante d'une ressource de résultat
958 *
959 * @param Ressource $r Ressource de résultat (issu de sql_select)
960 * @param string $t Structure de résultat attendu (défaut MYSQLI_ASSOC)
961 * @param string $serveur Nom de la connexion
962 * @param bool $requeter Inutilisé
963 * @return array Ligne de résultat
964 */
965 function spip_mysql_fetch($r, $t = '', $serveur = '', $requeter = true) {
966 if (!$t) {
967 $t = MYSQLI_ASSOC;
968 }
969 if ($r) {
970 return mysqli_fetch_array($r, $t);
971 }
972 }
973
974 /**
975 * Place le pointeur de résultat sur la position indiquée
976 *
977 * @param Ressource $r Ressource de résultat
978 * @param int $row_number Position. Déplacer le pointeur à cette ligne
979 * @param string $serveur Nom de la connexion
980 * @param bool $requeter Inutilisé
981 * @return bool True si déplacement réussi, false sinon.
982 **/
983 function spip_mysql_seek($r, $row_number, $serveur = '', $requeter = true) {
984 if ($r and mysqli_num_rows($r)) {
985 return mysqli_data_seek($r, $row_number);
986 }
987 }
988
989
990 /**
991 * Retourne le nombre de lignes d'une sélection
992 *
993 * @param array|string $from Tables à consulter (From)
994 * @param array|string $where Conditions a remplir (Where)
995 * @param array|string $groupby Critère de regroupement (Group by)
996 * @param array $having Tableau des des post-conditions à remplir (Having)
997 * @param string $serveur Nom de la connexion
998 * @param bool $requeter Exécuter la requête, sinon la retourner
999 * @return int|string
1000 * - String Texte de la requête si demandé
1001 * - int Nombre de lignes (0 si la requête n'a pas réussie)
1002 **/
1003 function spip_mysql_countsel(
1004 $from = array(),
1005 $where = array(),
1006 $groupby = '',
1007 $having = array(),
1008 $serveur = '',
1009 $requeter = true
1010 ) {
1011 $c = !$groupby ? '*' : ('DISTINCT ' . (is_string($groupby) ? $groupby : join(',', $groupby)));
1012
1013 $r = spip_mysql_select("COUNT($c)", $from, $where, '', '', '', $having, $serveur, $requeter);
1014 if (!$requeter) {
1015 return $r;
1016 }
1017 if (!$r instanceof mysqli_result) {
1018 return 0;
1019 }
1020 list($c) = mysqli_fetch_array($r, MYSQLI_NUM);
1021 mysqli_free_result($r);
1022
1023 return $c;
1024 }
1025
1026
1027 /**
1028 * Retourne la dernière erreur generée
1029 *
1030 * @note
1031 * Bien spécifier le serveur auquel on s'adresse,
1032 * mais à l'install la globale n'est pas encore complètement définie.
1033 *
1034 * @uses sql_error_backtrace()
1035 *
1036 * @param string $query
1037 * Requête qui était exécutée
1038 * @param string $serveur
1039 * Nom de la connexion
1040 * @param bool $requeter
1041 * Inutilisé
1042 * @return string
1043 * Erreur eventuelle
1044 **/
1045 function spip_mysql_error($query = '', $serveur = '', $requeter = true) {
1046 $link = $GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0]['link'];
1047 $s = mysqli_error($link);
1048 if ($s) {
1049 $trace = debug_backtrace();
1050 if ($trace[0]['function'] != "spip_mysql_error") {
1051 spip_log("$s - $query - " . sql_error_backtrace(), 'mysql.' . _LOG_ERREUR);
1052 }
1053 }
1054
1055 return $s;
1056 }
1057
1058
1059 /**
1060 * Retourne le numero de la dernière erreur SQL
1061 *
1062 * @param string $serveur
1063 * Nom de la connexion
1064 * @param bool $requeter
1065 * Inutilisé
1066 * @return int
1067 * 0, pas d'erreur. Autre, numéro de l'erreur.
1068 **/
1069 function spip_mysql_errno($serveur = '', $requeter = true) {
1070 $link = $GLOBALS['connexions'][$serveur ? $serveur : 0]['link'];
1071 $s = mysqli_errno($link);
1072 // 2006 MySQL server has gone away
1073 // 2013 Lost connection to MySQL server during query
1074 if (in_array($s, array(2006, 2013))) {
1075 define('spip_interdire_cache', true);
1076 }
1077 if ($s) {
1078 spip_log("Erreur mysql $s", _LOG_ERREUR);
1079 }
1080
1081 return $s;
1082 }
1083
1084
1085 /**
1086 * Retourne le nombre de lignes d’une ressource de sélection obtenue
1087 * avec `sql_select()`
1088 *
1089 * @param Ressource $r Ressource de résultat
1090 * @param string $serveur Nom de la connexion
1091 * @param bool $requeter Inutilisé
1092 * @return int Nombre de lignes
1093 */
1094 function spip_mysql_count($r, $serveur = '', $requeter = true) {
1095 if ($r) {
1096 return mysqli_num_rows($r);
1097 }
1098 }
1099
1100
1101 /**
1102 * Libère une ressource de résultat
1103 *
1104 * Indique à MySQL de libérer de sa mémoire la ressoucre de résultat indiquée
1105 * car on n'a plus besoin de l'utiliser.
1106 *
1107 * @param Ressource $r Ressource de résultat
1108 * @param string $serveur Nom de la connexion
1109 * @param bool $requeter Inutilisé
1110 * @return bool True si réussi
1111 */
1112 function spip_mysql_free($r, $serveur = '', $requeter = true) {
1113 return (($r instanceof mysqli_result) ? mysqli_free_result($r) : false);
1114 }
1115
1116
1117 /**
1118 * Insère une ligne dans une table
1119 *
1120 * @param string $table
1121 * Nom de la table SQL
1122 * @param string $champs
1123 * Liste des colonnes impactées,
1124 * @param string $valeurs
1125 * Liste des valeurs,
1126 * @param array $desc
1127 * Tableau de description des colonnes de la table SQL utilisée
1128 * (il sera calculé si nécessaire s'il n'est pas transmis).
1129 * @param string $serveur
1130 * Nom du connecteur
1131 * @param bool $requeter
1132 * Exécuter la requête, sinon la retourner
1133 * @return bool|string|int|array
1134 * - int|true identifiant de l'élément inséré (si possible), ou true, si réussite
1135 * - Texte de la requête si demandé,
1136 * - False en cas d'erreur,
1137 * - Tableau de description de la requête et du temps d'exécution, si var_profile activé
1138 **/
1139 function spip_mysql_insert($table, $champs, $valeurs, $desc = array(), $serveur = '', $requeter = true) {
1140
1141 $connexion = &$GLOBALS['connexions'][$serveur ? strtolower($serveur) : 0];
1142 $link = $connexion['link'];
1143 $table = prefixer_table_spip($table, $connexion['prefixe']);
1144
1145 $query = "INSERT INTO $table $champs VALUES $valeurs";
1146 if (!$requeter) {
1147 return $query;
1148 }
1149
1150 if (isset($_GET['var_profile'])) {
1151 include_spip('public/tracer');
1152 $t = trace_query_start();
1153 $e = '';
1154 } else {
1155 $t = 0;
1156 }
1157
1158 $connexion['last'] = $query;
1159 #spip_log($query, 'mysql.'._LOG_DEBUG);
1160 $r = false;
1161 if (mysqli_query($link, $query)) {
1162 $r = mysqli_insert_id($link);
1163 } else {
1164 // Log de l'erreur eventuelle
1165 if ($e = spip_mysql_errno($serveur)) {
1166 $e .= spip_mysql_error($query, $serveur);
1167 } // et du fautif
1168 }
1169
1170 return $t ? trace_query_end($query, $t, $r, $e, $serveur) : $r;
1171
1172 // return $r ? $r : (($r===0) ? -1 : 0); pb avec le multi-base.
1173 }
1174
1175 /**
1176 * Insère une ligne dans une table, en protégeant chaque valeur
1177 *
1178 * @param string $table
1179 * Nom de la table SQL
1180 * @param string $couples
1181 * Couples (colonne => valeur)
1182 * @param array $desc
1183 * Tableau de description des colonnes de la table SQL utilisée
1184 * (il sera calculé si nécessaire s'il n'est pas transmis).
1185 * @param string $serveur
1186 * Nom du connecteur
1187 * @param bool $requeter
1188 * Exécuter la requête, sinon la retourner
1189 * @return bool|string|int|array
1190 * - int|true identifiant de l'élément inséré (si possible), ou true, si réussite
1191 * - Texte de la requête si demandé,
1192 * - False en cas d'erreur,
1193 * - Tableau de description de la requête et du temps d'exécution, si var_profile activé
1194 **/
1195 function spip_mysql_insertq($table, $couples = array(), $desc = array(), $serveur = '', $requeter = true) {
1196
1197 if (!$desc) {
1198 $desc = description_table($table, $serveur);
1199 }
1200 if (!$desc) {
1201 $couples = array();
1202 }
1203 $fields = isset($desc['field']) ? $desc['field'] : array();
1204
1205 foreach ($couples as $champ => $val) {
1206 $couples[$champ] = spip_mysql_cite($val, $fields[$champ]);
1207 }
1208
1209 return spip_mysql_insert($table, "(" . join(',', array_keys($couples)) . ")", "(" . join(',', $couples) . ")", $desc,
1210 $serveur, $requeter);
1211 }
1212
1213
1214 /**
1215 * Insère plusieurs lignes d'un coup dans une table
1216 *
1217 * @param string $table
1218 * Nom de la table SQL
1219 * @param array $tab_couples
1220 * Tableau de tableaux associatifs (colonne => valeur)
1221 * @param array $desc
1222 * Tableau de description des colonnes de la table SQL utilisée
1223 * (il sera calculé si nécessaire s'il n'est pas transmis).
1224 * @param string $serveur
1225 * Nom du connecteur
1226 * @param bool $requeter
1227 * Exécuter la requête, sinon la retourner
1228 * @return int|bool|string
1229 * - int|true identifiant du dernier élément inséré (si possible), ou true, si réussite
1230 * - Texte de la requête si demandé,
1231 * - False en cas d'erreur.
1232 **/
1233 function spip_mysql_insertq_multi($table, $tab_couples = array(), $desc = array(), $serveur = '', $requeter = true) {
1234
1235 if (!$desc) {
1236 $desc = description_table($table, $serveur);
1237 }
1238 if (!$desc) {
1239 $tab_couples = array();
1240 }
1241 $fields = isset($desc['field']) ? $desc['field'] : array();
1242
1243 $cles = "(" . join(',', array_keys(reset($tab_couples))) . ')';
1244 $valeurs = array();
1245 $r = false;
1246
1247 // Quoter et Inserer par groupes de 100 max pour eviter un debordement de pile
1248 foreach ($tab_couples as $couples) {
1249 foreach ($couples as $champ => $val) {
1250 $couples[$champ] = spip_mysql_cite($val, $fields[$champ]);
1251 }
1252 $valeurs[] = '(' . join(',', $couples) . ')';
1253 if (count($valeurs) >= 100) {
1254 $r = spip_mysql_insert($table, $cles, join(', ', $valeurs), $desc, $serveur, $requeter);
1255 $valeurs = array();
1256 }
1257 }
1258 if (count($valeurs)) {
1259 $r = spip_mysql_insert($table, $cles, join(', ', $valeurs), $desc, $serveur, $requeter);
1260 }
1261
1262 return $r; // dans le cas d'une table auto_increment, le dernier insert_id
1263 }
1264
1265 /**
1266 * Met à jour des enregistrements d'une table SQL
1267 *
1268 * @param string $table
1269 * Nom de la table
1270 * @param array $champs
1271 * Couples (colonne => valeur)
1272 * @param string|array $where
1273 * Conditions a remplir (Where)
1274 * @param array $desc
1275 * Tableau de description des colonnes de la table SQL utilisée
1276 * (il sera calculé si nécessaire s'il n'est pas transmis).
1277 * @param string $serveur
1278 * Nom de la connexion
1279 * @param bool $requeter
1280 * Exécuter la requête, sinon la retourner
1281 * @return array|bool|string
1282 * - string : texte de la requête si demandé
1283 * - true si la requête a réussie, false sinon
1284 * - array Tableau décrivant la requête et son temps d'exécution si var_profile est actif
1285 */
1286 function spip_mysql_update($table, $champs, $where = '', $desc = array(), $serveur = '', $requeter = true) {
1287 $set = array();
1288 foreach ($champs as $champ => $val) {
1289 $set[] = $champ . "=$val";
1290 }
1291 if (!empty($set)) {
1292 return spip_mysql_query(
1293 calculer_mysql_expression('UPDATE', $table, ',')
1294 . calculer_mysql_expression('SET', $set, ',')
1295 . calculer_mysql_expression('WHERE', $where),
1296 $serveur, $requeter);
1297 }
1298 }
1299
1300 /**
1301 * Met à jour des enregistrements d'une table SQL et protège chaque valeur
1302 *
1303 * Protège chaque valeur transmise avec sql_quote(), adapté au type
1304 * de champ attendu par la table SQL
1305 *
1306 * @note
1307 * Les valeurs sont des constantes à mettre entre apostrophes
1308 * sauf les expressions de date lorsqu'il s'agit de fonctions SQL (NOW etc)
1309 *
1310 * @param string $table
1311 * Nom de la table
1312 * @param array $champs
1313 * Couples (colonne => valeur)
1314 * @param string|array $where
1315 * Conditions a remplir (Where)
1316 * @param array $desc
1317 * Tableau de description des colonnes de la table SQL utilisée
1318 * (il sera calculé si nécessaire s'il n'est pas transmis).
1319 * @param string $serveur
1320 * Nom de la connexion
1321 * @param bool $requeter
1322 * Exécuter la requête, sinon la retourner
1323 * @return array|bool|string
1324 * - string : texte de la requête si demandé
1325 * - true si la requête a réussie, false sinon
1326 * - array Tableau décrivant la requête et son temps d'exécution si var_profile est actif
1327 */
1328 function spip_mysql_updateq($table, $champs, $where = '', $desc = array(), $serveur = '', $requeter = true) {
1329
1330 if (!$champs) {
1331 return;
1332 }
1333 if (!$desc) {
1334 $desc = description_table($table, $serveur);
1335 }
1336 if (!$desc) {
1337 $champs = array();
1338 } else {
1339 $fields = $desc['field'];
1340 }
1341 $set = array();
1342 foreach ($champs as $champ => $val) {
1343 $set[] = $champ . '=' . spip_mysql_cite($val, @$fields[$champ]);
1344 }
1345
1346 return spip_mysql_query(
1347 calculer_mysql_expression('UPDATE', $table, ',')
1348 . calculer_mysql_expression('SET', $set, ',')
1349 . calculer_mysql_expression('WHERE', $where),
1350 $serveur, $requeter);
1351 }
1352
1353 /**
1354 * Supprime des enregistrements d'une table
1355 *
1356 * @param string $table Nom de la table SQL
1357 * @param string|array $where Conditions à vérifier
1358 * @param string $serveur Nom du connecteur
1359 * @param bool $requeter Exécuter la requête, sinon la retourner
1360 * @return bool|string
1361 * - int : nombre de suppressions réalisées,
1362 * - Texte de la requête si demandé,
1363 * - False en cas d'erreur.
1364 **/
1365 function spip_mysql_delete($table, $where = '', $serveur = '', $requeter = true) {
1366 $res = spip_mysql_query(
1367 calculer_mysql_expression('DELETE FROM', $table, ',')
1368 . calculer_mysql_expression('WHERE', $where),
1369 $serveur, $requeter);
1370 if (!$requeter) {
1371 return $res;
1372 }
1373 if ($res) {
1374 $link = _mysql_link($serveur);
1375
1376 return mysqli_affected_rows($link);
1377 } else {
1378 return false;
1379 }
1380 }
1381
1382
1383 /**
1384 * Insère où met à jour une entrée d’une table SQL
1385 *
1386 * La clé ou les cles primaires doivent être présentes dans les données insérés.
1387 * La fonction effectue une protection automatique des données.
1388 *
1389 * Préférez updateq ou insertq.
1390 *
1391 * @param string $table
1392 * Nom de la table SQL
1393 * @param array $couples
1394 * Couples colonne / valeur à modifier,
1395 * @param array $desc
1396 * Tableau de description des colonnes de la table SQL utilisée
1397 * (il sera calculé si nécessaire s'il n'est pas transmis).
1398 * @param string $serveur
1399 * Nom du connecteur
1400 * @param bool $requeter
1401 * Exécuter la requête, sinon la retourner
1402 * @return bool|string
1403 * - true si réussite
1404 * - Texte de la requête si demandé,
1405 * - False en cas d'erreur.
1406 **/
1407 function spip_mysql_replace($table, $couples, $desc = array(), $serveur = '', $requeter = true) {
1408 return spip_mysql_query("REPLACE $table (" . join(',', array_keys($couples)) . ') VALUES (' . join(',',
1409 array_map('_q', $couples)) . ')', $serveur, $requeter);
1410 }
1411
1412
1413 /**
1414 * Insère où met à jour des entrées d’une table SQL
1415 *
1416 * La clé ou les cles primaires doivent être présentes dans les données insérés.
1417 * La fonction effectue une protection automatique des données.
1418 *
1419 * Préférez insertq_multi et sql_updateq
1420 *
1421 * @param string $table
1422 * Nom de la table SQL
1423 * @param array $tab_couples
1424 * Tableau de tableau (colonne / valeur à modifier),
1425 * @param array $desc
1426 * Tableau de description des colonnes de la table SQL utilisée
1427 * (il sera calculé si nécessaire s'il n'est pas transmis).
1428 * @param string $serveur
1429 * Nom du connecteur
1430 * @param bool $requeter
1431 * Exécuter la requête, sinon la retourner
1432 * @return bool|string
1433 * - true si réussite
1434 * - Texte de la requête si demandé,
1435 * - False en cas d'erreur.
1436 **/
1437 function spip_mysql_replace_multi($table, $tab_couples, $desc = array(), $serveur = '', $requeter = true) {
1438 $cles = "(" . join(',', array_keys($tab_couples[0])) . ')';
1439 $valeurs = array();
1440 foreach ($tab_couples as $couples) {
1441 $valeurs[] = '(' . join(',', array_map('_q', $couples)) . ')';
1442 }
1443 $valeurs = implode(', ', $valeurs);
1444
1445 return spip_mysql_query("REPLACE $table $cles VALUES $valeurs", $serveur, $requeter);
1446 }
1447
1448
1449 /**
1450 * Retourne l'instruction SQL pour obtenir le texte d'un champ contenant
1451 * une balise `<multi>` dans la langue indiquée
1452 *
1453 * Cette sélection est mise dans l'alias `multi` (instruction AS multi).
1454 *
1455 * @param string $objet Colonne ayant le texte
1456 * @param string $lang Langue à extraire
1457 * @return string Texte de sélection pour la requête
1458 */
1459 function spip_mysql_multi($objet, $lang) {
1460 $lengthlang = strlen("[$lang]");
1461 $posmulti = "INSTR(" . $objet . ", '<multi>')";
1462 $posfinmulti = "INSTR(" . $objet . ", '</multi>')";
1463 $debutchaine = "LEFT(" . $objet . ", $posmulti-1)";
1464 $finchaine = "RIGHT(" . $objet . ", CHAR_LENGTH(" . $objet . ") -(7+$posfinmulti))";
1465 $chainemulti = "TRIM(SUBSTRING(" . $objet . ", $posmulti+7, $posfinmulti -(7+$posmulti)))";
1466 $poslang = "INSTR($chainemulti,'[" . $lang . "]')";
1467 $poslang = "IF($poslang=0,INSTR($chainemulti,']')+1,$poslang+$lengthlang)";
1468 $chainelang = "TRIM(SUBSTRING(" . $objet . ", $posmulti+7+$poslang-1,$posfinmulti -($posmulti+7+$poslang-1) ))";
1469 $posfinlang = "INSTR(" . $chainelang . ", '[')";
1470 $chainelang = "IF($posfinlang>0,LEFT($chainelang,$posfinlang-1),$chainelang)";
1471 //$chainelang = "LEFT($chainelang,$posfinlang-1)";
1472 $retour = "(TRIM(IF($posmulti = 0 , " .
1473 " TRIM(" . $objet . "), " .
1474 " CONCAT( " .
1475 " $debutchaine, " .
1476 " IF( " .
1477 " $poslang = 0, " .
1478 " $chainemulti, " .
1479 " $chainelang" .
1480 " ), " .
1481 " $finchaine" .
1482 " ) " .
1483 "))) AS multi";
1484
1485 return $retour;
1486 }
1487
1488 /**
1489 * Prépare une chaîne hexadécimale
1490 *
1491 * Par exemple : FF ==> 0xFF en MySQL
1492 *
1493 * @param string $v
1494 * Chaine hexadecimale
1495 * @return string
1496 * Valeur hexadécimale pour MySQL
1497 **/
1498 function spip_mysql_hex($v) {
1499 return "0x" . $v;
1500 }
1501
1502 /**
1503 * Échapper une valeur selon son type ou au mieux
1504 * comme le fait `_q()` mais pour MySQL avec ses spécificités
1505 *
1506 * @param string|array|number $v
1507 * Texte, nombre ou tableau à échapper
1508 * @param string $type
1509 * Description du type attendu
1510 * (par exemple description SQL de la colonne recevant la donnée)
1511 * @return string|number
1512 * Donnée prête à être utilisée par le gestionnaire SQL
1513 */
1514 function spip_mysql_quote($v, $type = '') {
1515 if ($type) {
1516 if (!is_array($v)) {
1517 return spip_mysql_cite($v, $type);
1518 }
1519 // si c'est un tableau, le parcourir en propageant le type
1520 foreach ($v as $k => $r) {
1521 $v[$k] = spip_mysql_quote($r, $type);
1522 }
1523
1524 return $v;
1525 }
1526 // si on ne connait pas le type, s'en remettre a _q :
1527 // on ne fera pas mieux
1528 else {
1529 return _q($v);
1530 }
1531 }
1532
1533 /**
1534 * Tester si une date est proche de la valeur d'un champ
1535 *
1536 * @param string $champ
1537 * Nom du champ a tester
1538 * @param int $interval
1539 * Valeur de l'intervalle : -1, 4, ...
1540 * @param string $unite
1541 * Utité utilisée (DAY, MONTH, YEAR, ...)
1542 * @return string
1543 * Expression SQL
1544 **/
1545 function spip_mysql_date_proche($champ, $interval, $unite) {
1546 return '('
1547 . $champ
1548 . (($interval <= 0) ? '>' : '<')
1549 . (($interval <= 0) ? 'DATE_SUB' : 'DATE_ADD')
1550 . '('
1551 . sql_quote(date('Y-m-d H:i:s'))
1552 . ', INTERVAL '
1553 . (($interval > 0) ? $interval : (0 - $interval))
1554 . ' '
1555 . $unite
1556 . '))';
1557 }
1558
1559
1560 /**
1561 * Retourne une expression IN pour le gestionnaire de base de données
1562 *
1563 * IN (...) est limité à 255 éléments, d'où cette fonction assistante
1564 *
1565 * @param string $val
1566 * Colonne SQL sur laquelle appliquer le test
1567 * @param string|array $valeurs
1568 * Liste des valeurs possibles (séparés par des virgules si string)
1569 * @param string $not
1570 * - '' sélectionne les éléments correspondant aux valeurs
1571 * - 'NOT' inverse en sélectionnant les éléments ne correspondant pas aux valeurs
1572 * @param string $serveur
1573 * Nom du connecteur
1574 * @param bool $requeter
1575 * Inutilisé
1576 * @return string
1577 * Expression de requête SQL
1578 **/
1579 function spip_mysql_in($val, $valeurs, $not = '', $serveur = '', $requeter = true) {
1580 $n = $i = 0;
1581 $in_sql = "";
1582 while ($n = strpos($valeurs, ',', $n + 1)) {
1583 if ((++$i) >= 255) {
1584 $in_sql .= "($val $not IN (" .
1585 substr($valeurs, 0, $n) .
1586 "))\n" .
1587 ($not ? "AND\t" : "OR\t");
1588 $valeurs = substr($valeurs, $n + 1);
1589 $i = $n = 0;
1590 }
1591 }
1592 $in_sql .= "($val $not IN ($valeurs))";
1593
1594 return "($in_sql)";
1595 }
1596
1597
1598 /**
1599 * Retourne une expression IN pour le gestionnaire de base de données
1600 *
1601 * Pour compatibilité. Ne plus utiliser.
1602 *
1603 * @deprecated Utiliser sql_in()
1604 *
1605 * @param string $val Nom de la colonne
1606 * @param string|array $valeurs Valeurs
1607 * @param string $not NOT pour inverser
1608 * @return string Expression de requête SQL
1609 */
1610 function calcul_mysql_in($val, $valeurs, $not = '') {
1611 if (is_array($valeurs)) {
1612 $valeurs = join(',', array_map('_q', $valeurs));
1613 } elseif ($valeurs[0] === ',') {
1614 $valeurs = substr($valeurs, 1);
1615 }
1616 if (!strlen(trim($valeurs))) {
1617 return ($not ? "0=0" : '0=1');
1618 }
1619
1620 return spip_mysql_in($val, $valeurs, $not);
1621 }
1622
1623
1624 /**
1625 * Renvoie les bons echappements (mais pas sur les fonctions comme NOW())
1626 *
1627 * @param string|number $v Texte ou nombre à échapper
1628 * @param string $type Type de donnée attendue, description SQL de la colonne de destination
1629 * @return string|number Texte ou nombre échappé
1630 */
1631 function spip_mysql_cite($v, $type) {
1632 if (is_null($v)
1633 and stripos($type, "NOT NULL") === false
1634 ) {
1635 return 'NULL';
1636 } // null php se traduit en NULL SQL
1637 if (sql_test_date($type) and preg_match('/^\w+\(/', $v)) {
1638 return $v;
1639 }
1640 if (sql_test_int($type)) {
1641 if (is_numeric($v) or (ctype_xdigit(substr($v, 2))
1642 and $v[0] == '0' and $v[1] == 'x')
1643 ) {
1644 return $v;
1645 } // si pas numerique, forcer le intval
1646 else {
1647 return intval($v);
1648 }
1649 }
1650
1651 return ("'" . addslashes($v) . "'");
1652 }
1653
1654
1655 // Ces deux fonctions n'ont pas d'equivalent exact PostGres
1656 // et ne sont la que pour compatibilite avec les extensions de SPIP < 1.9.3
1657
1658 /**
1659 * Poser un verrou SQL local
1660 *
1661 * Changer de nom toutes les heures en cas de blocage MySQL (ca arrive)
1662 *
1663 * @deprecated Pas d'équivalence actuellement en dehors de MySQL
1664 * @see spip_release_lock()
1665 *
1666 * @param string $nom
1667 * Inutilisé. Le nom est calculé en fonction de la connexion principale
1668 * @param int $timeout
1669 * @return string|bool
1670 * - Nom du verrou si réussite,
1671 * - false sinon
1672 */
1673 function spip_get_lock($nom, $timeout = 0) {
1674
1675 define('_LOCK_TIME', intval(time() / 3600 - 316982));
1676
1677 $connexion = &$GLOBALS['connexions'][0];
1678 $bd = $connexion['db'];
1679 $prefixe = $connexion['prefixe'];
1680 $nom = "$bd:$prefixe:$nom" . _LOCK_TIME;
1681
1682 $connexion['last'] = $q = "SELECT GET_LOCK(" . _q($nom) . ", $timeout) AS n";
1683 $q = @sql_fetch(mysql_query($q));
1684 if (!$q) {
1685 spip_log("pas de lock sql pour $nom", _LOG_ERREUR);
1686 }
1687
1688 return $q['n'];
1689 }
1690
1691
1692 /**
1693 * Relâcher un verrou SQL local
1694 *
1695 * @deprecated Pas d'équivalence actuellement en dehors de MySQL
1696 * @see spip_get_lock()
1697 *
1698 * @param string $nom
1699 * Inutilisé. Le nom est calculé en fonction de la connexion principale
1700 * @return string|bool
1701 * True si réussite, false sinon.
1702 */
1703 function spip_release_lock($nom) {
1704
1705 $connexion = &$GLOBALS['connexions'][0];
1706 $bd = $connexion['db'];
1707 $prefixe = $connexion['prefixe'];
1708 $nom = "$bd:$prefixe:$nom" . _LOCK_TIME;
1709
1710 $connexion['last'] = $q = "SELECT RELEASE_LOCK(" . _q($nom) . ")";
1711 mysqli_query(_mysql_link(), $q);
1712 }
1713
1714
1715 /**
1716 * Teste si on a les fonctions MySQLi (pour l'install)
1717 *
1718 * @return bool
1719 * True si on a les fonctions, false sinon
1720 */
1721 function spip_versions_mysql() {
1722 charger_php_extension('mysqli');
1723
1724 return function_exists('mysqli_query');
1725 }
1726
1727
1728 /**
1729 * Tester si mysql ne veut pas du nom de la base dans les requêtes
1730 *
1731 * @param string $server_db
1732 * @return string
1733 * - chaîne vide si nom de la base utile
1734 * - chaîne : code compilé pour le faire désactiver par SPIP sinon
1735 */
1736 function test_rappel_nom_base_mysql($server_db) {
1737 $GLOBALS['mysql_rappel_nom_base'] = true;
1738 sql_delete('spip_meta', "nom='mysql_rappel_nom_base'", $server_db);
1739 $ok = spip_query("INSERT INTO spip_meta (nom,valeur) VALUES ('mysql_rappel_nom_base', 'test')", $server_db);
1740
1741 if ($ok) {
1742 sql_delete('spip_meta', "nom='mysql_rappel_nom_base'", $server_db);
1743
1744 return '';
1745 } else {
1746 $GLOBALS['mysql_rappel_nom_base'] = false;
1747
1748 return "\$GLOBALS['mysql_rappel_nom_base'] = false; " .
1749 "/* echec de test_rappel_nom_base_mysql a l'installation. */\n";
1750 }
1751 }
1752
1753 /**
1754 * Teste si on peut changer les modes de MySQL
1755 *
1756 * @link http://dev.mysql.com/doc/refman/5.0/fr/server-sql-mode.html
1757 *
1758 * @param string $server_db Nom de la connexion
1759 * @return string
1760 * - chaîne vide si on ne peut pas appliquer de mode
1761 * - chaîne : code compilé pour l'indiquer le résultat du test à SPIP
1762 */
1763 function test_sql_mode_mysql($server_db) {
1764 $res = sql_select("version() as v", '', '', '', '', '', '', $server_db);
1765 $row = sql_fetch($res, $server_db);
1766 if (version_compare($row['v'], '5.0.0', '>=')) {
1767 defined('_MYSQL_SET_SQL_MODE') || define('_MYSQL_SET_SQL_MODE', true);
1768
1769 return "defined('_MYSQL_SET_SQL_MODE') || define('_MYSQL_SET_SQL_MODE',true);\n";
1770 }
1771
1772 return '';
1773 }