Overview
Comment:Déplacement de la grosse majorité de la logique de DB dans KD2, suppression des méthodes simple* dépréciées
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | dev
Files: files | file ages | folders
SHA1: 8cf95684983160d5b681b7a0c8ad5a08b426385f
User & Date: bohwaz on 2017-06-30 05:42:55
Other Links: branch diff | manifest | tags
Context
2017-07-03
07:30
Modernisation objets comptes check-in: bbf4e7b183 user: bohwaz tags: dev
2017-06-30
05:42
Déplacement de la grosse majorité de la logique de DB dans KD2, suppression des méthodes simple* dépréciées check-in: 8cf9568498 user: bohwaz tags: dev
2017-06-20
00:44
Ajout signal édition membre cf [743d7e1483] check-in: 225b85cec2 user: bohwaz tags: dev
Changes

Modified src/include/lib/Garradin/Config.php from [db09fc48dc] to [12077bf319].

279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
                if (!$champs->get($value))
                {
                    throw new UserException('Le champ '.$value.' n\'existe pas pour la configuration de '.$key);
                }

                // Vérification que le champ est unique pour l'identifiant
                if ($key == 'champ_identifiant' 
                    && !$db->simpleQuerySingle('SELECT (COUNT(DISTINCT '.$value.') = COUNT(*)) 
                        FROM membres WHERE '.$value.' IS NOT NULL AND '.$value.' != \'\';'))
                {
                    throw new UserException('Le champ '.$value.' comporte des doublons et ne peut donc pas servir comme identifiant pour la connexion.');
                }
                break;
            }
            case 'categorie_cotisations':







|







279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
                if (!$champs->get($value))
                {
                    throw new UserException('Le champ '.$value.' n\'existe pas pour la configuration de '.$key);
                }

                // Vérification que le champ est unique pour l'identifiant
                if ($key == 'champ_identifiant' 
                    && !$db->firstColumn('SELECT (COUNT(DISTINCT '.$value.') = COUNT(*)) 
                        FROM membres WHERE '.$value.' IS NOT NULL AND '.$value.' != \'\';'))
                {
                    throw new UserException('Le champ '.$value.' comporte des doublons et ne peut donc pas servir comme identifiant pour la connexion.');
                }
                break;
            }
            case 'categorie_cotisations':

Modified src/include/lib/Garradin/Cotisations.php from [afff89c062] to [8bdc434117].

63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
			{
				throw new UserException('La date de fin est invalide.');
			}
		}

		if (isset($data['id_categorie_compta']))
		{
			if ($data['id_categorie_compta'] != 0 && !$db->simpleQuerySingle('SELECT 1 FROM compta_categories WHERE id = ?;', false, (int) $data['id_categorie_compta']))
			{
				throw new UserException('Catégorie comptable inconnue');
			}

			$data['id_categorie_compta'] = (int) $data['id_categorie_compta'];
		}
	}







|







63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
			{
				throw new UserException('La date de fin est invalide.');
			}
		}

		if (isset($data['id_categorie_compta']))
		{
			if ($data['id_categorie_compta'] != 0 && !$db->firstColumn('SELECT 1 FROM compta_categories WHERE id = ?;', (int) $data['id_categorie_compta']))
			{
				throw new UserException('Catégorie comptable inconnue');
			}

			$data['id_categorie_compta'] = (int) $data['id_categorie_compta'];
		}
	}

Modified src/include/lib/Garradin/DB.php from [e4161210dd] to [ef10557379].

1
2
3
4


5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
...
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
<?php

namespace Garradin;



class DB
{
    /**
     * Application ID pour SQLite
     * @link https://www.sqlite.org/pragma.html#pragma_application_id
     */
    const APPID = 0x5da2d811;

    static protected $_instance = null;

    /**
     * Instance SQLite3
     * @var SQLite3
     */
    protected $db = null;

    /**
     * Options d'initialisation de SQLite3
     * @var null
     */
    protected $flags = null;

    /**
     * Transaction en cours?
     * @var integer
     */
    protected $transaction = 0;

    /**
     * Modes de retour des résultats
     */
    const NUM = \SQLITE3_NUM;
    const ASSOC = \SQLITE3_ASSOC;
    const BOTH = \SQLITE3_BOTH;
    const OBJ = 4; // SQLITE3_ASSOC, NUM and BOTH are 1, 2 and 3, so let's start at 4

    /**
     * Format de date utilisé pour le stockage
     */
    const DATE_FORMAT = 'Y-m-d H:i:s';

    static public function getInstance($create = false)
    {
        return self::$_instance ?: self::$_instance = new DB($create);
    }

    private function __clone()
    {
        // Désactiver le clonage, car on ne veut qu'une seule instance
    }

    public function __construct($create = false)
    {
        $this->flags = \SQLITE3_OPEN_READWRITE;

        if ($create)
        {
            $this->flags |= \SQLITE3_OPEN_CREATE;
        }

        // Fermer toute transaction en cours à la fin du script
        // pour éviter de locker la base
        // ceci est utile notamment en cas d'erreur dans le script ou de
        // max_execution_time
        register_shutdown_function(function () {
            DB::getInstance()->commit(true);
        });

        // Ne pas se connecter ici, on ne se connectera que quand une requête sera faite
    }

    public function close()
    {
        $this->db->close();
        $this->db = null;
    }

    public function connect()
    {
        if ($this->db)
        {
            return true;
        }

        $this->db = new \SQLite3(DB_FILE, $this->flags);

        $this->db->enableExceptions(true);

        // Le timeout par défaut est 0, on le met à 1 seconde, si ça ne suffit pas on augmentera plus tard
        $this->db->busyTimeout(1000);

        // Activer les contraintes des foreign keys
        $this->db->exec('PRAGMA foreign_keys = ON;');

        $this->db->createFunction('transliterate_to_ascii', ['Garradin\Utils', 'transliterateToAscii']);
        $this->db->createFunction('base64', 'base64_encode');
        $this->db->createFunction('rank', ['\KD2\DB', 'sqlite_rank']);
        $this->db->createFunction('haversine_distance', ['\KD2\DB', 'sqlite_haversine']);
    }

    public function escape($str)
    {
        // escapeString n'est pas binary safe: https://bugs.php.net/bug.php?id=62361
        $str = str_replace("\0", "\\0", $str);

        $this->connect();
        return $this->db->escapeString($str);
    }

    public function quote($str)
    {
        return '\'' . $this->escape($str) . '\'';
    }

    public function begin()
    {
        if (!$this->transaction)
        {
            $this->connect();
            $this->db->exec('BEGIN;');
        }

        $this->transaction++;

        return $this->transaction == 1 ? true : false;
    }

    public function commit($force = false)
    {
        if ($force || $this->transaction == 1)
        {
            $this->connect();
            $this->db->exec('END;');
            $this->transaction = 0;
        }
        else if ($this->transaction > 1)
        {
            $this->transaction--;
        }

        return $this->transaction ? false : true;
    }

    public function rollback()
    {
        $this->connect();
        $this->db->exec('ROLLBACK;');
        $this->transaction = 0;
        return true;
    }

    public function getArgType(&$arg, $name = '')
    {
        switch (gettype($arg))
        {
            case 'double':
                return \SQLITE3_FLOAT;
            case 'integer':
            case 'boolean':
                return \SQLITE3_INTEGER;
            case 'NULL':
                return \SQLITE3_NULL;
            case 'string':
                return \SQLITE3_TEXT;
            case 'array':
                if (count($arg) == 2 
                    && in_array($arg[0], [\SQLITE3_FLOAT, \SQLITE3_INTEGER, \SQLITE3_NULL, \SQLITE3_TEXT, \SQLITE3_BLOB]))
                {
                    $type = $arg[0];
                    $arg = $arg[1];

                    return $type;
                }
            case 'object':
                if ($arg instanceof \DateTime)
                {
                    $arg = clone $arg;
                    $arg->setTimezone(new \DateTimezone('UTC'));
                    $arg = $arg->format(self::DATE_FORMAT);
                    return \SQLITE3_TEXT;
                }
            default:
                throw new \InvalidArgumentException('Argument '.$name.' is of invalid type '.gettype($arg));
        }
    }

    /**
     * Performe une requête en utilisant les arguments contenus dans le tableau $args
     * @param  string       $query Requête SQL
     * @param  array|object $args  Arguments à utiliser comme bindings pour la requête
     * @return \SQLite3Statement|boolean Retourne un booléen si c'est une requête 
     * qui exécute une opération d'écriture, ou un statement si c'est une requête de lecture.
     *
     * Note: le fait que cette fonction retourne un booléen est un comportement
     * volontaire pour éviter un bug dans le module SQLite3 de PHP, qui provoque
     * un risque de faire des opérations en double en cas d'exécution de 
     * ->fetchResult() sur un statement d'écriture.
     */
    public function query($query, Array $args = [])
    {
        assert(is_string($query));
        assert(is_array($args) || is_object($args));
        
        // Forcer en tableau
        $args = (array) $args;

        $this->connect();
        $statement = $this->db->prepare($query);
        $nb = $statement->paramCount();

        if (!empty($args))
        {
            if (is_array($args) && count($args) == 1 && is_array(current($args)))
            {
                $args = current($args);
            }
            
            if (count($args) != $nb)
            {
                throw new \LengthException(sprintf('Arguments error: %d supplied, but %d are required by query.', 
                    count($args), $nb));
            }

            reset($args);

            if (is_int(key($args)))
            {
                foreach ($args as $i=>$arg)
                {
                    if (is_string($i))
                    {
                        throw new \InvalidArgumentException(sprintf('%s requires argument to be a keyed array, but key %s is a string.', __FUNCTION__, $i));
                    }

                    $type = $this->getArgType($arg, $i+1);
                    $statement->bindValue((int)$i+1, $arg, $type);
                }
            }
            else
            {
                foreach ($args as $key=>$value)
                {
                    if (is_int($key))
                    {
                        throw new \InvalidArgumentException(sprintf('%s requires argument to be a named-associative array, but key %s is an integer.', __FUNCTION__, $key));
                    }

                    $type = $this->getArgType($value, $key);
                    $statement->bindValue(':' . $key, $value, $type);
                }
            }
        }

        try {
            // Return a boolean for write queries to avoid accidental duplicate execution
            // see https://bugs.php.net/bug.php?id=64531
            
            $result = $statement->execute();
            return $statement->readOnly() ? $result : (bool) $result;
        }
        catch (\Exception $e)
        {
            throw new \RuntimeException($e->getMessage() . "\n" . $query . "\n" . json_encode($args, true));
        }
    }

    /**
     * Exécute une requête et retourne le résultat sous forme de tableau
     * @param  string $query Requête SQL
     * @return array Tableau contenant des objets
     *
     * Accepte un ou plusieurs arguments supplémentaires utilisés comme bindings
     * pour la clause WHERE.
     */
    public function get($query)
    {
        $args = array_slice(func_get_args(), 1);
        
        $out = [];

        foreach ($this->fetch($this->query($query, $args), self::OBJ) as $key=>$row)
        {
            $out[$key] = $row;
        }

        return $out;
    }

    /**
     * Exécute une requête et retourne le résultat sous forme de tableau associatif
     * en utilisant les deux premières colonnes retournées,
     * de la forme [colonne1 => colonne2, colonne1 => colonne2, ...]
     * @param  string $query Requête SQL
     * @return array Tableau associatif
     *
     * Accepte un ou plusieurs arguments supplémentaires utilisés comme bindings
     * pour la clause WHERE.
     */
    public function getAssoc($query)
    {
        $args = array_slice(func_get_args(), 1);

        $out = [];
        
        foreach ($this->fetchAssoc($this->query($query, $args)) as $key=>$row)
        {
            $out[$key] = $row;
        }

        return $out;
    }

    /**
     * Exécute une requête et retourne le résultat sous forme de tableau associatif
     * en utilisant la première colonne comme clé:
     * [colonne1 => (object) [colonne1 => valeur1, colonne2 => valeur2, ...], ...]
     * @param  string $query Requête SQL
     * @return array Tableau associatif contenant des objets
     *
     * Accepte un ou plusieurs arguments supplémentaires utilisés comme bindings
     * pour la clause WHERE.
     */
    public function getAssocKey($query)
    {
        $args = array_slice(func_get_args(), 1);

        $out = [];

        foreach ($this->fetchAssocKey($this->query($query, $args), self::OBJ) as $key=>$row)
        {
            $out[$key] = $row;
        }

        return $out;
    }

    /**
     * Insère une ligne dans la table $table, en remplissant avec les champs donnés
     * dans $fields (tableau associatif ou objet)
     * @param  string $table  Table où insérer
     * @param  string $fields Champs à remplir
     * @return boolean
     */
    public function insert($table, $fields)
    {
        assert(is_array($fields) || is_object($fields));

        $fields = (array) $fields;

        $fields_names = array_keys($fields);
        $query = sprintf('INSERT INTO %s (%s) VALUES (:%s);', $table, 
            implode(', ', $fields_names), implode(', :', $fields_names));

        return $this->query($query, $fields);
    }

    /**
     * Met à jour une ou plusieurs lignes de la table
     * @param  string       $table  Nom de la table
     * @param  array|object $fields Liste des champs à mettre à jour
     * @param  string       $where  Clause WHERE
     * @param  array|object $args   Arguments pour la clause WHERE
     * @return boolean
     */
    public function update($table, $fields, $where = null, $args = [])
    {
        assert(is_string($table));
        assert((is_string($where) && strlen($where)) || is_null($where));
        assert(is_array($fields) || is_object($fields));
        assert(is_array($args) || is_object($args));

        // Forcer en tableau
        $fields = (array) $fields;
        $args = (array) $args;

        // No fields to update? no need to do a query
        if (empty($fields))
        {
            return false;
        }

        $column_updates = [];
        
        foreach ($fields as $key=>$value)
        {
            // Append to arguments
            $args['field_' . $key] = $value;

            $column_updates[] = sprintf('%s = :field_%s', $key, $key);
        }

        if (is_null($where))
        {
            $where = '1';
        }

        // Assemblage de la requête
        $column_updates = implode(', ', $column_updates);
        $query = sprintf('UPDATE %s SET %s WHERE %s;', $table, $column_updates, $where);

        return $this->query($query, $args);
    }

    /**
     * Supprime une ou plusieurs lignes d'une table
     * @param  string $table Nom de la table
     * @param  string $where Clause WHERE
     * @return boolean
     *
     * Accepte un ou plusieurs arguments supplémentaires utilisés comme bindings
     * pour la clause WHERE.
     */
    public function delete($table, $where)
    {
        $query = sprintf('DELETE FROM %s WHERE %s;', $table, $where);
        return $this->query($query, array_slice(func_get_args(), 2));
    }

    /**
     * Exécute une requête SQL (alias pour query)
     * @param  string $query Requête SQL
     * @return boolean
     *
     * N'accepte PAS d'arguments supplémentaires
     */
    public function exec($query)
    {
        return $this->db->exec($query);
    }

    /**
     * Import a file containing SQL commands
     * Allows to use the statement ".import other_file.sql" to load other files
     * @param  string $file Path to file containing SQL commands
     * @return boolean
     */
................................................................................

        $sql = preg_replace_callback('/^\.import (.+\.sql)$/m', function ($match) use ($dir) {
            return file_get_contents($dir . DIRECTORY_SEPARATOR . $match[1]) . "\n";
        }, $sql);

        return $this->db->exec($sql);
    }

    /**
     * Exécute une requête et retourne la première ligne
     * @param  string $query Requête SQL
     * @return object
     *
     * Accepte un ou plusieurs arguments supplémentaires utilisés comme bindings.
     */
    public function first($query)
    {
        $res = $this->query($query, array_slice(func_get_args(), 1));

        $row = $res->fetchArray(SQLITE3_ASSOC);
        $res->finalize();

        return is_array($row) ? (object) $row : false;
    }

    /**
     * Exécute une requête et retourne la première colonne de la première ligne
     * @param  string $query Requête SQL
     * @return object
     *
     * Accepte un ou plusieurs arguments supplémentaires utilisés comme bindings.
     */
    public function firstColumn($query)
    {
        $res = $this->query($query, array_slice(func_get_args(), 1));

        $row = $res->fetchArray(\SQLITE3_NUM);

        return count($row) > 0 ? $row[0] : false;
    }

    /**
     * Récupère le résultat d'un statement
     * @param  \SQLite3Result $result Résultat de statement
     * @param  integer        $mode   Mode de récupération (BOTH, OBJ, NUM ou ASSOC)
     * @return array
     */
    public function fetch(\SQLite3Result $result, $mode = null)
    {
        $as_obj = false;

        if ($mode === self::OBJ)
        {
            $as_obj = true;
            $mode = self::ASSOC;
        }

        while ($row = $result->fetchArray($mode))
        {
            yield ($as_obj ? (object) $row : $row);
        }

        $result->finalize();
        unset($result, $row);

        return;
    }

    /**
     * Récupère le résultat d'un statement sous forme de tableau associatif
     * avec colonne1 comme clé et colonne2 comme valeur.
     * 
     * @param  \SQLite3Result $result Résultat de statement
     * @param  integer        $mode   Mode de récupération (BOTH, OBJ, NUM ou ASSOC)
     * @return array
     */
    protected function fetchAssoc(\SQLite3Result $result)
    {
        while ($row = $result->fetchArray(\SQLITE3_NUM))
        {
            yield $row[0] => $row[1];
        }

        $result->finalize();
        unset($result, $row);

        return;
    }

    /**
     * Récupère le résultat d'un statement sous forme de tableau associatif 
     * avec colonne1 comme clé et la ligne comme valeur.
     * @param  \SQLite3Result $result Résultat de statement
     * @param  integer        $mode   Mode de récupération (BOTH, OBJ, NUM ou ASSOC)
     * @return array
     */
    protected function fetchAssocKey(\SQLite3Result $result, $mode = null)
    {
        $as_obj = false;

        if ($mode === self::OBJ)
        {
            $as_obj = true;
            $mode = self::ASSOC;
        }

        while ($row = $result->fetchArray($mode))
        {
            $key = current($row);
            yield $key => ($as_obj ? (object) $row : $row);
        }

        $result->finalize();
        unset($result, $row, $key);

        return;
    }

    /**
     * Compte le nombre de lignes dans un résultat
     * @param  \SQLite3Result $result Résultat SQLite3
     * @return integer
     */
    public function countRows(\SQLite3Result $result)
    {
        $i = 0;

        while ($result->fetchArray(\SQLITE3_NUM))
        {
            $i++;
        }

        $result->reset();

        return $i;
    }

    public function lastInsertRowId()
    {
        return $this->db->lastInsertRowId();
    }

    /**
     * Préparer un statement SQLite3
     * @param  string $query Requête SQL
     * @return \SQLite3Statement
     */
    public function prepare($query)
    {
        return $this->db->prepare($query);
    }

    public function openBlob($table, $column, $rowid)
    {
        return $this->db->openBlob($table, $column, $rowid);
    }

    /**
     * @deprecated
     */
    public function simpleInsert($table, Array $fields)
    {
        return $this->insert($table, $fields);
    }

    /**
     * @deprecated
     */
    public function simpleUpdate($table, Array $fields, $where)
    {
        return $this->update($table, $fields, $where);
    }

    /**
     * @deprecated
     */
    public function simpleExec($query)
    {
        return $this->simpleStatement($query, array_slice(func_get_args(), 1));
    }

    /**
     * @deprecated
     */
    public function escapeString($str)
    {
        return $this->escape($str);
    }

    /**
     * @deprecated
     */
    public function simpleStatement($query, Array $args = [])
    {
        return $this->query($query, $args);
    }

    /**
     * @deprecated
     */
    public function simpleStatementFetch($query, $mode = null)
    {
        $args = array_slice(func_get_args(), 2);
        $result = $this->query($query, $args);
        return $this->fetch($result, $mode);
    }

    /**
     * @deprecated
     */
    public function simpleStatementFetchAssoc($query)
    {
        $args = array_slice(func_get_args(), 1);
        return $this->getAssoc($query, $args);
    }

    /**
     * @deprecated
     */
    public function simpleStatementFetchAssocKey($query, $mode = self::ASSOC)
    {
        $args = array_slice(func_get_args(), 2);
        $result = $this->query($query, $args);
        $out = [];

        while ($row = $result->fetchArray($mode))
        {
            $key = current($row);
            $out[$key] = $row;
        }

        return $out;
    }

    /**
     * @deprecated
     */
    public function queryFetch($query, $mode = null)
    {
        return $this->simpleStatementFetch($query, $mode);
    }

    /**
     * @deprecated
     */
    public function queryFetchAssoc($query)
    {
        return $this->simpleStatementFetchAssoc($query);
    }

    /**
     * @deprecated
     */
    public function queryFetchAssocKey($query, $mode = null)
    {
        return $this->simpleStatementFetchAssocKey($query, $mode);
    }

    /**
     * @deprecated
     */
    public function simpleQuerySingle($query, $all_columns = false)
    {
        $res = $this->query($query, array_slice(func_get_args(), 2));

        $row = $res->fetchArray($all_columns ? SQLITE3_ASSOC : SQLITE3_NUM);
        $res->finalize();

        if (!$all_columns)
        {
            if (isset($row[0]))
            {
                return $row[0];
            }

            return false;
        }
        else
        {
            return $row;
        }
    }
}




>
>
|









<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<












|



|


|
<
<
<
<
<
<




|

<
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<





<
<
<


<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







 







|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16































17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36






37
38
39
40
41
42




43












44
45
46
47
48



49
50









































































































































































































































































































































51
52
53
54
55
56
57
..
63
64
65
66
67
68
69
70



















































































































































































































































































<?php

namespace Garradin;

use KD2\DB_SQLite3;

class DB extends DB_SQLite3
{
    /**
     * Application ID pour SQLite
     * @link https://www.sqlite.org/pragma.html#pragma_application_id
     */
    const APPID = 0x5da2d811;

    static protected $_instance = null;
































    static public function getInstance($create = false)
    {
        return self::$_instance ?: self::$_instance = new DB($create);
    }

    private function __clone()
    {
        // Désactiver le clonage, car on ne veut qu'une seule instance
    }

    public function __construct($create = false)
    {
        $flags = \SQLITE3_OPEN_READWRITE;

        if ($create)
        {
            $flags |= \SQLITE3_OPEN_CREATE;
        }

        parent::__construct(DB_FILE, $flags);







        // Ne pas se connecter ici, on ne se connectera que quand une requête sera faite
    }

    public function connect()
    {




        parent::connect();













        // Activer les contraintes des foreign keys
        $this->db->exec('PRAGMA foreign_keys = ON;');

        $this->db->createFunction('transliterate_to_ascii', ['Garradin\Utils', 'transliterateToAscii']);



    }











































































































































































































































































































































    /**
     * Import a file containing SQL commands
     * Allows to use the statement ".import other_file.sql" to load other files
     * @param  string $file Path to file containing SQL commands
     * @return boolean
     */
................................................................................

        $sql = preg_replace_callback('/^\.import (.+\.sql)$/m', function ($match) use ($dir) {
            return file_get_contents($dir . DIRECTORY_SEPARATOR . $match[1]) . "\n";
        }, $sql);

        return $this->db->exec($sql);
    }
}



















































































































































































































































































Modified src/include/lib/Garradin/Membres.php from [c8a6c9bf0d] to [dac59ded18].

210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
            if ($db->firstColumn('SELECT 1 FROM wiki_revisions WHERE id_auteur = ?;', (int)$id)
                || $db->firstColumn('SELECT 1 FROM compta_journal WHERE id_auteur = ?;', (int)$id)
                || $db->firstColumn('SELECT 1 FROM compta_rapprochement WHERE id_auteur = ?;', (int)$id)
                || $db->firstColumn('SELECT 1 FROM membres_operations WHERE id_membre = ?;', (int)$id)
                || $db->firstColumn('SELECT 1 FROM cotisations_membres WHERE id_membre = ?;', (int)$id)
                || $db->firstColumn('SELECT 1 FROM rappels_envoyes WHERE id_membre = ?;', (int)$id)
                || $db->firstColumn('SELECT 1 FROM fichiers_membres WHERE id = ?;', (int)$id))
            # FIXME || $db->simpleQuerySingle('SELECT 1 FROM wiki_suivi WHERE id_membre = ?;', false, (int)$id))
            {
                throw new UserException('Le numéro n\'est pas modifiable pour ce membre car des contenus sont liés à ce numéro de membre (wiki, compta, etc.).');
            }
        }

        if (!empty($data['passe']) && trim($data['passe']))
        {







|







210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
            if ($db->firstColumn('SELECT 1 FROM wiki_revisions WHERE id_auteur = ?;', (int)$id)
                || $db->firstColumn('SELECT 1 FROM compta_journal WHERE id_auteur = ?;', (int)$id)
                || $db->firstColumn('SELECT 1 FROM compta_rapprochement WHERE id_auteur = ?;', (int)$id)
                || $db->firstColumn('SELECT 1 FROM membres_operations WHERE id_membre = ?;', (int)$id)
                || $db->firstColumn('SELECT 1 FROM cotisations_membres WHERE id_membre = ?;', (int)$id)
                || $db->firstColumn('SELECT 1 FROM rappels_envoyes WHERE id_membre = ?;', (int)$id)
                || $db->firstColumn('SELECT 1 FROM fichiers_membres WHERE id = ?;', (int)$id))
            # FIXME || $db->firstColumn('SELECT 1 FROM wiki_suivi WHERE id_membre = ?;', false, (int)$id))
            {
                throw new UserException('Le numéro n\'est pas modifiable pour ce membre car des contenus sont liés à ce numéro de membre (wiki, compta, etc.).');
            }
        }

        if (!empty($data['passe']) && trim($data['passe']))
        {

Modified src/include/lib/Garradin/Membres/Categories.php from [04d2da3478] to [7cda995183].

100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
        $config = Config::getInstance();

        if ($id == $config->get('categorie_membres'))
        {
            throw new UserException('Il est interdit de supprimer la catégorie définie par défaut dans la configuration.');
        }

        if ($db->simpleQuerySingle('SELECT 1 FROM membres WHERE id_categorie = ?;', false, (int)$id))
        {
            throw new UserException('La catégorie contient encore des membres, il n\'est pas possible de la supprimer.');
        }

        $db->update(
            'wiki_pages',
            [







|







100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
        $config = Config::getInstance();

        if ($id == $config->get('categorie_membres'))
        {
            throw new UserException('Il est interdit de supprimer la catégorie définie par défaut dans la configuration.');
        }

        if ($db->firstColumn('SELECT 1 FROM membres WHERE id_categorie = ?;', (int)$id))
        {
            throw new UserException('La catégorie contient encore des membres, il n\'est pas possible de la supprimer.');
        }

        $db->update(
            'wiki_pages',
            [

Modified src/include/lib/Garradin/Membres/Champs.php from [ddca1d72f4] to [d384371350].

42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
...
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
        'type',
        'title',
        'help',
        'editable',
        'list_row',
        'mandatory',
        'private',
        'options',
        'rules'
    ];

    static protected $presets = null;

	public function __toString()
	{
		return Utils::write_ini_string($this->champs);
................................................................................
	public function getTypes()
	{
		return $this->types;
	}

	public function get($champ, $key = null)
	{
        if ($champ == 'id')
        {
            return (object) ['title' => 'Numéro unique', 'type' => 'number'];
        }

        if (!property_exists($this->champs, $champ))
            return null;

        if ($key !== null)
        {
            if (property_exists($this->champs->$champ, $key))
                return $this->champs->$champ->$key;







|
<







 







<
<
<
<
<







42
43
44
45
46
47
48
49

50
51
52
53
54
55
56
...
115
116
117
118
119
120
121





122
123
124
125
126
127
128
        'type',
        'title',
        'help',
        'editable',
        'list_row',
        'mandatory',
        'private',
        'options'

    ];

    static protected $presets = null;

	public function __toString()
	{
		return Utils::write_ini_string($this->champs);
................................................................................
	public function getTypes()
	{
		return $this->types;
	}

	public function get($champ, $key = null)
	{





        if (!property_exists($this->champs, $champ))
            return null;

        if ($key !== null)
        {
            if (property_exists($this->champs->$champ, $key))
                return $this->champs->$champ->$key;

Modified src/include/lib/Garradin/Membres/Cotisations.php from [720be42932] to [df3fbb87a3].

22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
..
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66

        if (empty($data['date']) || !Utils::checkDate($data['date']))
        {
            throw new UserException('Date vide ou invalide.');
        }

		if (empty($data['id_cotisation']) 
			|| !$db->simpleQuerySingle('SELECT 1 FROM cotisations WHERE id = ?;', false, (int) $data['id_cotisation']))
		{
			throw new UserException('Cotisation inconnue.');
		}

		$data['id_cotisation'] = (int) $data['id_cotisation'];

		if (empty($data['id_membre']) 
			|| !$db->simpleQuerySingle('SELECT 1 FROM membres WHERE id = ?;', false, (int) $data['id_membre']))
		{
			throw new UserException('Membre inconnu ou invalide.');
		}

		$data['id_membre'] = (int) $data['id_membre'];

		if ($compta)
................................................................................
			if ($data['moyen_paiement'] != 'ES')
	        {
	            if (trim($data['banque']) == '')
	            {
	                throw new UserException('Le compte bancaire choisi est invalide.');
	            }

	            if (!$db->simpleQuerySingle('SELECT 1 FROM compta_comptes_bancaires WHERE id = ?;',
	            	false, $data['banque']))
	            {
	                throw new UserException('Le compte bancaire choisi n\'existe pas.');
	            }
	        }

	        if (!isset($data['montant']) || !is_numeric($data['montant']) || $data['montant'] < 0)
	        {







|







|







 







|
<







22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
..
51
52
53
54
55
56
57
58

59
60
61
62
63
64
65

        if (empty($data['date']) || !Utils::checkDate($data['date']))
        {
            throw new UserException('Date vide ou invalide.');
        }

		if (empty($data['id_cotisation']) 
			|| !$db->firstColumn('SELECT 1 FROM cotisations WHERE id = ?;', (int) $data['id_cotisation']))
		{
			throw new UserException('Cotisation inconnue.');
		}

		$data['id_cotisation'] = (int) $data['id_cotisation'];

		if (empty($data['id_membre']) 
			|| !$db->firstColumn('SELECT 1 FROM membres WHERE id = ?;', (int) $data['id_membre']))
		{
			throw new UserException('Membre inconnu ou invalide.');
		}

		$data['id_membre'] = (int) $data['id_membre'];

		if ($compta)
................................................................................
			if ($data['moyen_paiement'] != 'ES')
	        {
	            if (trim($data['banque']) == '')
	            {
	                throw new UserException('Le compte bancaire choisi est invalide.');
	            }

	            if (!$db->firstColumn('SELECT 1 FROM compta_comptes_bancaires WHERE id = ?;', $data['banque']))

	            {
	                throw new UserException('Le compte bancaire choisi n\'existe pas.');
	            }
	        }

	        if (!isset($data['montant']) || !is_numeric($data['montant']) || $data['montant'] < 0)
	        {

Modified src/include/lib/Garradin/Plugin.php from [12a29cbd97] to [778f6c082f].

284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
...
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
		}

		$db = DB::getInstance();

		// Signaux exclusifs, qui ne peuvent être attribués qu'à un seul plugin
		if (strpos($signal, 'boucle.') === 0)
		{
			$registered = $db->simpleQuerySingle('SELECT plugin FROM plugins_signaux WHERE signal = ? AND plugin != ?;', false, $signal, $this->id);

			if ($registered)
			{
				throw new \LogicException('Le signal ' . $name . ' est exclusif et déjà associé au plugin "'.$registered.'"');
			}
		}

................................................................................
	/**
	 * Liste des plugins installés (en DB)
	 * @return array Liste des plugins triés par nom
	 */
	static public function listInstalled()
	{
		$db = DB::getInstance();
		$plugins = $db->getAssocKey('SELECT id, * FROM plugins ORDER BY nom;');
		$system = explode(',', PLUGINS_SYSTEM);

		foreach ($plugins as &$row)
		{
			$row->system = in_array($row->id, $system);
		}








|







 







|







284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
...
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
		}

		$db = DB::getInstance();

		// Signaux exclusifs, qui ne peuvent être attribués qu'à un seul plugin
		if (strpos($signal, 'boucle.') === 0)
		{
			$registered = $db->firstColumn('SELECT plugin FROM plugins_signaux WHERE signal = ? AND plugin != ?;', $signal, $this->id);

			if ($registered)
			{
				throw new \LogicException('Le signal ' . $name . ' est exclusif et déjà associé au plugin "'.$registered.'"');
			}
		}

................................................................................
	/**
	 * Liste des plugins installés (en DB)
	 * @return array Liste des plugins triés par nom
	 */
	static public function listInstalled()
	{
		$db = DB::getInstance();
		$plugins = $db->getGrouped('SELECT id, * FROM plugins ORDER BY nom;');
		$system = explode(',', PLUGINS_SYSTEM);

		foreach ($plugins as &$row)
		{
			$row->system = in_array($row->id, $system);
		}

Modified src/include/lib/Garradin/Rappels.php from [c017a2b4d9] to [15cdccc907].

10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
	 * @return void
	 */
	protected function _checkFields(&$data)
	{
		$db = DB::getInstance();

        if (empty($data['id_cotisation'])
        	|| !$db->simpleQuerySingle('SELECT 1 FROM cotisations WHERE id = ?;', false, (int) $data['id_cotisation']))
        {
            throw new UserException('Cotisation inconnue.');
        }

		$data['id_cotisation'] = (int) $data['id_cotisation'];

		if ((trim($data['delai']) === '') || !is_numeric($data['delai']))







|







10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
	 * @return void
	 */
	protected function _checkFields(&$data)
	{
		$db = DB::getInstance();

        if (empty($data['id_cotisation'])
        	|| !$db->firstColumn('SELECT 1 FROM cotisations WHERE id = ?;', (int) $data['id_cotisation']))
        {
            throw new UserException('Cotisation inconnue.');
        }

		$data['id_cotisation'] = (int) $data['id_cotisation'];

		if ((trim($data['delai']) === '') || !is_numeric($data['delai']))

Modified src/include/lib/Garradin/Rappels_Envoyes.php from [4dd5416887] to [4cb0920949].

23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
..
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
...
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
...
230
231
232
233
234
235
236
237
238
239
240
	 * @return void
	 */
	protected function _checkFields(&$data)
	{
		$db = DB::getInstance();

		if (!array_key_exists('id_rappel', $data) 
			|| (!is_null($data['id_rappel']) && (empty($data['id_rappel']) || !$db->simpleQuerySingle('SELECT 1 FROM rappels WHERE id = ?;', false, (int) $data['id_rappel']))))
		{
			throw new \LogicException('ID rappel non fourni ou inexistant dans la table rappels');
		}

        if (isset($data['id_cotisation']))
        {
        	if (!$db->simpleQuerySingle('SELECT 1 FROM cotisations WHERE id = ?;', false, (int) $data['id_cotisation']))
	        {
	            throw new UserException('Cotisation inconnue.');
	        }

	        $data['id_cotisation'] = (int) $data['id_cotisation'];
	    }

        if (empty($data['id_membre'])
        	|| !$db->simpleQuerySingle('SELECT 1 FROM membres WHERE id = ?;', false, (int) $data['id_membre']))
        {
            throw new UserException('Membre inconnu.');
        }

		$data['id_membre'] = (int) $data['id_membre'];

		if (empty($data['media']) || !is_numeric($data['media']) 
................................................................................
	/**
	 * Renvoie les données sur un rappel
	 * @param  integer $id Numéro du rappel
	 * @return array     Données du rappel
	 */
	public function get($id)
	{
		return DB::getInstance()->simpleQuerySingle('SELECT * FROM rappels_envoyes WHERE id = ?;', true, (int)$id);
	}

	/**
	 * Remplacer les tags dans le contenu/sujet du mail
	 * @param  string $content Chaîne à traiter
	 * @param  array  $data    Données supplémentaires à utiliser comme tags (tableau associatif)
	 * @return string          $content dont les tags ont été remplacés par le contenu correct
................................................................................
	/**
	 * Nombre de rappels pour une cotisation donnée
	 * @param  integer $id Numéro de la cotisation
	 * @return integer Nombre de rappels envoyés
	 */
	public function countForCotisation($id)
	{
		return DB::getInstance()->simpleQuerySingle('SELECT COUNT(*) FROM rappels_envoyes
			WHERE id_rappel IN (SELECT id FROM rappels WHERE id_cotisation = ?);',
			false, (int)$id);
	}

	/**
	 * Liste des rappels envoyés pour un rappel automatique
	 * @param  integer $id Numéro du rappel
	 * @param  integer $page Numéro de page de liste
	 * @return array Liste des rappels envoyés
................................................................................
	/**
	 * Nombre de rappels envoyés pour un rappel automatique
	 * @param  integer $id Numéro du rappel
	 * @return integer Nombre de rappels envoyés pour ce rappel
	 */
	public function countForRappel($id)
	{
		return DB::getInstance()->simpleQuerySingle('SELECT COUNT(*) FROM rappels_envoyes 
			WHERE id_rappel = ?;', false, (int)$id);
	}
}







|






|








|







 







|







 







|

|







 







|
|


23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
..
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
...
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
...
230
231
232
233
234
235
236
237
238
239
240
	 * @return void
	 */
	protected function _checkFields(&$data)
	{
		$db = DB::getInstance();

		if (!array_key_exists('id_rappel', $data) 
			|| (!is_null($data['id_rappel']) && (empty($data['id_rappel']) || !$db->firstColumn('SELECT 1 FROM rappels WHERE id = ?;', (int) $data['id_rappel']))))
		{
			throw new \LogicException('ID rappel non fourni ou inexistant dans la table rappels');
		}

        if (isset($data['id_cotisation']))
        {
        	if (!$db->firstColumn('SELECT 1 FROM cotisations WHERE id = ?;', (int) $data['id_cotisation']))
	        {
	            throw new UserException('Cotisation inconnue.');
	        }

	        $data['id_cotisation'] = (int) $data['id_cotisation'];
	    }

        if (empty($data['id_membre'])
        	|| !$db->firstColumn('SELECT 1 FROM membres WHERE id = ?;', (int) $data['id_membre']))
        {
            throw new UserException('Membre inconnu.');
        }

		$data['id_membre'] = (int) $data['id_membre'];

		if (empty($data['media']) || !is_numeric($data['media']) 
................................................................................
	/**
	 * Renvoie les données sur un rappel
	 * @param  integer $id Numéro du rappel
	 * @return array     Données du rappel
	 */
	public function get($id)
	{
		return DB::getInstance()->first('SELECT * FROM rappels_envoyes WHERE id = ?;', (int)$id);
	}

	/**
	 * Remplacer les tags dans le contenu/sujet du mail
	 * @param  string $content Chaîne à traiter
	 * @param  array  $data    Données supplémentaires à utiliser comme tags (tableau associatif)
	 * @return string          $content dont les tags ont été remplacés par le contenu correct
................................................................................
	/**
	 * Nombre de rappels pour une cotisation donnée
	 * @param  integer $id Numéro de la cotisation
	 * @return integer Nombre de rappels envoyés
	 */
	public function countForCotisation($id)
	{
		return DB::getInstance()->firstColumn('SELECT COUNT(*) FROM rappels_envoyes
			WHERE id_rappel IN (SELECT id FROM rappels WHERE id_cotisation = ?);',
			(int)$id);
	}

	/**
	 * Liste des rappels envoyés pour un rappel automatique
	 * @param  integer $id Numéro du rappel
	 * @param  integer $page Numéro de page de liste
	 * @return array Liste des rappels envoyés
................................................................................
	/**
	 * Nombre de rappels envoyés pour un rappel automatique
	 * @param  integer $id Numéro du rappel
	 * @return integer Nombre de rappels envoyés pour ce rappel
	 */
	public function countForRappel($id)
	{
		return DB::getInstance()->firstColumn('SELECT COUNT(*) FROM rappels_envoyes 
			WHERE id_rappel = ?;', (int)$id);
	}
}

Modified src/include/lib/Garradin/Sauvegarde.php from [8cfee07aa3] to [e1a44c08fc].

403
404
405
406
407
408
409
410
411
412
	/**
	 * Taille occupée par les fichiers dans la base de données
	 * @return integer Taille en octets
	 */
	public function getDBFilesSize()
	{
		$db = DB::getInstance();
		return (int) $db->simpleQuerySingle('SELECT SUM(taille) FROM fichiers_contenu;');
	}
}







|


403
404
405
406
407
408
409
410
411
412
	/**
	 * Taille occupée par les fichiers dans la base de données
	 * @return integer Taille en octets
	 */
	public function getDBFilesSize()
	{
		$db = DB::getInstance();
		return (int) $db->firstColumn('SELECT SUM(taille) FROM fichiers_contenu;');
	}
}

Modified src/include/lib/Garradin/Wiki.php from [a4ede7290f] to [a7fb957d19].

470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
    {
        $db = DB::getInstance();
        $flat = [
            (object) [
                'id' => 0,
                'parent' => null,
                'titre' => 'Racine',
                'children' => $db->getAssocKey('SELECT id, parent, titre FROM wiki_pages
                    WHERE parent = ? ORDER BY transliterate_to_ascii(titre) COLLATE NOCASE;',
                    0)
            ]
        ];

        $max = 0;

        do
        {
            $parent = $db->simpleQuerySingle('SELECT parent FROM wiki_pages WHERE id = ? LIMIT 1;', false, (int)$id);

            $flat[$id] = (object) [
                'id'        =>  $id,
                'parent'    =>  $id ? (int)$parent : null,
                'titre'     =>  $id ? $this->getTitle($id) : 'Racine',
                'children'  =>  $db->getAssocKey('SELECT id, parent, titre FROM wiki_pages
                    WHERE parent = ? ORDER BY transliterate_to_ascii(titre) COLLATE NOCASE;',
                    (int)$id)
            ];

            $id = (int)$parent;
        }
        while ($id != 0 && $max++ < 20);







|









|





|







470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
    {
        $db = DB::getInstance();
        $flat = [
            (object) [
                'id' => 0,
                'parent' => null,
                'titre' => 'Racine',
                'children' => $db->getGrouped('SELECT id, parent, titre FROM wiki_pages
                    WHERE parent = ? ORDER BY transliterate_to_ascii(titre) COLLATE NOCASE;',
                    0)
            ]
        ];

        $max = 0;

        do
        {
            $parent = $db->get('SELECT parent FROM wiki_pages WHERE id = ? LIMIT 1;', (int)$id);

            $flat[$id] = (object) [
                'id'        =>  $id,
                'parent'    =>  $id ? (int)$parent : null,
                'titre'     =>  $id ? $this->getTitle($id) : 'Racine',
                'children'  =>  $db->getGrouped('SELECT id, parent, titre FROM wiki_pages
                    WHERE parent = ? ORDER BY transliterate_to_ascii(titre) COLLATE NOCASE;',
                    (int)$id)
            ];

            $id = (int)$parent;
        }
        while ($id != 0 && $max++ < 20);

Modified src/www/admin/membres/recherche.php from [88e89e6801] to [d54744adc2].

43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
    {
        Utils::redirect('/admin/membres/fiche.php?id=' . (int)$result[0]->id);
    }
}

$champs_liste = $champs->getList();

$champs_liste = array_merge(
    ['id' => ['title' => 'Numéro unique', 'type' => 'number']],
    $champs_liste
);

$champs_entete = $champs->getListedFields();

if (!array_key_exists($champ, $champs_entete))
{
    $champs_entete = array_merge(
        [$champ => $champs_liste->$champ],







<
|
<
<







43
44
45
46
47
48
49

50


51
52
53
54
55
56
57
    {
        Utils::redirect('/admin/membres/fiche.php?id=' . (int)$result[0]->id);
    }
}

$champs_liste = $champs->getList();


$champs_liste->id = (object) ['title' => 'Numéro unique', 'type' => 'number'];



$champs_entete = $champs->getListedFields();

if (!array_key_exists($champ, $champs_entete))
{
    $champs_entete = array_merge(
        [$champ => $champs_liste->$champ],