source: spip-zone/_plugins_/extension_mysqli/req/mysql.php @ 36712

Last change on this file since 36712 was 36712, checked in by gilles.vincent@…, 10 years ago
  • Ajouts d'accents dans les commentaires (le fichier doit être encodé en UTF8)
  • Bloc de Commentaire au format PHPDoc en début du fichier
  • Ajout du tag @package, fixé à "plugin"
  • Le tag @subpackage est le nom du plugin
  • La @category est utilisée pour regrouper différents plugins

Ce découpage est un essai, nous verrons si c'est pratique par la suite

  • Property svn:keywords set to Id
File size: 38.2 KB
Line 
1<?php
2
3/**
4 * Utilisation du connecteur MySQLi pour SPIP
5 *
6 * @license    GNU/GPL
7 * @package    plugin
8 * @subpackage mysqli
9 * @category   BDD
10 * @version    $Id: mysql.php 36712 2010-03-27 22:25:29Z gilles.vincent@gmail.com $
11 */
12
13
14if (!defined("_ECRIRE_INC_VERSION")) return;
15
16if (!extension_loaded('mysqli')) {
17        charger_php_extension('mysqli');
18}
19
20define('_DEFAULT_DB', 'spip');
21
22/**
23 * fonction pour changer la connexion aux serveurs MySQL en gardant les paramètres existants
24 * Cette fonction sert de constructeur de l'instance de connexion MySQLi
25 *
26 * @staticvar  array    $last_connect  mémorise les paramètres de connexion
27 * @param      string   $host          serveur MySQL
28 * @param      string   $port          port utilisé
29 * @param      string   $login         login MySQL
30 * @param      string   $pass          mot de passe
31 * @param      string   $db            base utilisée
32 * @param      string   $prefixe       préfixe utilisé
33 * @param      bool     $reconnect     indique si on utilise $last_connect pour se connecter
34 * @return     array
35 */
36function req_mysql_dist($host, $port, $login, $pass, $db='', $prefixe='', $reconnect=FALSE) {
37        static $last_connect = array(); // Pour se reconnecter si neccessaire
38
39        // Il faut initialiser le connexion
40        $connexion = mysqli_init();
41
42        if (!$reconnect) {
43                // On ne vient pas d'un select_db
44                // Possibilite de stocker en php.ini les parametre de connexion
45                if (!$host) $host = ini_get('mysqli.default_host');
46                if (!$host) $host = 'localhost';
47                if (!$port) $port = ini_get('mysqli.default_port');
48                if (!$login) $login = ini_get('mysqli.default_user');
49                if (!$pass) $pass = ini_get('mysqli.default_pw');
50                if (!$db) $db = _DEFAULT_DB;
51        } else {
52                if (empty($host) && empty($port) && empty($login) && empty($pass)){
53                        foreach (array('host','port','login','pass','prefixe') as $a){
54                                $$a = $last_connect[$a];
55                        }
56                }
57        }
58       
59        // Connexion proprement dite
60        // On pourrait encore jouer sur des options et sur le socket a utiliser
61        if (@$connexion->real_connect($host, $login, $pass, $db, $port)) {
62                $last_connect = array (
63                        'host' => $host,
64                        'port' => $port,
65                        'login' => $login,
66                        'pass' => $pass,
67                        'db' => $db,
68                        'prefixe' => $prefixe
69                );
70        } else {
71                return FALSE;
72        }
73
74        // Afin d'eviter les bugs, il peut etre utile d'etre en 'STRICT_ALL_TABLES' par ex.
75        // voir 'STRICT_ALL_TABLES,ANSI_QUOTES,NO_AUTO_VALUE_ON_ZERO,NO_ZERO_DATE,NO_ZERO_IN_DATE'
76        if (defined('_MYSQL_SET_SQL_MODE')) {
77                @$connexion->query("set sql_mode='"._q(_MYSQL_SET_SQL_MODE)."'");
78        }
79
80        return array(
81                'db' => $db,
82                'last' => '',
83                'prefixe' => $prefixe,
84                'link' => $connexion
85        );
86
87}
88
89/**
90 * Tableau de correspondance des appels sql / API mysqli
91 *
92 * @global  array   $GLOBALS['spip_mysql_functions_1']
93 * @name    $spip_mysql_functions_1
94 */
95$GLOBALS['spip_mysql_functions_1'] = array(
96        'alter' => 'spip_mysqli_alter',
97        'count' => 'spip_mysqli_count',
98        'countsel' => 'spip_mysqli_countsel',
99        'create' => 'spip_mysqli_create',
100        'create_base' => 'spip_mysqli_create_base',
101        'create_view' => 'spip_mysqli_create_view',
102        'date_proche' => 'spip_mysqli_date_proche',
103        'delete' => 'spip_mysqli_delete',
104        'drop_table' => 'spip_mysqli_drop_table',
105        'drop_view' => 'spip_mysqli_drop_view',
106        'errno' => 'spip_mysqli_errno',
107        'error' => 'spip_mysqli_error',
108        'explain' => 'spip_mysqli_explain',
109        'fetch' => 'spip_mysqli_fetch',
110        'seek' => 'spip_mysqli_seek',
111        'free' => 'spip_mysqli_free',
112        'hex' => 'spip_mysqli_hex',
113        'in' => 'spip_mysqli_in', 
114        'insert' => 'spip_mysqli_insert',
115        'insertq' => 'spip_mysqli_insertq',
116        'insertq_multi' => 'spip_mysqli_insertq_multi',
117        'listdbs' => 'spip_mysqli_listdbs',
118        'multi' => 'spip_mysqli_multi',
119        'optimize' => 'spip_mysqli_optimize',
120        'query' => 'spip_mysqli_query',
121        'quote' => 'spip_mysqli_quote',
122        'replace' => 'spip_mysqli_replace',
123        'replace_multi' => 'spip_mysqli_replace_multi',
124        'repair' => 'spip_mysqli_repair',
125        'select' => 'spip_mysqli_select',
126        'selectdb' => 'spip_mysqli_selectdb',
127        'set_charset' => 'spip_mysqli_set_charset',
128        'get_charset' => 'spip_mysqli_get_charset',
129        'showbase' => 'spip_mysqli_showbase',
130        'showtable' => 'spip_mysqli_showtable',
131        'update' => 'spip_mysqli_update',
132        'updateq' => 'spip_mysqli_updateq',
133        //// association de chaque nom http d'un charset aux couples MySQL
134        'charsets' => array(
135                'cp1250'=>array('charset'=>'cp1250','collation'=>'cp1250_general_ci'),
136                'cp1251'=>array('charset'=>'cp1251','collation'=>'cp1251_general_ci'),
137                'cp1256'=>array('charset'=>'cp1256','collation'=>'cp1256_general_ci'),
138                'iso-8859-1'=>array('charset'=>'latin1','collation'=>'latin1_swedish_ci'),
139                //'iso-8859-6'=>array('charset'=>'latin1','collation'=>'latin1_swedish_ci'),
140                'iso-8859-9'=>array('charset'=>'latin5','collation'=>'latin5_turkish_ci'),
141                //'iso-8859-15'=>array('charset'=>'latin1','collation'=>'latin1_swedish_ci'),
142                'utf-8'=>array('charset'=>'utf8','collation'=>'utf8_general_ci')
143                )
144        );
145
146
147/**
148 * Appelée à chaque connexion, cette requête fixe le charset utilisé pour les futures requêtes
149 *
150 * @link    http://doc.spip.org/@spip_mysql_set_charset
151 * @param   string  $charset   Le charset à utiliser
152 * @param   string  $serveur   Identifiant du serveur concerné
153 * @param   bool    $requeter  Inutilisé
154 * @param   bool    $requeter  Inutilisé
155 * @return  bool    TRUE si l'affectation a réussi
156 */
157function spip_mysqli_set_charset($charset, $serveur='',$requeter=true,$requeter=true){
158        $link = &$GLOBALS['connexions'][$serveur ? $serveur : 0]['link'];
159        $ok = FALSE;
160        if (version_compare(PHP_VERSION , '5.1.5', '>=')) {
161                $ok = $link->set_charset($charset);
162        }
163        if (!$ok) {
164            $ok = $link->query("SET NAMES '"._q($charset)."'");
165        }
166        return $ok;
167}
168
169/**
170 * Récupère les charsets disponibles
171 *
172 * @link    http://doc.spip.org/@spip_mysql_get_charset
173 * @param   array   $charset   Pattern pour restreindre les résultats
174 * @param   string  $serveur   Identifiant du serveur
175 * @param   bool    $requeter  Inutilisé
176 * @return  mysqli_result
177 */
178function spip_mysqli_get_charset($charset=array(), $serveur='',$requeter=true){
179        $connexion = &$GLOBALS['connexions'][$serveur ? $serveur : 0];
180        $connexion['last'] = $c = "SHOW CHARACTER SET"
181        . (!$charset ? '' : (" LIKE "._q($charset['charset'])));
182        $link = $connexion['link'];
183        return $link->query($c);
184}
185
186/**
187 * Fonction de requête générale, munie d'une trace à la demande
188 *
189 * @link    http://doc.spip.org/@spip_mysql_query
190 * @param   string  $query     La requête MySQL
191 * @param   string  $serveur   Identifiant du connecteur à utiliser
192 * @param   bool    $requeter  Inutilisé
193 * @return  mixed
194 */
195function spip_mysqli_query($query, $serveur='',$requeter=true) {
196
197        $connexion = &$GLOBALS['connexions'][$serveur ? $serveur : 0];
198        $prefixe = $connexion['prefixe'];
199        $link = $connexion['link'];
200        $db = $connexion['db'];
201
202        $query = traite_mysqli_query($query, $db, $prefixe);
203
204        // renvoyer la requete inerte si demandee
205        if (!$requeter) return $query;
206
207        if (isset($_GET['var_profile'])) {
208                include_spip('public/tracer');
209                $t = trace_query_start();
210        } else $t = 0 ;
211 
212        $connexion['last'] = $query;
213        $r = $link->real_query($query);
214        if ($r) $result = $link->use_result();
215        if ($result) $r = $result;
216
217        return $t ? trace_query_end($query, $t, $r , $serveur) : $r;
218}
219
220/**
221 * Modifie la structure d'une table ou base
222 *
223 * @link    http://doc.spip.org/@spip_mysql_alter
224 * @param   string  $query     La requête MySQL
225 * @param   string  $serveur   Identifiant du connecteur à utiliser
226 * @param   bool    $requeter  Inutilisé
227 * @return  mysqli_result
228 */
229function spip_mysqli_alter($query, $serveur='',$requeter=true){
230        return spip_mysqli_query("ALTER ".$query, $serveur, $requeter); # i.e. que PG se debrouille
231}
232
233/**
234 * Lance la défragmentation d'une table
235 *
236 * @link    http://doc.spip.org/@spip_mysql_optimize
237 * @param   string  $table     La table à défragmenter
238 * @param   string  $serveur   Identifiant du connecteur à utiliser
239 * @param   bool    $requeter  Inutilisé
240 * @return  bool
241 */
242function spip_mysqli_optimize($table, $serveur='',$requeter=true){
243        spip_mysqli_query("OPTIMIZE TABLE ". $table);
244        return true;
245}
246
247/**
248 * Obtenir des informations sur les SELECT
249 *
250 * @link    http://doc.spip.org/@spip_mysql_explain
251 * @param   string  $query         La requête MySQL
252 * @param   string  $serveur   Identifiant du connecteur à utiliser
253 * @param   bool    $requeter    Inutilisé
254 * @return  array
255 */
256function spip_mysqli_explain($query, $serveur='',$requeter=true){
257        if (strpos(ltrim($query), 'SELECT') !== 0) return array();
258        $connexion = &$GLOBALS['connexions'][$serveur ? $serveur : 0];
259        $prefixe = $connexion['prefixe'];
260        $link = $connexion['link'];
261        $db = $connexion['db'];
262
263        $query = 'EXPLAIN ' . traite_mysqli_query($query, $db, $prefixe);
264        $r = $link->query($query);
265        return $r;
266}
267
268/**
269 * fonction instance de sql_select
270 *
271 * voir ses specs dans {@link http://doc.spip.org/@abstract.php abstract.php}
272 * traite_mysqli_query pourrait y etre fait d'avance ce serait moins cher
273 * Les \n et \t sont utiles au debusqueur.
274 *
275 * @link    http://doc.spip.org/@spip_mysql_select
276 * @param   string|array    $select     liste des champs à récupérer
277 * @param   string|array    $from       Liste des tables
278 * @param   string|array    $where      Conditions que les lignes sélectionnées doivent satisfaire
279 * @param   string|array    $groupby    Colonnes qui déterminent le tri des lignes
280 * @param   string|array    $orderby    Ordre des résultats
281 * @param   string|array    $limit      Nombre de résultats + Offset
282 * @param   string          $having     Peut servir de fonction d'aggrégation
283 * @param   string          $serveur    Identifiant du connecteur à utiliser
284 * @param   bool            $requeter   Inutilisé
285 * @return  mixed
286 */
287function spip_mysqli_select($select, $from, $where='',
288                           $groupby='', $orderby='', $limit='', $having='',
289                           $serveur='',$requeter=true) {
290
291
292        $from = (!is_array($from) ? $from : spip_mysqli_select_as($from));
293        $query = 
294                  calculer_mysqli_expression('SELECT', $select, ', ')
295                . calculer_mysqli_expression('FROM', $from, ', ')
296                . calculer_mysqli_expression('WHERE', $where)
297                . calculer_mysqli_expression('GROUP BY', $groupby, ',')
298                . calculer_mysqli_expression('HAVING', $having)
299                . ($orderby ? ("\nORDER BY " . spip_mysqli_order($orderby)) :'')
300                . ($limit ? "\nLIMIT $limit" : '');
301
302        // renvoyer la requete inerte si demandee
303        if ($requeter === false) return $query;
304        $r = spip_mysqli_query($query, $serveur, $requeter);
305        return $r ? $r : $query;
306}
307
308/**
309 * Définir l'ordre des résultats
310 *
311 * 0+x avec un champ x commencant par des chiffres est converti par MySQL
312 * en le nombre qui commence x.
313 * Pas portable malheureusement, on laisse pour le moment.
314 *
315 * @link    http://doc.spip.org/@spip_mysql_order
316 * @param   string|array    $orderby   Le ou les colonnes
317 * @return  string
318 */
319function spip_mysqli_order($orderby)
320{
321        return (is_array($orderby)) ? join(", ", $orderby) :  $orderby;
322}
323
324
325/**
326 * Construction d'une expression_where
327 *
328 * @link    http://doc.spip.org/@calculer_mysqli_where
329 * @param   array|string    $v   arbre abstrait des conditions
330 * @return  string
331 */
332function calculer_mysqli_where($v)
333{
334        if (!is_array($v))
335          return $v ;
336
337        $op = array_shift($v);
338        if (!($n=count($v)))
339                return $op;
340        else {
341                $arg = calculer_mysqli_where(array_shift($v));
342                if ($n==1) {
343                          return "$op($arg)";
344                } else {
345                        $arg2 = calculer_mysqli_where(array_shift($v));
346                        if ($n==2) {
347                                return "($arg $op $arg2)";
348                        } else return "($arg $op ($arg2) : $v[0])";
349                }
350        }
351}
352
353/**
354 * Calcule un bloc d'expression MySQL
355 *
356 * @link    http://doc.spip.org/@calculer_mysql_expression
357 * @param   string          $expression   Expression déjà évaluée
358 * @param   array|string    $v            tableau des éléments à rassembler
359 * @param   string          $join         séparateur servant à rassembler les éléments de $v
360 * @return  string
361 */
362function calculer_mysqli_expression($expression, $v, $join = 'AND'){
363        if (empty($v))
364                return '';
365       
366        $exp = "\n$expression ";
367       
368        if (!is_array($v)) {
369                return $exp . $v;
370        } else {
371                if (strtoupper($join) === 'AND')
372                        return $exp . join("\n\t$join ", array_map('calculer_mysqli_where', $v));
373                else
374                        return $exp . join($join, $v);
375        }
376}
377
378/**
379 * Création de la liste des éléments à sélectionner dans la requête SQL
380 *
381 * @link    http://doc.spip.org/@spip_mysql_select_as
382 * @param   array   $args   Liste des éléments
383 * @return  string
384 */
385function spip_mysqli_select_as($args)
386{
387        $res = '';
388        foreach($args as $k => $v) {
389                if (substr($k,-1)=='@') {
390                        // c'est une jointure qui se refere au from precedent
391                        // pas de virgule
392                  $res .= '  ' . $v ;
393                }
394                else {
395                  if (!is_numeric($k)) {
396                        $p = strpos($v, " ");
397                        if ($p)
398                          $v = substr($v,0,$p) . " AS `$k`" . substr($v,$p);
399                        else $v .= " AS `$k`";
400                  }
401                     
402                  $res .= ', ' . $v ;
403                }
404        }
405        return substr($res,2);
406}
407
408/**
409 * Changer les noms des tables ($table_prefix)
410 * Quand tous les appels SQL seront abstraits on pourra l'améliorer
411 */
412define('_SQL_PREFIXE_TABLE', '/([,\s])spip_/S');
413
414/**
415 * Prépare une requête incomplète
416 *
417 * @link    http://doc.spip.org/@traite_mysql_query
418 * @param   string  $query    La requête partielle
419 * @param   string  $db       Le nom de la base
420 * @param   string  $prefixe  Prefixe des tables SPIP
421 * @return  string
422 */
423function traite_mysqli_query($query, $db='', $prefixe='') {
424
425        if ($GLOBALS['mysqli_rappel_nom_base'] AND $db)
426                $pref = '`'. $db.'`.';
427        else $pref = '';
428
429        if ($prefixe)
430                $pref .= $prefixe . "_";
431
432        if (!preg_match('/\s(SET|VALUES|WHERE|DATABASE)\s/i', $query, $regs)) {
433                $suite ='';
434        } else {
435                $suite = strstr($query, $regs[0]);
436                $query = substr($query, 0, -strlen($suite));
437                if (preg_match('/^(.*?)([(]\s*SELECT\b.*)$/si', $suite, $r)) {
438                  $suite = $r[1] . traite_mysqli_query($r[2], $db, $prefixe);
439                }
440        }
441        $r = preg_replace(_SQL_PREFIXE_TABLE, '\1'.$pref, $query) . $suite;
442        # spip_log("traite_mysqli_query: " . substr($r,0, 50) . ".... $db, $prefixe");
443        return $r;
444}
445
446/**
447 * Sélectionne une base de données par défaut pour les requêtes
448 *
449 * @link    http://doc.spip.org/@spip_mysql_selectdb
450 * @param   string  $db        Nom de la base
451 * @param   string  $serveur   Identifiant du connecteur SPIP
452 * @param   bool    $requeter  Inutilisé
453 * @return  bool    TRUE en cas de succès
454 */
455function spip_mysqli_selectdb($db, $serveur='',$requeter=true) {
456        $link = &$GLOBALS['connexions'][$serveur ? $serveur : 0]['link'];
457        return $link->select_db($db);
458}
459
460/**
461 * Retourne les bases accessibles
462 * Attention on n'a pas toujours les droits
463 *
464 * @link    http://doc.spip.org/@spip_mysql_listdbs
465 * @param   string  $serveur   Identifiant du connecteur SPIP
466 * @param   bool    $requeter  Inutilisé
467 * @return  mysqli_result
468 */
469function spip_mysqli_listdbs($serveur='',$requeter=true) {
470        return spip_mysqli_query("show databases",$serveur,$requeter);
471}
472
473/**
474 * Fonction de création d'une table SQL nommée $nom
475 *
476 * Cette fonction utilise 2 tableaux PHP :
477 * champs: champ => type
478 * cles: type-de-cle => champ(s)
479 * si $autoinc, c'est une auto-increment (i.e. serial) sur la Primary Key
480 * Le nom des caches doit être inférieur à 64 caractères
481 *
482 * @link    http://doc.spip.org/@spip_mysql_create
483 * @param   string  $nom        Table à créer
484 * @param   array   $champs     Liste des champs
485 * @param   array   $cles       Liste des clés
486 * @param   bool    $autoinc    TRUE si auto-increment sur la clé primaire
487 * @param   bool    $temporary  TRUE si Table temporaire
488 * @param   string  $serveur    Identifiant du connecteur concernée
489 * @param   bool    $requeter   Inutilisé
490 * @return  bool    TRUE si la création s'est bien passée
491 */
492function spip_mysqli_create($nom, $champs, $cles, $autoinc=false, $temporary=false, $serveur='',$requeter=true) {
493
494        $query = ''; $keys = ''; $s = ''; $p='';
495
496        // certains plugins declarent les tables  (permet leur inclusion dans le dump)
497        // sans les renseigner (laisse le compilo recuperer la description)
498        if (!is_array($champs) || !is_array($cles)) 
499                return;
500
501        foreach($cles as $k => $v) {
502                $keys .= "$s\n\t\t$k ($v)";
503                if ($k == "PRIMARY KEY")
504                        $p = $v;
505                $s = ",";
506        }
507        $s = '';
508       
509        $character_set = "";
510        if (@$GLOBALS['meta']['charset_sql_base'])
511                $character_set .= " CHARACTER SET ".$GLOBALS['meta']['charset_sql_base'];
512        if (@$GLOBALS['meta']['charset_collation_sql_base'])
513                $character_set .= " COLLATE ".$GLOBALS['meta']['charset_collation_sql_base'];
514
515        foreach($champs as $k => $v) {
516                if (preg_match(',([a-z]*\s*(\(\s*[0-9]*\s*\))?(\s*binary)?),i',$v,$defs)){
517                        if (preg_match(',(char|text),i',$defs[1]) AND !preg_match(',binary,i',$defs[1]) ){
518                                $v = $defs[1] . $character_set . ' ' . substr($v,strlen($defs[1]));
519                        }
520                }
521
522                $query .= "$s\n\t\t$k $v"
523                        . (($autoinc && ($p == $k) && preg_match(',\b(big|small|medium)?int\b,i', $v))
524                                ? " auto_increment"
525                                : ''
526                        );
527                $s = ",";
528        }
529        $temporary = $temporary ? 'TEMPORARY':'';
530        $q = "CREATE $temporary TABLE IF NOT EXISTS $nom ($query" . ($keys ? ",$keys" : '') . ")".
531        ($character_set?" DEFAULT $character_set":"")
532        ."\n";
533        return spip_mysqli_query($q, $serveur);
534}
535
536/**
537 * Créer une base de données sur un connecteur SPIP
538 *
539 * @param   string  $nom       Nom de la base
540 * @param   string  $serveur   Identifiant du connecteur à utiliser
541 * @param   bool    $requeter  Inutilisé
542 * @return  bool    TRUE si succès
543 */
544function spip_mysqli_create_base($nom, $serveur='',$requeter=true) {
545  return spip_mysqli_query("CREATE DATABASE `$nom`", $serveur, $requeter);
546}
547
548/**
549 * Fonction de création d'une vue SQL nommée $nom
550 *
551 * @link    http://doc.spip.org/@spip_mysql_create_view
552 * @param   string  $nom
553 * @param   string  $query_select
554 * @param   string  $serveur         Identifiant du connecteur à utiliser
555 * @param   bool    $requeter        Inutilisé
556 * @return  bool
557 */
558function spip_mysqli_create_view($nom, $query_select, $serveur='',$requeter=true) {
559        if (!$query_select) return false;
560        // vue deja presente
561        if (sql_showtable($nom, false, $serveur)) {
562                spip_log("Echec creation d'une vue sql ($nom) car celle-ci existe deja (serveur:$serveur)");
563                return false;
564        }
565       
566        $query = "CREATE VIEW $nom AS ". $query_select;
567        return spip_mysqli_query($query, $serveur, $requeter);
568}
569
570
571/**
572 * Supprime une table MySQL
573 *
574 * @link    http://doc.spip.org/@spip_mysql_drop_table
575 * @param   string  $table      La table
576 * @param   string  $exist      Est-ce qu'on teste l'existence de la table
577 * @param   string  $serveur    Identifiant du connecteur à utiliser
578 * @param   bool    $requeter   Inutilisé
579 * @return  bool    TRUE si la suppression a fonctionné
580 */
581function spip_mysqli_drop_table($table, $exist='', $serveur='',$requeter=true)
582{
583        if ($exist) $exist =" IF EXISTS";
584        return spip_mysqli_query("DROP TABLE$exist $table", $serveur, $requeter);
585}
586
587/**
588 * Supprime une vue
589 *
590 * @link    http://doc.spip.org/@spip_mysql_drop_view
591 * @param   string  $view      Nom de la vue
592 * @param   string  $exist     Doit-on tester son existence ?
593 * @param   string  $serveur   Identifiant du connecteur à utiliser
594 * @param   bool    $requeter  Inutilisé
595 * @return  bool    TRUE si la suppression a fonctionné
596 */
597function spip_mysqli_drop_view($view, $exist='', $serveur='',$requeter=true) {
598        if ($exist) $exist =" IF EXISTS";
599        return spip_mysqli_query("DROP VIEW$exist $view", $serveur, $requeter);
600}
601
602/**
603 * Liste des tables selon un certain critère
604 *
605 * @link    http://doc.spip.org/@spip_mysql_showbase
606 * @param   string  $match     Critère de recherche
607 * @param   string  $serveur   Identifiant du connecteur à utiliser
608 * @param   bool    $requeter  Inutilisé
609 * @return  mysqli_result
610 */
611function spip_mysqli_showbase($match, $serveur='',$requeter=true)
612{
613        return spip_mysqli_query("SHOW TABLES LIKE '$match'", $serveur, $requeter);
614}
615
616/**
617 * Tente la réparation d'une table
618 *
619 * @link    http://doc.spip.org/@spip_mysql_repair
620 * @param   string  $table     La table
621 * @param   string  $serveur   Identifiant du connecteur à utiliser
622 * @param   bool    $requeter  Inutilisé
623 * @return  bool    TRUE en cas de succès
624 */
625function spip_mysqli_repair($table, $serveur='',$requeter=true)
626{
627        return spip_mysqli_query("REPAIR TABLE $table", $serveur, $requeter);
628}
629
630/**
631 * Récupère la definition d'une table ou d'une vue MySQL
632 * colonnes, indexes, etc.
633 * au même format que la définition des tables de SPIP
634 *
635 * @link    http://doc.spip.org/@spip_mysql_showtable
636 * @param   string  $nom_table   nom de la table ou vue MySQL
637 * @param   string  $serveur     Identifiant du connecteur à utiliser
638 * @param   bool    $requeter    TRUE pour créer une représentation abstraite SPIP
639 * @return  mixed
640 */
641function spip_mysqli_showtable($nom_table, $serveur='',$requeter=true)
642{
643        $s = spip_mysqli_query("SHOW CREATE TABLE `$nom_table`", $serveur, $requeter);
644        if (!$s) return '';
645        if (!$requeter) return $s;
646
647        list(,$a) = mysqli_fetch_array($s ,MYSQLI_NUM);
648        if (preg_match("/^[^(),]*\((([^()]*\([^()]*\)[^()]*)*)\)[^()]*$/", $a, $r)){
649                $dec = $r[1];
650                if (preg_match("/^(.*?),([^,]*KEY.*)$/s", $dec, $r)) {
651                  $namedkeys = $r[2];
652                  $dec = $r[1];
653                }
654                else 
655                  $namedkeys = "";
656
657                $fields = array();
658                foreach(preg_split("/,\s*`/",$dec) as $v) {
659                  preg_match("/^\s*`?([^`]*)`\s*(.*)/",$v,$r);
660                  $fields[strtolower($r[1])] = $r[2];
661                }
662                $keys = array();
663
664                foreach(preg_split('/\)\s*,?/',$namedkeys) as $v) {
665                  if (preg_match("/^\s*([^(]*)\((.*)$/",$v,$r)) {
666                        $k = str_replace("`", '', trim($r[1]));
667                        $t = strtolower(str_replace("`", '', $r[2]));
668                        if ($k && !isset($keys[$k])) $keys[$k] = $t; else $keys[] = $t;
669                  }
670                }
671                $s->free();
672                return array('field' => $fields, 'key' => $keys);
673        }
674
675        $res = spip_mysqli_query("SHOW COLUMNS FROM `$nom_table`", $serveur);
676        if($res) {
677          $nfields = array();
678          $nkeys = array();
679          while($val = spip_mysqli_fetch($res)) {
680                $nfields[$val["Field"]] = $val['Type'];
681                if($val['Null']=='NO') {
682                  $nfields[$val["Field"]] .= ' NOT NULL'; 
683                }
684                if($val['Default'] === '0' || $val['Default']) {
685                  if(preg_match('/[A-Z_]/',$val['Default'])) {
686                        $nfields[$val["Field"]] .= ' DEFAULT '.$val['Default'];           
687                  } else {
688                        $nfields[$val["Field"]] .= " DEFAULT '".$val['Default']."'";             
689                  }
690                }
691                if($val['Extra'])
692                  $nfields[$val["Field"]] .= ' '.$val['Extra'];
693                if($val['Key'] == 'PRI') {
694                  $nkeys['PRIMARY KEY'] = $val["Field"];
695                } else if($val['Key'] == 'MUL') {
696                  $nkeys['KEY '.$val["Field"]] = $val["Field"];
697                } else if($val['Key'] == 'UNI') {
698                  $nkeys['UNIQUE KEY '.$val["Field"]] = $val["Field"];
699                }
700          }
701          $res->free();
702          return array('field' => $nfields, 'key' => $nkeys);
703        }
704        return "";
705}
706
707//
708// Récuperation des résultats
709//
710
711/**
712 * Retourne un tableau qui correspond à la ligne lue ou NULL s'il n'y a plus
713 * de lignes dans le jeu de résultats $r
714 *
715 * @link http://doc.spip.org/@spip_mysql_fetch
716 * @param   mysqli_result   $r         Le jeu de résultats
717 * @param   int             $t         Type de résultat : MYSQLI_ASSOC, MYSQLI_NUM ou MYSQLI_BOTH
718 * @param   string          $serveur   Inutilisé
719 * @param   bool            $requeter  Inutilisé
720 * @return  mixed           le résultat sous forme de tableau ou NULL si pas de résultat
721 */
722function spip_mysqli_fetch($r, $t='', $serveur='',$requeter=true) {
723        $res = NULL;
724        if (!$t) $t = MYSQLI_ASSOC;
725        if ($r) {
726            $res = $r->fetch_array($t);
727        }
728        return $res;
729}
730
731/**
732 * Déplace le pointeur interne de résultat associé au jeu de résultats
733 * représenté par result, en le faisant pointer sur la ligne spécifiée
734 * par row_number
735 *
736 * @param   mysqli_result  $r
737 * @param   int            $row_number
738 * @param   string         $serveur       Inutilisé
739 * @param   bool           $requeter       Inutilisé
740 * @return  bool           TRUE en cas de succès
741 */
742function spip_mysqli_seek($r, $row_number, $serveur='',$requeter=true) {
743        if ($r instanceof MySQLi_Result) {
744            $res = $r->data_seek($row_number);
745            $r->free();
746            return $res;
747        }
748}
749
750
751/**
752 * Retourne le nombre de résultats d'un SELECT
753 *
754 * @link    http://doc.spip.org/@spip_mysql_countsel
755 * @param   array|string   $from
756 * @param   array|string   $where
757 * @param   array|string   $groupby
758 * @param   array|string   $having
759 * @param   string         $serveur   Identifiant du connecteur à utiliser
760 * @param   bool           $requeter
761 * @return  mixed
762 */
763function spip_mysqli_countsel($from = array(), $where = array(),
764                             $groupby = '', $having = array(), $serveur='',$requeter=true)
765{
766        $c = !$groupby ? '*' : ('DISTINCT ' . (is_string($groupby) ? $groupby : join(',', $groupby)));
767
768        $r = spip_mysqli_select("COUNT($c)", $from, $where,'', '', '', $having, $serveur, $requeter);
769
770        if (!$requeter) return $r;
771        if (!($r)) return 0;
772        list($c) = $r->fetch_array(MYSQLI_NUM);
773        $r->free();
774        return $c;
775}
776
777/**
778 * Retourne une chaîne décrivant la dernière erreur
779 *
780 * @link    http://doc.spip.org/@spip_mysql_error
781 * @param   string   $serveur   Identifiant du connecteur à utiliser
782 * @return  string
783 */
784function spip_mysqli_error($serveur='') {
785        $connexion = &$GLOBALS['connexions'][$serveur ? $serveur : 0];
786        $link = $connexion['link'];
787        return $link->error . $connexion['last'];
788}
789
790/**
791 * Retourne le dernier code d'erreur produit
792 * En cas de perte de connexion avec le serveur, il ne faut pas recalculer le cache
793 *
794 * @link    http://doc.spip.org/@spip_mysql_errno
795 * @param   string  $serveur   Identifiant du connecteur à utiliser
796 * @return  int
797 */
798function spip_mysqli_errno($serveur='') {
799        $connexion = &$GLOBALS['connexions'][$serveur ? $serveur : 0];
800        $link = $connexion['link'];
801        $s = $link->errno;
802        // 2006 MySQL server has gone away
803        // 2013 Lost connection to MySQL server during query
804        if (in_array($s, array(2006,2013)))
805                define('spip_interdire_cache', true);
806        return $s;
807}
808
809/**
810 * Retourne le nombre de lignes dans le jeu de résultats.
811 *
812 * @link    http://doc.spip.org/@spip_mysql_count
813 * @param   mysqli_result   $r
814 * @param   string          $serveur   Identifiant du connecteur à utiliser
815 * @param   bool            $requeter  Inutilisé
816 * @return  int
817 */
818function spip_mysqli_count($r, $serveur='',$requeter=true) {
819        if ($r instanceof MySQLi_Result)
820            return $r->num_rows;
821}
822
823
824/**
825 * Libère la mémoire associée à un résultat
826 *
827 * @link    http://doc.spip.org/@spip_mysql_free
828 * @param   mysqli_result   $r
829 * @param   string          $serveur    Identifiant du connecteur à utiliser
830 * @param   bool            $requeter   Inutilisé
831 * @return  void
832 */
833function spip_mysqli_free($r, $serveur='',$requeter=true) {
834        if ($r instanceof MySQLi_Result)
835            $r->free();
836}
837
838/**
839 * insère une nouvelle ligne dans une table existante.
840 *
841 * @link    http://doc.spip.org/@spip_mysql_insert
842 * @param   string  $table     La table
843 * @param   string  $champs    Liste des champs
844 * @param   string  $valeurs   valeurs de chaque champ
845 * @param   string  $desc      Inutilisé
846 * @param   string  $serveur   Identifiant du connecteur à utiliser
847 * @param   bool    $requeter  Inutilisé
848 * @return  mixed   L'identifiant de l'élément inséré, FALSE en cas d'échec
849 */
850function spip_mysqli_insert($table, $champs, $valeurs, $desc='', $serveur='',$requeter=true) {
851
852        $connexion = &$GLOBALS['connexions'][$serveur ? $serveur : 0];
853        $prefixe = $connexion['prefixe'];
854        $link = $connexion['link'];
855        $db = $connexion['db'];
856
857        if ($prefixe) $table = preg_replace('/^spip/', $prefixe, $table);
858
859        if (isset($_GET['var_profile'])) {
860                include_spip('public/tracer');
861                $t = trace_query_start();
862        } else $t = 0 ;
863 
864        $connexion['last'] = $query ="INSERT INTO $table $champs VALUES $valeurs";
865#       spip_log($query);
866        if ($link->query($query))
867                $r = $link->insert_id;
868        else $r = false;
869
870        return $t ? trace_query_end($query, $t, $r, $serveur) : $r;
871
872        // return $r ? $r : (($r===0) ? -1 : 0); pb avec le multi-base.
873}
874
875/**
876 * insère une nouvelle ligne dans une table existante.
877 *
878 * @link    http://doc.spip.org/@spip_mysql_insertq
879 * @param   string  $table     La table
880 * @param   array   $couples   couples champ => valeur
881 * @param   array   $desc      description de la table
882 * @param   string  $serveur   Identifiant du connecteur à utiliser
883 * @param   bool    $requeter  Inutilisé
884 * @return  mixed   L'identifiant de l'élément inséré, FALSE en cas d'échec
885 */
886function spip_mysqli_insertq($table, $couples=array(), $desc=array(), $serveur='',$requeter=true) {
887
888        if (!$desc) $desc = description_table($table);
889        if (!$desc) $couples = array();
890        $fields =  isset($desc['field'])?$desc['field']:array();
891
892        foreach ($couples as $champ => $val) {
893                $couples[$champ]= spip_mysqli_cite($val, $fields[$champ]);
894        }
895
896        return spip_mysqli_insert($table, "(".join(',',array_keys($couples)).")", "(".join(',', $couples).")", $desc, $serveur, $requeter);
897}
898
899
900/**
901 * insère plusieures lignes dans une table existante.
902 *
903 * @link    http://doc.spip.org/@spip_mysql_insertq_multi
904 * @param   string  $table         La table
905 * @param   array   $tab_couples   couples champ => valeur
906 * @param   array   $desc          description de la table
907 * @param   string  $serveur       Identifiant du connecteur à utiliser
908 * @param   bool    $requeter      Inutilisé
909 * @return  mixed   le dernier insert_id ou FALSE en cas d'échec
910 */
911function spip_mysqli_insertq_multi($table, $tab_couples=array(), $desc=array(), $serveur='',$requeter=true) {
912
913        if (!$desc) $desc = description_table($table);
914        if (!$desc) $tab_couples = array();
915        $fields =  isset($desc['field'])?$desc['field']:array();
916       
917        $cles = "(" . join(',',array_keys($tab_couples[0])) . ')';
918        $valeurs = array();
919        foreach ($tab_couples as $couples) {
920                foreach ($couples as $champ => $val){
921                        $couples[$champ]= spip_mysqli_cite($val, $fields[$champ]);
922                }
923                $valeurs[] = '(' .join(',', $couples) . ')';
924        }
925
926        // Inserer par groupes de 100 max pour eviter un debordement de pile
927        $r = false;
928        do {
929                $ins = array_slice($valeurs,0,100);
930                $valeurs = array_slice($valeurs,100);
931                $r = spip_mysqli_insert($table, $cles, join(', ', $ins), $desc, $serveur, $requeter);
932        }  while (count($valeurs));
933
934        return $r; // dans le cas d'une table auto_increment, le dernier insert_id
935}
936
937/**
938 * Modifie une ligne dans une table existante.
939 *
940 * @link    http://doc.spip.org/@spip_mysql_update
941 * @param   string  $table    La table
942 * @param   array   $champs   tableau associatif champ => valeur
943 * @param   string  $where    Condition que doivent respecter les lignes modifiées
944 * @param   string  $desc     Inutilisé
945 * @param   string  $serveur  Identifiant du connecteur à utiliser
946 * @param   bool    $requeter
947 * @return  mixed
948 */
949function spip_mysqli_update($table, $champs, $where='', $desc='', $serveur='',$requeter=true) {
950        $set = array();
951        foreach ($champs as $champ => $val)
952                $set[] = $champ . "=$val";
953        if (!empty($set))
954                return spip_mysqli_query(
955                          calculer_mysqli_expression('UPDATE', $table, ',')
956                        . calculer_mysqli_expression('SET', $set, ',')
957                        . calculer_mysqli_expression('WHERE', $where), 
958                        $serveur, $requeter);
959}
960
961/**
962 * Modifie une ligne dans une table existante.
963 *
964 * les valeurs sont des constantes a mettre entre apostrophes
965 * sauf les expressions de date lorsqu'il s'agit de fonctions SQL (NOW etc)
966 *
967 * @link    http://doc.spip.org/@spip_mysql_updateq
968 * @param   string          $table    La table
969 * @param   array           $champs   tableau associatif champ => valeur
970 * @param   array|string    $where    Condition que doivent respecter les lignes modifiées
971 * @param   array           $desc     description des champs de la table
972 * @param   string          $serveur  Identifiant du connecteur à utiliser
973 * @param   bool            $requeter
974 * @return  mixed
975 */
976function spip_mysqli_updateq($table, $champs, $where='', $desc=array(), $serveur='',$requeter=true) {
977
978        if (!$champs) return;
979        if (!$desc) $desc = description_table($table);
980        if (!$desc) $champs = array(); else $fields =  $desc['field'];
981        $set = array();
982        foreach ($champs as $champ => $val) {
983                $set[] = $champ . '=' . spip_mysqli_cite($val, $fields[$champ]);
984        }
985        return spip_mysqli_query(
986                          calculer_mysqli_expression('UPDATE', $table, ',')
987                        . calculer_mysqli_expression('SET', $set, ',')
988                        . calculer_mysqli_expression('WHERE', $where),
989                        $serveur, $requeter);
990}
991
992/**
993 * Suppression d'une ligne d'une table existente.
994 *
995 * @link    http://doc.spip.org/@spip_mysql_delete
996 * @param   string         $table      La table
997 * @param   array|string   $where      Condition que doivent respecter les lignes supprimées
998 * @param   string         $serveur    Identifiant du connecteur à utiliser
999 * @param   bool           $requeter   Inutilisé
1000 * @return  mixed          Le nombre de lignes supprimées, FALSE en cas d'échec
1001 */
1002function spip_mysqli_delete($table, $where='', $serveur='',$requeter=true) {
1003        $res = spip_mysqli_query(
1004                          calculer_mysqli_expression('DELETE FROM', $table, ',')
1005                        . calculer_mysqli_expression('WHERE', $where),
1006                        $serveur, $requeter);
1007        if ($res){
1008                $link = $GLOBALS['connexions'][$serveur ? $serveur : 0]['link'];
1009                return $link->affected_rows;
1010        }
1011        else
1012                return false;
1013}
1014
1015/**
1016 * insère une ligne dans une table existante.
1017 *
1018 * Si une vieille ligne a la même valeur pour un index UNIQUE ou une clef primaire,
1019 * la vieille ligne sera remplacée par la nouvelle.
1020 * Le nombre de lignes affectées sera alors de 2
1021 *
1022 * @link    http://doc.spip.org/@spip_mysql_replace
1023 * @param   string  $table     La table
1024 * @param   array   $couples   tableau associatif champ => valeur
1025 * @param   array   $desc      Inutilisé
1026 * @param   string  $serveur   Identifiant du connecteur à utiliser
1027 * @param   bool    $requeter
1028 * @return  bool    FALSE en cas d'échec
1029 */
1030function spip_mysqli_replace($table, $couples, $desc=array(), $serveur='',$requeter=true) {
1031        return spip_mysqli_query("REPLACE $table (" . join(',',array_keys($couples)) . ') VALUES (' .join(',',array_map('_q', $couples)) . ')', $serveur, $requeter);
1032}
1033
1034
1035/**
1036 * Remplace plusieures lignes d'une table existente
1037 *
1038 * @link    http://doc.spip.org/@spip_mysql_replace_multi
1039 * @param   string  $table         La table
1040 * @param   array   $tab_couples   tableau associatif champ => valeur
1041 * @param   array   $desc          Inutilisé
1042 * @param   string  $serveur       Identifiant du connecteur à utiliser
1043 * @param   bool    $requeter
1044 * @return  bool
1045 */
1046function spip_mysqli_replace_multi($table, $tab_couples, $desc=array(), $serveur='',$requeter=true) {
1047        $cles = "(" . join(',',array_keys($tab_couples[0])). ')';
1048        $valeurs = array();
1049        foreach ($tab_couples as $couples) {
1050                $valeurs[] = '(' .join(',',array_map('_q', $couples)) . ')';
1051        }
1052        $valeurs = implode(', ',$valeurs);
1053        return spip_mysqli_query("REPLACE $table $cles VALUES $valeurs", $serveur, $requeter);
1054}
1055
1056
1057/**
1058 * Construit une expression pour extraire un champ multi dans une requête
1059 *
1060 * @link    http://doc.spip.org/@spip_mysql_multi
1061 * @param   string  $objet  champ MySQL contenant éventuellement un champ multi
1062 * @param   string  $lang   code langue qui determine le champ multi à récupérer
1063 * @return  string
1064 */
1065function spip_mysqli_multi ($objet, $lang) {
1066        $lengthlang = strlen("[$lang]");
1067        $posmulti = "INSTR(".$objet.", '<multi>')";
1068        $posfinmulti = "INSTR(".$objet.", '</multi>')";
1069        $debutchaine = "LEFT(".$objet.", $posmulti-1)";
1070        $finchaine = "RIGHT(".$objet.", CHAR_LENGTH(".$objet.") -(7+$posfinmulti))";
1071        $chainemulti = "TRIM(SUBSTRING(".$objet.", $posmulti+7, $posfinmulti -(7+$posmulti)))";
1072        $poslang = "INSTR($chainemulti,'[".$lang."]')";
1073        $poslang = "IF($poslang=0,INSTR($chainemulti,']')+1,$poslang+$lengthlang)";
1074        $chainelang = "TRIM(SUBSTRING(".$objet.", $posmulti+7+$poslang-1,$posfinmulti -($posmulti+7+$poslang-1) ))";
1075        $posfinlang = "INSTR(".$chainelang.", '[')";
1076        $chainelang = "IF($posfinlang>0,LEFT($chainelang,$posfinlang-1),$chainelang)";
1077        //$chainelang = "LEFT($chainelang,$posfinlang-1)";
1078        $retour = "(TRIM(IF($posmulti = 0 , ".
1079                "     TRIM(".$objet."), ".
1080                "     CONCAT( ".
1081                "          $debutchaine, ".
1082                "          IF( ".
1083                "               $poslang = 0, ".
1084                "                     $chainemulti, ".
1085                "               $chainelang".
1086                "          ), ". 
1087                "          $finchaine".
1088                "     ) ".
1089                "))) AS multi";
1090
1091        return $retour;
1092}
1093
1094/**
1095 * Contruire une chaîne permettant d'utiliser un code hexadécimal dans une requête
1096 *
1097 * @link    http://doc.spip.org/@spip_mysql_hex
1098 * @param   int    $v
1099 * @return  string
1100 */
1101function spip_mysqli_hex($v)
1102{
1103        return "0x" . $v;
1104}
1105
1106/**
1107 * Echappe une chaine pour la rendre utilisable par MySQL
1108 *
1109 * @param   mixed   $v      valeur à traiter
1110 * @param   string  $type   type
1111 * @return  mixed
1112 */
1113function spip_mysqli_quote($v, $type='')
1114{
1115        return ($type === 'int' AND !$v) ? '0' :  spip_mysqli_q($v);
1116}
1117
1118/**
1119 * Préparer un élément pour qu'il puisse être utilisé dans les requêtes plus tard
1120 *
1121 * @link http://doc.spip.org/@_q
1122 * @internal Le traitement se fait de manière récursive sur les tableaux
1123 * @param   mixed   $a
1124 * @return  mixed
1125 */
1126function spip_mysqli_q($a) {
1127        $link = &$GLOBALS['connexions'][0]['link'];
1128        return (is_numeric($a)) ? strval($a) :
1129                (!is_array($a) ? ("'" . $link->real_escape_string($a) . "'")
1130                 : join(",", array_map('spip_mysqli_q', $a)));
1131}
1132
1133
1134/**
1135 * Calcul de date
1136 *
1137 * @param   string   $champ
1138 * @param   int      $interval
1139 * @param   string   $unite
1140 * @return  string
1141 */
1142function spip_mysqli_date_proche($champ, $interval, $unite)
1143{
1144        return '('
1145        . $champ
1146        . (($interval <= 0) ? '>' : '<')
1147        . (($interval <= 0) ? 'DATE_SUB' : 'DATE_ADD')
1148        . '('
1149        . sql_quote(date('Y-m-d H:i:s'))
1150        . ', INTERVAL '
1151        . (($interval > 0) ? $interval : (0-$interval))
1152        . ' '
1153        . $unite
1154        . '))';
1155}
1156
1157/**
1158 * IN (...) est limité à 255 elements, d'où cette fonction assistante
1159 *
1160 * @link    http://doc.spip.org/@spip_mysql_in
1161 * @param   string  $val
1162 * @param   string  $valeurs
1163 * @param   string  $not
1164 * @param   string  $serveur   Identifiant du connecteur à utiliser
1165 * @param   bool    $requeter  Inutilisé
1166 * @return  string
1167 */
1168function spip_mysqli_in($val, $valeurs, $not='', $serveur='',$requeter=true) {
1169        $n = $i = 0;
1170        $in_sql ="";
1171        while ($n = strpos($valeurs, ',', $n+1)) {
1172          if ((++$i) >= 255) {
1173                        $in_sql .= "($val $not IN (" .
1174                          substr($valeurs, 0, $n) .
1175                          "))\n" .
1176                          ($not ? "AND\t" : "OR\t");
1177                        $valeurs = substr($valeurs, $n+1);
1178                        $i = $n = 0;
1179                }
1180        }
1181        $in_sql .= "($val $not IN ($valeurs))";
1182
1183        return "($in_sql)";
1184}
1185
1186/**
1187 * Echappe une chaine pour l'utiliser dans une requête MySQL
1188 *
1189 * @link    http://doc.spip.org/@spip_mysql_cite
1190 * @param   string  $v      la valeur à traiter
1191 * @param   string  $type   son type
1192 * @return  string  la chaine préparée
1193 */
1194function spip_mysqli_cite($v, $type) {
1195        $link = &$GLOBALS['connexions'][0]['link'];
1196        if (sql_test_date($type) AND preg_match('/^\w+\(/', $v)
1197        OR (sql_test_int($type)
1198                 AND (is_numeric($v)
1199                      OR (ctype_xdigit(substr($v,2))
1200                          AND $v[0]=='0' AND $v[1]=='x'))))
1201                return $v;
1202        else return  ("'" . $link->real_escape_string($v) . "'");
1203}
1204
1205
1206?>
Note: See TracBrowser for help on using the repository browser.