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