Changes In Branch dev Through [311932a7d6] Excluding Merge-Ins

This is equivalent to a diff from a0c15d9d73 to 311932a7d6

2018-10-19
19:33
Intercepter les recherches où rien n'a été sélectionné check-in: 9c5436a829 user: bohwaz tags: dev
16:40
N'utiliser FallbackResource que si disponible check-in: 311932a7d6 user: bohwaz tags: dev
2018-10-17
17:07
Utilisation de FallbackResource plutôt que RewriteRule ! check-in: 87d466f80e user: bohwaz tags: dev
2018-08-26
01:19
Evitons les conflits de noms de champs quand quelqu'un a déjà créé un champ nommé "catégorie" check-in: 96c5022fb3 user: bohwaz tags: trunk, stable
2018-08-02
22:58
Merge avec trunk check-in: e9ab0666a3 user: bohwaz tags: dev
22:52
Erreur plus explicite quand on essaye de modifier une écriture qui n'existe pas check-in: a0c15d9d73 user: bohwaz tags: trunk, stable
14:18
Ne pas afficher l'ID check-in: b5b5a3632c user: bohwaz tags: trunk, stable
2018-07-20
22:15
Erreur plus explicite quand on essaye de modifier une écriture qui n'existe pas check-in: 1f94d65a20 user: bohwaz tags: dev

Modified src/README from [fbebcdff15] to [b24af5ed67].

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

17
1
2
3
4
5
6
7
8
9
10




11

12
13










-
-
-
-

-
+

Garradin - Gestionnaire d'association libre
===========================================

Inclus les bibliothèques suivantes :

- Gibberish AES
  https://github.com/mdp/gibberish-aes
  Copyright : Mark Percival 2008 - http://markpercival.us
  Licence : MIT

- Template_Lite
  Copyright : 2003,2004,2005 by Paul Lockaby, 2005,2006 Mark Dickenson, 2005-2012 BohwaZ
  Licence : GNU GPL v2.1

- KD2fw
  Copyright : 2001-2015 BohwaZ
  Copyright : 2001-2018 BohwaZ
  Licence : GNU AGPL v3

Modified src/VERSION from [d1cc680d2c] to [f8bcdfe8a5].

1


1
-
+
0.8.5
0.9.0-rc4

Modified src/config.dist.php from [1f09233dd8] to [4b1bdaf310].

234
235
236
237
238
239
240
241

242
243
244


245
246
247
248
249


250


251

252
253

234
235
236
237
238
239
240

241
242


243
244

245



246
247
248
249
250

251
252

253







-
+

-
-
+
+
-

-
-
-
+
+

+
+
-
+

-
+
 * STARTTLS = utilisation de STARTTLS (moyennement sécurisé)
 *
 * Défaut : STARTTLS
 */
const SMTP_SECURITY = 'STARTTLS';

/**
 * Forcer la valeur de l'expéditeur des emails
 * Activer les sauvegardes automatiques
 * 
 * false, null ou vide = désactivé
 * chaîne = adresse email qui sera utilisé dans le champ From
 * Utile à désactiver si vous avez déjà des sauvegardes effectuées
 * automatiquement au niveau du système.
 * des emails envoyés
 * 
 * Utile pour les services d'envoi SMTP tiers comme Amazon SES.
 * Si activé le "From" sera : "Nom de l'association" <adresse@email.tld>
 * avec le Reply-To positionné sur l'adresse de l'association
 * Sinon les sauvegardes seront effectuées soit par la tâche cron
 * soit à l'affichage de la page d'accueil (si nécessaire).
 * 
 * Voir paramètre USE_CRON aussi
 * 
 * Défaut : false
 * Défaut : true
 */
const FORCE_EMAIL_FROM = false;
const ENABLE_AUTOMATIC_BACKUPS = true;

Modified src/cron.php from [d4a8acbd51] to [0a002d599e].

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
1
2
3
4
5
6
7
8

9
10
11
12
13

14
15
16
17
18
19
20
21
22
23
24








-
+




-











<?php

namespace Garradin;

require_once __DIR__ . '/include/init.php';

// Exécution des tâches automatiques

if ($config->get('frequence_sauvegardes') && $config->get('nombre_sauvegardes'))
if (ENABLE_AUTOMATIC_BACKUPS && $config->get('frequence_sauvegardes') && $config->get('nombre_sauvegardes'))
{
	$s = new Sauvegarde;
	$s->auto();
}


// Exécution des rappels automatiques
$rappels = new Rappels;

if ($rappels->countAll())
{
	$rappels->sendPending();
}

// Nettoyage du cache statique
Static_Cache::clean();

Deleted src/include/data/0.4.0.sql version [19c558523f].

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







































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
CREATE TABLE compta_exercices
-- Exercices
(
    id INTEGER PRIMARY KEY,

    libelle TEXT NOT NULL,

    debut TEXT NOT NULL DEFAULT CURRENT_DATE,
    fin TEXT NULL DEFAULT NULL,

    clos INTEGER NOT NULL DEFAULT 0
);


CREATE TABLE compta_comptes
-- Plan comptable
(
    id TEXT PRIMARY KEY,
    parent TEXT NOT NULL DEFAULT 0,

    libelle TEXT NOT NULL,

    position INTEGER NOT NULL, -- position actif/passif/charge/produit
    plan_comptable INTEGER NOT NULL DEFAULT 1 -- 1 = fait partie du plan comptable, 0 = a été ajouté par l'utilisateur
);

CREATE INDEX compta_comptes_parent ON compta_comptes (parent);

CREATE TABLE compta_comptes_bancaires
-- Comptes bancaires
(
    id TEXT PRIMARY KEY,

    banque TEXT NOT NULL,

    iban TEXT,
    bic TEXT,

    FOREIGN KEY(id) REFERENCES compta_comptes(id)
);

CREATE TABLE compta_journal
-- Journal des opérations comptables
(
    id INTEGER PRIMARY KEY,

    libelle TEXT NOT NULL,
    remarques TEXT,
    numero_piece TEXT, -- N° de pièce comptable

    montant REAL,

    date TEXT DEFAULT CURRENT_DATE,
    moyen_paiement TEXT DEFAULT NULL,
    numero_cheque TEXT DEFAULT NULL,

    compte_debit INTEGER, -- N° du compte dans le plan
    compte_credit INTEGER, -- N° du compte dans le plan

    id_exercice INTEGER NULL DEFAULT NULL, -- En cas de compta simple, l'exercice est permanent (NULL)
    id_auteur INTEGER NOT NULL,
    id_categorie INTEGER NULL, -- Numéro de catégorie (en mode simple)

    FOREIGN KEY(moyen_paiement) REFERENCES compta_moyens_paiement(code),
    FOREIGN KEY(compte_debit) REFERENCES compta_comptes(id),
    FOREIGN KEY(compte_credit) REFERENCES compta_comptes(id),
    FOREIGN KEY(id_exercice) REFERENCES compta_exercices(id),
    FOREIGN KEY(id_auteur) REFERENCES membres(id),
    FOREIGN KEY(id_categorie) REFERENCES compta_categories(id)
);

CREATE INDEX compta_operations_exercice ON compta_journal (id_exercice);
CREATE INDEX compta_operations_date ON compta_journal (date);
CREATE INDEX compta_operations_comptes ON compta_journal (compte_debit, compte_credit);
CREATE INDEX compta_operations_auteur ON compta_journal (id_auteur);

CREATE TABLE compta_moyens_paiement
-- Moyens de paiement
(
    code TEXT PRIMARY KEY,
    nom TEXT
);

INSERT INTO compta_moyens_paiement (code, nom) VALUES ('CB', 'Carte bleue');
INSERT INTO compta_moyens_paiement (code, nom) VALUES ('CH', 'Chèque');
INSERT INTO compta_moyens_paiement (code, nom) VALUES ('ES', 'Espèces');
INSERT INTO compta_moyens_paiement (code, nom) VALUES ('PR', 'Prélèvement');
INSERT INTO compta_moyens_paiement (code, nom) VALUES ('TI', 'TIP');
INSERT INTO compta_moyens_paiement (code, nom) VALUES ('VI', 'Virement');

CREATE TABLE compta_categories
-- Catégories pour simplifier le plan comptable
(
    id INTEGER PRIMARY KEY,
    type INTEGER DEFAULT 1, -- 1 = recette, -1 = dépense, 0 = autre (utilisé uniquement pour l'interface)

    intitule TEXT NOT NULL,
    description TEXT,

    compte TEXT NOT NULL, -- Compte affecté par cette catégorie

    FOREIGN KEY(compte) REFERENCES compta_comptes(id)
);

Deleted src/include/data/0.4.3.sql version [074264b547].

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















































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
DROP TABLE compta_exercices;

CREATE TABLE compta_exercices
-- Exercices
(
	id INTEGER PRIMARY KEY,

	libelle TEXT NOT NULL,

	debut TEXT NOT NULL DEFAULT CURRENT_DATE,
	fin TEXT NULL DEFAULT NULL,

	cloture INTEGER NOT NULL DEFAULT 0
);

INSERT INTO compta_exercices (libelle, debut, fin, cloture)
	VALUES (
		'Premier exercice',
		(CASE WHEN
			(SELECT strftime('%Y-01-01', date) FROM compta_journal ORDER BY date ASC LIMIT 1)
			IS NOT NULL THEN (SELECT strftime('%Y-01-01', date) FROM compta_journal ORDER BY date ASC LIMIT 1)
			ELSE strftime('%Y-01-01', 'now') END
		),
		(CASE WHEN
			(SELECT strftime('%Y-12-31', date) FROM compta_journal ORDER BY date DESC LIMIT 1)
			IS NOT NULL THEN (SELECT strftime('%Y-12-31', date) FROM compta_journal ORDER BY date DESC LIMIT 1)
			ELSE strftime('%Y-12-31', 'now') END
		),
		0
	);

BEGIN;
ALTER TABLE compta_journal RENAME TO old_compta_journal;
DROP INDEX compta_operations_exercice;
DROP INDEX compta_operations_date;
DROP INDEX compta_operations_comptes;
DROP INDEX compta_operations_auteur;

CREATE TABLE compta_journal
-- Journal des opérations comptables
(
	id INTEGER PRIMARY KEY,

	libelle TEXT NOT NULL,
	remarques TEXT,
	numero_piece TEXT, -- N° de pièce comptable

	montant REAL,

	date TEXT DEFAULT CURRENT_DATE,
	moyen_paiement TEXT DEFAULT NULL,
	numero_cheque TEXT DEFAULT NULL,

	compte_debit TEXT, -- N° du compte dans le plan
	compte_credit TEXT, -- N° du compte dans le plan

	id_exercice INTEGER NULL DEFAULT NULL, -- En cas de compta simple, l'exercice est permanent (NULL)
	id_auteur INTEGER NULL,
	id_categorie INTEGER NULL, -- Numéro de catégorie (en mode simple)

	FOREIGN KEY(moyen_paiement) REFERENCES compta_moyens_paiement(code),
	FOREIGN KEY(compte_debit) REFERENCES compta_comptes(id),
	FOREIGN KEY(compte_credit) REFERENCES compta_comptes(id),
	FOREIGN KEY(id_exercice) REFERENCES compta_exercices(id),
	FOREIGN KEY(id_auteur) REFERENCES membres(id),
	FOREIGN KEY(id_categorie) REFERENCES compta_categories(id)
);

CREATE INDEX compta_operations_exercice ON compta_journal (id_exercice);
CREATE INDEX compta_operations_date ON compta_journal (date);
CREATE INDEX compta_operations_comptes ON compta_journal (compte_debit, compte_credit);
CREATE INDEX compta_operations_auteur ON compta_journal (id_auteur);

INSERT INTO compta_journal SELECT * FROM old_compta_journal;

UPDATE compta_journal SET id_exercice = 1;

DROP TABLE old_compta_journal;
END;

Deleted src/include/data/0.6.0.sql version [95aac309bf].

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














































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
CREATE TABLE cotisations
-- Types de cotisations et activités
(
    id INTEGER PRIMARY KEY,
    id_categorie_compta INTEGER NULL, -- NULL si le type n'est pas associé automatiquement à la compta

    intitule TEXT NOT NULL,
    description TEXT NULL,
    montant REAL NOT NULL,

    duree INTEGER NULL, -- En jours
    debut TEXT NULL, -- timestamp
    fin TEXT NULL,

    FOREIGN KEY (id_categorie_compta) REFERENCES compta_categories (id)
);

CREATE TABLE cotisations_membres
-- Enregistrement des cotisations et activités
(
    id INTEGER NOT NULL PRIMARY KEY,
    id_membre INTEGER NOT NULL REFERENCES membres (id),
    id_cotisation INTEGER NOT NULL REFERENCES cotisations (id),

    date TEXT NOT NULL DEFAULT CURRENT_DATE
);

CREATE UNIQUE INDEX cm_unique ON cotisations_membres (id_membre, id_cotisation, date);

CREATE TABLE membres_operations
-- Liaision des enregistrement des paiements en compta
(
    id_membre INTEGER NOT NULL REFERENCES membres (id),
    id_operation INTEGER NOT NULL REFERENCES compta_journal (id),
    id_cotisation INTEGER NULL REFERENCES cotisations_membres (id),

    PRIMARY KEY (id_membre, id_operation)
);

CREATE TABLE rappels
-- Rappels de devoir renouveller une cotisation
(
    id INTEGER PRIMARY KEY,
    id_cotisation INTEGER NOT NULL REFERENCES cotisations (id),

    delai INTEGER NOT NULL, -- Délai en jours pour envoyer le rappel

    sujet TEXT NOT NULL,
    texte TEXT NOT NULL
);

CREATE TABLE rappels_envoyes
-- Enregistrement des rappels envoyés à qui et quand
(
    id INTEGER PRIMARY KEY,

    id_membre INTEGER NOT NULL REFERENCES membres (id),
    id_cotisation INTEGER NOT NULL REFERENCES cotisations (id),

    date TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,

    media INTEGER NOT NULL -- Média utilisé pour le rappel : 1 = email, 2 = courrier, 3 = autre
);

CREATE TABLE plugins
-- Plugins / extensions
(
    id TEXT PRIMARY KEY,
    officiel INTEGER NOT NULL DEFAULT 0,
    nom TEXT NOT NULL,
    description TEXT,
    auteur TEXT,
    url TEXT,
    version TEXT NOT NULL,
    menu INTEGER NOT NULL DEFAULT 0,
    config TEXT
);

-- Mise à jour des catégories

CREATE TABLE membres_categories_tmp
-- Catégories de membres
(
    id INTEGER PRIMARY KEY,
    nom TEXT,
    description TEXT,

    droit_wiki INT DEFAULT 1,
    droit_membres INT DEFAULT 1,
    droit_compta INT DEFAULT 1,
    droit_inscription INT DEFAULT 0,
    droit_connexion INT DEFAULT 1,
    droit_config INT DEFAULT 0,
    cacher INT DEFAULT 0,

    id_cotisation_obligatoire INTEGER NULL REFERENCES cotisations (id)
);

-- Remise des anciennes infos
INSERT INTO membres_categories_tmp SELECT id, nom, description, droit_wiki, droit_membres, 
    droit_compta, droit_inscription, droit_connexion, droit_config, cacher, NULL FROM membres_categories;

-- Suppression de l'ancienne table et renommage de la nouvelle
DROP TABLE membres_categories;
ALTER TABLE membres_categories_tmp RENAME TO membres_categories;

-- Ajout désactivation compte
ALTER TABLE compta_comptes ADD COLUMN desactive INTEGER NOT NULL DEFAULT 0;

PRAGMA foreign_keys = ON;

Added src/include/data/0.9.0.sql version [9f23495ff4].




































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-- Désactivation de l'accès aux membres, pour les groupes qui n'avaient que le droit de lecture
-- car maintenant ce droit permet de voir les fiches de membres complètes
UPDATE membres_categories SET droit_membres = 0 WHERE droit_membres = 1;

-- Suppression de la colonne description des catégories
ALTER TABLE membres_categories RENAME TO membres_categories_old;

-- Mise à jour table compta_rapprochement: la foreign key sur membres est passée
-- à ON DELETE SET NULL
ALTER TABLE compta_rapprochement RENAME TO compta_rapprochement_old;

-- Re-créer la table
-- Créer également les nouvelles tables email
.read schema.sql

-- Copie des données, sauf la colonne description
INSERT INTO membres_categories SELECT id, nom, droit_wiki,
	droit_membres, droit_compta, droit_inscription,
	droit_connexion, droit_config, cacher,
	id_cotisation_obligatoire FROM membres_categories_old;

-- Suppression des anciennes tables
DROP TABLE membres_categories_old;

-- Migration des données
INSERT INTO compta_rapprochement SELECT * FROM compta_rapprochement_old;
DROP TABLE compta_rapprochement_old;

-- Cette variable n'est plus utilisée
DELETE FROM config WHERE cle = 'email_envoi_automatique';

ALTER TABLE plugins ADD COLUMN menu_condition TEXT NULL;

-- Supprimer le début dans le nom des plugins
UPDATE plugins_signaux SET callback = replace(callback, 'Garradin\Plugin\', '');

Modified src/include/data/schema.sql from [80be656e5d] to [efe750515a].

9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
9
10
11
12
13
14
15

16
17
18
19
20
21
22







-







-- compta_categorie_dons => id_categorie

CREATE TABLE IF NOT EXISTS membres_categories
-- Catégories de membres
(
    id INTEGER PRIMARY KEY NOT NULL,
    nom TEXT NOT NULL,
    description TEXT NULL,

    droit_wiki INTEGER NOT NULL DEFAULT 1,
    droit_membres INTEGER NOT NULL DEFAULT 1,
    droit_compta INTEGER NOT NULL DEFAULT 1,
    droit_inscription INTEGER NOT NULL DEFAULT 0,
    droit_connexion INTEGER NOT NULL DEFAULT 1,
    droit_config INTEGER NOT NULL DEFAULT 0,
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
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







+

















-
+







    officiel INTEGER NOT NULL DEFAULT 0,
    nom TEXT NOT NULL,
    description TEXT NULL,
    auteur TEXT NULL,
    url TEXT NULL,
    version TEXT NOT NULL,
    menu INTEGER NOT NULL DEFAULT 0,
    menu_condition TEXT NULL,
    config TEXT NULL
);

CREATE TABLE IF NOT EXISTS plugins_signaux
-- Association entre plugins et signaux (hooks)
(
    signal TEXT NOT NULL,
    plugin TEXT NOT NULL REFERENCES plugins (id),
    callback TEXT NOT NULL,
    PRIMARY KEY (signal, plugin)
);

CREATE TABLE IF NOT EXISTS compta_rapprochement
-- Rapprochement entre compta et relevés de comptes
(
    id_operation INTEGER NOT NULL PRIMARY KEY REFERENCES compta_journal (id) ON DELETE CASCADE,
    date TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP CHECK (datetime(date) IS NOT NULL AND datetime(date) = date),
    id_auteur INTEGER NULL REFERENCES membres (id)
    id_auteur INTEGER NULL REFERENCES membres (id) ON DELETE SET NULL
);

CREATE TABLE IF NOT EXISTS fichiers
-- Données sur les fichiers
(
    id INTEGER NOT NULL PRIMARY KEY,
    nom TEXT NOT NULL, -- nom de fichier (par exemple image1234.jpeg)
382
383
384
385
386
387
388












382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400







+
+
+
+
+
+
+
+
+
+
+
+
CREATE TABLE IF NOT EXISTS fichiers_compta_journal
-- Associations entre fichiers et journal de compta (pièce comptable par exemple)
(
    fichier INTEGER NOT NULL REFERENCES fichiers (id),
    id INTEGER NOT NULL REFERENCES compta_journal (id),
    PRIMARY KEY(fichier, id)
);

CREATE TABLE IF NOT EXISTS recherches
-- Recherches enregistrées
(
    id INTEGER NOT NULL PRIMARY KEY,
    id_membre INTEGER NULL REFERENCES membres (id) ON DELETE CASCADE, -- Si non NULL, alors la recherche ne sera visible que par le membre associé
    intitule TEXT NOT NULL,
    creation TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP CHECK (datetime(creation) IS NOT NULL AND datetime(creation) = creation),
    cible TEXT NOT NULL, -- "membres" ou "compta_journal"
    type TEXT NOT NULL, -- "json" ou "sql"
    contenu TEXT NOT NULL
);

Modified src/include/init.php from [5ff1d716c3] to [bfe2446b5f].

105
106
107
108
109
110
111
112

113
114
115
116
117
118
119
105
106
107
108
109
110
111

112
113
114
115
116
117
118
119







-
+







    'SMTP_HOST'             => false,
    'SMTP_USER'             => null,
    'SMTP_PASSWORD'         => null,
    'SMTP_PORT'             => 587,
    'SMTP_SECURITY'         => 'STARTTLS',
    'ADMIN_URL'             => WWW_URL . 'admin/',
    'NTP_SERVER'            => 'fr.pool.ntp.org',
    'FORCE_EMAIL_FROM'      => false,
    'ENABLE_AUTOMATIC_BACKUPS' => true,
];

foreach ($default_config as $const => $value)
{
    $const = sprintf('Garradin\\%s', $const);

    if (!defined($const))

Modified src/include/lib/Garradin/Compta/Import.php from [19a1972b2f] to [d8cc440c1c].

1
2
3
4
5
6
7
8




9
10
11
12
13
14
15
16
1
2
3
4




5
6
7
8

9
10
11
12
13
14
15




-
-
-
-
+
+
+
+
-







<?php

namespace Garradin\Compta;

use \Garradin\DB;
use \Garradin\Utils;
use \Garradin\UserException;

use Garradin\DB;
use Garradin\Utils;
use Garradin\UserException;
use Garradin\Config;
use KD2\ODSWriter;

class Import
{
	protected $header = [
		'Numéro mouvement',
		'Date',
		'Type de mouvement',
25
26
27
28
29
30
31
32

33
34
35
36
37
38
39
24
25
26
27
28
29
30

31
32
33
34
35
36
37
38







-
+







		'Numéro de chèque',
		'Numéro de pièce',
		'Remarques'
	];

	protected function export($exercice)
	{
		return DB::getInstance()->prepare('SELECT
		return DB::getInstance()->iterate('SELECT
			journal.id,
			strftime(\'%d/%m/%Y\', date) AS date,
			(CASE cat.type WHEN 1 THEN \'Recette\' WHEN -1 THEN \'Dépense\' ELSE \'Autre\' END) AS type,
			(CASE cat.intitule WHEN NULL THEN \'\' ELSE cat.intitule END) AS cat,
			journal.libelle,
			montant,
			compte_debit,
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
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







-
+


-
+

-
-
+
-
-
+
-

-
-
-
-
-
+
-
-
-
+
+


-
-
-
+
+
+
-
-
-
+
-
-
-
-
-
-
-
-
-
-







			FROM compta_journal AS journal
				LEFT JOIN compta_categories AS cat ON cat.id = journal.id_categorie
				LEFT JOIN compta_comptes AS debit ON debit.id = journal.compte_debit
				LEFT JOIN compta_comptes AS credit ON credit.id = journal.compte_credit
				LEFT JOIN compta_moyens_paiement AS moyen ON moyen.code = journal.moyen_paiement
			WHERE id_exercice = '.(int)$exercice.'
			ORDER BY journal.date;
		')->execute();
		');
	}

	public function toCSV($exercice)
	protected function exportName()
	{
		$res = $this->export($exercice);

		return sprintf('Export comptabilité - %s - %s', Config::getInstance()->get('nom_asso'), date('Y-m-d'));
		$fp = fopen('php://output', 'w');

	}
		fputcsv($fp, $this->header);

		while ($row = $res->fetchArray(SQLITE3_ASSOC))
		{
			fputcsv($fp, $row);
		}

	public function toCSV($exercice)
		fclose($fp);

		return true;
	{
		return Utils::toCSV($this->exportName(), $this->export($exercice), $this->header);
	}

    public function toODS($exercice)
    {
    	$result = $this->export($exercice);
	public function toODS($exercice)
	{
		return Utils::toODS($this->exportName(), $this->export($exercice), $this->header);
        $ods = new ODSWriter;
        $ods->table_name = 'Journal';

	}
        $ods->add($this->header);

        while ($row = $result->fetchArray(SQLITE3_ASSOC))
        {
        	unset($row->passe);
        	$ods->add($row);
        }

        $ods->output();
    }

	public function fromCSV($path)
	{
		if (!file_exists($path) || !is_readable($path))
		{
			throw new \RuntimeException('Fichier inconnu : '.$path);
		}

Modified src/include/lib/Garradin/Config.php from [868d015351] to [52d2a7f213].

16
17
18
19
20
21
22





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







+
+
+
+
+







     * Singleton simple
     * @return Config
     */
    static public function getInstance()
    {
        return self::$_instance ?: self::$_instance = new Config;
    }

    static public function deleteInstance()
    {
        self::$_instance = null;
    }

    /**
     * Empêche de cloner l'objet
     * @return void
     */
    private function __clone()
    {
65
66
67
68
69
70
71


72
73
74
75
76
77
78
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85







+
+







            'champ_identite'          =>  $string,
            
            'version'                 =>  $string,
            
            'couleur1'                =>  $string,
            'couleur2'                =>  $string,
            'image_fond'              =>  $string,

            'desactiver_site'         => $bool,
        ];

        $db = DB::getInstance();

        $this->config = $db->getAssoc('SELECT cle, valeur FROM config ORDER BY cle;');

        foreach ($this->config as $key=>&$value)

Modified src/include/lib/Garradin/DB.php from [1dead0b7c1] to [69073dd36f].

22
23
24
25
26
27
28





29
30
31
32
33
34
35
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40







+
+
+
+
+







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

    public function __construct($create = false)
    {
        if (!defined('\SQLITE3_OPEN_READWRITE'))
        {
            throw new \Exception('Module SQLite3 de PHP non présent. Merci de l\'installer.');
        }

        $flags = \SQLITE3_OPEN_READWRITE;

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

45
46
47
48
49
50
51





52
53
54
55
56
57
58
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68







+
+
+
+
+







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

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

    public function close()
    {
        parent::close();
        self::$_instance = null;
    }

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

Modified src/include/lib/Garradin/Install.php from [e286e959c9] to [c6e1ea55f9].

1
2
3
4
5
6
7
8
9
10




















11
12
13
14
15
16
17
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










+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







<?php

namespace Garradin;

/**
 * Pour procéder à l'installation de l'instance Garradin
 * Utile pour automatiser l'installation sans passer par la page d'installation
 */
class Install
{
	static public function reset(Membres\Session $session, $password, array $options = [])
	{
		$config = (object) Config::getInstance()->getConfig();
		$user = $session->getUser();

		if (!$session->checkPassword($password, $user->passe))
		{
			throw new UserException('Le mot de passe ne correspond pas.');
		}

		(new Sauvegarde)->create(date('Y-m-d-His-') . 'avant-remise-a-zero');

		DB::getInstance()->close();
		Config::deleteInstance();

		unlink(DB_FILE);

		return self::install($config->nom_asso, $config->adresse_asso, $config->email_asso, 'Bureau', $user->identite, $user->email, $password, $config->site_asso);
	}

	static public function install($nom_asso, $adresse_asso, $email_asso, $nom_categorie, $nom_membre, $email_membre, $passe_membre, $site_asso = WWW_URL)
	{
		$db = DB::getInstance(true);

		// Taille de la page de DB, on force à 4096 (défaut dans les dernières
		// versions de SQLite mais pas les vieilles)
		$db->exec('PRAGMA page_size = 4096;');
35
36
37
38
39
40
41
42

43
44
45
46
47
48
49
55
56
57
58
59
60
61

62
63
64
65
66
67
68
69







-
+







		$config->setVersion(garradin_version());

		$champs = Membres\Champs::importInstall();
		$champs->save(false); // Pas de copie car pas de table membres existante

		$config->set('champ_identifiant', 'email');
		$config->set('champ_identite', 'nom');
		

		// Création catégories
		$cats = new Membres\Categories;
		$id = $cats->add([
			'nom' => 'Membres actifs',
		]);
		$config->set('categorie_membres', $id);

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
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+










-
-
-
-
+
+
-
-
-
-
-
+
+
+
+

-
-
-
-
-
+
+
+
+
+















-
+







		$ex = new Compta\Exercices;
		$ex->add([
			'libelle'   =>  'Premier exercice',
			'debut'     =>  date('Y-01-01'),
			'fin'       =>  date('Y-12-31')
		]);

		// Ajout d'une recherche avancée en exemple
		$query = [
			'query' => [[
				'operator' => 'AND',
				'conditions' => [
					[
						'column'   => 'lettre_infos',
						'operator' => '= 1',
						'values'   => [],
					],
				],
			]],
			'order' => 'numero',
			'desc' => true,
			'limit' => '10000',
		];

		$recherche = new Recherche;
		$recherche->add('Membres inscrits à la lettre d\'information', null, $recherche::TYPE_JSON, 'membres', $query);

		return $config->save();
	}

	static public function checkAndCreateDirectories()
	{
		// Vérifier que les répertoires vides existent, sinon les créer
		$paths = [DATA_ROOT, PLUGINS_ROOT, CACHE_ROOT, CACHE_ROOT . '/static', CACHE_ROOT . '/compiled'];

		foreach ($paths as $path)
		{
		    if (!file_exists($path))
		    {
		        mkdir($path, 0777, true);
		    }
			Utils::safe_mkdir($path);


		    if (!is_dir($path))
		    {
		    	throw new UserException('Le répertoire '.$path.' n\'existe pas ou n\'est pas un répertoire.');
		    }
			if (!is_dir($path))
			{
				throw new UserException('Le répertoire '.$path.' n\'existe pas ou n\'est pas un répertoire.');
			}

		    // On en profite pour vérifier qu'on peut y lire et écrire
		    if (!is_writable($path) || !is_readable($path))
		    {
		    	throw new UserException('Le répertoire '.$path.' n\'est pas accessible en lecture/écriture.');
		    }
			// On en profite pour vérifier qu'on peut y lire et écrire
			if (!is_writable($path) || !is_readable($path))
			{
				throw new UserException('Le répertoire '.$path.' n\'est pas accessible en lecture/écriture.');
			}
		}

		return true;
	}

	static public function setLocalConfig($key, $value)
	{
		$path = ROOT . DIRECTORY_SEPARATOR . 'config.local.php';
		$new_line = sprintf('const %s = %s;', $key, var_export($value, true));

		if (file_exists($path))
		{
			$config = file_get_contents($path);

			$pattern = sprintf('/^.*(?:const\s+%s|define\s*\(.*%1$s).*$/m', $key);
			

			$config = preg_replace($pattern, $new_line, $config, -1, $count);

			if (!$count)
			{
				$config = preg_replace('/\?>.*/s', '', $config);
				$config .= PHP_EOL . $new_line . PHP_EOL;
			}

Modified src/include/lib/Garradin/Membres.php from [79cc6c1e41] to [c01bb47300].

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
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







-
+

+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+


-
-
+
-

-
-
-
+
-
-
+
-
-
+
-
-
-
-
+

-
+
-
-
+
-
-
-
-
+
+
+

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
-
-
+
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-







    }

    public function getIDWithNumero($numero)
    {
        return DB::getInstance()->firstColumn('SELECT id FROM membres WHERE numero = ?;', (int) $numero);
    }

    public function search($field, $query)
    public function getSearchHeaderFields(array $result)
    {
        if (!count($result))
        {
            return false;
        }

        $db = DB::getInstance();
        $champs = Config::getInstance()->get('champs_membres');
        $fields = [];

        foreach (reset($result) as $field=>$value)
        {
            if ($config = $champs->get($field))
            {
                $fields[$field] = $config;
            }
        }

        return $fields;
    }

    public function sendMessage(array $recipients, $subject, $message, $send_copy)
    {
        $config = Config::getInstance();

        $champs = $config->get('champs_membres');

        foreach ($recipients as $recipient)
        if (!$champs->get($field))
        {
            throw new \UnexpectedValueException($field . ' is not a valid field');
        }

            Utils::sendEmail(Utils::EMAIL_CONTEXT_BULK, $recipient->email, $subject, $message, $recipient->id);
        $champ = $champs->get($field);

        }
        if ($champ->type == 'multiple')
        {

            $where = 'WHERE '.$field.' & (1 << '.(int)$query.')';
            $order = false;
        }
        elseif ($champ->type == 'tel')
        if ($send_copy)
        {
            $query = Utils::normalizePhoneNumber($query);
            Utils::sendEmail(Utils::EMAIL_CONTEXT_BULK, $config->get('email_asso'), $subject, $message);
            $query = preg_replace('!^0+!', '', $query);

        }
            if ($query == '')
            {
                return false;
            }

        return true;
    }

            $where = sprintf('WHERE %s LIKE %s', $field, $db->quote('%' . $query . '%'));
            $order = $field;
        }
        elseif (!$champs->isText($field))
        {
            $where = sprintf('WHERE %s = %s', $field, $db->quote($query));
            $order = $field;
        }
        else
        {
            // Si le champ est de type 'select' (sélecteur à choix unique), ne pas utiliser de LIKE mais valeur exacte
            // @link https://fossil.kd2.org/garradin/info/587f730b661a7ce16bad215d4bd02195e754ec57
            if ($champ->type != 'select')
            {
                $query = '%' . $query . '%';
            }

    public function listAllByCategory($id_categorie)
            $where = sprintf('WHERE transliterate_to_ascii(%s) LIKE %s', $field, $db->quote(Utils::transliterateToAscii($query)));
            $order = sprintf('transliterate_to_ascii(%s) COLLATE NOCASE', $field);
        }

    {
        $fields = array_keys((array)$champs->getListedFields());

        return DB::getInstance()->get('SELECT id, email FROM membres WHERE id_categorie = ?;', (int)$id_categorie);
        if (!in_array($field, $fields))
        {
            $fields[] = $field;
        }

        if (!in_array('email', $fields))
        {
            $fields[] = 'email';
        }

        $query = sprintf('SELECT id, id_categorie, %s, %s AS identite,
            strftime(\'%%s\', date_inscription) AS date_inscription
            FROM membres %s %s LIMIT 1000;',
            implode(', ', $fields),
            $config->get('champ_identite'),
            $where,
            $order ? 'ORDER BY ' . $order : ''
        );

        return $db->get($query);
    }

    public function listByCategory($cat, $fields, $page = 1, $order = null, $desc = false)
    {
        $begin = ($page - 1) * self::ITEMS_PER_PAGE;

        $db = DB::getInstance();
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
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447

448































































































+
+
+
+
+
+
+
+
+









-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
    }

    static protected function _deleteMembres($membres)
    {
        foreach ($membres as &$id)
        {
            $id = (int) $id;

            // Suppression des fichiers liés
            $files = Fichiers::listLinkedFiles(Fichiers::LIEN_MEMBRES, $id, null);

            foreach ($files as $file)
            {
                $file = new Fichiers($file->id, $file);
                $file->remove();
            }
        }

        Plugin::fireSignal('membre.suppression', $membres);

        $db = DB::getInstance();

        // Suppression du membre
        return $db->delete('membres', $db->where('id', $membres));
    }

}
    /**
     * @deprecated remplacer par envoyer message à tableau de membres
     */
    public function sendMessageToCategory($dest, $sujet, $message, $subscribed_only = false)
    {
        $config = Config::getInstance();

        $message .= "\n\n--\n".$config->get('nom_asso')."\n".$config->get('site_asso');

        if ($dest == 0)
            $where = 'id_categorie NOT IN (SELECT id FROM membres_categories WHERE cacher = 1)';
        else
            $where = 'id_categorie = '.(int)$dest;

        // FIXME: filtrage plus intelligent, car le champ lettre_infos peut ne pas exister
        if ($subscribed_only)
        {
            $champs = Config::getInstance()->get('champs_membres');

            if ($champs->get('lettre_infos'))
            {
                $where .= ' AND lettre_infos = 1';
            }
        }

        $db = DB::getInstance();
        $res = $db->query('SELECT email FROM membres WHERE LENGTH(email) > 0 AND '.$where.' ORDER BY id;');

        $sujet = '['.$config->get('nom_asso').'] '.$sujet;

        while ($row = $res->fetchArray(SQLITE3_ASSOC))
        {
            Utils::mail($row['email'], $sujet, $message);
        }

        return true;
    }

    public function searchSQL($query)
    {
        $db = DB::getInstance();

        if (!preg_match('/LIMIT\s+/i', $query))
        {
            $query = preg_replace('/;?\s*$/', '', $query);
            $query .= ' LIMIT 100';
        }

        if (preg_match('/;\s*(.+?)$/', $query))
        {
            throw new UserException('Une seule requête peut être envoyée en même temps.');
        }

        $st = $db->prepare($query);

        if (!$st->readOnly())
        {
            throw new UserException('Seules les requêtes en lecture sont autorisées.');
        }

        $res = $st->execute();
        $out = [];

        while ($row = $res->fetchArray(SQLITE3_ASSOC))
        {
            if (array_key_exists('passe', $row))
            {
                unset($row['passe']);
            }
            
            $out[] = $row;
        }

        return $out;
    }

    public function schemaSQL()
    {
        $db = DB::getInstance();

        $tables = [
            'membres'   =>  $db->firstColumn('SELECT sql FROM sqlite_master WHERE type = \'table\' AND name = \'membres\';'),
            'categories'=>  $db->firstColumn('SELECT sql FROM sqlite_master WHERE type = \'table\' AND name = \'membres_categories\';'),
        ];

        return $tables;
    }
}

Modified src/include/lib/Garradin/Membres/Categories.php from [af4fccf952] to [5b61a5fe1b].

45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
45
46
47
48
49
50
51





52
53
54
55
56
57
58







-
-
-
-
-







        }
    }

    public function add($data)
    {
        $this->_checkData($data);

        if (!isset($data['description']))
        {
            $data['description'] = '';
        }

        foreach ($this->droits as $key=>$value)
        {
            if (!isset($data['droit_'.$key]))
                $data['droit_'.$key] = $value;
            else
                $data['droit_'.$key] = (int)$data['droit_'.$key];
        }

Modified src/include/lib/Garradin/Membres/Champs.php from [6839687f0f] to [a027cb81e3].

248
249
250
251
252
253
254
255

256
257
258
259
260
261
262
248
249
250
251
252
253
254

255
256
257
258
259
260
261
262







-
+







            }
            elseif ($config->type == 'number' || $config->type == 'multiple')
            {
                $rules[] = 'numeric';
            }
            elseif ($config->type == 'select')
            {
                $rules[] = 'in:' . range(0, count($this->options) - 1);
                $rules[] = 'in:' . range(0, count($config->options) - 1);
            }
            elseif ($config->type == 'checkbox')
            {
                $rules[] = 'boolean';
            }

            if ($name == 'passe')
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
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







-
-
+
+

-
-
-
-
-
-
+
+
+
+
+
+


-
+

+







    /**
     * Enregistre les changements de champs en base de données
     * @param  boolean $enable_copy Recopier les anciennes champs dans les nouveaux ?
     * @return boolean true
     */
    public function save($enable_copy = true)
    {
    	$db = DB::getInstance();
    	$config = Config::getInstance();
        $db = DB::getInstance();
        $config = Config::getInstance();

    	// Champs à créer
    	$create = [
    		'id INTEGER PRIMARY KEY, -- Numéro attribué automatiquement',
    		'id_categorie INTEGER NOT NULL, -- Numéro de catégorie',
            'date_connexion TEXT NULL, -- Date de dernière connexion',
            'date_inscription TEXT NOT NULL DEFAULT CURRENT_DATE, -- Date d\'inscription',
        // Champs à créer
        $create = [
            'id INTEGER PRIMARY KEY, -- Numéro attribué automatiquement',
            'id_categorie INTEGER NOT NULL,',
            'date_connexion TEXT NULL CHECK (date_connexion IS NULL OR datetime(date_connexion) = date_connexion), -- Date de dernière connexion',
            'date_inscription TEXT NOT NULL DEFAULT CURRENT_DATE CHECK (date(date_inscription) IS NOT NULL AND date(date_inscription) = date_inscription), -- Date d\'inscription',
            'secret_otp TEXT NULL, -- Code secret pour TOTP',
            'clef_pgp TEXT NULL, -- Clé publique PGP'
    	];
        ];

        // Clés à créer, permet aussi de clôturer la syntaxe du tableau, noter l'absence de virgule dans cette ligne
        $create_keys = [
            'FOREIGN KEY (id_categorie) REFERENCES membres_categories (id)'
        ];

    	// Champs à recopier
    	$copy = [
    		'id' => 'id',

Modified src/include/lib/Garradin/Membres/Import.php from [032b787733] to [3f31ffe733].

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
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










-
-


-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+













-
-
-
-
+
-
-
+
-
-
+
-
-
-
-
-
-
-
-
-

-
+
-
+











+
+
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







-
+


-
+
+

-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+













-
-





-
+



-
+







<?php

namespace Garradin\Membres;

use Garradin\Membres;
use Garradin\Config;
use Garradin\DB;
use Garradin\Utils;
use Garradin\UserException;

use KD2\ODSWriter;

class Import
{
	/**
	 * Champs du CSV de Galette
	 * les lignes vides ('') ne seront pas proposées à l'import
	 * @var array
	 */
	public $galette_fields = [
		'Numéro',
		1,
		'Nom',
		'Prénom',
		'Pseudo',
		'Société',
		2,
		'Date de naissance',
		3,
		'Adresse, ligne 1',
		'Adresse, ligne 2',
		'Code postal',
		'Ville',
		'Pays',
		'Téléphone fixe',
		'Téléphone mobile',
		'E-Mail',
		'Site web',
		'ICQ',
		'MSN',
		'Jabber',
		'Infos (réservé administrateur)',
		'Infos (public)',
		'Profession',
		'Identifiant',
		'Mot de passe',
		'Date création fiche',
		'Date modification fiche',
		4, // activite_adh
		5, // bool_admin_adh
		6, // bool_exempt_adh
		7, // bool_display_info
		8, // date_echeance
		9, // pref_lang
		'Lieu de naissance',
		10, // GPG id
		11 // Fingerprint
	];

	/**
	 * Importer un CSV de la liste des membres depuis Galette
	 * @param  string $path              Chemin vers le CSV
	 * @param  array  $translation_table Tableau indiquant la correspondance à effectuer entre les champs
	 * de Galette et ceux de Garradin. Par exemple : ['Date création fiche' => 'date_inscription']
	 * @return boolean                   TRUE en cas de succès
	 */
	public function fromGalette($path, $translation_table)
	public function getCSVAsArray($path)
	{
		if (!file_exists($path) || !is_readable($path))
		{
			throw new \RuntimeException('Fichier inconnu : '.$path);
		}

		$fp = fopen($path, 'r');

		if (!$fp)
		{
			return false;
		}

		$db = DB::getInstance();
		$db->begin();
		$membres = new Membres;

		$delim = Utils::find_csv_delim($fp);
		$columns = array_flip($this->galette_fields);

		Utils::skip_bom($fp);
		$col = function($column) use (&$row, &$columns)
		{

			if (!isset($columns[$column]))
				return null;

			if (!isset($row[$columns[$column]]))
				return null;

			return $row[$columns[$column]];
		};

		$line = 0;
		$delim = Utils::find_csv_delim($fp);
		$out = [];
		Utils::skip_bom($fp);
		$nb_columns = null;

		while (!feof($fp))
		{
			$row = fgetcsv($fp, 4096, $delim);
			$line++;

			if (empty($row))
			{
				continue;
			}

			if (null === $nb_columns)
			{
				$nb_columns = count($row);
			}

			if (count($row) != count($columns))
			if (count($row) != $nb_columns)
			{
				throw new UserException('Erreur sur la ligne ' . $line . ' : incohérence dans le nombre de colonnes avec la première ligne.');
			}

			$out[$line] = $row;
		}

		fclose($fp);

		return $out;
	}

	/**
	 * Importer un CSV générique
	 * @param  string $path              Chemin vers le CSV
	 * @param  array  $translation_table Tableau indiquant la correspondance à effectuer entre les colonnes
	 * du CSV et les champs de Garradin. Par exemple : ['Date création fiche' => 'date_inscription']
	 * @return boolean                   TRUE en cas de succès
	 */
	public function fromArray(array $table, $translation_table, $skip_lines = 0)
	{
		$db = DB::getInstance();
		$db->begin();
		$membres = new Membres;
		$champs = Config::getInstance()->get('champs_membres');

		$nb_columns = count($translation_table);

		if ($skip_lines)
		{
			$table = array_slice($table, $skip_lines, null, true);
		}

		foreach ($table as $line => $row)
		{
			if (empty($row))
			{
				continue;
			}

			if (count($row) != $nb_columns)
			{
				$db->rollback();
				throw new UserException('Erreur sur la ligne ' . $line . ' : le nombre de colonnes est incorrect.');
			}

			$data = [];

			foreach ($translation_table as $galette=>$garradin)
			foreach ($translation_table as $column_index => $garradin_field)
			{
				// Champs qu'on ne veut pas importer
				if (empty($garradin))
				if (empty($garradin_field))
				{
					continue;

				// Concaténer plusieurs champs
				if (isset($data[$garradin]))
					$data[$garradin] .= "\n" . $col($galette);
				else
					$data[$garradin] = $col($galette);
				}

				// Concaténer plusieurs champs, si on choisit d'indiquer plusieurs fois
				// le même champ pour plusieurs colonnes (par exemple pour mettre nom et prénom
				// dans un seul champ)
				if (isset($data[$garradin_field]))
				{
					$champ = $champs->get($garradin_field);

					if ($champ->type == 'text')
					{
						$data[$garradin_field] .= ' ' . $row[$column_index];
					}
					elseif ($champ->type == 'textarea')
					{
						$data[$garradin_field] .= "\n" . $row[$column_index];
					}
					else
					{
						throw new UserException(sprintf('Erreur sur la ligne %d : impossible de concaténer des colonnes avec le champ %s : n\'est pas un champ de type texte', $line, $champ->title));
					}
				}
				else
				{
					$data[$garradin_field] = $row[$column_index];
				}
			}

			try {
				$membres->add($data, false);
			}
			catch (UserException $e)
			{
				$db->rollback();
				throw new UserException('Erreur sur la ligne ' . $line . ' : ' . $e->getMessage());
			}
		}

		$db->commit();

		fclose($fp);
		return true;
	}

	/**
	 * Importer un CSV de la liste des membres depuis un export Garradin
	 * @param  string $path 	Chemin vers le CSV
	 * @param  string $path     Chemin vers le CSV
	 * @param  int    $current_user_id
	 * @return boolean          TRUE en cas de succès
	 */
	public function fromCSV($path, $current_user_id)
	public function fromGarradinCSV($path, $current_user_id)
	{
		if (!file_exists($path) || !is_readable($path))
		{
			throw new \RuntimeException('Fichier inconnu : '.$path);
		}

		$fp = fopen($path, 'r');
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
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







-
+

-
+

-
-
+
+
+

-
-
+
+
+
+

+
-
+
-
-
+
-
-
-
-
+
-
-
-
+
-
-
-
-
+
-
-
+
-
-
-
-
+
-
-
-
+
-
-
-
-
+
+
+

-
-
-
+
+
+
-
-
-
+
-
-
+
-
-
-
-
-
-
-
-


		$db->commit();

		fclose($fp);
		return true;
	}

	protected function export()
	protected function export(array $list = null)
	{
        $db = DB::getInstance();
		$db = DB::getInstance();

        $champs = Config::getInstance()->get('champs_membres')->getKeys();
        $champs_sql = 'm.' . implode(', m.', $champs);
		$champs = Config::getInstance()->get('champs_membres')->getKeys();
		$champs_sql = 'm.' . implode(', m.', $champs);
		$where = $list ? 'WHERE ' . $db->where('m.id', $list) : '';

        $res = $db->prepare('SELECT ' . $champs_sql . ', c.nom AS categorie FROM membres AS m 
            LEFT JOIN membres_categories AS c ON m.id_categorie = c.id ORDER BY c.id;')->execute();
		$res = $db->iterate('SELECT ' . $champs_sql . ', c.nom AS categorie FROM membres AS m 
			LEFT JOIN membres_categories AS c ON m.id_categorie = c.id
			' . $where . '
			ORDER BY c.id;');

		return [
        return [array_merge($champs, ['categorie']), $res];
			array_merge($champs, ['categorie']),
	}

			$res,
    public function toCSV()
    {
    	list($champs, $result) = $this->export();

			sprintf('Export membres - %s - %s', Config::getInstance()->get('nom_asso'), date('Y-m-d')),
        $fp = fopen('php://output', 'w');
        $header = false;

		];
        while ($row = $result->fetchArray(SQLITE3_ASSOC))
        {
            unset($row->passe);

	}
            if (!$header)
            {

                fputs($fp, Utils::row_to_csv(array_keys($row)));
                $header = true;
            }

	public function toCSV(array $list = null)
            fputs($fp, Utils::row_to_csv($row));
        }

	{
        fclose($fp);

        return true;
    }
		list($champs, $result, $name) = $this->export($list);
		return Utils::toCSV($name, $result, $champs);
	}

    public function toODS()
    {
    	list($champs, $result) = $this->export();
	public function toODS(array $list = null)
	{
		list($champs, $result, $name) = $this->export($list);
        $ods = new ODSWriter;
        $ods->table_name = 'Membres';

		return Utils::toODS($name, $result, $champs);
        $ods->add($champs);

	}
        while ($row = $result->fetchArray(SQLITE3_ASSOC))
        {
        	unset($row->passe);
        	$ods->add($row);
        }

        $ods->output();
    }
}

Modified src/include/lib/Garradin/Membres/Session.php from [b9cd12a177] to [641c204780].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1
2
3
4
5
6
7
8
9
10
11
12
13

14
15
16
17
18
19
20













-







<?php

namespace Garradin\Membres;

use Garradin\Config;
use Garradin\DB;
use Garradin\Utils;
use Garradin\Membres;
use Garradin\UserException;

use const Garradin\SECRET_KEY;
use const Garradin\WWW_URL;
use const Garradin\ADMIN_URL;
use const Garradin\FORCE_EMAIL_FROM;

use KD2\Security;
use KD2\Security_OTP;
use KD2\QRCode;

class Session extends \KD2\UserSession
{
108
109
110
111
112
113
114
115

116
117
118
119

120
121


122


123
124
125
126
127
128
129
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







-
+



-
+


+
+
-
+
+








	protected function deleteAllRememberMeSelectors($user_id)
	{
		return $this->db->delete('membres_sessions', $this->db->where('id_membre', $user_id));
	}

	// Ajout de la gestion de LOCAL_LOGIN
	public function isLogged()
	public function isLogged($disable_local_login = false)
	{
		$logged = parent::isLogged();

		if (!$logged && defined('\Garradin\LOCAL_LOGIN')
		if (!$disable_local_login && defined('\Garradin\LOCAL_LOGIN')
			&& is_int(\Garradin\LOCAL_LOGIN) && \Garradin\LOCAL_LOGIN > 0)
		{
			if (!$logged || ($logged && $this->user->id != \Garradin\LOCAL_LOGIN))
			{
			$logged = $this->create(\Garradin\LOCAL_LOGIN);
				$logged = $this->create(\Garradin\LOCAL_LOGIN);
			}
		}

		return $logged;
	}

	// Ici checkOTP utilise NTP en second recours
	public function checkOTP($secret, $code)
189
190
191
192
193
194
195
196

197
198
199
200
201
202
203
204
191
192
193
194
195
196
197

198

199
200
201
202
203
204
205







-
+
-







		$query = sprintf('%s.%s.%s', $id, $expire, $hash);

		$message = "Bonjour,\n\nVous avez oublié votre mot de passe ? Pas de panique !\n\n";
		$message.= "Il vous suffit de cliquer sur le lien ci-dessous pour recevoir un nouveau mot de passe.\n\n";
		$message.= ADMIN_URL . 'password.php?c=' . $query;
		$message.= "\n\nSi vous n'avez pas demandé à recevoir ce message, ignorez-le, votre mot de passe restera inchangé.";

		Utils::mail($membre->email, '['.$config->get('nom_asso').'] Mot de passe perdu ?', $message, [], $membre->clef_pgp);
		return Utils::sendEmail(Utils::EMAIL_CONTEXT_SYSTEM, $membre->email, 'Mot de passe perdu ?', $message, $membre->id, $membre->clef_pgp);
		return true;
	}

	static public function recoverPasswordConfirm($code)
	{
		if (substr_count($code, '.') !== 2)
		{
			return false;
241
242
243
244
245
246
247
248

249
250
251
252
253
254
255
242
243
244
245
246
247
248

249
250
251
252
253
254
255
256







-
+







		$message.= "Votre nouveau mot de passe : ".$password."\n\n";
		$message.= "Si vous n'avez pas demandé à recevoir ce message, merci de nous le signaler.";

		$password = Membres::hashPassword($password);

		$db->update('membres', ['passe' => $password], 'id = :id', ['id' => (int)$id]);

		return Utils::mail($membre->email, '['.$config->get('nom_asso').'] Nouveau mot de passe', $message, [], $membre->clef_pgp);
		return Utils::sendEmail(Utils::EMAIL_CONTEXT_SYSTEM, $membre->email, 'Nouveau mot de passe', $message, $membre->id, $membre->clef_pgp);
	}

	public function editUser($data)
	{
		(new Membres)->edit($this->user->id, $data, false);
		$this->refresh();

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
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







-

+
-
-
+
+
-
-
+



-
+


-
+








		return $out;
	}

	public function sendMessage($dest, $sujet, $message, $copie = false)
	{
		$user = $this->getUser();
		$config = Config::getInstance();

		$content = "Ce message vous a été envoyé par :\n";
		$from = sprintf('"%s" <%s>', sprintf('=?UTF-8?B?%s?=', base64_encode($user->identite)), FORCE_EMAIL_FROM ?: $config->get('email_asso'));

		$content.= sprintf("%s\n%s\n\n", $user->identite, $user->email);
		$content.= str_repeat('=', 70) . "\n\n";
		$message .= "\n\n--\nCe message a été envoyé par un membre de ".$config->get('nom_asso');
		$message .= ", merci de contacter ".$config->get('email_asso')." en cas d'abus.";
		$content.= $message;

		if ($copie)
		{
			Utils::mail($from, $sujet, $message);
			Utils::sendEmail(Utils::EMAIL_CONTEXT_PRIVATE, $user->email, $sujet, $content, $user->id);
		}

		return Utils::mail($dest, $sujet, $message, ['From' => $from, 'Reply-To' => $user->email]);
		return Utils::sendEmail(Utils::EMAIL_CONTEXT_PRIVATE, $dest, $sujet, $content);
	}

	public function editSecurity(Array $data = [])
	{
		$allowed_fields = ['passe', 'clef_pgp', 'secret_otp'];

		foreach ($data as $key=>$value)

Modified src/include/lib/Garradin/Plugin.php from [a13d03f59d] to [d351935f52].

1
2
3
4
5
6


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






+
+







<?php

namespace Garradin;

class Plugin
{
	const PLUGIN_ID_SYNTAX = '[a-z]+(?:_[a-z]+)*';

	protected $id = null;
	protected $plugin = null;
	protected $config_changed = false;

	protected $mimes = [
		'css' => 'text/css',
		'gif' => 'image/gif',
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
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







-
-
-
+










+
+
-
+
+
+
+







		'pdf' => 'application/pdf',
		'png' => 'image/png',
		'swf' => 'application/shockwave-flash',
		'xml' => 'text/xml',
		'svg' => 'image/svg+xml',
	];

	static protected $signal_files = [];

	static public function getPath($id)
	static public function getPath($id, $fail_with_exception = true)
	{
		if (file_exists(PLUGINS_ROOT . '/' . $id . '.tar.gz'))
		{
			return 'phar://' . PLUGINS_ROOT . '/' . $id . '.tar.gz';
		}
		elseif (is_dir(PLUGINS_ROOT . '/' . $id))
		{
			return PLUGINS_ROOT . '/' . $id;
		}

		if ($fail_with_exception)
		{
		throw new \LogicException(sprintf('Le plugin "%s" n\'existe pas dans le répertoire des plugins.', $id));
			throw new \LogicException(sprintf('Le plugin "%s" n\'existe pas dans le répertoire des plugins.', $id));
		}

		return false;
	}

	/**
	 * Construire un objet Plugin pour un plugin
	 * @param string $id Identifiant du plugin
	 * @throws UserException Si le plugin n'est pas installé (n'existe pas en DB)
	 */
58
59
60
61
62
63
64



65
66
67
68
69
70
71
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79







+
+
+








		$this->plugin->config = json_decode($this->plugin->config);
		
		if (!is_object($this->plugin->config))
		{
			$this->plugin->config = new \stdClass;
		}

		// Juste pour vérifier que le fichier source du plugin existe bien
		self::getPath($id);

		$this->id = $id;
	}

	/**
	 * Enregistrer les changements dans la config
	 */
186
187
188
189
190
191
192
193

194
195
196
197
198
199
200
194
195
196
197
198
199
200

201
202
203
204
205
206
207
208







-
+







		$file = preg_replace('!^[./]*!', '', $file);

		if (preg_match('!(?:\.\.|[/\\\\]\.|\.[/\\\\])!', $file))
		{
			throw new \RuntimeException('Chemin de fichier incorrect.');
		}

		$forbidden = ['install.php', 'garradin_plugin.ini', 'upgrade.php', 'uninstall.php', 'signals.php'];
		$forbidden = ['install.php', 'garradin_plugin.ini', 'upgrade.php', 'uninstall.php'];

		if (in_array($file, $forbidden))
		{
			throw new UserException('Le fichier ' . $file . ' ne peut être appelé par cette méthode.');
		}

		if (!file_exists($this->path() . '/www/' . $file))
258
259
260
261
262
263
264
265

266
267
268
269
270
271
272
266
267
268
269
270
271
272

273
274
275
276
277
278
279
280







-
+







	 * Renvoie TRUE si le plugin a besoin d'être mis à jour
	 * (si la version notée dans la DB est différente de la version notée dans garradin_plugin.ini)
	 * @return boolean TRUE si le plugin doit être mis à jour, FALSE sinon
	 */
	public function needUpgrade()
	{
		$infos = (object) parse_ini_file($this->path() . '/garradin_plugin.ini', false);
		

		if (version_compare($this->plugin->version, $infos->version, '!='))
			return true;

		return false;
	}

	/**
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
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







-
-
+
+
+
+
+
+
-
-
+
+
+
-
















+
+
+
+
+
+













+
+







		{
			$plugin = $this;
			include $this->path() . '/upgrade.php';
		}

		$infos = (object) parse_ini_file($this->path() . '/garradin_plugin.ini', false);

		return DB::getInstance()->update('plugins', 
			['version' => $infos->version],
		return DB::getInstance()->update('plugins', [
			'nom'		=>	$infos->nom,
			'description'=>	$infos->description,
			'auteur'	=>	$infos->auteur,
			'url'		=>	$infos->url,
			'version'	=>	$infos->version,
			'id = :id',
			['id' => $this->id]
			'menu'		=>	(int)(bool)$infos->menu,
			'menu_condition' => $infos->menu && isset($infos->menu_condition) ? trim($infos->menu_condition) : null,
		], 'id = :id', ['id' => $this->id]);
		);
	}

	/**
	 * Associer un signal à un callback du plugin
	 * @param  string $signal   Nom du signal (par exemple boucle.agenda pour la boucle de type AGENDA)
	 * @param  mixed  $callback Callback, sous forme d'un nom de fonction ou de méthode statique
	 * @return boolean TRUE
	 */
	public function registerSignal($signal, $callback)
	{
		$callable_name = '';

		if (!is_callable($callback, true, $callable_name) || !is_string($callable_name))
		{
			throw new \LogicException('Le callback donné n\'est pas valide.');
		}

		// pour empêcher d'appeler des méthodes de Garradin après un import de base de données "hackée"
		if (strpos($callable_name, 'Garradin\\Plugin\\') !== 0)
		{
			throw new \LogicException('Le callback donné n\'utilise pas le namespace Garradin\\Plugin');
		}

		$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 ' . $signal . ' est exclusif et déjà associé au plugin "'.$registered.'"');
			}
		}

		$callable_name = str_replace('Garradin\\Plugin\\', '', $callable_name);

		$st = $db->prepare('INSERT OR REPLACE INTO plugins_signaux VALUES (:signal, :plugin, :callback);');
		$st->bindValue(':signal', $signal);
		$st->bindValue(':plugin', $this->id);
		$st->bindValue(':callback', $callable_name);
		return $st->execute();
	}
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
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







+











+
+
+
+
+







		$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);
			$row->disabled = !self::getPath($row->id, false);
		}

		return $plugins;
	}

	/**
	 * Vérifie que les plugins système sont bien installés et sinon les réinstalle
	 * @return void
	 */
	static public function checkAndInstallSystemPlugins()
	{
		if (!PLUGINS_SYSTEM)
		{
			return true;
		}

		$system = explode(',', PLUGINS_SYSTEM);

		if (count($system) == 0)
		{
			return true;
		}

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
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
441
442
443
444
445
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







-
+


-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+


















-
+





-
+







		return true;
	}

	/**
	 * Liste les plugins qui doivent être affichés dans le menu
	 * @return array Tableau associatif id => nom (ou un tableau vide si aucun plugin ne doit être affiché)
	 */
	static public function listMenu()
	static public function listMenu($user)
	{
		$db = DB::getInstance();
		return $db->getAssoc('SELECT id, nom FROM plugins WHERE menu = 1 ORDER BY nom;');
		$list = $db->getGrouped('SELECT id, nom, menu_condition FROM plugins WHERE menu = 1 ORDER BY nom;');

		foreach ($list as $id => &$row)
		{
			if (!self::getPath($row->id, false))
			{
				// Ne pas lister les plugins dont le code a disparu
				unset($list[$id]);
				continue;
			}

			if (!$row->menu_condition)
			{
				$row = $row->nom;
				continue;
			}

			$condition = strtr($row->menu_condition, [
				'{Membres::DROIT_AUCUN}' => Membres::DROIT_AUCUN,
				'{Membres::DROIT_ACCES}' => Membres::DROIT_ACCES,
				'{Membres::DROIT_ECRITURE}' => Membres::DROIT_ECRITURE,
				'{Membres::DROIT_ADMIN}' => Membres::DROIT_ADMIN,
			]);

			$condition = preg_replace_callback('/\{\$user\.(\w+)\}/', function ($m) use ($user) { return $user->{$m[1]}; }, $condition);
			$query = 'SELECT 1 WHERE ' . $condition . ';';
			$st = $db->prepare($query);

			if (!$st->readOnly())
			{
				throw new \LogicException('Requête plugin pour affichage dans le menu n\'est pas en lecture : ' . $query);
			}

			$res = $st->execute();

			if (!$res->fetchArray(\SQLITE3_NUM))
			{
				unset($list[$id]);
				continue;
			}

			$row = $row->nom;
		}

		unset($row);

		return $list;
	}

	/**
	 * Liste les plugins téléchargés mais non installés
	 * @return array Liste des plugins téléchargés
	 */
	static public function listDownloaded()
	{
		$installed = self::listInstalled();

		$list = [];
		$dir = dir(PLUGINS_ROOT);

		while ($file = $dir->read())
		{
			if (substr($file, 0, 1) == '.')
				continue;

			if (preg_match('!^([a-zA-Z0-9_.-]+)\.tar\.gz$!i', $file, $match))
			if (preg_match('!^(' . self::PLUGIN_ID_SYNTAX . ')\.tar\.gz$!', $file, $match))
			{
				// Sélectionner les archives PHAR
				$file = $match[1];
			}
			elseif (is_dir(PLUGINS_ROOT . '/' . $file)
				&& preg_match('!^([a-zA-Z0-9_.-]+)$!i', $file)
				&& preg_match('!^' . self::PLUGIN_ID_SYNTAX . '$!', $file)
				&& is_file(sprintf('%s/%s/garradin_plugin.ini', PLUGINS_ROOT, $file)))
			{
				// Rien à faire, le nom valide du plugin est déjà dans "$file"
			}
			else
			{
				// ignorer tout ce qui n'est pas un répertoire ou une archive PHAR valides
547
548
549
550
551
552
553
554

555
556
557
558
559
560
561
619
620
621
622
623
624
625

626
627
628
629
630
631
632
633







-
+







		catch (\Exception $e)
		{
			throw new UserException('Le téléchargement du plugin '.$id.' a échoué : ' . $e->getMessage());
		}

		if (!self::checkHash($id))
		{
			unlink(PLUGINS_ROOT . '/' . $id . '.tar.gz');
			Utils::safe_unlink(PLUGINS_ROOT . '/' . $id . '.tar.gz');
			throw new \RuntimeException('L\'archive du plugin '.$id.' est corrompue (le hash SHA1 ne correspond pas).');
		}

		self::install($id, true);

		return true;
	}
635
636
637
638
639
640
641

642
643
644
645
646
647
648
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721







+







			'officiel' 	=> 	(int)(bool)$official,
			'nom'		=>	$infos->nom,
			'description'=>	$infos->description,
			'auteur'	=>	$infos->auteur,
			'url'		=>	$infos->url,
			'version'	=>	$infos->version,
			'menu'		=>	(int)(bool)$infos->menu,
			'menu_condition' => $infos->menu && isset($infos->menu_condition) ? trim($infos->menu_condition) : null,
			'config'	=>	$config,
		]);

		if (file_exists($path . '/install.php'))
		{
			$plugin = new Plugin($id);
			require $plugin->path() . '/install.php';
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
738
739
740
741
742
743
744

745
746
747
748
749
750


751
752



753


754

755



756
757
758
759
760
761







-
+





-
-
+
+
-
-
-
+
-
-
+
-

-
-
-
+
+
+
+



	/**
	 * Déclenche le signal donné auprès des plugins enregistrés
	 * @param  string $signal Nom du signal
	 * @param  array  $params Paramètres du callback (array ou null)
	 * @return NULL 		  NULL si aucun plugin n'a été appelé, true sinon
	 */
	static public function fireSignal($signal, $params = null, &$return = null)
	static public function fireSignal($signal, $params = null, &$callback_return = null)
	{
		$list = DB::getInstance()->get('SELECT * FROM plugins_signaux WHERE signal = ?;', $signal);

		foreach ($list as $row)
		{
			if (!in_array($row->plugin, self::$signal_files))
			{
			$return = call_user_func_array('Garradin\\Plugin\\' . $row->callback, [&$params, &$callback_return]);

				require_once self::getPath($row->plugin) . '/signals.php';
			}

			if ($return)
			$return = call_user_func_array($row->callback, [&$params, &$return]);

			{
			if ($return)
				return $return;
		}

		return !empty($list) ? true : null;
			}
		}

		return !empty($list) ? false : null;
	}
}

Modified src/include/lib/Garradin/Rappels_Envoyes.php from [4ab2f9f384] to [fcaa354186].

146
147
148
149
150
151
152
153

154
155
156
157
158
159
160
146
147
148
149
150
151
152

153
154
155
156
157
158
159
160







-
+







		$replace['nb_jours'] = abs($replace['nb_jours']);
		$replace['delai'] = abs($replace['delai']);

		$subject = $this->replaceTagsInContent($data->sujet, $replace);
		$text = $this->replaceTagsInContent($data->texte, $replace);

		// Envoi du mail
		Utils::mail($data->email, $subject, $text);
		Utils::sendEmail(Utils::EMAIL_CONTEXT_PRIVATE, $data->email, $subject, $text, $data->id);

		// Enregistrement en DB
		$this->add([
			'id_cotisation' => $data->id_cotisation,
			'id_membre'     => $data->id,
			'id_rappel'     => $data->id_rappel,
			'media'         => Rappels_Envoyes::MEDIA_EMAIL,

Added src/include/lib/Garradin/Recherche.php version [330b3b6bc8].


























































































































































































































































































































































































































































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
441
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
<?php

namespace Garradin;

class Recherche
{
	const TYPE_JSON = 'json';
	const TYPE_SQL = 'sql';

	const TARGETS = [
		'membres',
		'compta_journal',
	];

	protected function _checkFields($data)
	{
		$db = DB::getInstance();

		if (array_key_exists('intitule', $data) && trim($data['intitule']) === '')
		{
			throw new UserException('Le champ intitulé ne peut être vide.');
		}

		if (array_key_exists('id_membre', $data) && null !== $data['id_membre'])
		{
			$data['id_membre'] = (int)$data['id_membre'];
		}

		if (array_key_exists('id_membre', $data) && null !== $data['id_membre'] && !$db->test('membres', 'id = ?', $data['id_membre']))
		{
			throw new \InvalidArgumentException('Numéro d\'utilisateur inconnu.');
		}

		if (array_key_exists('type', $data) && $data['type'] !== self::TYPE_SQL && $data['type'] !== self::TYPE_JSON)
		{
			throw new \InvalidArgumentException('Type de recherche inconnu.');
		}

		if (array_key_exists('cible', $data) && !in_array($data['cible'], self::TARGETS, true))
		{
			throw new \InvalidArgumentException('Cible de recherche invalide.');
		}

		$query = null;

		if (array_key_exists('type', $data))
		{
			if (empty($data['contenu']))
			{
				throw new UserException('Le contenu ne peut être vide.');
			}

			if ($data['type'] == self::TYPE_SQL && !is_string($data['contenu']))
			{
				throw new \InvalidArgumentException('Recherche invalide pour le type SQL');
			}

			$query = $data['contenu'];

			if ($data['type']  == self::TYPE_JSON)
			{
				if (!is_array($query))
				{
					throw new \InvalidArgumentException('Recherche invalide pour le type JSON');
				}

				$query = json_encode($query);

				if (!json_decode($query))
				{
					throw new \InvalidArgumentException('JSON invalide pour le type JSON');
				}
			}
		}

		return $query;
	}

	public function edit($id, $data)
	{
		$allowed = ['intitule', 'id_membre', 'type', 'cible', 'contenu'];

		// Supprimer les champs qui ne sont pas ceux de la BDD
		$data = array_intersect_key($data, array_flip($allowed));

		$query = $this->_checkFields($data);

		if (isset($data['contenu']))
		{
			$data['contenu'] = $query;
		}

		return DB::getInstance()->update('recherches', $data, 'id = ' . (int)$id);
	}

	public function add($intitule, $id_membre, $type, $cible, $contenu)
	{
		$data = compact('intitule', 'id_membre', 'type', 'cible', 'contenu');
		$data['contenu'] = $this->_checkFields($data);

		$db = DB::getInstance();

		$db->insert('recherches', $data);

		return $db->lastInsertRowId();
	}

	public function remove($id)
	{
		return DB::getInstance()->delete('recherches', 'id = ?', (int) $id);
	}

	public function get($id)
	{
		$r = DB::getInstance()->first('SELECT * FROM recherches WHERE id = ?;', (int) $id);

		if ($r && $r->type == self::TYPE_JSON)
		{
			$q = json_decode($r->contenu, true);

			$r->query = $q['query'];
			$r->order = $q['order'];
			$r->desc = $q['desc'];
			$r->limit = $q['limit'];

			unset($q);
		}

		return $r;
	}

	public function getList($id_membre, $cible)
	{
		return DB::getInstance()->get('SELECT id, type, intitule, type, id_membre FROM recherches 
			WHERE (id_membre IS NULL OR id_membre = ?) AND cible = ?
			ORDER BY intitule;', (int)$id_membre, $cible);
	}

	/**
	 * Lancer une recherche enregistrée
	 */
	public function search($id, $force_select = null)
	{
		$search = $this->get($id);

		if (!$search)
		{
			return false;
		}

		if ($search->type == self::TYPE_JSON)
		{
			$search->contenu = $this->buildQuery($search->cible, $search->query, $search->order, $search->desc, $search->limit);
		}

		return $this->searchSQL($search->cible, $search->contenu, $force_select);
	}

	/**
	 * Renvoie la liste des colonnes d'une cible
	 */
	public function getColumns($target)
	{
		$columns = [];
		$db = DB::getInstance();

		if ($target == 'membres')
		{
			$champs = Config::getInstance()->get('champs_membres');

			$columns['id_categorie'] = (object) [
					'realType' => 'select',
					'textMatch'=> false,
					'label'    => 'Catégorie',
					'type'     => 'enum',
					'null'     => false,
					'values'   => $db->getAssoc('SELECT id, nom FROM membres_categories ORDER BY nom;'),
				];

			foreach ($champs->getList() as $champ => $config)
			{
				$column = (object) [
					'realType' => $config->type,
					'textMatch'=> $champs->isText($champ),
					'label'    => $config->title,
					'type'     => 'text',
					'null'     => true,
				];

				if ($config->type == 'checkbox')
				{
					$column->type = 'boolean';
				}
				elseif ($config->type == 'select')
				{
					$column->type = 'enum';
					$column->values = $config->options;
				}
				elseif ($config->type == 'multiple')
				{
					$column->type = 'bitwise';
					$column->values = $config->options;
				}
				elseif ($config->type == 'date' || $config->type == 'datetime')
				{
					$column->type = $config->type;
				}
				elseif ($config->type == 'number' || $champ == 'numero')
				{
					$column->type = 'integer';
				}

				$columns[$champ] = $column;
			}
		}

		return $columns;
	}

	/**
	 * Construire une recherche SQL à partir d'un objet généré par QueryBuilder
	 * @param  string  $target Cible de la requête : membres, compta_journal, etc.
	 * @param  array   $groups Groupes de critères
	 * @param  string  $order  Ordre de tri
	 * @param  boolean $desc   Inverser le tri
	 * @param  integer $limit  Limite
	 * @return string Chaîne SQL
	 */
	public function buildQuery($target, array $groups, $order, $desc = false, $limit = 100)
	{
		if (!in_array($target, self::TARGETS, true))
		{
			throw new \InvalidArgumentException('Cible inconnue : ' . $target);
		}

		if ($target == 'membres')
		{
			$config = Config::getInstance();
			$champs = $config->get('champs_membres');
		}

		$db = DB::getInstance();
		$target_columns = $this->getColumns($target);
		$query_columns = [];

		$query_groups = [];

		foreach ($groups as $group)
		{
			if (!isset($group['conditions'], $group['operator'])
				|| !is_array($group['conditions'])
				|| ($group['operator'] != 'AND' && $group['operator'] != 'OR'))
			{
				// Ignorer les groupes de conditions invalides
				continue;
			}

			$query_group_conditions = [];

			foreach ($group['conditions'] as $condition)
			{
				if (!isset($condition['column'], $condition['operator'])
					|| (isset($condition['values']) && !is_array($condition['values'])))
				{
					// Ignorer les conditions invalides
					continue;
				}

				if (!array_key_exists($condition['column'], $target_columns))
				{
					// Ignorer une condition qui se rapporte à une colonne
					// qui n'existe pas, cas possible si on reprend une recherche
					// après avoir modifié les fiches de membres
					throw new UserException('Cette recherche fait référence à un champ qui n\'existe plus dans les fiches de membres.');
				}

				$query_columns[] = $condition['column'];
				$column = $target_columns[$condition['column']];

				if ($column->textMatch == 'text')
				{
					$query = sprintf('transliterate_to_ascii(%s) COLLATE NOCASE %s', $db->quoteIdentifier($condition['column']), $condition['operator']);
				}
				else
				{
					$query = sprintf('%s %s', $db->quoteIdentifier($condition['column']), $condition['operator']);
				}

				$values = isset($condition['values']) ? $condition['values'] : [];

				$values = array_map(['Garradin\Utils', 'transliterateToAscii'], $values);

				if ($column->type == 'tel')
				{
					// Normaliser le numéro de téléphone
					$values = array_map(['Garradin\Utils', 'normalizePhoneNumber'], $values);
				}

				// L'opérateur binaire est un peu spécial
				if ($condition['operator'] == '&')
				{
					$new_query = [];

					foreach ($values as $value)
					{
						$new_query[] = sprintf('%s (1 << %d)', $query, (int) $value);
					}

					$query = '(' . implode(' AND ', $new_query) . ')';
				}
				// Remplacement de liste
				elseif (strpos($query, '??') !== false)
				{
					$values = array_map([$db, 'quote'], $values);
					$query = str_replace('??', implode(', ', $values), $query);
				}
				// Remplacement de recherche LIKE
				elseif (preg_match('/%\?%|%\?|\?%/', $query, $match))
				{
					$value = str_replace(['%_'], ['\\%', '\\_'], reset($values));
					$value = str_replace('?', $value, $match[0]);
					$query = str_replace($match[0], sprintf('%s ESCAPE \'\\\'', $db->quote($value)), $query);
				}
				// Remplacement de paramètre
				elseif (strpos($query, '?') !== false)
				{
					$expected = substr_count($query, '?');
					$found = count($values);

					if ($expected != $found)
					{
						throw new \RuntimeException(sprintf('Operator %s expects at least %d parameters, only %d supplied', $condition['operator'], $expected, $found));
					}

					for ($i = 0; $i < $expected; $i++)
					{
						$pos = strpos($query, '?');
						$query = substr_replace($query, $db->quote(array_shift($values)), $pos, 1);
					}
				}

				$query_group_conditions[] = $query;
			}

			if (count($query_group_conditions))
			{
				$query_groups[] = implode(' ' . $group['operator'] . ' ', $query_group_conditions);
			}
		}

		$query_columns = array_unique($query_columns);

		// Ajout du champ identité si pas présent
		if ($target == 'membres' && !in_array($config->get('champ_identite'), $query_columns))
		{
			array_unshift($query_columns, $config->get('champ_identite'));
		}

		if ($target_columns[$order]->textMatch)
		{
			$order = sprintf('transliterate_to_ascii(%s) COLLATE NOCASE', $db->quoteIdentifier($order));
		}
		else
		{
			$order = $db->quoteIdentifier($order);
		}

		$query_columns = array_map([$db, 'quoteIdentifier'], $query_columns);

		$sql_query = sprintf('SELECT id, %s FROM %s WHERE %s ORDER BY %s %s LIMIT %d;',
			implode(', ', $query_columns),
			$target,
			'(' . implode(') AND (', $query_groups) . ')',
			$order,
			$desc ? 'DESC' : 'ASC',
			(int) $limit);

		return $sql_query;
	}

	/**
	 * Lancer une recherche SQL
	 */
	public function searchSQL($target, $query, $force_select = null)
	{
		if (!in_array($target, self::TARGETS, true))
		{
			throw new \InvalidArgumentException('Cible inconnue : ' . $target);
		}

		$db = DB::getInstance();

		if ($force_select)
		{
			$query = preg_replace('/^\s*SELECT.*FROM\s+/Ui', 'SELECT ' . $force_select . ' FROM ', $query);
		}

		if (!preg_match('/LIMIT\s+/i', $query))
		{
			$query = preg_replace('/;?\s*$/', '', $query);
			$query .= ' LIMIT 100';
		}

		if (preg_match('/;\s*(.+?)$/', $query))
		{
			throw new UserException('Une seule requête peut être envoyée en même temps.');
		}

		$st = $db->prepare($query);

		if (!$st->readOnly())
		{
			throw new UserException('Seules les requêtes en lecture sont autorisées.');
		}

		$res = $st->execute();
		$out = [];

		while ($row = $res->fetchArray(SQLITE3_ASSOC))
		{
			$out[] = (object) $row;
		}

		return $out;
	}

	public function schema($target)
	{
		$db = DB::getInstance();

		if ($target == 'membres')
		{
			$tables = [
				'membres'   =>  $db->firstColumn('SELECT sql FROM sqlite_master WHERE type = \'table\' AND name = \'membres\';'),
				'categories'=>  $db->firstColumn('SELECT sql FROM sqlite_master WHERE type = \'table\' AND name = \'membres_categories\';'),
			];
		}

		return $tables;
	}
}

Modified src/include/lib/Garradin/Sauvegarde.php from [ba865c4dc1] to [3200010089].

142
143
144
145
146
147
148
149

150
151
152
153
154
155
156
142
143
144
145
146
147
148

149
150
151
152
153
154
155
156







-
+







	{
		if (preg_match('!\.\.+!', $file) || !preg_match('!^[\w\d._-]+\.sqlite$!i', $file) 
			|| $file == basename(DB_FILE))
		{
			throw new UserException('Nom de fichier non valide.');
		}

		return unlink(DATA_ROOT . '/' . $file);
		return Utils::safe_unlink(DATA_ROOT . '/' . $file);
	}

	/**
	 * Renvoie sur la sortie courante le contenu du fichier de base de données courant
	 * @return boolean true
	 */
	public function dump()
227
228
229
230
231
232
233
234

235
236
237
238
239
240
241
227
228
229
230
231
232
233

234
235
236
237
238
239
240
241







-
+







			}
		}

		$r = $this->restoreDB($file['tmp_name'], $user_id);

		if ($r)
		{
			unlink($file['tmp_name']);
			Utils::safe_unlink($file['tmp_name']);
		}

		return $r;
	}

	/**
	 * Vérifie l'intégrité d'une sauvegarde Garradin

Modified src/include/lib/Garradin/Squelette.php from [a6a2b8cd25] to [fc1698f7ac].

878
879
880
881
882
883
884
885

886
887
888
889
890
891
892
893
894
895
896

897


898
899
900
901
902
903
904
878
879
880
881
882
883
884

885
886
887
888
889
890
891
892
893
894
895
896
897

898
899
900
901
902
903
904
905
906







-
+











+
-
+
+








    static private function compile_store($tpl, $content)
    {
        $path = self::compile_get_path($tpl);

        if (!file_exists(dirname($path)))
        {
            mkdir(dirname($path), 0777, true);
            Utils::safe_mkdir(dirname($path), 0777, true);
        }

        file_put_contents($path, $content);
        return true;
    }

    static public function compile_clear($tpl)
    {
        $path = self::compile_get_path($tpl);

        if (file_exists($path))
        {
            unlink($path);
            Utils::safe_unlink($path);
        }

        return true;
    }

    protected function getVariable($var)
    {
        if (isset($this->current[$var]))
951
952
953
954
955
956
957
958

959
960
961
962
963
964
965
966
967
968

969
970
971
972
973
974
975
953
954
955
956
957
958
959

960
961
962
963
964
965
966
967
968
969

970
971
972
973
974
975
976
977







-
+









-
+







    static public function resetSource($template)
    {
        if (!preg_match('!^[\w\d_-]+(?:\.[\w\d_-]+)*$!i', $template))
            return false;

        if (file_exists(DATA_ROOT . '/www/squelettes/' . $template))
        {
            return unlink(DATA_ROOT . '/www/squelettes/' . $template);
            return Utils::safe_unlink(DATA_ROOT . '/www/squelettes/' . $template);
        }

        return false;
    }

    static public function listSources()
    {
        if (!file_exists(DATA_ROOT . '/www/squelettes'))
        {
            mkdir(DATA_ROOT . '/www/squelettes', 0775, true);
            Utils::safe_mkdir(DATA_ROOT . '/www/squelettes', 0775, true);
        }

        $sources = [];

        $dir = dir(ROOT . '/www/squelettes-dist');

        while ($file = $dir->read())

Modified src/include/lib/Garradin/Squelette_Filtres.php from [b4203fb585] to [a2925f4fde].

251
252
253
254
255
256
257
258
259
260
261
262


263
264
265
266
267
268
269
251
252
253
254
255
256
257





258
259
260
261
262
263
264
265
266







-
-
-
-
-
+
+







    static public function supprimer_tags($value, $replace = '')
    {
        return preg_replace('!<[^>]*>!', $replace, $value);
    }

    static public function supprimer_skriv($value)
    {
        $value = preg_replace('!\[\[([^\]]+)(?:\|[^\]]*)?\]!U', '$1', $value);
        $value = preg_replace('!\{+([^\}]*)\}+!', '$1', $value);
        $value = preg_replace('!^=+([^=]+)=*$!mU', '$1', $value);
        $value = preg_replace('!<<<.*>>>!mU', '', $value);
        return $value;
        $value = self::formatter_texte($value);
        return self::supprimer_tags($value);
    }

    static public function couper($texte, $taille, $etc = ' (...)')
    {
        if (strlen($texte) > $taille)
        {
            $texte = substr($texte, 0, $taille);

Modified src/include/lib/Garradin/Static_Cache.php from [453588f5aa] to [1abb2553b4].

9
10
11
12
13
14
15
16

17
18
19
20
21
22
23
9
10
11
12
13
14
15

16
17
18
19
20
21
22
23







-
+








	protected static function _getCacheDir()
	{
		$dir = CACHE_ROOT . '/static';

		if (!file_exists($dir))
		{
			mkdir(CACHE_ROOT . '/static', 0777, true);
			Utils::safe_mkdir(CACHE_ROOT . '/static', 0777, true);
		}

		return CACHE_ROOT . '/static';
	}

	protected static function _getCachePath($id)
	{
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
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







-
+


















-
+








	{
		return file_exists(self::_getCachePath($id));
	}

	static public function remove($id)
	{
		$path = self::_getCachePath($id);
		return @unlink($path);
		return Utils::safe_unlink($path);
	}

	static public function clean($expire = self::CLEAN_EXPIRE)
	{
		$dir = self::_getCacheDir();
		$d = dir($dir);

		$expire = time() - $expire;

		while ($file = $d->read())
		{
			if ($file[0] == '.')
			{
				continue;
			}

			if (filemtime($dir . '/' . $file) > $expire)
			{
				unlink($dir . '/' . $file);
				Utils::safe_unlink($dir . '/' . $file);
			}
		}

		$d->close();

		return true;
	}
}

Modified src/include/lib/Garradin/Template.php from [8050b58d78] to [1f698e9c13].

15
16
17
18
19
20
21


22
23
24

25
26

27

28

29
30







31
32
33
34
35
36
37
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







+
+


-
+


+
-
+
-
+

-
+
+
+
+
+
+
+








	private function __clone()
	{
	}

	public function __construct()
	{
		parent::__construct();

		if (!file_exists(CACHE_ROOT . '/compiled'))
		{
			mkdir(CACHE_ROOT . '/compiled', 0777, true);
			Utils::safe_mkdir(CACHE_ROOT . '/compiled', 0777, true);
		}

		$this->setTemplatesDir(ROOT . '/templates');
		self::setCompileDir(CACHE_ROOT . '/compiled');
		$this->setCompiledDir(CACHE_ROOT . '/compiled');
		self::setTemplateDir(ROOT . '/templates');
		$this->setNamespace('Garradin');

		parent::__construct();
		// Hash de la version pour les éléments statiques (cache)
		// On ne peut pas utiliser la version directement comme query string
		// pour les éléments statiques (genre /admin/static/admin.css?v0.9.0)
		// car cela dévoilerait la version de Garradin utilisée, posant un souci
		// en cas de faille, on cache donc la version utilisée, chaque instance
		// aura sa propre version
		$this->assign('version_hash', substr(sha1(VERSION . ROOT . SECRET_KEY), 0, 10));

		$this->assign('www_url', WWW_URL);
		$this->assign('self_url', Utils::getSelfUrl());
		$this->assign('self_url_no_qs', Utils::getSelfUrl(false));

		$this->assign('is_logged', false);

359
360
361
362
363
364
365



366
367
368
369
370
371
372
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384







+
+
+







					{
						$binary |= 0x01 << $k;
					}
				}

				$value = $binary;
			}

			// Forcer la valeur à être un entier (depuis PHP 7.1)
			$value = (int)$value;

			foreach ($options as $k=>$v)
			{
				$b = 0x01 << (int)$k;
				$field .= '<label><input type="checkbox" name="' 
					. htmlspecialchars($params['name'], ENT_QUOTES, 'UTF-8') . '[' . (int)$k . ']" value="1" '
					. (($value & $b) ? 'checked="checked"' : '') . ' ' . $attributes . '/> ' 

Modified src/include/lib/Garradin/Utils.php from [4680da7c34] to [68bab2cb49].

1
2
3
4
5
6
7
8

9
10
11




12
13
14
15
16
17
18
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23








+



+
+
+
+







<?php

namespace Garradin;

use KD2\Security;
use KD2\Form;
use KD2\Translate;
use KD2\SMTP;
use KD2\ODSWriter;

class Utils
{
    const EMAIL_CONTEXT_BULK = 'bulk';
    const EMAIL_CONTEXT_PRIVATE = 'private';
    const EMAIL_CONTEXT_SYSTEM = 'system';

    static protected $skriv = null;

    static private $french_date_names = [
        'January'=>'Janvier', 'February'=>'Février', 'March'=>'Mars', 'April'=>'Avril', 'May'=>'Mai',
        'June'=>'Juin', 'July'=>'Juillet', 'August'=>'Août', 'September'=>'Septembre', 'October'=>'Octobre',
        'November'=>'Novembre', 'December'=>'Décembre', 'Monday'=>'Lundi', 'Tuesday'=>'Mardi', 'Wednesday'=>'Mercredi',
        'Thursday'=>'Jeudi','Friday'=>'Vendredi','Saturday'=>'Samedi','Sunday'=>'Dimanche',
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
441
442
443
444
445
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
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







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
















-
-
-
+
-






+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







        $str = preg_replace('/<em>(\V*?)<\/em>/', '\'\'$1\'\'', $str);
        $str = preg_replace('/<li>(\V*?)<\/li>/', '* $1', $str);
        $str = preg_replace('/<ul>|<\/ul>/', '', $str);
        $str = preg_replace('/<a href="([^"]*?)">(\V*?)<\/a>/', '[[$2 | $1]]', $str);
        return $str;
    }

    static public function mail($to, $subject, $content, array $headers = [], $pgp_key = null)
    {
        // Création du contenu du message
        $content = wordwrap($content);
        $content = trim($content);

        $content = preg_replace("#(?<!\r)\n#si", "\r\n", $content);
        $config = Config::getInstance();

        $headers['Return-Path'] = FORCE_EMAIL_FROM ?: $config->get('email_asso');

        if (empty($headers['From']))
        {
            if (FORCE_EMAIL_FROM)
            {
                $headers['Reply-To'] = !empty($headers['From']) ? $headers['From'] : $config->get('email_asso');
                $headers['From'] = sprintf('"%s" <%s>', sprintf('=?UTF-8?B?%s?=', base64_encode($config->get('nom_asso'))), FORCE_EMAIL_FROM);
                $headers['Return-Path'] = FORCE_EMAIL_FROM;
            }
            else
            {
                $headers['From'] = sprintf('"%s" <%s>', sprintf('=?UTF-8?B?%s?=', base64_encode($config->get('nom_asso'))), $config->get('email_asso'));
            }
        }

        $headers['MIME-Version'] = '1.0';
        $headers['Content-type'] = 'text/plain; charset=UTF-8';

        $hash = sha1(uniqid() . var_export([$headers, $to, $subject, $content], true));
        $headers['Message-ID'] = sprintf('%s@%s', $hash, isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : gethostname());

        if ($pgp_key)
        {
            $content = Security::encryptWithPublicKey($pgp_key, $content);
        }

        if (!is_array($to))
        {
            $to = [$to];
        }

        foreach ($to as $recipient)
        {
            // Ne pas envoyer de mail à des adresses invalides
            if (!SMTP::checkEmailIsValid($recipient, false))
            {
                continue;
            }

            if (!self::_sendMail($recipient, $subject, $content, $headers))
            {
                throw new \RuntimeException('Impossible d\'envoyer l\'email');
            }
        }

        return true;
    }

    static protected function _sendMail($to, $subject, $content, array $headers)
    {
        if (SMTP_HOST)
        {
            $const = '\KD2\SMTP::' . strtoupper(SMTP_SECURITY);
            
            if (!defined($const))
            {
                throw new \LogicException('Configuration: SMTP_SECURITY n\'a pas une valeur reconnue. Valeurs acceptées: STARTTLS, TLS, SSL, NONE.');
            }

            $secure = constant($const);

            $smtp = new SMTP(SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD, $secure);
            return $smtp->send($to, $subject, $content, $headers);
        }
        else
        {
            // Encodage du sujet
            $subject = sprintf('=?UTF-8?B?%s?=', base64_encode($subject));
            $raw_headers = '';

            // Sérialisation des entêtes
            foreach ($headers as $name=>$value)
            {
                $raw_headers .= sprintf("%s: %s\r\n", $name, $value);
            }

            return mail($to, $subject, $content, $raw_headers);
        }
    }

    static public function clearCaches($path = false)
    {
        if (!$path)
        {
            self::clearCaches('compiled');
            self::clearCaches('static');
            return true;
        }

        $path = CACHE_ROOT . '/' . $path;
        $dir = dir($path);

        while ($file = $dir->read())
        {
            if ($file[0] != '.')
            {
            	if (file_exists($path . DIRECTORY_SEPARATOR . $file))
            	{
                	unlink($path . DIRECTORY_SEPARATOR . $file);
                self::safe_unlink($path . DIRECTORY_SEPARATOR . $file);
                }
            }
        }

        $dir->close();
        return true;
    }

    static public function safe_unlink($path)
    {
        if (!@unlink($path))
        {
            return true;
        }
        
        if (!file_exists($path))
        {
            return true;
        }

        throw new \RuntimeException(sprintf('Impossible de supprimer le fichier %s: %s', $path, error_get_last()));

        return true;
    }

    static public function safe_mkdir($path, $mode = 0777, $recursive = false)
    {
        return @mkdir($path, $mode, $recursive) || is_dir($path);
    }

    static public function suggestPassword()
    {
        return Security::getRandomPassphrase(ROOT . '/include/data/dictionary.fr');
    }

    static public function checkIBAN($value)
659
660
661
662
663
664
665
666

667
668
669
670
671
672
673
593
594
595
596
597
598
599

600
601
602
603
604
605
606
607







-
+







            if (is_dir($path . '/' . $file))
            {
                if (!self::deleteRecursive($path . '/' . $file))
                    return false;
            }
            else
            {
                unlink($path . '/' . $file);
                utils::safe_unlink($path . '/' . $file);
            }
        }

        $dir->close();
        rmdir($path);

        return true;
739
740
741
742
743
744
745
746

















































































































































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
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824







-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

        array_walk($row, function (&$field) {
            $field = strtr($field, ['"' => '""', "\r\n" => "\n"]);
        });

        return sprintf("\"%s\"\r\n", implode('","', $row));
    }
}

    static public function toCSV($name, $iterator, $header = null)
    {
        header('Content-type: application/csv');
        header(sprintf('Content-Disposition: attachment; filename="%s.csv"', $name));

        $fp = fopen('php://output', 'w');

        if ($header)
        {
            fputs($fp, self::row_to_csv($header));
        }

        foreach ($iterator as $row)
        {
            if (!$header)
            {
                fputs($fp, self::row_to_csv(array_keys($row)));
                $header = true;
            }

            fputs($fp, self::row_to_csv($row));
        }

        fclose($fp);

        return true;
    }

    static public function toODS($name, $iterator, $header = null)
    {
        header('Content-type: application/vnd.oasis.opendocument.spreadsheet');
        header(sprintf('Content-Disposition: attachment; filename="%s.ods"', $name));

        $ods = new ODSWriter;
        $ods->table_name = $name;

        if ($header)
        {
            $ods->add((array) $header);
        }

        foreach ($iterator as $row)
        {
            if (!$header)
            {
                $ods->add(array_keys($row));
                $header = true;
            }

            $ods->add((array) $row);
        }

        $ods->output();

        return true;
    }

    static public function sendEmail($context, $recipient, $subject, $content, $id_membre = null, $pgp_key = null)
    {
        // Ne pas envoyer de mail à des adresses invalides
        if (!SMTP::checkEmailIsValid($recipient, false))
        {
            throw new UserException('Adresse email invalide: ' . $recipient);
        }

        $config = Config::getInstance();
        $subject = sprintf('[%s] %s', $config->get('nom_asso'), $subject);

        // Tentative d'envoi du message en utilisant un plugin
        $email_sent_via_plugin = Plugin::fireSignal('email.envoi', compact('context', 'recipient', 'subject', 'content', 'id_membre', 'pgp_key'));

        if (!$email_sent_via_plugin)
        {
            // L'envoi d'email n'a pas été effectué par un plugin, utilisons l'envoi interne
            // via mail() ou SMTP donc
            return self::mail($context, $recipient, $subject, $content, $id_membre, $pgp_key);
        }

        return true;
    }

    static public function mail($context, $to, $subject, $content, $id_membre, $pgp_key)
    {
        $headers = [];
        $config = Config::getInstance();

        $content = wordwrap($content);
        $content = trim($content);

        $content .= sprintf("\n\n-- \n%s\n%s\n\n", $config->get('nom_asso'), $config->get('site_asso'));
        $content .= "Vous recevez ce message car vous êtes inscrit comme membre de\nl'association.\n";
        $content .= "Pour ne plus recevoir de message de notre part merci de nous contacter :\n" . $config->get('email_asso');

        $content = preg_replace("#(?<!\r)\n#si", "\r\n", $content);

        if ($pgp_key)
        {
            $content = Security::encryptWithPublicKey($pgp_key, $content);
        }

        $headers['From'] = sprintf('"%s" <%s>', sprintf('=?UTF-8?B?%s?=', base64_encode($config->get('nom_asso'))), $config->get('email_asso'));
        $headers['Return-Path'] = $config->get('email_asso');

        $headers['MIME-Version'] = '1.0';
        $headers['Content-type'] = 'text/plain; charset=UTF-8';

        if ($contexte == self::EMAIL_CONTEXT_BULK)
        {
            $headers['Precedence'] = 'bulk';
        }

        $hash = sha1(uniqid() . var_export([$headers, $to, $subject, $content], true));
        $headers['Message-ID'] = sprintf('%s@%s', $hash, isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : gethostname());

        if (SMTP_HOST)
        {
            $const = '\KD2\SMTP::' . strtoupper(SMTP_SECURITY);
            
            if (!defined($const))
            {
                throw new \LogicException('Configuration: SMTP_SECURITY n\'a pas une valeur reconnue. Valeurs acceptées: STARTTLS, TLS, SSL, NONE.');
            }

            $secure = constant($const);

            $smtp = new SMTP(SMTP_HOST, SMTP_PORT, SMTP_USER, SMTP_PASSWORD, $secure);
            return $smtp->send($to, $subject, $content, $headers);
        }
        else
        {
            // Encodage du sujet
            $subject = sprintf('=?UTF-8?B?%s?=', base64_encode($subject));
            $raw_headers = '';

            // Sérialisation des entêtes
            foreach ($headers as $name=>$value)
            {
                $raw_headers .= sprintf("%s: %s\r\n", $name, $value);
            }

            return \mail($to, $subject, $content, $raw_headers);
        }
    }
}

Added src/include/test_required.php version [69fc54870f].






























































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
<?php

/*
 * Tests : vérification que les conditions pour s'exécuter sont remplies
 */

function test_requis($condition, $message)
{
    if ($condition)
    {
        return true;
    }

    if (PHP_SAPI != 'cli')
    {
        header('Content-Type: text/html; charset=utf-8');
        echo "<!DOCTYPE html>\n<html>\n<head>\n<title>Erreur</title>\n<meta charset=\"utf-8\" />\n";
        echo '<style type="text/css">body { font-family: sans-serif; } ';
        echo '.error { color: darkred; padding: .5em; margin: 1em; border: 3px double red; background: yellow; }</style>';
        echo "\n</head>\n<body>\n<h2>Erreur</h2>\n<h3>Le problème suivant empêche Garradin de fonctionner :</h3>\n";
        echo '<p class="error">' . htmlspecialchars($message, ENT_QUOTES, 'UTF-8') . '</p>';
        echo '<hr /><p>Pour plus d\'informations consulter ';
        echo '<a href="http://dev.kd2.org/garradin/Probl%C3%A8mes%20fr%C3%A9quents">l\'aide sur les problèmes à l\'installation</a>.</p>';
        echo "\n</body>\n</html>";
    }
    else
    {
        echo "[ERREUR] Le problème suivant empêche Garradin de fonctionner :\n";
        echo $message . "\n";
        echo "Pour plus d'informations consulter http://dev.kd2.org/garradin/Probl%C3%A8mes%20fr%C3%A9quents\n";
    }

    exit;
}

test_requis(
    version_compare(phpversion(), '5.6', '>='),
    'PHP 5.6 ou supérieur requis. PHP version ' . phpversion() . ' installée.'
);

test_requis(
    defined('CRYPT_BLOWFISH') && CRYPT_BLOWFISH,
    'L\'algorithme de hashage de mot de passe Blowfish n\'est pas présent (pas installé ou pas compilé).'
);

test_requis(
    class_exists('SQLite3'),
    'Le module de base de données SQLite3 n\'est pas disponible.'
);

$v = \SQLite3::version();

test_requis(
    version_compare($v['versionString'], '3.7.4', '>='),
    'SQLite3 version 3.7.4 ou supérieur requise. Version installée : ' . $v['versionString']
);

test_requis(
    file_exists(__DIR__ . '/lib/KD2'),
    'Librairie KD2 non disponible.'
);

Modified src/index.html from [23f2c346a2] to [008e979f23].

1
2

3
4
1
2
3
4
5


+


<h1>Erreur</h1>
<p>Garradin n'est pas installé sur un sous-domaine dédié.</p>
<p>Ce mode de fonctionnement n'est pas supporté officiellement.</p>
<p>Merci de positionner un sous-domaine dédié sur le répertoire www/</p>
<p>Voir <a href="http://dev.kd2.org/garradin/Installation">la documentation</a>.</p>

Modified src/templates/admin/_head.tpl from [aa97d73757] to [d5fc274287].

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
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







-
+

-
+



-
+




-
+




-
+




-
+


-
-
+
+







<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" lang="fr">
<head>
    <meta charset="utf-8" />
    <title>{$title}</title>
    <link rel="icon" type="image/png" href="{$admin_url}static/icon.png" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, target-densitydpi=device-dpi" />
    <link rel="stylesheet" type="text/css" href="{$admin_url}static/admin.css?2018-07-01" media="all" />
    <link rel="stylesheet" type="text/css" href="{$admin_url}static/admin.css?{$version_hash}1" media="all" />
    {if isset($js) || isset($custom_js)}
        <script type="text/javascript" src="{$admin_url}static/scripts/global.js"></script>
        <script type="text/javascript" src="{$admin_url}static/scripts/global.js?{$version_hash}"></script>
    {/if}
    {if isset($custom_js)}
        {foreach from=$custom_js item="js"}
            <script type="text/javascript" src="{$admin_url}static/scripts/{$js}"></script>
            <script type="text/javascript" src="{$admin_url}static/scripts/{$js}?{$version_hash}"></script>
        {/foreach}
    {/if}
    {if isset($custom_css)}
        {foreach from=$custom_css item="css"}
            <link rel="stylesheet" type="text/css" href="{$admin_url}static/{$css}" media="all" />
            <link rel="stylesheet" type="text/css" href="{$admin_url}static/{$css}?{$version_hash}" media="all" />
        {/foreach}
    {/if}
    {if isset($plugin_css)}
        {foreach from=$plugin_css item="css"}
            <link rel="stylesheet" type="text/css" href="{plugin_url file=$css}" />
            <link rel="stylesheet" type="text/css" href="{plugin_url file=$css}?{$version_hash}" />
        {/foreach}
    {/if}
    {if isset($plugin_js)}
        {foreach from=$plugin_js item="js"}
            <script type="text/javascript" src="{plugin_url file=$js}"></script>
            <script type="text/javascript" src="{plugin_url file=$js}?{$version_hash}"></script>
        {/foreach}
    {/if}
    <link rel="stylesheet" type="text/css" href="{$admin_url}static/print.css?b" media="print" />
    <link rel="stylesheet" type="text/css" href="{$admin_url}static/handheld.css?a" media="handheld,screen and (max-width:981px)" />
    <link rel="stylesheet" type="text/css" href="{$admin_url}static/print.css?{$version_hash}" media="print" />
    <link rel="stylesheet" type="text/css" href="{$admin_url}static/handheld.css?{$version_hash}" media="handheld,screen and (max-width:981px)" />
    {if isset($config)}
        {custom_colors config=$config}
    {/if}
</head>

<body{if !empty($body_id)} id="{$body_id}"{/if} data-url="{$admin_url}">

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
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







-
-
+
+




-
+

-
+



-
-
-
+
-




-
+


-
+




-
+






-
+









-
+







-
+











    <?php
    $current_parent = substr($current, 0, strpos($current, '/'));
    ?>
        <li class="home{if $current == 'home'} current{elseif $current_parent == 'home'} current_parent{/if}">
            <a href="{$admin_url}"><b class="icn">⌂</b><i> Accueil</i></a>
            {if !empty($plugins_menu)}
                <ul>
                {foreach from=$plugins_menu key="id" item="name"}
                    <li class="plugins {if $current == sprintf("plugin_%s", $id)} current{/if}"><a href="{plugin_url id=$id}">{$name}</a></li>
                {foreach from=$plugins_menu key="plugin_id" item="name"}
                    <li class="plugins {if $current == sprintf("plugin_%s", $plugin_id)} current{/if}"><a href="{plugin_url id=$plugin_id}">{$name}</a></li>
                {/foreach}
                </ul>
            {/if}
        </li>
        {if $session->canAccess('membres', Garradin\Membres::DROIT_ACCES)}
        {if $session->canAccess('membres', Membres::DROIT_ACCES)}
            <li class="member list{if $current == 'membres'} current{elseif $current_parent == 'membres'} current_parent{/if}"><a href="{$admin_url}membres/"><b class="icn">👪</b><i> Membres</i></a>
            {if $session->canAccess('membres', Garradin\Membres::DROIT_ECRITURE)}
            {if $session->canAccess('membres', Membres::DROIT_ECRITURE)}
            <ul>
                <li class="member new{if $current == 'membres/ajouter'} current{/if}"><a href="{$admin_url}membres/ajouter.php">Ajouter</a></li>
                <li class="member cotisations{if $current == 'membres/cotisations'} current{/if}"><a href="{$admin_url}membres/cotisations/">Cotisations</a></li>
                {if $session->canAccess('membres', Garradin\Membres::DROIT_ADMIN)}
                <li class="member admin config{if $current == 'membres/categories'} current{/if}"><a href="{$admin_url}membres/categories/">Catégories</a></li>
                <li class="members admin mail{if $current == 'membres/message_collectif'} current{/if}"><a href="{$admin_url}membres/message_collectif.php">Message collectif</a></li>
                <li class="member message{if $current == 'membres/message'} current{/if}"><a href="{$admin_url}membres/message_collectif.php">Message collectif</a></li>
                {/if}
            </ul>
            {/if}
            </li>
        {/if}
        {if $session->canAccess('compta', Garradin\Membres::DROIT_ACCES)}
        {if $session->canAccess('compta', Membres::DROIT_ACCES)}
            <li class="compta{if $current == 'compta'} current{elseif $current_parent == 'compta'} current_parent{/if}"><a href="{$admin_url}compta/"><b>€</b><i> Comptabilité</i></a>
            <ul>
            {if $session->canAccess('compta', Garradin\Membres::DROIT_ECRITURE)}
            {if $session->canAccess('compta', Membres::DROIT_ECRITURE)}
                <li class="compta new{if $current == 'compta/saisie'} current{/if}"><a href="{$admin_url}compta/operations/saisir.php">Saisie</a></li>
            {/if}
                <li class="compta list{if $current == 'compta/gestion'} current{/if}"><a href="{$admin_url}compta/operations/">Suivi des opérations</a></li>
                <li class="compta banks{if $current == 'compta/banques'} current{/if}"><a href="{$admin_url}compta/banques/">Banques &amp; caisse</a></li>
            {if $session->canAccess('compta', Garradin\Membres::DROIT_ADMIN)}
            {if $session->canAccess('compta', Membres::DROIT_ADMIN)}
                <li class="compta admin config{if $current == 'compta/categories'} current{/if}"><a href="{$admin_url}compta/categories/">Catégories &amp; comptes</a></li>
            {/if}
                <li class="compta admin reports{if $current == 'compta/exercices'} current{/if}"><a href="{$admin_url}compta/exercices/">Exercices &amp; projets</a></li>
            </ul>
            </li>
        {/if}
        {if $session->canAccess('wiki', Garradin\Membres::DROIT_ACCES)}
        {if $session->canAccess('wiki', Membres::DROIT_ACCES)}
            <li class="wiki{if $current == 'wiki'} current{elseif $current_parent == 'wiki'} current_parent{/if}"><a href="{$admin_url}wiki/"><b class="icn">✎</b><i> Wiki</i></a>
            <ul>
                <li class="wiki list{if $current == 'wiki/recent'} current{/if}"><a href="{$admin_url}wiki/recent.php">Dernières modifications</a>
                <li class="wiki search{if $current == 'wiki/chercher'} current{/if}"><a href="{$admin_url}wiki/chercher.php">Recherche</a>
                {*<li class="wiki follow{if $current == 'wiki/suivi'} current{/if}"><a href="{$admin_url}wiki/suivi.php">Mes pages suivies</a>*}
                {*<li class="wiki follow{if $current == 'wiki/contribution'} current{/if}"><a href="{$admin_url}wiki/contributions.php">Mes contributions</a>*}
            </ul>
            </li>
        {/if}
        {if $session->canAccess('config', Garradin\Membres::DROIT_ADMIN)}
        {if $session->canAccess('config', Membres::DROIT_ADMIN)}
            <li class="main config{if $current == 'config'} current{elseif $current_parent == 'config'} current_parent{/if}"><a href="{$admin_url}config/"><b class="icn">☸</b><i> Configuration</i></a>
        {/if}
        <li class="my config{if $current == 'mes_infos'} current{elseif $current_parent == 'mes_infos'} current_parent{/if}"><a href="{$admin_url}mes_infos.php"><b class="icn">👤</b><i> Mes infos personnelles</i></a>
            <ul>
                <li class="my cotisations{if $current == 'mes_cotisations'} current{/if}"><a href="{$admin_url}mes_cotisations.php">Mes cotisations</a></li>
            </ul>
        </li>
        {if !defined('Garradin\LOCAL_LOGIN') || !Garradin\LOCAL_LOGIN}
        {if !defined('Garradin\LOCAL_LOGIN') || !LOCAL_LOGIN}
        <li class="logout"><a href="{$admin_url}logout.php"><b class="icn">⤝</b><i> Déconnexion</i></a></li>
        {/if}
    {/if}
    </ul>
    </nav>

    <h1>{$title}</h1>
</header>
{/if}

<main>

Modified src/templates/admin/compta/banques/index.tpl from [9584129965] to [dfa7e103a3].

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
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







-
+


-
+











-
+







                <td>{$compte.banque}</td>
                <th>{$compte.libelle}</th>
                <td><strong>{$compte.solde|escape|html_money} {$config.monnaie}</strong></td>
                <td>{$compte.iban|escape|format_iban}</td>
                <td>{$compte.bic}</td>
                <td class="actions">
                    <a class="icn" href="{$admin_url}compta/comptes/journal.php?id={$compte.id}&amp;suivi" title="Journal">𝍢</a>
                    {if $session->canAccess('compta', Garradin\Membres::DROIT_ECRITURE)}
                    {if $session->canAccess('compta', Membres::DROIT_ECRITURE)}
                        <a class="icn" href="{$admin_url}compta/banques/rapprocher.php?id={$compte.id}" title="Rapprocher">☑</a>
                    {/if}
                    {if $session->canAccess('compta', Garradin\Membres::DROIT_ADMIN)}
                    {if $session->canAccess('compta', Membres::DROIT_ADMIN)}
                        <a class="icn" href="{$admin_url}compta/banques/modifier.php?id={$compte.id}" title="Modifier">✎</a>
                        <a class="icn" href="{$admin_url}compta/banques/supprimer.php?id={$compte.id}" title="Supprimer">✘</a>
                    {/if}
                </td>
            </tr>
        {/foreach}
        </tbody>
    </table>
    </dl>
{/if}

{if $session->canAccess('compta', Garradin\Membres::DROIT_ADMIN)}
{if $session->canAccess('compta', Membres::DROIT_ADMIN)}
    {form_errors}

    <form method="post" action="{$self_url}">

        <fieldset>
            <legend>Ajouter un compte bancaire</legend>
            <dl>

Modified src/templates/admin/compta/banques/rapprocher.tpl from [8c929d9cb6] to [8d52e0242d].

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
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







-
-
+
+











-
+








<form method="get" action="{$self_url_no_qs}">
    {if !empty($prev) && !empty($next)}
    <fieldset class="shortFormRight">
        <legend>Rapprochement par mois</legend>
        <dl>
            <dd class="actions">
            <a class="icn" href="{$self_url_no_qs}?id={$compte.id}&amp;debut={$prev|date_fr:'Y-m-01'}&amp;fin={$prev|date_fr:'Y-m-t'}{if \Garradin\qg('sauf')}&amp;sauf=1{/if}">&larr; {$prev|date_fr:'F Y'}</a>
            | <a class="icn" href="{$self_url_no_qs}?id={$compte.id}&amp;debut={$next|date_fr:'Y-m-01'}&amp;fin={$next|date_fr:'Y-m-t'}{if \Garradin\qg('sauf')}&amp;sauf=1{/if}">{$next|date_fr:'F Y'} &rarr;</a>
            <a class="icn" href="{$self_url_no_qs}?id={$compte.id}&amp;debut={$prev|date_fr:'Y-m-01'}&amp;fin={$prev|date_fr:'Y-m-t'}{if qg('sauf')}&amp;sauf=1{/if}">&larr; {$prev|date_fr:'F Y'}</a>
            | <a class="icn" href="{$self_url_no_qs}?id={$compte.id}&amp;debut={$next|date_fr:'Y-m-01'}&amp;fin={$next|date_fr:'Y-m-t'}{if qg('sauf')}&amp;sauf=1{/if}">{$next|date_fr:'F Y'} &rarr;</a>
            </dd>
        </dl>
    </fieldset>
    {/if}
    <fieldset>
        <legend>Période de rapprochement</legend>
        <p>
            Du
            <span><input type="date" name="debut" id="f_debut" value="{$debut}" /></span>
            au
            <span><input type="date" name="fin" id="f_fin" value="{$fin}" /></span>
            <label><input type="checkbox" name="sauf" value="1" {if \Garradin\qg('sauf')} checked="checked"{/if} /> Ne pas inclure les écritures déjà rapprochées</label>
            <label><input type="checkbox" name="sauf" value="1" {if qg('sauf')} checked="checked"{/if} /> Ne pas inclure les écritures déjà rapprochées</label>
            <input type="hidden" name="id" value="{$compte.id}" />
            <input type="submit" value="Afficher" />
        </p>
    </fieldset>
</form>

{form_errors}
63
64
65
66
67
68
69
70

71
72
73
74
75
76
77
63
64
65
66
67
68
69

70
71
72
73
74
75
76
77







-
+







                <th>Solde au {$debut|format_sqlite_date_to_french}</th>
            </tr>
        {foreach from=$journal item="ligne"}
            <tr>
                <td class="check"><input type="checkbox" name="rapprocher[{$ligne.id}]" value="1" {if $ligne.date_rapprochement}checked="checked"{/if} /></td>
                <td class="num"><a href="{$admin_url}compta/operations/voir.php?id={$ligne.id}">{$ligne.id}</a></td>
                <td class="actions">
                {if $session->canAccess('compta', Garradin\Membres::DROIT_ADMIN)}
                {if $session->canAccess('compta', Membres::DROIT_ADMIN)}
                    <a class="icn" href="{$admin_url}compta/operations/modifier.php?id={$ligne.id}" title="Modifier cette opération">✎</a>
                {/if}
                </td>
                <td>{$ligne.date|date_fr:'d/m/Y'}</td>
                <td>{if $ligne.compte_credit == $compte.id}-{else}+{/if}{$ligne.montant|escape|html_money}</td>
                <td>{$ligne.solde|escape|html_money}</td>
                <th>{$ligne.libelle}</th>

Modified src/templates/admin/compta/comptes/journal.tpl from [ac05873329] to [76d3a0a881].

30
31
32
33
34
35
36
37

38
39
40
41
42
43
44
30
31
32
33
34
35
36

37
38
39
40
41
42
43
44







-
+







        </tr>
    </thead>
    <tbody>
    {foreach from=$journal item="ligne"}
        <tr>
            <td class="num"><a href="{$admin_url}compta/operations/voir.php?id={$ligne.id}">{$ligne.id}</a></td>
            <td class="actions">
            {if $session->canAccess('compta', Garradin\Membres::DROIT_ADMIN)}
            {if $session->canAccess('compta', Membres::DROIT_ADMIN)}
                <a class="icn" href="{$admin_url}compta/operations/modifier.php?id={$ligne.id}" title="Modifier cette opération">✎</a>
            {/if}
            </td>
            <td>{$ligne.date|date_fr:'d/m/Y'}</td>
            <td>{if $ligne.compte_credit == $compte.id}{$credit}{else}{$debit}{/if}{$ligne.montant|escape|html_money}</td>
            <td>{$ligne.solde|escape|html_money}</td>
            <th>{$ligne.libelle}</th>

Modified src/templates/admin/compta/exercices/index.tpl from [e945d5a3d0] to [ec3f086414].

24
25
26
27
28
29
30
31

32
33
34
35
36
37
38
24
25
26
27
28
29
30

31
32
33
34
35
36
37
38







-
+







        </dd>
        <dd class="desc">
            <a href="{$admin_url}compta/rapports/journal.php?exercice={$exercice.id}">Journal général</a>
            | <a href="{$admin_url}compta/rapports/grand_livre.php?exercice={$exercice.id}">Grand livre</a>
            | <a href="{$admin_url}compta/rapports/compte_resultat.php?exercice={$exercice.id}">Compte de résultat</a>
            | <a href="{$admin_url}compta/rapports/bilan.php?exercice={$exercice.id}">Bilan</a>
        </dd>
        {if $session->canAccess('compta', Garradin\Membres::DROIT_ADMIN)}
        {if $session->canAccess('compta', Membres::DROIT_ADMIN)}
        <dd class="actions">
            {if !$exercice.cloture}
            <a class="icn" href="{$admin_url}compta/exercices/modifier.php?id={$exercice.id}" title="Modifier">✎</a>
            <a class="icn" href="{$admin_url}compta/exercices/supprimer.php?id={$exercice.id}" title="Supprimer">✘</a>
            <a class="icn" href="{$admin_url}compta/exercices/cloturer.php?id={$exercice.id}" title="Clôturer cet exercice">🔒</a>
            {elseif $exercice.cloture && $exercice.nb_operations == 0}
            <a class="icn" href="{$admin_url}compta/exercices/supprimer.php?id={$exercice.id}" title="Supprimer">✘</a>

Modified src/templates/admin/compta/index.tpl from [07195fed01] to [9d6a3b1dd3].

1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







{include file="admin/_head.tpl" title="Comptabilité" current="compta"}

{if $session->canAccess('compta', Garradin\Membres::DROIT_ADMIN)}
{if $session->canAccess('compta', Membres::DROIT_ADMIN)}
<ul class="actions">
    <li><a href="{$admin_url}compta/import.php">Import / export</a></li>
    <li><a href="{$admin_url}compta/operations/recherche_sql.php">Recherche par requête SQL</a></li>
</ul>
{/if}

<div class="infos">

Modified src/templates/admin/compta/operations/cotisation.tpl from [00adde6ecd] to [a490130f82].

1
2
3
4
5
6

7
8
9
10
11
12
13
1
2
3
4
5

6
7
8
9
10
11
12
13





-
+







{include file="admin/_head.tpl" title="Écritures liées à une cotisation" current="compta/gestion"}

<ul class="actions">
    <li><a href="{$admin_url}membres/fiche.php?id={$membre.id}"><b>{$membre.identite}</b></a></li>
    <li><a href="{$admin_url}membres/modifier.php?id={$membre.id}">Modifier</a></li>
    {if $session->canAccess('membres', Garradin\Membres::DROIT_ADMIN)}
    {if $session->canAccess('membres', Membres::DROIT_ADMIN)}
        <li><a href="{$admin_url}membres/supprimer.php?id={$membre.id}">Supprimer</a></li>
    {/if}
    <li><a href="{$admin_url}membres/cotisations.php?id={$membre.id}">Suivi des cotisations</a></li>
</ul>

{if empty($journal)}
    <p class="alert">Aucune écriture comptable n'est associée à cette cotisation.</p>
32
33
34
35
36
37
38
39

40
41
42
43
44
45
46
32
33
34
35
36
37
38

39
40
41
42
43
44
45
46







-
+







        </tr>
    </thead>
    <tbody>
    {foreach from=$journal item="ligne"}
        <tr>
            <td><a href="{$admin_url}compta/operations/voir.php?id={$ligne.id}">{$ligne.id}</a></td>
            <td class="actions">
            {if $session->canAccess('compta', Garradin\Membres::DROIT_ADMIN)}
            {if $session->canAccess('compta', Membres::DROIT_ADMIN)}
                <a class="icn" href="{$admin_url}compta/operations/modifier.php?id={$ligne.id}" title="Modifier cette opération">✎</a>
            {/if}
            </td>
            <td>{$ligne.date|format_sqlite_date_to_french}</td>
            <td>{$ligne.montant|escape|html_money}</td>
            <th>{$ligne.libelle}</th>
            <td>{$ligne.compte_debit} — {$ligne.compte_debit|get_nom_compte}</td>

Modified src/templates/admin/compta/operations/index.tpl from [14d2513231] to [5baa65eca3].

1
2
3
4
5
6



7
8

9
10
11
12
13

14
15
16
17

18
19
20
21
22
23
24
1
2
3



4
5
6
7

8
9
10
11
12

13
14
15
16

17
18
19
20
21
22
23
24



-
-
-
+
+
+

-
+




-
+



-
+







{include file="admin/_head.tpl" title="Suivi des opérations" current="compta/gestion"}

<ul class="actions">
    <li class="recettes{if $type == Garradin\Compta\Categories::RECETTES} current{/if}"><a href="{$admin_url}compta/operations/?recettes">Recettes</a></li>
    <li class="depenses{if $type == Garradin\Compta\Categories::DEPENSES} current{/if}"><a href="{$admin_url}compta/operations/?depenses">Dépenses</a></li>
    <li class="autres{if $type == Garradin\Compta\Categories::AUTRES} current{/if}"><a href="{$admin_url}compta/operations/?autres">Autres</a></li>
    <li class="recettes{if $type == Compta\Categories::RECETTES} current{/if}"><a href="{$admin_url}compta/operations/?recettes">Recettes</a></li>
    <li class="depenses{if $type == Compta\Categories::DEPENSES} current{/if}"><a href="{$admin_url}compta/operations/?depenses">Dépenses</a></li>
    <li class="autres{if $type == Compta\Categories::AUTRES} current{/if}"><a href="{$admin_url}compta/operations/?autres">Autres</a></li>
    {*<li><a href="{$admin_url}compta/operations/recherche.php">Recherche d'opération</a></li>*}
    {if $session->canAccess('compta', Garradin\Membres::DROIT_ADMIN)}
    {if $session->canAccess('compta', Membres::DROIT_ADMIN)}
        <li><a href="{$admin_url}compta/operations/recherche_sql.php">Recherche par requête SQL</a></li>
    {/if}
</ul>

{if $type != Garradin\Compta\Categories::AUTRES}
{if $type != Compta\Categories::AUTRES}
<form method="get" action="{$self_url}">
    <fieldset>
        <legend>Filtrer par catégorie</legend>
        <select name="cat" onchange="if (!this.value) location.href = '?{if $type == Garradin\Compta\Categories::RECETTES}recettes{else}depenses{/if}'; else this.form.submit();">
        <select name="cat" onchange="if (!this.value) location.href = '?{if $type == Compta\Categories::RECETTES}recettes{else}depenses{/if}'; else this.form.submit();">
            <option value="">-- Toutes</option>
        {foreach from=$liste_cats item="cat"}
            <option value="{$cat.id}"{if $cat.id == $categorie.id} selected="selected"{/if}>{$cat.intitule}</option>
        {/foreach}
        </select>
        <input type="submit" value="OK" />
    </fieldset>

Modified src/templates/admin/compta/operations/membre.tpl from [c7d34a4a3b] to [8524d38029].

1
2
3
4
5
6

7
8
9
10
11
12
13
1
2
3
4
5

6
7
8
9
10
11
12
13





-
+







{include file="admin/_head.tpl" title="Écritures réalisées par le membre" current="compta/gestion"}

<ul class="actions">
    <li><a href="{$admin_url}membres/fiche.php?id={$membre.id}"><b>{$membre.identite}</b></a></li>
    <li><a href="{$admin_url}membres/modifier.php?id={$membre.id}">Modifier</a></li>
    {if $session->canAccess('membres', Garradin\Membres::DROIT_ADMIN)}
    {if $session->canAccess('membres', Membres::DROIT_ADMIN)}
        <li><a href="{$admin_url}membres/supprimer.php?id={$membre.id}">Supprimer</a></li>
    {/if}
    <li><a href="{$admin_url}membres/cotisations.php?id={$membre.id}">Suivi des cotisations</a></li>
</ul>

<form method="get" action="{$self_url}">
    <fieldset>
54
55
56
57
58
59
60
61

62
63
64
65
66
67
68
54
55
56
57
58
59
60

61
62
63
64
65
66
67
68







-
+







        </tr>
    </thead>
    <tbody>
    {foreach from=$journal item="ligne"}
        <tr>
            <td><a href="{$admin_url}compta/operations/voir.php?id={$ligne.id}">{$ligne.id}</a></td>
            <td class="actions">
            {if $session->canAccess('compta', Garradin\Membres::DROIT_ADMIN)}
            {if $session->canAccess('compta', Membres::DROIT_ADMIN)}
                <a class="icn" href="{$admin_url}compta/operations/modifier.php?id={$ligne.id}" title="Modifier cette opération">✎</a>
            {/if}
            </td>
            <td>{$ligne.date|format_sqlite_date_to_french}</td>
            <td>{$ligne.montant|escape|html_money}</td>
            <th>{$ligne.libelle}</th>
            <td>{$ligne.compte_debit} — {$ligne.compte_debit|get_nom_compte}</td>

Modified src/templates/admin/compta/operations/modifier.tpl from [fd3771f52b] to [da5bdb03fc].

34
35
36
37
38
39
40
41

42
43

44
45
46
47
48
49
50
34
35
36
37
38
39
40

41
42

43
44
45
46
47
48
49
50







-
+

-
+







            </dd>
            <dt class="f_cheque"><label for="f_numero_cheque">Numéro de chèque</label></dt>
            <dd class="f_cheque"><input type="text" name="numero_cheque" id="f_numero_cheque" value="{form_field name=numero_cheque data=$operation}" /></dd>
            <dt class="f_banque"><label for="f_banque">Compte bancaire</label></dt>
            <dd class="f_banque">
                <select name="banque" id="f_banque">
                {foreach from=$comptes_bancaires item="compte"}
                    <option value="{$compte.id}"{if ($type == Garradin\Compta\Categories::DEPENSES && $compte.id == $operation.compte_credit) || $compte.id == $operation.compte_debit} selected="selected"{/if}>{$compte.libelle} - {$compte.banque}</option>
                    <option value="{$compte.id}"{if ($type == Compta\Categories::DEPENSES && $compte.id == $operation.compte_credit) || $compte.id == $operation.compte_debit} selected="selected"{/if}>{$compte.libelle} - {$compte.banque}</option>
                {/foreach}
                {if ($type == Garradin\Compta\Categories::RECETTES)}
                {if ($type == Compta\Categories::RECETTES)}
                    <option value="{$id_cheque_a_encaisser}"{form_field name="banque" selected=$id_cheque_a_encaisser default=$operation.compte_debit}>Chèques à encaisser</option>
                    <option value="{$id_carte_a_encaisser}"{form_field name="banque" selected=$id_carte_a_encaisser default=$operation.compte_debit}>Paiement CB à encaisser</option>
                {/if}
                </select>
            </dd>
{/if}

Modified src/templates/admin/compta/operations/voir.tpl from [1a3a9051ba] to [eaf9901138].

1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







{include file="admin/_head.tpl" title="Opération n°%d"|args:$operation.id current="compta/gestion"}

{if $session->canAccess('compta', Garradin\Membres::DROIT_ADMIN) && $operation.compte_credit !== null && $operation.compte_debit !== null}
{if $session->canAccess('compta', Membres::DROIT_ADMIN) && $operation.compte_credit !== null && $operation.compte_debit !== null}
<ul class="actions">
    <li class="edit"><a href="{$admin_url}compta/operations/modifier.php?id={$operation.id}">Modifier cette opération</a></li>
    <li class="delete"><a href="{$admin_url}compta/operations/supprimer.php?id={$operation.id}">Supprimer cette opération</a></li>
</ul>
{/if}

<dl class="describe">
30
31
32
33
34
35
36
37

38
39
40
41
42
43
44
30
31
32
33
34
35
36

37
38
39
40
41
42
43
44







-
+







        {if $operation.moyen_paiement && $operation.moyen_paiement != 'ES'}
            <dt>Compte bancaire</dt>
            <dd>{$compte}</dd>
        {/if}

        <dt>Catégorie</dt>
        <dd>
            <a href="{$admin_url}compta/operations/?{if $categorie.type == Garradin\Compta\Categories::DEPENSES}depenses{else}recettes{/if}">{if $categorie.type == Garradin\Compta\Categories::DEPENSES}Dépense{else}Recette{/if}</a>&nbsp;:
            <a href="{$admin_url}compta/operations/?{if $categorie.type == Compta\Categories::DEPENSES}depenses{else}recettes{/if}">{if $categorie.type == Compta\Categories::DEPENSES}Dépense{else}Recette{/if}</a>&nbsp;:
            <a href="{$admin_url}compta/operations/?cat={$operation.id_categorie}">{$categorie.intitule}</a>
        </dd>
    {/if}

    <dt>Exercice</dt>
    <dd>
        <a href="{$admin_url}compta/exercices/">{$exercice.libelle}</a>
52
53
54
55
56
57
58
59

60
61
62
63
64
65
66
52
53
54
55
56
57
58

59
60
61
62
63
64
65
66







-
+







            <a href="{$admin_url}compta/projets/">{$projet.libelle}</a>
        </dd>
    {/if}

    <dt>Opération créée par</dt>
    <dd>
        {if $operation.id_auteur}
            {if $session->canAccess('compta', Garradin\Membres::DROIT_ACCES)}
            {if $session->canAccess('compta', Membres::DROIT_ACCES)}
                <a href="{$admin_url}membres/fiche.php?id={$operation.id_auteur}">{$nom_auteur}</a>
            {else}
                {$nom_auteur}
            {/if}
        {else}
            <em>membre supprimé</em>
        {/if}

Modified src/templates/admin/compta/projets/index.tpl from [4cae68a2fd] to [c946bff41c].

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
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







-
+









-
+







            <dd class="compte">{$projet.nb_operations} opérations</dd>
            <dd class="desc">
                <a href="{$admin_url}compta/rapports/journal.php?projet={$projet.id}">Journal général</a>
                | <a href="{$admin_url}compta/rapports/grand_livre.php?projet={$projet.id}">Grand livre</a>
                | <a href="{$admin_url}compta/rapports/compte_resultat.php?projet={$projet.id}">Compte de résultat</a>
                | <a href="{$admin_url}compta/rapports/bilan.php?projet={$projet.id}">Bilan</a>
            </dd>
            {if $session->canAccess('compta', Garradin\Membres::DROIT_ADMIN)}
            {if $session->canAccess('compta', Membres::DROIT_ADMIN)}
            <dd class="actions">
                <a class="icn" href="{$admin_url}compta/projets/?modifier={$projet.id}" title="Modifier">✎</a>
                <a class="icn" href="{$admin_url}compta/projets/?supprimer={$projet.id}" title="Supprimer">✘</a>
            </dd>
            {/if}
        {/foreach}
        </dl>
    {/if}

    {if $session->canAccess('compta', Garradin\Membres::DROIT_ADMIN)}
    {if $session->canAccess('compta', Membres::DROIT_ADMIN)}
    <form method="post" action="{$self_url}">
        <fieldset>
            <legend>Ajouter un nouveau projet</legend>
            <dl>
                <dt><label for="f_libelle">Libellé</label></dt>
                <dd><input type="text" name="libelle" id="f_labelle" value="{form_field name=libelle}" /></dd>
            </dl>

Modified src/templates/admin/config/_menu.tpl from [b62a4fdea8] to [53db92aa96].

1
2
3
4
5





6
7

8
1




2
3
4
5
6


7
8

-
-
-
-
+
+
+
+
+
-
-
+

<ul class="actions">
    <li{if $current == 'index'} class="current"{/if}><a href="{$admin_url}config/">Général</a></li>
    <li{if $current == 'membres'} class="current"{/if}><a href="{$admin_url}config/membres.php">Fiche des membres</a></li>
    <li{if $current == 'site'} class="current"{/if}><a href="{$admin_url}config/site.php">Site public</a></li>
    <li{if $current == 'donnees'} class="current"{/if}><a href="{$admin_url}config/donnees.php">Données&nbsp;: sauvegarde et restauration</a></li>
	<li{if $current == 'index'} class="current"{/if}><a href="{$admin_url}config/">Général</a></li>
	<li{if $current == 'categories'} class="current"{/if}><a href="{$admin_url}config/categories/">Catégories de membres</a></li>
	<li{if $current == 'fiches_membres'} class="current"{/if}><a href="{$admin_url}config/membres.php">Fiche des membres</a></li>
	<li{if $current == 'site'} class="current"{/if}><a href="{$admin_url}config/site.php">Site public</a></li>
	<li{if $current == 'donnees'} class="current"{/if}><a href="{$admin_url}config/donnees/">Sauvegarde et restauration</a></li>
    <li{if $current == 'import'} class="current"{/if}><a href="{$admin_url}config/import.php">Import &amp; export</a></li>
    <li{if $current == 'plugins'} class="current"{/if}><a href="{$admin_url}config/plugins.php">Extensions</a></li>
	<li{if $current == 'plugins'} class="current"{/if}><a href="{$admin_url}config/plugins.php">Extensions</a></li>
</ul>

Added src/templates/admin/config/categories/index.tpl version [7ab8e26a24].



















































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{include file="admin/_head.tpl" title="Catégories de membres" current="config"}

{include file="admin/config/_menu.tpl" current="categories"}

<table class="list">
    <thead>
        <th>Nom</th>
        <td class="num">Membres</td>
        <td>Droits</td>
        <td></td>
    </thead>
    <tbody>
        {foreach from=$liste item="cat"}
            <tr>
                <th>{$cat.nom}</th>
                <td class="num">{$cat.nombre}</td>
                <td class="droits">
                    {format_droits droits=$cat}
                </td>
                <td class="actions">
                    <a class="icn" href="{$admin_url}membres/?cat={$cat.id}" title="Liste des membres">👪</a>
                    <a class="icn" href="{$admin_url}config/categories/modifier.php?id={$cat.id}" title="Modifier">✎</a>
                    {if $cat.id != $user.id_categorie}
                    <a class="icn" href="{$admin_url}config/categories/supprimer.php?id={$cat.id}" title="Supprimer">✘</a>
                    {/if}
                </td>
            </tr>
        {/foreach}
    </tbody>
</table>

<form method="post" action="{$self_url}">

    <fieldset>
        <legend>Ajouter une catégorie</legend>
        <dl>
            <dt><label for="f_nom">Nom</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
            <dd><input type="text" name="nom" id="f_nom" value="{form_field name=nom}" required="required" /></dd>
        </dl>
    </fieldset>

    <p class="submit">
        {csrf_field key="new_cat"}
        <input type="submit" name="save" value="Enregistrer &rarr;" />
    </p>

</form>


{include file="admin/_foot.tpl"}

Added src/templates/admin/config/categories/modifier.tpl version [7b22c4c7fb].

































































































































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{include file="admin/_head.tpl" title="Modifier une catégorie de membre" current="config"}

{include file="admin/config/_menu.tpl" current="categories"}

{form_errors}

<form method="post" action="{$self_url}">

    <fieldset>
        <legend>Informations générales</legend>
        <dl>
            <dt><label for="f_nom">Nom</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
            <dd><input type="text" name="nom" id="f_nom" value="{form_field data=$cat name=nom}" required="required" /></dd>
            <dt>
                <input type="checkbox" name="cacher" value="1" id="f_cacher" {if $cat.cacher}checked="checked"{/if} />
                <label for="f_cacher">Catégorie cachée</label>
            </dt>
            <dd class="help">
                Si coché cette catégorie ne sera visible qu'aux administrateurs et ne recevra pas
                de messages collectifs ou de rappels.
            </dd>
        </dl>
    </fieldset>

    <fieldset>
        <legend>Cotisation obligatoire</legend>
        <dl>
            <dt><label for="f_id_cotisation_obligatoire">Cotisation obligatoire</label></dt>
            <dd>
                <select name="id_cotisation_obligatoire" id="f_id_cotisation_obligatoire">
                    <option value="">-- Non</option>
                    {foreach from=$cotisations item="cotisation"}
                    <option value="{$cotisation.id}" {form_field name="id_cotisation_obligatoire" selected=$cotisation.id data=$cat}>
                        {$cotisation.intitule} 
                        — {$cotisation.montant|escape|html_money} {$config.monnaie}
                        — {if $cotisation.duree}pour {$cotisation.duree} jours
                        {elseif $cotisation.debut}
                            du {$cotisation.debut|format_sqlite_date_to_french} au {$cotisation.fin|format_sqlite_date_to_french}
                        {else}
                            ponctuelle
                        {/if}
                    </option>
                    {/foreach}
                </select>
            </dd>
        </dl>
    </fieldset>

    <fieldset>
        <legend>Droits</legend>
        <dl class="droits">
            <dt><label for="f_droit_connexion_aucun">Les membres de cette catégorie peuvent-ils se connecter ?</label></dt>
            {if $readonly}
                <dd class="help">
                    Il n'est pas possible de désactiver ce droit pour votre propre catégorie.
                </dd>
            {/if}
            <dd>
                <input type="radio" name="droit_connexion" value="{$membres::DROIT_AUCUN}" id="f_droit_connexion_aucun" {if $cat.droit_connexion == $membres::DROIT_AUCUN}checked="checked"{/if} {$readonly} />
                <label for="f_droit_connexion_aucun"><b class="aucun">C</b> Non</label>
            </dd>
            <dd>
                <input type="radio" name="droit_connexion" value="{$membres::DROIT_ACCES}" id="f_droit_connexion_acces" {if $cat.droit_connexion == $membres::DROIT_ACCES}checked="checked"{/if} {$readonly} />
                <label for="f_droit_connexion_acces"><b class="acces">C</b> Oui</label>
            </dd>
        </dl>
        <dl class="droits">
            <dt><label for="f_droit_inscription_aucun">Les membres de cette catégorie peuvent-ils s'inscrire d'eux-même ?</label></dt>
            <dd>
                <input type="radio" name="droit_inscription" value="{$membres::DROIT_AUCUN}" id="f_droit_inscription_aucun" {if $cat.droit_inscription == $membres::DROIT_AUCUN}checked="checked"{/if} />
                <label for="f_droit_inscription_aucun"><b class="aucun">I</b> Non</label>
            </dd>
            <dd>
                <input type="radio" name="droit_inscription" value="{$membres::DROIT_ACCES}" id="f_droit_inscription_acces" {if $cat.droit_inscription == $membres::DROIT_ACCES}checked="checked"{/if} />
                <label for="f_droit_inscription_acces"><b class="acces">I</b> Oui</label>
            </dd>
        </dl>
        <dl class="droits">
            <dt><label for="f_droit_membres_aucun">Gestion des membres :</label></dt>
            <dd>
                <input type="radio" name="droit_membres" value="{$membres::DROIT_AUCUN}" id="f_droit_membres_aucun" {if $cat.droit_membres == $membres::DROIT_AUCUN}checked="checked"{/if} />
                <label for="f_droit_membres_aucun"><b class="aucun">M</b> Pas d'accès</label>
            </dd>
            <dd>
                <input type="radio" name="droit_membres" value="{$membres::DROIT_ACCES}" id="f_droit_membres_acces" {if $cat.droit_membres == $membres::DROIT_ACCES}checked="checked"{/if} />
                <label for="f_droit_membres_acces"><b class="acces">M</b> Lecture uniquement</label>
            </dd>
            <dd>
                <input type="radio" name="droit_membres" value="{$membres::DROIT_ECRITURE}" id="f_droit_membres_ecriture" {if $cat.droit_membres == $membres::DROIT_ECRITURE}checked="checked"{/if} />
                <label for="f_droit_membres_ecriture"><b class="ecriture">M</b> Lecture &amp; écriture</label>
            </dd>
            <dd>
                <input type="radio" name="droit_membres" value="{$membres::DROIT_ADMIN}" id="f_droit_membres_admin" {if $cat.droit_membres == $membres::DROIT_ADMIN}checked="checked"{/if} />
                <label for="f_droit_membres_admin"><b class="admin">M</b> Administration</label>
            </dd>
        </dl>
        <dl class="droits">
            <dt><label for="f_droit_compta_aucun">Comptabilité :</label></dt>
            <dd>
                <input type="radio" name="droit_compta" value="{$membres::DROIT_AUCUN}" id="f_droit_compta_aucun" {if $cat.droit_compta == $membres::DROIT_AUCUN}checked="checked"{/if} />
                <label for="f_droit_compta_aucun"><b class="aucun">€</b> Pas d'accès</label>
            </dd>
            <dd>
                <input type="radio" name="droit_compta" value="{$membres::DROIT_ACCES}" id="f_droit_compta_acces" {if $cat.droit_compta == $membres::DROIT_ACCES}checked="checked"{/if} />
                <label for="f_droit_compta_acces"><b class="acces">€</b> Lecture uniquement</label>
            </dd>
            <dd>
                <input type="radio" name="droit_compta" value="{$membres::DROIT_ECRITURE}" id="f_droit_compta_ecriture" {if $cat.droit_compta == $membres::DROIT_ECRITURE}checked="checked"{/if} />
                <label for="f_droit_compta_ecriture"><b class="ecriture">€</b> Lecture &amp; écriture</label>
            </dd>
            <dd>
                <input type="radio" name="droit_compta" value="{$membres::DROIT_ADMIN}" id="f_droit_compta_admin" {if $cat.droit_compta == $membres::DROIT_ADMIN}checked="checked"{/if} />
                <label for="f_droit_compta_admin"><b class="admin">€</b> Administration</label>
            </dd>
        </dl>
        <dl class="droits">
            <dt><label for="f_droit_wiki_aucun">Wiki :</label></dt>
            <dd>
                <input type="radio" name="droit_wiki" value="{$membres::DROIT_AUCUN}" id="f_droit_wiki_aucun" {if $cat.droit_wiki == $membres::DROIT_AUCUN}checked="checked"{/if} />
                <label for="f_droit_wiki_aucun"><b class="aucun">W</b> Pas d'accès</label>
            </dd>
            <dd>
                <input type="radio" name="droit_wiki" value="{$membres::DROIT_ACCES}" id="f_droit_wiki_acces" {if $cat.droit_wiki == $membres::DROIT_ACCES}checked="checked"{/if} />
                <label for="f_droit_wiki_acces"><b class="acces">W</b> Lecture uniquement</label>
            </dd>
            <dd>
                <input type="radio" name="droit_wiki" value="{$membres::DROIT_ECRITURE}" id="f_droit_wiki_ecriture" {if $cat.droit_wiki == $membres::DROIT_ECRITURE}checked="checked"{/if} />
                <label for="f_droit_wiki_ecriture"><b class="ecriture">W</b> Lecture &amp; écriture</label>
            </dd>
            <dd>
                <input type="radio" name="droit_wiki" value="{$membres::DROIT_ADMIN}" id="f_droit_wiki_admin" {if $cat.droit_wiki == $membres::DROIT_ADMIN}checked="checked"{/if} />
                <label for="f_droit_wiki_admin"><b class="admin">W</b> Administration</label>
            </dd>
        </dl>
        <dl class="droits">
            <dt><label for="f_droit_config_aucun">Les membres de cette catégorie peuvent-ils modifier la configuration ?</label></dt>
            {if $readonly}
                <dd class="help">
                    Il n'est pas possible de désactiver ce droit pour votre propre catégorie.
                </dd>
            {/if}
            <dd>
                <input type="radio" name="droit_config" value="{$membres::DROIT_AUCUN}" id="f_droit_config_aucun" {if $cat.droit_config == $membres::DROIT_AUCUN}checked="checked"{/if} {$readonly} />
                <label for="f_droit_config_aucun"><b class="aucun">&#x2611;</b> Non</label>
            </dd>
            <dd>
                <input type="radio" name="droit_config" value="{$membres::DROIT_ADMIN}" id="f_droit_config_admin" {if $cat.droit_config == $membres::DROIT_ADMIN}checked="checked"{/if} {$readonly} />
                <label for="f_droit_config_admin"><b class="admin">&#x2611;</b> Oui</label>
            </dd>
        </dl>
    </fieldset>

    <p class="submit">
        {csrf_field key="edit_cat_"|cat:$cat.id}
        <input type="submit" name="save" value="Enregistrer &rarr;" />
    </p>

</form>

{include file="admin/_foot.tpl"}

Added src/templates/admin/config/categories/supprimer.tpl version [9ebed2a419].

































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{include file="admin/_head.tpl" title="Supprimer une catégorie de membre" current="config"}

{include file="admin/config/_menu.tpl" current="categories"}

{form_errors}

<form method="post" action="{$self_url}">

    <fieldset>
        <legend>Supprimer la catégorie de membres ?</legend>
        <h3 class="warning">
            Êtes-vous sûr de vouloir supprimer la catégorie «&nbsp;{$cat.nom}&nbsp;» ?
        </h3>
        <p class="help">
            Attention, la catégorie ne doit plus contenir de membres pour pouvoir
            être supprimée.
        </p>
        <p class="help">
            Notez que si des pages du wiki étaient restreintes à la lecture ou à l'écriture
            aux seuls membres de ce groupe, elles redeviendront lisibles et modifiables
            par tous les membres ayant accès au wiki !
        </p>
    </fieldset>

    <p class="submit">
        {csrf_field key="delete_cat_"|cat:$cat.id}
        <input type="submit" name="delete" value="Supprimer &rarr;" />
    </p>

</form>

{include file="admin/_foot.tpl"}

Deleted src/templates/admin/config/donnees.tpl version [396a0ea87b].

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

























































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
{include file="admin/_head.tpl" title="Données — Sauvegarde et restauration" current="config"}

{include file="admin/config/_menu.tpl" current="donnees"}

{form_errors}

{if $code == Garradin\Sauvegarde::INTEGRITY_FAIL && Garradin\ALLOW_MODIFIED_IMPORT}
    <p class="alert">Pour passer outre, renvoyez le fichier en cochant la case «&nbsp;Ignorer les erreurs&nbsp;».
    Attention, si vous avez effectué des modifications dans la base de données, cela peut créer des bugs&nbsp;!</p>
{/if}

{if $ok}
    <p class="confirm">
        {if $ok == 'config'}La configuration a bien été enregistrée.
        {elseif $ok == 'create'}Une nouvelle sauvegarde a été créée.
        {elseif $ok == 'restore'}La restauration a bien été effectuée. Si vous désirez revenir en arrière, vous pouvez utiliser la sauvegarde automatique nommée <em>date-du-jour.avant_restauration.sqlite</em>, sinon vous pouvez l'effacer.
            {if $ok_code & Garradin\Sauvegarde::NOT_AN_ADMIN}
            </p>
            <p class="alert">
                <strong>Vous n'êtes pas administrateur dans cette sauvegarde.</strong> Garradin a donné les droits d'administration à toutes les catégories afin d'empêcher de ne plus pouvoir se connecter.
                Merci de corriger les droits des catégories maintenant.
            {/if}
        {elseif $ok == 'remove'}La sauvegarde a été supprimée.
        {/if}
    </p>
{/if}

<form method="post" action="{$self_url_no_qs}">

<p class="help">
    Info : la base de données fait actuellement {$db_size|format_bytes} (dont {$files_size|format_bytes} pour les documents et images).
</p>

<fieldset>
    <legend>Sauvegarde automatique</legend>
    <p class="help">
        En activant cette option une sauvegarde sera automatiquement créée à chaque intervalle donné.
        Par exemple en activant une sauvegarde hebdomadaire, une copie des données sera réalisée
        une fois par semaine, sauf si aucune modification n'a été effectuée sur les données
        ou que personne ne s'est connecté.
    </p>
    <dl>
        <dt><label for="f_frequency">Intervalle de sauvegarde</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
        <dd>
            <select name="frequence_sauvegardes" required="required" id="f_frequency">
                <option value="0"{form_field name=frequence_sauvegardes data=$config selected=0}>Aucun — les sauvegardes automatiques sont désactivées</option>
                <option value="1"{form_field name=frequence_sauvegardes data=$config selected=1}>Quotidien, tous les jours</option>
                <option value="7"{form_field name=frequence_sauvegardes data=$config selected=7}>Hebdomadaire, tous les 7 jours</option>
                <option value="15"{form_field name=frequence_sauvegardes data=$config selected=15}>Bimensuel, tous les 15 jours</option>
                <option value="30"{form_field name=frequence_sauvegardes data=$config selected=30}>Mensuel</option>
                <option value="90"{form_field name=frequence_sauvegardes data=$config selected=90}>Trimestriel</option>
                <option value="365{form_field name=frequence_sauvegardes data=$config selected=365}">Annuel</option>
            </select>
        </dd>
        <dt><label for="f_max_backups">Nombre de sauvegardes conservées</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
        <dd class="help">
            Par exemple avec l'intervalle mensuel, en indiquant de conserver 12 sauvegardes,
            vous pourrez garder un an d'historique de sauvegardes.
        </dd>
        <dd class="help">
            <strong>Attention :</strong> si vous choisissez un nombre important et un intervalle réduit,
            l'espace disque occupé par vos sauvegardes va rapidement augmenter.
        </dd>
        <dd><input type="number" name="nombre_sauvegardes" value="{form_field name=nombre_sauvegardes data=$config}" if="f_max_backups" min="1" max="90" required="required" /></dd>
    </dl>
    <p>
        {csrf_field key="backup_config"}
        <input type="submit" name="config" value="Enregistrer &rarr;" />
    </p>
</fieldset>

</form>
<form method="post" action="{$self_url_no_qs}">

<fieldset>
    <legend>Sauvegarde manuelle</legend>
    <p>
        {csrf_field key="backup_create"}
        <input type="submit" name="create" value="Créer une nouvelle sauvegarde des données &rarr;" />
    </p>
</fieldset>

</form>
<form method="post" action="{$self_url_no_qs}">

<fieldset>
    <legend>Copies de sauvegarde disponibles</legend>
    {if empty($liste)}
        <p class="help">Aucune copie de sauvegarde disponible.</p>
    {else}
        <dl>
            <dt><label for="f_select">Sélectionner une sauvegarde</label></dt>
            <dd>
                <select name="file" id="f_select">
                {foreach from=$liste key="f" item="d"}
                    <option value="{$f}">{$f} — {$d|date_fr:'d/m/Y à H:i'}</option>
                {/foreach}
                </select>
            </dd>
            <dd class="help">
                Attention, en cas de restauration, l'intégralité des données courantes seront effacées et remplacées par celles contenues dans la sauvegarde sélectionnée. Cependant, afin de prévenir toute erreur
                une sauvegarde des données sera réalisée avant la restauration.
            </dd>
        </dl>
        <p>
            {csrf_field key="backup_manage"}
            <input type="submit" name="restore" value="Restaurer cette sauvegarde" />
            <input type="submit" name="remove" value="Supprimer cette sauvegarde" />
        </p>
    {/if}
</fieldset>

</form>
<form method="post" action="{$self_url_no_qs}">

<fieldset>
    <legend>Téléchargement</legend>
    <p>
        {csrf_field key="backup_download"}
        <input type="submit" name="download" value="Télécharger une copie des données sur mon ordinateur" />
    </p>
</fieldset>

</form>
<form method="post" action="{$self_url_no_qs}" enctype="multipart/form-data">

<fieldset>
    <legend><label for="f_file">Restaurer depuis un fichier</label></legend>
    <p class="alert">
        Attention, l'intégralité des données courantes seront effacées et remplacées par celles
        contenues dans le fichier fourni.
    </p>
    <p class="help">
        Une sauvegarde des données courantes sera effectuée avant le remplacement,
        en cas de besoin d'annuler cette restauration.
    </p>
    <p>
        {csrf_field key="backup_restore"}
        <input type="hidden" name="MAX_FILE_SIZE" value="{$max_file_size}" />
        <input type="file" name="file" id="f_file" required="required" />
        (maximum {$max_file_size|format_bytes})
        <input type="submit" name="restore_file" value="Restaurer depuis le fichier sélectionné &rarr;" />
    </p>
    {if $code && ($code == Garradin\Sauvegarde::INTEGRITY_FAIL && Garradin\ALLOW_MODIFIED_IMPORT)}
    <p>
        <label><input type="checkbox" name="force_import" value="1" /> Ignorer les erreurs, je sais ce que je fait</label>
    </p>
    {/if}
</fieldset>

</form>

{include file="admin/_foot.tpl"}

Added src/templates/admin/config/donnees/_menu.tpl version [27f416469b].










1
2
3
4
5
6
7
8
9
+
+
+
+
+
+
+
+
+
<ul class="actions sub">
	<li{if $current == 'index'} class="current"{/if}><a href="{$admin_url}config/donnees/">Sauvegarder et restaurer</a></li>
	<li{if $current == 'import'} class="current"{/if}><a href="{$admin_url}config/donnees/import.php">Import et export</a></li>
	<li{if $current == 'local'} class="current"{/if}><a href="{$admin_url}config/donnees/local.php">Gestion des sauvegardes</a></li>
	{if ENABLE_AUTOMATIC_BACKUPS}
	<li{if $current == 'automatique'} class="current"{/if}><a href="{$admin_url}config/donnees/automatique.php">Configuration de la sauvegarde automatique</a></li>
	{/if}
	<li{if $current == 'reset'} class="current"{/if}><a href="{$admin_url}config/donnees/reset.php">Remise à zéro</a></li>
</ul>

Added src/templates/admin/config/donnees/automatique.tpl version [1804eff257].
























































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{include file="admin/_head.tpl" title="Sauvegarde et restauration" current="config"}

{include file="admin/config/_menu.tpl" current="donnees"}

{include file="admin/config/donnees/_menu.tpl" current="automatique"}

{form_errors}

{if $ok == 'config'}
	<p class="confirm">La configuration a bien été enregistrée.</p>
{/if}

<form method="post" action="{$self_url_no_qs}">

<fieldset>
	<legend>Configuration de la sauvegarde automatique</legend>
	<p class="help">
		En activant cette option une sauvegarde sera automatiquement créée à chaque intervalle donné.
		Par exemple en activant une sauvegarde hebdomadaire, une copie des données sera réalisée
		une fois par semaine, sauf si aucune modification n'a été effectuée sur les données
		ou que personne ne s'est connecté.
	</p>
	<dl>
		<dt><label for="f_frequency">Intervalle de sauvegarde</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
		<dd>
			<select name="frequence_sauvegardes" required="required" id="f_frequency">
				<option value="0"{form_field name=frequence_sauvegardes data=$config selected=0}>Aucun — les sauvegardes automatiques sont désactivées</option>
				<option value="1"{form_field name=frequence_sauvegardes data=$config selected=1}>Quotidien, tous les jours</option>
				<option value="7"{form_field name=frequence_sauvegardes data=$config selected=7}>Hebdomadaire, tous les 7 jours</option>
				<option value="15"{form_field name=frequence_sauvegardes data=$config selected=15}>Bimensuel, tous les 15 jours</option>
				<option value="30"{form_field name=frequence_sauvegardes data=$config selected=30}>Mensuel</option>
				<option value="90"{form_field name=frequence_sauvegardes data=$config selected=90}>Trimestriel</option>
				<option value="365{form_field name=frequence_sauvegardes data=$config selected=365}">Annuel</option>
			</select>
		</dd>
		<dt><label for="f_max_backups">Nombre de sauvegardes conservées</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
		<dd class="help">
			Par exemple avec l'intervalle mensuel, en indiquant de conserver 12 sauvegardes,
			vous pourrez garder un an d'historique de sauvegardes.
		</dd>
		<dd class="help">
			<strong>Attention :</strong> si vous choisissez un nombre important et un intervalle réduit,
			l'espace disque occupé par vos sauvegardes va rapidement augmenter.
		</dd>
		<dd><input type="number" name="nombre_sauvegardes" value="{form_field name=nombre_sauvegardes data=$config}" if="f_max_backups" min="1" max="90" required="required" /></dd>
	</dl>
	<p>
		{csrf_field key="backup_config"}
		<input type="submit" name="config" value="Enregistrer &rarr;" />
	</p>
</fieldset>

</form>

{include file="admin/_foot.tpl"}

Added src/templates/admin/config/donnees/import.tpl version [79252f8a0f].





















1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{include file="admin/_head.tpl" title="Import & export" current="config"}

{include file="admin/config/_menu.tpl" current="donnees"}

{include file="admin/config/donnees/_menu.tpl" current="import"}

<fieldset>
<dl>
	<dt>Membres</dt>
	<dd><a href="{$admin_url}membres/import.php">Import de la liste des membres</a></dd>
	<dd><a href="{$admin_url}membres/import.php?export=ods">Export de la liste des membres au format tableur Calc / Excel</a></dd>
	<dd><a href="{$admin_url}membres/import.php?export=csv">Export de la liste des membres au format CSV</a></dd>
	<dt>Comptabilité</dt>
	<dd><a href="{$admin_url}compta/import.php">Import des données comptables</a></dd>
	<dd><a href="{$admin_url}compta/import.php?export=ods">Export des données comptables au format tableur Calc / Excel</a></dd>
	<dd><a href="{$admin_url}compta/import.php?export=csv">Export des données comptables au format CSV</a></dd>
</dl>
</fieldset>

{include file="admin/_foot.tpl"}

Added src/templates/admin/config/donnees/index.tpl version [87fb524e37].









































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{include file="admin/_head.tpl" title="Sauvegarde et restauration" current="config"}

{include file="admin/config/_menu.tpl" current="donnees"}

{include file="admin/config/donnees/_menu.tpl" current="index"}

{form_errors}

{if $code == Sauvegarde::INTEGRITY_FAIL && ALLOW_MODIFIED_IMPORT}
    <p class="alert">Pour passer outre, renvoyez le fichier en cochant la case «&nbsp;Ignorer les erreurs&nbsp;».
    Attention, si vous avez effectué des modifications dans la base de données, cela peut créer des bugs&nbsp;!</p>
{/if}

{if $ok}
    <p class="confirm">
        {if $ok == 'restore'}La restauration a bien été effectuée. Si vous désirez revenir en arrière, vous pouvez utiliser la sauvegarde automatique nommée <em>{$now_date}.avant_restauration.sqlite</em>, sinon vous pouvez l'effacer.
            {if $ok_code & Sauvegarde::NOT_AN_ADMIN}
            </p>
            <p class="alert">
                <strong>Vous n'êtes pas administrateur dans cette sauvegarde.</strong> Garradin a donné les droits d'administration à toutes les catégories afin d'empêcher de ne plus pouvoir se connecter.
                Merci de corriger les droits des catégories maintenant.
            {/if}
        {elseif $ok == 'remove'}La sauvegarde a été supprimée.
        {/if}
    </p>
{/if}


<form method="post" action="{$self_url_no_qs}">

<fieldset>
    <legend>Téléchargement d'une sauvegarde</legend>
	<p class="help">
		Info : la base de données fait actuellement {$db_size|format_bytes} (dont {$files_size|format_bytes} pour les documents et images).
	</p>
    <p>
        {csrf_field key="backup_download"}
        <input type="submit" name="download" value="Télécharger une copie de la base de données sur mon ordinateur &rarr;" />
    </p>
</fieldset>

</form>

<form method="post" action="{$self_url_no_qs}" enctype="multipart/form-data">

<fieldset>
    <legend><label for="f_file">Restaurer depuis un fichier de sauvegarde</label></legend>
    <p class="alert">
        Attention, l'intégralité des données courantes seront effacées et remplacées par celles
        contenues dans le fichier fourni.
    </p>
    <p class="help">
        Une sauvegarde des données courantes sera effectuée avant le remplacement,
        en cas de besoin d'annuler cette restauration.
    </p>
    <p>
        {csrf_field key="backup_restore"}
        <input type="hidden" name="MAX_FILE_SIZE" value="{$max_file_size}" />
        <input type="file" name="file" id="f_file" required="required" />
        (maximum {$max_file_size|format_bytes})
        <input type="submit" name="restore_file" value="Restaurer depuis le fichier sélectionné &rarr;" />
    </p>
    {if $code && ($code == Sauvegarde::INTEGRITY_FAIL && ALLOW_MODIFIED_IMPORT)}
    <p>
        <label><input type="checkbox" name="force_import" value="1" /> Ignorer les erreurs, je sais ce que je fait</label>
    </p>
    {/if}
</fieldset>

</form>

{include file="admin/_foot.tpl"}

Added src/templates/admin/config/donnees/local.tpl version [4984d48811].




































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{include file="admin/_head.tpl" title="Gestion des sauvegardes" current="config"}

{include file="admin/config/_menu.tpl" current="donnees"}

{include file="admin/config/donnees/_menu.tpl" current="local"}

{form_errors}

{if $ok}
    <p class="confirm">
        {if $ok == 'create'}Une nouvelle sauvegarde a été créée.
        {elseif $ok == 'restore'}La restauration a bien été effectuée. Si vous désirez revenir en arrière, vous pouvez utiliser la sauvegarde automatique nommée <em>date-du-jour.avant_restauration.sqlite</em>, sinon vous pouvez l'effacer.
            {if $ok_code & Sauvegarde::NOT_AN_ADMIN}
            </p>
            <p class="alert">
                <strong>Vous n'êtes pas administrateur dans cette sauvegarde.</strong> Garradin a donné les droits d'administration à toutes les catégories afin d'empêcher de ne plus pouvoir se connecter.
                Merci de corriger les droits des catégories maintenant.
            {/if}
        {elseif $ok == 'remove'}La sauvegarde a été supprimée.
        {/if}
    </p>
{/if}

<form method="post" action="{$self_url_no_qs}">

<fieldset>
    <legend>Copies de sauvegarde disponibles</legend>
    {if empty($liste)}
        <p class="help">Aucune copie de sauvegarde disponible.</p>
    {else}
        <dl>
            <dt><label for="f_select">Sélectionner une sauvegarde</label></dt>
            <dd>
                <select name="file" id="f_select">
                {foreach from=$liste key="f" item="d"}
                    <option value="{$f}">{$f} — {$d|date_fr:'d/m/Y à H:i'}</option>
                {/foreach}
                </select>
            </dd>
            <dd class="help">
                Attention, en cas de restauration, l'intégralité des données courantes seront effacées et remplacées par celles contenues dans la sauvegarde sélectionnée. Cependant, afin de prévenir toute erreur
                une sauvegarde des données sera réalisée avant la restauration.
            </dd>
        </dl>
        <p>
            {csrf_field key="backup_manage"}
            <input type="submit" name="restore" value="Restaurer cette sauvegarde" />
            <input type="submit" name="remove" value="Supprimer cette sauvegarde" />
        </p>
    {/if}
</fieldset>

</form>

<form method="post" action="{$self_url_no_qs}">

<fieldset>
    <legend>Sauvegarde manuelle</legend>
    <p>
        {csrf_field key="backup_create"}
        <input type="submit" name="create" value="Créer une nouvelle sauvegarde des données &rarr;" />
    </p>
</fieldset>

</form>

{include file="admin/_foot.tpl"}

Added src/templates/admin/config/donnees/reset.tpl version [b44198b72b].







































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{include file="admin/_head.tpl" title="Remise à zéro" current="config"}

{include file="admin/config/_menu.tpl" current="donnees"}

{include file="admin/config/donnees/_menu.tpl" current="reset"}

{form_errors}

{if $ok !== null}
    <p class="confirm">La remise à zéro a été effectuée. Une sauvegarde a également été créée.</p>
    </p>
{/if}

<form method="post" action="{$self_url_no_qs}">

<fieldset>
    <legend>Remise à zéro</legend>
	<p class="error">
		Attention : toutes les données seront effacées&nbsp;! Ceci inclut les membres, les opérations comptables, les pages du wiki, etc.
        Seul votre compte membre sera re-créé avec le même email et mot de passe.
	</p>
    <p class="help">
        Une sauvegarde sera automatiquement créée avant de procéder à la remise à zéro.
    </p>
    <dl>
        <dt><label for="f_passe_verif">Votre mot de passe</label> (pour vérification)</dt>
        <dd><input type="password" name="passe_verif" id="f_passe_verif" /></dd>
    </dl>
    <p>
        {csrf_field key="reset"}
        <input type="submit" name="reset_ok" value="Oui, je veux remettre à zéro" />
    </p>
</fieldset>

</form>


{include file="admin/_foot.tpl"}

Deleted src/templates/admin/config/import.tpl version [e2b6c250be].

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
















-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
{include file="admin/_head.tpl" title="Import & export" current="config"}

{include file="admin/config/_menu.tpl" current="import"}

<fieldset>
<dl>
	<dt>Membres</dt>
    <dd><a href="{$admin_url}membres/import.php">Import de la liste des membres</a></dd>
    <dd><a href="{$admin_url}membres/import.php?export">Export de la liste des membres en CSV (pour tableurs)</a></dd>
    <dt>Comptabilité</dt>
    <dd><a href="{$admin_url}compta/import.php">Import des données comptables</a></dd>
    <dd><a href="{$admin_url}compta/import.php?export">Export des données comptables en CSV</a></dd>
</dl>
</fieldset>

{include file="admin/_foot.tpl"}

Modified src/templates/admin/config/index.tpl from [8b2246c56b] to [6078c55f63].

1
2
3
4
5

6
7
8
9
10
11
12
1
2
3
4

5
6
7
8
9
10
11
12




-
+







{include file="admin/_head.tpl" title="Configuration" current="config"}

{include file="admin/config/_menu.tpl" current="index"}

{if $ok}
{if $ok && !$form->hasErrors()}
    <p class="confirm">
        La configuration a bien été enregistrée.
    </p>
{/if}

{form_errors}

Modified src/templates/admin/config/membres.tpl from [79611cc039] to [fcc1959f81].

1
2
3

4
5
6
7
8
9
10
1
2

3
4
5
6
7
8
9
10


-
+







{include file="admin/_head.tpl" current="config" js=1}

{include file="admin/config/_menu.tpl" current="membres"}
{include file="admin/config/_menu.tpl" current="fiches_membres"}

{if isset($status) && $status == 'OK'}
    <p class="confirm">
        La configuration a bien été enregistrée.
    </p>
{elseif isset($status) && $status == 'ADDED'}
    <p class="confirm">

Modified src/templates/admin/config/plugins.tpl from [f3dbb74805] to [d99e6b054a].

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
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







-
+






-
+




+
+
+
+
+
+















+







    </form>
{else}
    {if !empty($liste_installes)}
        <table class="list">
            <thead>
                <tr>
                    <th>Extension</th>
                    <td>Auteur</td>
                    <td></td>
                    <td>Version installée</td>
                    <td></td>
                </tr>
            </thead>
            <tbody>
                {foreach from=$liste_installes item="plugin"}
                <tr>
                <tr{if $plugin.disabled} class="disabled"{/if}>
                    <th>
                        <h4>{$plugin.nom}</h4>
                        <small>{$plugin.description}</small>
                    </th>
                    {if $plugin.disabled}
                    <td colspan="3">
                        <span class="alert">Code source du plugin non trouvé dans le répertoire <em>plugins</em>&nbsp;!</span><br />
                        Ce plugin ne peut fonctionner ou être désinstallé.
                    </td>
                    {else}
                    <td>
                        <a href="{$plugin.url}" onclick="return !window.open(this.href);">{$plugin.auteur}</a>
                    </td>
                    <td>
                        {$plugin.version}
                    </td>
                    <td class="actions">
                        {if empty($plugin.system)}
                            <a href="{$admin_url}config/plugins.php?delete={$plugin.id}">Désinstaller</a>
                        {/if}
                        {if !empty($plugin.config)}
                            {if empty($plugin.system)}|{/if}
                            <a href="{plugin_url id=$plugin.id file="config.php"}">Configurer</a>
                        {/if}
                    </td>
                    {/if}
                </tr>
                {/foreach}
            </tbody>
        </table>
    {else}
        <p class="help">
            Aucune extension n'est installée.

Modified src/templates/admin/config/site.tpl from [012fc113de] to [04150e10c9].

1
2
3
4
5
6











7

8
9
10
11
12
13
14
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






+
+
+
+
+
+
+
+
+
+
+
-
+







{include file="admin/_head.tpl" title="Configuration — Site public" current="config" js=1}

{form_errors}

{include file="admin/config/_menu.tpl" current="site"}

{if $config.desactiver_site}
    <div class="alert">
        <h3>Site public désactivé</h3>
        <p>Le site public est désactivé, les visiteurs sont redirigés automatiquement vers la page de connexion.</p>
        <form method="post" action="{$self_url}">
            <p class="submit">
                {csrf_field key="config_site"}
                <input type="submit" name="activer_site" value="Réactiver le site public &rarr;" />
            </p>
        </form>
    </div>
{if isset($edit)}
{elseif isset($edit)}
    <form method="post" action="{$self_url}">
        <h3>Éditer un squelette</h3>

        {if $ok}
        <p class="confirm">
            Modifications enregistrées.
        </p>
33
34
35
36
37
38
39
40
41




















42
43
44
45
46
47
48
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







-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







    var skel_list = {$sources|escape:json};
    var skel_current = "{$edit.file|escape:'js'}";
    </script>
    <script type="text/javascript" src="{$admin_url}static/scripts/skel_editor.js"></script>
{else}

    <fieldset>
        <legend>Gérer le site public</legend>
        <p><a href="{$admin_url}wiki/creer.php?public">Créer une nouvelle page sur le site web public</a></p>
        <legend>Activation du site public</legend>
        <dl>
            <dt>
                <form method="post" action="{$self_url}">
                <input type="submit" name="desactiver_site" value="Désactiver le site public" />
                {csrf_field key="config_site"}
                </form>
            </dt>
            <dd class="help">
                En désactivant le site public, les visiteurs seront automatiquement redirigés vers la page de connexion.<br />
                Cette option est utile si vous avez déjà un site web et ne souhaitez pas utiliser la fonctionnalité site web de Garradin.
            </dd>
        </dl>
    </fieldset>

    <fieldset>
        <legend>Gérer le contenu du site public</legend>
        <p class="help">
            Le contenu affiché sur le site est celui présent dans le wiki, il suffit de sélectionner «&nbsp;Cette page est visible ur le site de l'association&nbsp;» à l'édition d'une page wiki. Il est également possible de <a href="{$admin_url}wiki/creer.php?public">créer une nouvelle page publique sur le wiki</a>.
        </p>
    </fieldset>

    <form method="post" action="{$self_url}">
    <fieldset class="templatesList">
        <legend>Squelettes du site</legend>

        {if $reset_ok}

Modified src/templates/admin/index.tpl from [82c846a0c8] to [68896b2b83].

1


2
3
4
5
6
7
8
1
2
3
4
5
6
7
8
9
10

+
+







{include file="admin/_head.tpl" title="Bonjour %s !"|args:$user.identite current="home"}

{$banniere|raw}

<ul class="actions">
    <li><a href="{$admin_url}mes_infos.php">Modifier mes informations personnelles</a></li>
    {if $cotisation}
    <li>
        {if !$cotisation.a_jour}
            <b class="error">Cotisation en retard&nbsp;!</b>

Modified src/templates/admin/login.tpl from [a6078916db] to [ae198371c7].

1

2
3
4
5
6
7
8

1
2
3
4
5
6
7
8
-
+







{include file="admin/_head.tpl" title="Connexion"}
{include file="admin/_head.tpl" title="Connexion" js=1}

{form_errors}
{show_error if=$fail message="Connexion impossible. Vérifiez l'adresse e-mail et le mot de passe."}

{if !$ssl_enabled && $prefer_ssl}
    <p class="alert">
        <strong>Message de sécurité</strong><br />
43
44
45
46
47
48
49
50




51
43
44
45
46
47
48
49
50
51
52
53
54
55








+
+
+
+

    </p>

    <p class="help">
        <a href="{$admin_url}password.php">Pas de mot de passe ou mot de passe perdu ?</a>
    </p>

</form>

<script type="text/javascript">
g.enhancePasswordField($('#f_passe'));
</script>

{include file="admin/_foot.tpl"}

Added src/templates/admin/membres/_list_actions.tpl version [92e160c09f].




















1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
		<tfoot>
			<tr>
				{if $session->canAccess('membres', Membres::DROIT_ADMIN)}<td class="check"><input type="checkbox" value="Tout cocher / décocher" /></td>{/if}
				<td class="actions" colspan="{$colspan}">
					<em>Pour les membres cochés :</em>
					{csrf_field key="membres_action"}
					<select name="action">
						<option value="">— Choisir une action à effectuer —</option>
						<option value="move">Changer de catégorie</option>
						<option value="csv">Exporter en tableau CSV</option>
						<option value="ods">Exporter en classeur ODS</option>
						<option value="delete">Supprimer</option>
					</select>
					<noscript>
						<input type="submit" value="OK" />
					</noscript>
				</td>
			</tr>
		</tfoot>

Added src/templates/admin/membres/_nav.tpl version [308ec1b1ac].










1
2
3
4
5
6
7
8
9
+
+
+
+
+
+
+
+
+
<ul class="actions">
    <li{if $current == 'index'} class="current"{/if}><a href="{$admin_url}membres/">Liste des membres</a></li>
    <li{if $current == 'recherche'} class="current"{/if}><a href="{$admin_url}membres/recherche.php">Recherche avancée</a></li>
    <li{if $current == 'recherches'} class="current"{/if}><a href="{$admin_url}membres/recherches.php">Recherches enregistrées</a></li>
    {if $session->canAccess('membres', Membres::DROIT_ADMIN)}
        <li{if $current == 'sql'} class="current"{/if}><a href="{$admin_url}membres/recherche_sql.php">Recherche SQL</a></li>
        <li{if $current == 'import'} class="current"{/if}><a href="{$admin_url}membres/import.php">Import &amp; export</a></li>
    {/if}
</ul>

Modified src/templates/admin/membres/action.tpl from [d79c9cc41b] to [a933fed19a].

1




2
3
4
5
6
7
8
1
2
3
4
5
6
7
8
9
10
11
12

+
+
+
+







{include file="admin/_head.tpl" title="Action collective sur les membres" current="membres"}

<p class="alert">
    {$selected|count} membres sélectionnés
</p>

{form_errors}

<form method="post" action="{$self_url}">
    {foreach from=$selected item="id"}
        <input type="hidden" name="selected[]" value="{$id}" />
    {/foreach}

Modified src/templates/admin/membres/ajouter.tpl from [f017ed08b2] to [a19929b9a3].

27
28
29
30
31
32
33
34

35
36
37
38
39
40
41
27
28
29
30
31
32
33

34
35
36
37
38
39
40
41







-
+







            </dd>
            <dd><input type="password" name="passe" id="f_passe" value="{form_field name=passe}" pattern=".{ldelim}6,{rdelim}" /></dd>
            <dt><label for="f_repasse">Encore le mot de passe</label> (vérification)</dt>
            <dd><input type="password" name="passe_confirmed" id="f_repasse" value="{form_field name=passe_confirmed}" pattern=".{ldelim}6,{rdelim}" /></dd>
        </dl>
    </fieldset>

    {if $session->canAccess('membres', Garradin\Membres::DROIT_ADMIN)}
    {if $session->canAccess('membres', Membres::DROIT_ADMIN)}
    <fieldset>
        <legend>Général</legend>
        <dl>
            <dt><label for="f_cat">Catégorie du membre</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
            <dd>
                <select name="id_categorie" id="f_cat">
                {foreach from=$membres_cats key="id" item="nom"}

Deleted src/templates/admin/membres/categories/index.tpl version [9c762d5e02].

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















































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
{include file="admin/_head.tpl" title="Catégories de membres" current="membres/categories"}

<table class="list">
    <thead>
        <th>Nom</th>
        <td>Membres</td>
        <td>Droits</td>
        <td></td>
    </thead>
    <tbody>
        {foreach from=$liste item="cat"}
            <tr>
                <th>{$cat.nom}</th>
                <td class="num">{$cat.nombre}</td>
                <td class="droits">
                    {format_droits droits=$cat}
                </td>
                <td class="actions">
                    <a class="icn" href="{$admin_url}membres/categories/modifier.php?id={$cat.id}" title="Modifier">✎</a>
                    {if $cat.id != $user.id_categorie}
                    <a class="icn" href="{$admin_url}membres/categories/supprimer.php?id={$cat.id}" title="Supprimer">✘</a>
                    {/if}
                </td>
            </tr>
        {/foreach}
    </tbody>
</table>

<form method="post" action="{$self_url}">

    <fieldset>
        <legend>Ajouter une catégorie</legend>
        <dl>
            <dt><label for="f_nom">Nom</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
            <dd><input type="text" name="nom" id="f_nom" value="{form_field name=nom}" required="required" /></dd>
        </dl>
    </fieldset>

    <p class="submit">
        {csrf_field key="new_cat"}
        <input type="submit" name="save" value="Enregistrer &rarr;" />
    </p>

</form>


{include file="admin/_foot.tpl"}

Deleted src/templates/admin/membres/categories/modifier.tpl version [206fa39a37].

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





































































































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
{include file="admin/_head.tpl" title="Modifier une catégorie" current="membres/categories"}

{form_errors}

<form method="post" action="{$self_url}">

    <fieldset>
        <legend>Informations générales</legend>
        <dl>
            <dt><label for="f_nom">Nom</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
            <dd><input type="text" name="nom" id="f_nom" value="{form_field data=$cat name=nom}" required="required" /></dd>
            <dt><label for="f_description">Description</label></dt>
            <dd><textarea name="description" id="f_description" rows="5" cols="50">{form_field data=$cat name=description}</textarea></dd>
            <dt>
                <input type="checkbox" name="cacher" value="1" id="f_cacher" {if $cat.cacher}checked="checked"{/if} />
                <label for="f_cacher">Catégorie cachée</label>
            </dt>
            <dd class="help">
                Si coché cette catégorie ne sera visible qu'aux administrateurs et ne recevra pas
                de messages collectifs ou de rappels.
            </dd>
        </dl>
    </fieldset>

    <fieldset>
        <legend>Cotisation obligatoire</legend>
        <dl>
            <dt><label for="f_id_cotisation_obligatoire">Cotisation obligatoire</label></dt>
            <dd>
                <select name="id_cotisation_obligatoire" id="f_id_cotisation_obligatoire">
                    <option value="">-- Non</option>
                    {foreach from=$cotisations item="cotisation"}
                    <option value="{$cotisation.id}" {form_field name="id_cotisation_obligatoire" selected=$cotisation.id data=$cat}>
                        {$cotisation.intitule} 
                        — {$cotisation.montant|escape|html_money} {$config.monnaie}
                        — {if $cotisation.duree}pour {$cotisation.duree} jours
                        {elseif $cotisation.debut}
                            du {$cotisation.debut|format_sqlite_date_to_french} au {$cotisation.fin|format_sqlite_date_to_french}
                        {else}
                            ponctuelle
                        {/if}
                    </option>
                    {/foreach}
                </select>
            </dd>
        </dl>
    </fieldset>

    <fieldset>
        <legend>Droits</legend>
        <dl class="droits">
            <dt><label for="f_droit_connexion_aucun">Les membres de cette catégorie peuvent-ils se connecter ?</label></dt>
            {if $readonly}
                <dd class="help">
                    Il n'est pas possible de désactiver ce droit pour votre propre catégorie.
                </dd>
            {/if}
            <dd>
                <input type="radio" name="droit_connexion" value="{$membres::DROIT_AUCUN}" id="f_droit_connexion_aucun" {if $cat.droit_connexion == $membres::DROIT_AUCUN}checked="checked"{/if} {$readonly} />
                <label for="f_droit_connexion_aucun"><b class="aucun">C</b> Non</label>
            </dd>
            <dd>
                <input type="radio" name="droit_connexion" value="{$membres::DROIT_ACCES}" id="f_droit_connexion_acces" {if $cat.droit_connexion == $membres::DROIT_ACCES}checked="checked"{/if} {$readonly} />
                <label for="f_droit_connexion_acces"><b class="acces">C</b> Oui</label>
            </dd>
        </dl>
        <dl class="droits">
            <dt><label for="f_droit_inscription_aucun">Les membres de cette catégorie peuvent-ils s'inscrire d'eux-même ?</label></dt>
            <dd>
                <input type="radio" name="droit_inscription" value="{$membres::DROIT_AUCUN}" id="f_droit_inscription_aucun" {if $cat.droit_inscription == $membres::DROIT_AUCUN}checked="checked"{/if} />
                <label for="f_droit_inscription_aucun"><b class="aucun">I</b> Non</label>
            </dd>
            <dd>
                <input type="radio" name="droit_inscription" value="{$membres::DROIT_ACCES}" id="f_droit_inscription_acces" {if $cat.droit_inscription == $membres::DROIT_ACCES}checked="checked"{/if} />
                <label for="f_droit_inscription_acces"><b class="acces">I</b> Oui</label>
            </dd>
        </dl>
        <dl class="droits">
            <dt><label for="f_droit_membres_aucun">Gestion des membres :</label></dt>
            {if $readonly}
                <dd class="help">
                    Il n'est pas possible de désactiver ce droit pour votre propre catégorie.
                </dd>
            {/if}
            <dd>
                <input type="radio" name="droit_membres" value="{$membres::DROIT_AUCUN}" id="f_droit_membres_aucun" {if $cat.droit_membres == $membres::DROIT_AUCUN}checked="checked"{/if} {$readonly} />
                <label for="f_droit_membres_aucun"><b class="aucun">M</b> Pas d'accès</label>
            </dd>
            <dd>
                <input type="radio" name="droit_membres" value="{$membres::DROIT_ACCES}" id="f_droit_membres_acces" {if $cat.droit_membres == $membres::DROIT_ACCES}checked="checked"{/if} {$readonly} />
                <label for="f_droit_membres_acces"><b class="acces">M</b> Lecture uniquement</label>
            </dd>
            <dd>
                <input type="radio" name="droit_membres" value="{$membres::DROIT_ECRITURE}" id="f_droit_membres_ecriture" {if $cat.droit_membres == $membres::DROIT_ECRITURE}checked="checked"{/if} {$readonly} />
                <label for="f_droit_membres_ecriture"><b class="ecriture">M</b> Lecture &amp; écriture</label>
            </dd>
            <dd>
                <input type="radio" name="droit_membres" value="{$membres::DROIT_ADMIN}" id="f_droit_membres_admin" {if $cat.droit_membres == $membres::DROIT_ADMIN}checked="checked"{/if} {$readonly} />
                <label for="f_droit_membres_admin"><b class="admin">M</b> Administration</label>
            </dd>
        </dl>
        <dl class="droits">
            <dt><label for="f_droit_compta_aucun">Comptabilité :</label></dt>
            <dd>
                <input type="radio" name="droit_compta" value="{$membres::DROIT_AUCUN}" id="f_droit_compta_aucun" {if $cat.droit_compta == $membres::DROIT_AUCUN}checked="checked"{/if} />
                <label for="f_droit_compta_aucun"><b class="aucun">€</b> Pas d'accès</label>
            </dd>
            <dd>
                <input type="radio" name="droit_compta" value="{$membres::DROIT_ACCES}" id="f_droit_compta_acces" {if $cat.droit_compta == $membres::DROIT_ACCES}checked="checked"{/if} />
                <label for="f_droit_compta_acces"><b class="acces">€</b> Lecture uniquement</label>
            </dd>
            <dd>
                <input type="radio" name="droit_compta" value="{$membres::DROIT_ECRITURE}" id="f_droit_compta_ecriture" {if $cat.droit_compta == $membres::DROIT_ECRITURE}checked="checked"{/if} />
                <label for="f_droit_compta_ecriture"><b class="ecriture">€</b> Lecture &amp; écriture</label>
            </dd>
            <dd>
                <input type="radio" name="droit_compta" value="{$membres::DROIT_ADMIN}" id="f_droit_compta_admin" {if $cat.droit_compta == $membres::DROIT_ADMIN}checked="checked"{/if} />
                <label for="f_droit_compta_admin"><b class="admin">€</b> Administration</label>
            </dd>
        </dl>
        <dl class="droits">
            <dt><label for="f_droit_wiki_aucun">Wiki :</label></dt>
            <dd>
                <input type="radio" name="droit_wiki" value="{$membres::DROIT_AUCUN}" id="f_droit_wiki_aucun" {if $cat.droit_wiki == $membres::DROIT_AUCUN}checked="checked"{/if} />
                <label for="f_droit_wiki_aucun"><b class="aucun">W</b> Pas d'accès</label>
            </dd>
            <dd>
                <input type="radio" name="droit_wiki" value="{$membres::DROIT_ACCES}" id="f_droit_wiki_acces" {if $cat.droit_wiki == $membres::DROIT_ACCES}checked="checked"{/if} />
                <label for="f_droit_wiki_acces"><b class="acces">W</b> Lecture uniquement</label>
            </dd>
            <dd>
                <input type="radio" name="droit_wiki" value="{$membres::DROIT_ECRITURE}" id="f_droit_wiki_ecriture" {if $cat.droit_wiki == $membres::DROIT_ECRITURE}checked="checked"{/if} />
                <label for="f_droit_wiki_ecriture"><b class="ecriture">W</b> Lecture &amp; écriture</label>
            </dd>
            <dd>
                <input type="radio" name="droit_wiki" value="{$membres::DROIT_ADMIN}" id="f_droit_wiki_admin" {if $cat.droit_wiki == $membres::DROIT_ADMIN}checked="checked"{/if} />
                <label for="f_droit_wiki_admin"><b class="admin">W</b> Administration</label>
            </dd>
        </dl>
        <dl class="droits">
            <dt><label for="f_droit_config_aucun">Les membres de cette catégorie peuvent-ils modifier la configuration ?</label></dt>
            {if $readonly}
                <dd class="help">
                    Il n'est pas possible de désactiver ce droit pour votre propre catégorie.
                </dd>
            {/if}
            <dd>
                <input type="radio" name="droit_config" value="{$membres::DROIT_AUCUN}" id="f_droit_config_aucun" {if $cat.droit_config == $membres::DROIT_AUCUN}checked="checked"{/if} {$readonly} />
                <label for="f_droit_config_aucun"><b class="aucun">&#x2611;</b> Non</label>
            </dd>
            <dd>
                <input type="radio" name="droit_config" value="{$membres::DROIT_ADMIN}" id="f_droit_config_admin" {if $cat.droit_config == $membres::DROIT_ADMIN}checked="checked"{/if} {$readonly} />
                <label for="f_droit_config_admin"><b class="admin">&#x2611;</b> Oui</label>
            </dd>
        </dl>
    </fieldset>

    <p class="submit">
        {csrf_field key="edit_cat_"|cat:$cat.id}
        <input type="submit" name="save" value="Enregistrer &rarr;" />
    </p>

</form>

{include file="admin/_foot.tpl"}

Deleted src/templates/admin/membres/categories/supprimer.tpl version [145753eeb5].

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






























-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
{include file="admin/_head.tpl" title="Supprimer une catégorie" current="membres/categories"}

{form_errors}

<form method="post" action="{$self_url}">

    <fieldset>
        <legend>Supprimer la catégorie de membres ?</legend>
        <h3 class="warning">
            Êtes-vous sûr de vouloir supprimer la catégorie «&nbsp;{$cat.nom}&nbsp;» ?
        </h3>
        <p class="help">
            Attention, la catégorie ne doit plus contenir de membres pour pouvoir
            être supprimée.
        </p>
        <p class="help">
            Notez que si des pages du wiki étaient restreintes à la lecture ou à l'écriture
            aux seuls membres de ce groupe, elles redeviendront lisibles et modifiables
            par tous les membres ayant accès au wiki !
        </p>
    </fieldset>

    <p class="submit">
        {csrf_field key="delete_cat_"|cat:$cat.id}
        <input type="submit" name="delete" value="Supprimer &rarr;" />
    </p>

</form>

{include file="admin/_foot.tpl"}

Modified src/templates/admin/membres/cotisations.tpl from [90b6408dfe] to [c10bc42e53].

1
2
3
4
5
6


7
8
9
10
11
12
13
1
2
3
4


5
6
7
8
9
10
11
12
13




-
-
+
+







{include file="admin/_head.tpl" title="Cotisations du membre" current="membres/cotisations"}

<ul class="actions">
    <li><a href="{$admin_url}membres/fiche.php?id={$membre.id}"><b>{$membre.identite}</b></a></li>
    <li><a href="{$admin_url}membres/modifier.php?id={$membre.id}">Modifier</a></li>
    {if $session->canAccess('membres', Garradin\Membres::DROIT_ADMIN) && $user.id != $membre.id}
    {if $session->canAccess('membres', Membres::DROIT_ECRITURE)}<li><a href="{$admin_url}membres/modifier.php?id={$membre.id}">Modifier</a></li>{/if}
    {if $session->canAccess('membres', Membres::DROIT_ADMIN) && $user.id != $membre.id}
        <li><a href="{$admin_url}membres/supprimer.php?id={$membre.id}">Supprimer</a></li>
    {/if}
    <li class="current"><a href="{$admin_url}membres/cotisations.php?id={$membre.id}">Suivi des cotisations</a></li>
</ul>

<dl class="cotisation">
{if $cotisation}
78
79
80
81
82
83
84
85

86
87
88
89
90
91
92
78
79
80
81
82
83
84

85
86
87
88
89
90
91
92







-
+







                        du {$c.debut|format_sqlite_date_to_french} au {$c.fin|format_sqlite_date_to_french}
                    {else}
                        ponctuelle
                    {/if}
                    — {$c.montant|escape|html_money} {$config.monnaie}
                </td>
                <td>
                    {if $session->canAccess('compta', Garradin\Membres::DROIT_ECRITURE) && !empty($c.nb_operations)}
                    {if $session->canAccess('compta', Membres::DROIT_ECRITURE) && !empty($c.nb_operations)}
                        <a href="{$admin_url}compta/operations/cotisation.php?id={$c.id}">{$c.nb_operations} écriture{if $c.nb_operations > 1}s{/if}</a>
                    {/if}
                </td>
                <td class="actions">
                    <a class="icn" href="{$admin_url}membres/cotisations/voir.php?id={$c.id_cotisation}" title="Liste des membres inscrits à cette cotisation">👪</a>
                    <a class="icn" href="{$admin_url}membres/cotisations/supprimer.php?id={$c.id}" title="Supprimer cette cotisation pour ce membre">✘</a>
                </td>

Modified src/templates/admin/membres/cotisations/ajout.tpl from [3866bd8690] to [433111eec2].

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
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






-
+










-
+







-
+







{if $membre}
    {include file="admin/_head.tpl" title="Enregistrer une cotisation pour le membre" current="membres/cotisations" js=1}

    <ul class="actions">
        <li><a href="{$admin_url}membres/fiche.php?id={$membre.id}"><b>{$membre.identite}</b></a></li>
        <li><a href="{$admin_url}membres/modifier.php?id={$membre.id}">Modifier</a></li>
        {if $session->canAccess('membres', Garradin\Membres::DROIT_ADMIN) && $user.id != $membre.id}
        {if $session->canAccess('membres', Membres::DROIT_ADMIN) && $user.id != $membre.id}
            <li><a href="{$admin_url}membres/supprimer.php?id={$membre.id}">Supprimer</a></li>
        {/if}
        <li><a href="{$admin_url}membres/cotisations.php?id={$membre.id}">Suivi des cotisations</a></li>
    </ul>
{else}
    {include file="admin/_head.tpl" title="Enregistrer une cotisation" current="membres/cotisations" js=1}

    <ul class="actions">
        <li><a href="{$admin_url}membres/cotisations/">Cotisations</a></li>
        <li class="current"><a href="{$admin_url}membres/cotisations/ajout.php">Saisie d'une cotisation</a></li>
        {if $session->canAccess('membres', Garradin\Membres::DROIT_ADMIN)}
        {if $session->canAccess('membres', Membres::DROIT_ADMIN)}
            <li><a href="{$admin_url}membres/cotisations/gestion/rappels.php">Gestion des rappels automatiques</a></li>
        {/if}
    </ul>
{/if}

{form_errors}

{if $session->canAccess('compta', Garradin\Membres::DROIT_ECRITURE)}
{if $session->canAccess('compta', Membres::DROIT_ECRITURE)}
    <p class="help">
        Cette page sert à enregistrer les cotisations des membres de l'association.
        Pour enregistrer un don ou une dépense, comme le paiement d'un prestataire ou une facture, il est possible de <a href="{$admin_url}compta/operations/saisir.php">saisir une opération comptable</a>.
    </p>
{/if}

<form method="post" action="{$self_url}">

Modified src/templates/admin/membres/cotisations/gestion/modifier.tpl from [a056f65f4d] to [0430bb0891].

1
2
3
4
5
6

7
8
9
10
11
12
13
1
2
3
4
5

6
7
8
9
10
11
12
13





-
+







{include file="admin/_head.tpl" title="Modifier une cotisation" current="membres/cotisations" js=1}

<ul class="actions">
    <li class="current"><a href="{$admin_url}membres/cotisations/">Cotisations</a></li>
    <li><a href="{$admin_url}membres/cotisations/ajout.php">Saisie d'une cotisation</a></li>
    {if $session->canAccess('membres', Garradin\Membres::DROIT_ADMIN)}
    {if $session->canAccess('membres', Membres::DROIT_ADMIN)}
        <li><a href="{$admin_url}membres/cotisations/gestion/rappels.php">Gestion des rappels automatiques</a></li>
    {/if}
</ul>

{form_errors}

<form method="post" action="{$self_url}">

Modified src/templates/admin/membres/cotisations/gestion/supprimer.tpl from [0c6872fd04] to [002d1f0636].

1
2
3
4
5
6

7
8
9
10
11
12
13
1
2
3
4
5

6
7
8
9
10
11
12
13





-
+







{include file="admin/_head.tpl" title="Supprimer une cotisation" current="membres/cotisations"}

<ul class="actions">
    <li class="current"><a href="{$admin_url}membres/cotisations/">Cotisations</a></li>
    <li><a href="{$admin_url}membres/cotisations/ajout.php">Saisie d'une cotisation</a></li>
    {if $session->canAccess('membres', Garradin\Membres::DROIT_ADMIN)}
    {if $session->canAccess('membres', Membres::DROIT_ADMIN)}
        <li><a href="{$admin_url}membres/cotisations/gestion/rappels.php">Gestion des rappels automatiques</a></li>
    {/if}
</ul>

{form_errors}

<form method="post" action="{$self_url}">

Modified src/templates/admin/membres/cotisations/index.tpl from [94a8f288ff] to [c77fd21ece].

1
2
3
4
5
6

7
8
9
10
11
12
13
1
2
3
4
5

6
7
8
9
10
11
12
13





-
+







{include file="admin/_head.tpl" title="Cotisations" current="membres/cotisations" js=1}

<ul class="actions">
    <li class="current"><a href="{$admin_url}membres/cotisations/">Cotisations</a></li>
    <li><a href="{$admin_url}membres/cotisations/ajout.php">Saisie d'une cotisation</a></li>
    {if $session->canAccess('membres', Garradin\Membres::DROIT_ADMIN)}
    {if $session->canAccess('membres', Membres::DROIT_ADMIN)}
        <li><a href="{$admin_url}membres/cotisations/gestion/rappels.php">Gestion des rappels automatiques</a></li>
    {/if}
</ul>

<table class="list">
    <thead>
        <th>Cotisation</th>
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
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







-
+









-
+







                    {/if}
                </td>
                <td class="num">{$co.montant|escape|html_money} {$config.monnaie}</td>
                <td class="num">{$co.nb_membres}</td>
                <td class="num">{$co.nb_a_jour}</td>
                <td class="actions">
                    <a class="icn" href="{$admin_url}membres/cotisations/voir.php?id={$co.id}" title="Liste des membres cotisants">👪</a>
                    {if $session->canAccess('membres', Garradin\Membres::DROIT_ADMIN)}
                    {if $session->canAccess('membres', Membres::DROIT_ADMIN)}
                        <a class="icn" href="{$admin_url}membres/cotisations/gestion/modifier.php?id={$co.id}" title="Modifier">✎</a>
                        <a class="icn" href="{$admin_url}membres/cotisations/gestion/supprimer.php?id={$co.id}" title="Supprimer">✘</a>
                    {/if}
                </td>
            </tr>
        {/foreach}
    </tbody>
</table>

{if $session->canAccess('membres', Garradin\Membres::DROIT_ADMIN)}
{if $session->canAccess('membres', Membres::DROIT_ADMIN)}

{form_errors}

<p class="help">
    Idée : les cotisations peuvent également être utilisées pour suivre les activités auxquelles
    sont inscrits les membres de l'association.
</p>

Modified src/templates/admin/membres/cotisations/rappels.tpl from [5544f55d3d] to [487024f8c4].

1
2
3
4
5
6

7
8
9
10
11
12
13
1
2
3
4
5

6
7
8
9
10
11
12
13





-
+







{include file="admin/_head.tpl" title="Rappels pour cotisations du membre" current="membres/cotisations" js=1}

<ul class="actions">
    <li><a href="{$admin_url}membres/fiche.php?id={$membre.id}"><b>{$membre.identite}</b></a></li>
    <li><a href="{$admin_url}membres/modifier.php?id={$membre.id}">Modifier</a></li>
    {if $session->canAccess('membres', Garradin\Membres::DROIT_ADMIN) && $user.id != $membre.id}
    {if $session->canAccess('membres', Membres::DROIT_ADMIN) && $user.id != $membre.id}
        <li><a href="{$admin_url}membres/supprimer.php?id={$membre.id}">Supprimer</a></li>
    {/if}
    <li class="current"><a href="{$admin_url}membres/cotisations.php?id={$membre.id}">Suivi des cotisations</a></li>
</ul>

<form method="post" action="{$self_url}">
    <fieldset>
73
74
75
76
77
78
79
80

81
82

83
84

85
86
87
88
89
90
91
73
74
75
76
77
78
79

80
81

82
83

84
85
86
87
88
89
90
91







-
+

-
+

-
+







        <td class="actions"></td>
    </thead>
    <tbody>
        {foreach from=$rappels item="r"}
            <tr>
                <th>{$r.date|format_sqlite_date_to_french}</th>
                <td>
                    {if $r.media == Garradin\Rappels_envoyes::MEDIA_AUTRE}
                    {if $r.media == Rappels_envoyes::MEDIA_AUTRE}
                        Autre
                    {elseif $r.media == Garradin\Rappels_envoyes::MEDIA_COURRIER}
                    {elseif $r.media == Rappels_envoyes::MEDIA_COURRIER}
                        Courrier
                    {elseif $r.media == Garradin\Rappels_envoyes::MEDIA_TELEPHONE}
                    {elseif $r.media == Rappels_envoyes::MEDIA_TELEPHONE}
                        Téléphone
                    {else}
                        E-Mail
                    {/if}
                </td>
                <td>
                    {$r.intitule} — 

Modified src/templates/admin/membres/cotisations/supprimer.tpl from [14282381fd] to [979b1b65a7].

1
2
3
4
5
6

7
8
9
10
11
12
13
1
2
3
4
5

6
7
8
9
10
11
12
13





-
+







{include file="admin/_head.tpl" title="Supprimer une cotisation pour le membre n°%s"|args:$membre.id current="membres/cotisations"}

<ul class="actions">
    <li><a href="{$admin_url}membres/fiche.php?id={$membre.id}">Membre n°{$membre.id}</a></li>
    <li><a href="{$admin_url}membres/modifier.php?id={$membre.id}">Modifier</a></li>
    {if $session->canAccess('membres', Garradin\Membres::DROIT_ADMIN) && $user.id != $membre.id}
    {if $session->canAccess('membres', Membres::DROIT_ADMIN) && $user.id != $membre.id}
        <li><a href="{$admin_url}membres/supprimer.php?id={$membre.id}">Supprimer</a></li>
    {/if}
    <li class="current"><a href="{$admin_url}membres/cotisations.php?id={$membre.id}">Suivi des cotisations</a></li>
</ul>

{form_errors}

24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
24
25
26
27
28
29
30

31
32
33
34
35
36
37
38
39
40







-










                Celles-ci ne seront pas supprimées lors de la suppression de la cotisation membre.
            </p>
        {else}
            <p class="help">
                Aucune écriture comptable n'est liée à cette cotisation.
            </p>
        {/if}
    </fieldset>
    </fieldset>

    <p class="submit">
        {csrf_field key="del_cotisation_%s"|args:$cotisation.id}
        <input type="submit" name="delete" value="Supprimer &rarr;" />
    </p>
</form>


{include file="admin/_foot.tpl"}

Modified src/templates/admin/membres/cotisations/voir.tpl from [ae243e1d50] to [a660ef9f98].

1
2
3
4
5
6

7
8
9
10
11
12
13
1
2
3
4
5

6
7
8
9
10
11
12
13





-
+







{include file="admin/_head.tpl" title="Membres ayant cotisé" current="membres/cotisations"}

<ul class="actions">
    <li class="current"><a href="{$admin_url}membres/cotisations/">Cotisations</a></li>
    <li><a href="{$admin_url}membres/cotisations/ajout.php">Saisie d'une cotisation</a></li>
    {if $session->canAccess('membres', Garradin\Membres::DROIT_ADMIN)}
    {if $session->canAccess('membres', Membres::DROIT_ADMIN)}
        <li><a href="{$admin_url}membres/cotisations/gestion/rappels.php">Gestion des rappels automatiques</a></li>
    {/if}
</ul>

<dl class="cotisation">
    <dt>Cotisation</dt>
    <dd>{$cotisation.intitule} — 

Modified src/templates/admin/membres/fiche.tpl from [184a802b43] to [afe9db5157].

1
2
3
4
5
6


7
8
9
10
11
12
13
1
2
3
4


5
6
7
8
9
10
11
12
13




-
-
+
+







{include file="admin/_head.tpl" title="%s (%s)"|args:$membre.identite:$categorie.nom current="membres"}

<ul class="actions">
    <li class="current"><a href="{$admin_url}membres/fiche.php?id={$membre.id}"><b>{$membre.identite}</b></a></li>
    <li><a href="{$admin_url}membres/modifier.php?id={$membre.id}">Modifier</a></li>
    {if $session->canAccess('membres', Garradin\Membres::DROIT_ADMIN) && $user.id != $membre.id}
    {if $session->canAccess('membres', Membres::DROIT_ECRITURE)}<li><a href="{$admin_url}membres/modifier.php?id={$membre.id}">Modifier</a></li>{/if}
    {if $session->canAccess('membres', Membres::DROIT_ADMIN) && $user.id != $membre.id}
        <li><a href="{$admin_url}membres/supprimer.php?id={$membre.id}">Supprimer</a></li>
    {/if}
    <li><a href="{$admin_url}membres/cotisations.php?id={$membre.id}">Suivi des cotisations</a></li>
</ul>

<dl class="cotisation">
{if $cotisation}
42
43
44
45
46
47
48

49
50
51
52
53
54
55








56
57
58
59
60
61
62
42
43
44
45
46
47
48
49







50
51
52
53
54
55
56
57
58
59
60
61
62
63
64







+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+







        {else}
            Aucune cotisation enregistrée
        {/if} 
    </dt>
    <dd>
        <a href="{$admin_url}membres/cotisations.php?id={$membre.id}">Voir l'historique</a>
    </dd>
    {if $session->canAccess('membres', Membres::DROIT_ECRITURE)}
    <dd><form method="get" action="{$admin_url}membres/cotisations/ajout.php"><input type="submit" value="Enregistrer une cotisation &rarr;" /><input type="hidden" name="id" value="{$membre.id}" /></form></dd>
{if !empty($nb_operations)}
    <dt>Écritures comptables</dt>
    <dd>{$nb_operations} écritures comptables
        — <a href="{$admin_url}compta/operations/membre.php?id={$membre.id}">Voir la liste des écritures ajoutées par ce membre</a>
    </dd>
 {/if}
        <dd><form method="get" action="{$admin_url}membres/cotisations/ajout.php"><input type="submit" value="Enregistrer une cotisation &rarr;" /><input type="hidden" name="id" value="{$membre.id}" /></form></dd>
        {if !empty($nb_operations)}
            <dt>Écritures comptables</dt>
            <dd>{$nb_operations} écritures comptables
                — <a href="{$admin_url}compta/operations/membre.php?id={$membre.id}">Voir la liste des écritures ajoutées par ce membre</a>
            </dd>
        {/if}
    {/if}
</dl>

<aside class="describe">
	<dl class="describe">
		<dt>Catégorie</dt>
		<dd>{$categorie.nom} <span class="droits">{format_droits droits=$categorie}</span></dd>
		<dt>Inscription</dt>
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
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







-
-
+
+

-
+



-
+

-
-
-
-
+
+
+
+
+
-
-
+

-
+

-
+

-
+

-
+

-
+













				{/if}
		{/if}
		</dd>
	</dl>
</aside>

<dl class="describe">
    {foreach from=$champs key="c" item="config"}
    <dt>{$config.title}</dt>
    {foreach from=$champs key="c" item="c_config"}
    <dt>{$c_config.title}</dt>
    <dd>
        {if $config.type == 'checkbox'}
        {if $c_config.type == 'checkbox'}
            {if $membre->$c}Oui{else}Non{/if}
        {elseif empty($membre->$c)}
            <em>(Non renseigné)</em>
        {elseif $c == 'nom'}
        {elseif $c == $c_config.champ_identite}
            <strong>{$membre->$c}</strong>
        {elseif $c == 'email'}
            <a href="mailto:{$membre->$c}">{$membre->$c}</a>
            | <a href="{$admin_url}membres/message.php?id={$membre.id}"><b class="icn action">✉</b> Envoyer un message</a>
        {elseif $config.type == 'email'}
        {elseif $c_config.type == 'email'}
            <a href="mailto:{$membre->$c|escape:'url'}">{$membre->$c}</a>
            {if $c == 'email'}
                | <a href="{$admin_url}membres/message.php?id={$membre.id}"><b class="icn action">✉</b> Envoyer un message</a>
            {/if}
            <a href="mailto:{$membre->$c}">{$membre->$c}</a>
        {elseif $config.type == 'tel'}
        {elseif $c_config.type == 'tel'}
            <a href="tel:{$membre->$c}">{$membre->$c|format_tel}</a>
        {elseif $config.type == 'country'}
        {elseif $c_config.type == 'country'}
            {$membre->$c|get_country_name}
        {elseif $config.type == 'date' || $config.type == 'datetime'}
        {elseif $c_config.type == 'date' || $c_config.type == 'datetime'}
            {$membre->$c|format_sqlite_date_to_french}
        {elseif $config.type == 'password'}
        {elseif $c_config.type == 'password'}
            *******
        {elseif $config.type == 'multiple'}
        {elseif $c_config.type == 'multiple'}
            <ul>
            {foreach from=$config.options key="b" item="name"}
            {foreach from=$c_config.options key="b" item="name"}
                {if $membre->$c & (0x01 << $b)}
                    <li>{$name}</li>
                {/if}
            {/foreach}
            </ul>
        {else}
            {$membre->$c|escape|rtrim|nl2br}
        {/if}
    </dd>
    {/foreach}
</dl>

{include file="admin/_foot.tpl"}

Modified src/templates/admin/membres/import.tpl from [239f90c5ba] to [f774ddb844].

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
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


+
+
-
+














+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+









-
+



-
-
+
+


-
-
+
+


-
-
-
-
-
+
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-



+
+








-
-
-
-
-
-
-
-
-
-
-
-
-
-

{include file="admin/_head.tpl" title="Import & export des membres" current="membres" js=1}

{include file="admin/membres/_nav.tpl" current="import"}

<ul class="actions">
<ul class="actions sub">
    <li class="current"><a href="{$admin_url}membres/import.php">Importer</a></li>
    <li><a href="{$admin_url}membres/import.php?export=csv">Exporter en CSV</a></li>
    <li><a href="{$admin_url}membres/import.php?export=ods">Exporter en classeur Office</a></li>
</ul>

{form_errors}

{if $ok}
    <p class="confirm">
        L'import s'est bien déroulé.
    </p>
{/if}

<form method="post" action="{$self_url}" enctype="multipart/form-data">

    {if $csv_file}

    <fieldset>
        <legend>Importer depuis un fichier CSV générique</legend>
        <p class="help">{$csv_file|count} lignes trouvées dans le fichier</p>
        <dl>
            <dt><label><input type="checkbox" name="skip_first_line" value="1" checked="checked" /> Ne pas importer la première ligne</label></dt>
            <dd class="help">Décocher cette case si la première ligne ne contient pas l'intitulé des colonnes mais des données.</dd>
            <dt><label>Correspondance des champs</label></dt>
            <dd class="help">Indiquer la correspondance entre colonnes du CSV et champs des fiches membre.</dd>
            <dd>
                <table class="list auto">
                    <tbody>
                    {foreach from=$csv_first_line key="index" item="csv_field"}
                        <tr>
                            <th>{$csv_field}</th>
                            <td>
                                <select name="csv_translate[{$index}]">
                                    <option value="">-- Ne pas importer ce champ</option>
                                    {foreach from=$garradin_champs item="champ" key="name"}
                                        {if $champ.type == 'multiple' || $champ.type == 'file' || $name == 'passe'}{continue}{/if}
                                        <option value="{$name}">{$champ.title}</option>
                                    {/foreach}
                                </select>
                            </td>
                        </tr>
                    {/foreach}
                    </tbody>
                </table>
            </dd>
            <dd class="help">Pour fusionner des colonnes, il suffit d'indiquer le même nom de champ pour plusieurs colonnes.</dd>
        </dl>
    </fieldset>

    <input type="hidden" name="csv_encoded" value="{$csv_file|escape:'json'|escape}" />

    {else}

    <fieldset>
        <legend>Importer depuis un fichier</legend>
        <dl>
            <dt><label for="f_file">Fichier à importer</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
            <dd><input type="file" name="upload" id="f_file" required="required" /></dd>
            <dt><label for="f_type">Type de fichier</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
            <dd>
                <input type="radio" name="type" id="f_type" value="garradin" {form_field name=type checked="garradin" default="garradin"} />
                <label for="f_type">Export CSV de Garradin</label>
                <label for="f_type">Fichier CSV de Garradin</label>
            </dd>
            <dd class="help">
                Export de la liste des membres au format CSV provenant de Garradin.
                Les lignes comportant un numéro de membre mettront à jour les fiches des membres ayant ce numéro,
                les lignes sans numéro créeront de nouveaux membres.
                Les lignes comportant un numéro de membre mettront à jour les fiches des membres ayant ce numéro (si le numéro existe),
                les lignes sans numéro ou avec un numéro inexistant créeront de nouveaux membres.
            </dd>
            <dd>
                <input type="radio" name="type" id="f_type_galette" value="galette" {form_field name=type checked="galette"} />
                <label for="f_type_galette">Export CSV de Galette</label>
                <input type="radio" name="type" id="f_type_csv" value="csv" {form_field name=type checked="csv"} />
                <label for="f_type_csv">Fichier CSV générique</label>
            </dd>
            <dd class="help">
                Export des données au format CSV provenant du logiciel libre
                <a href="http://galette.eu/">Galette</a>.
            </dd>
            <dt class="galette"><label>Correspondance des champs</label></dt>
            <dd class="help">Indiquer quels champs des fiches membre de Garradin les données de Galette doivent remplir.</dd>
                Vous pourrez choisir la correspondance entre colonnes du CSV et champs des fiches membres
            <dd class="galette">
                <table class="list auto">
                dans le prochain écran.
                    <tbody>
                    {foreach from=$galette_champs item="galette"}
                        {if is_int($galette)}{continue}{/if}
                        <tr>
                            <th>{$galette}</th>
                            <td><select name="galette_translate[{$galette}]">
                                <option value="">-- Ne pas importer ce champ</option>
                                {foreach from=$garradin_champs item="champ" key="name"}
                                {if $champ.type == 'checkbox' || $champ.type == 'multiple'}{continue}{/if}
                                <option value="{$name}" {if (!empty($translate[$galette]) && $translate[$galette] == $name)}selected="selected"{/if}>{$champ.title}</option>
                                {/foreach}
                            </select></td>
                        </tr>
                    {/foreach}
                    </tbody>
                </table>
            </dd>
        </dl>
    </fieldset>

    {/if}

    <p class="submit">
        {csrf_field key="membres_import"}
        <input type="submit" name="import" value="Importer &rarr;" />
    </p>

</form>

<script type="text/javascript">
{literal}
(function () {
    function toggleGalette() {
        g.toggle('.galette', $('#f_type_galette').checked);
    }

    $('#f_type').onchange = toggleGalette;
    $('#f_type_galette').onchange = toggleGalette;
    toggleGalette();
})();
{/literal}
</script>

{include file="admin/_foot.tpl"}

Modified src/templates/admin/membres/index.tpl from [346b3591a7] to [079c03ecc2].

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
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


-
+
-
-
-
-
-
-
-
-
-












-
+










-
+


-
+




-
-
-
+

-
+



-
+









-
+









-
+




-
-
-
+
-
-
-
-
-
-
+

+


-
+



-
+

-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

-
-
-
-
-
-
-
-
-
-
-

{include file="admin/_head.tpl" title="Liste des membres" current="membres" js=1}

{if $session->canAccess('membres', Garradin\Membres::DROIT_ECRITURE)}
{include file="admin/membres/_nav.tpl" current="index"}
<ul class="actions">
    <li class="current"><a href="{$admin_url}membres/">Liste des membres</a></li>
    <li><a href="{$admin_url}membres/recherche.php">Recherche avancée</a></li>
    {if $session->canAccess('membres', Garradin\Membres::DROIT_ADMIN)}
        <li><a href="{$admin_url}membres/import.php">Import &amp; export</a></li>
        <li><a href="{$admin_url}membres/recherche_sql.php">Recherche par requête SQL</a></li>
    {/if}
</ul>
{/if}

{if $sent}
    <p class="confirm">Votre message a été envoyé.</p>
{/if}

{if !empty($membres_cats)}
<form method="get" action="{$self_url}" class="shortFormRight">
    <fieldset>
        <legend>Filtrer par catégorie</legend>
        <select name="cat" id="f_cat" onchange="this.form.submit();">
            <option value="0" {if $current_cat == 0} selected="selected"{/if}>-- Toutes</option>
        {foreach from=$membres_cats key="id" item="nom"}
            {if $session->canAccess('membres', Garradin\Membres::DROIT_ECRITURE)
            {if $session->canAccess('membres', Membres::DROIT_ECRITURE)
                || !array_key_exists($id, $membres_cats_cachees)}
            <option value="{$id}"{if $current_cat == $id} selected="selected"{/if}>{$nom}</option>
            {/if}
        {/foreach}
        </select>
        <noscript><input type="submit" value="Filtrer &rarr;" /></noscript>
    </fieldset>
</form>
{/if}

<form method="get" action="{$admin_url}membres/{if $session->canAccess('membres', Garradin\Membres::DROIT_ECRITURE)}recherche.php{/if}" class="shortFormLeft">
<form method="get" action="{$admin_url}membres/recherche.php" class="shortFormLeft">
    <fieldset>
        <legend>Rechercher un membre</legend>
        <input type="text" name="r" value="" />
        <input type="text" name="qt" value="" />
        <input type="submit" value="Chercher &rarr;" />
    </fieldset>
</form>

{if $session->canAccess('membres', Garradin\Membres::DROIT_ECRITURE)}

    <form method="post" action="action.php" class="memberList">
<form method="post" action="action.php" class="memberList">

    {if !empty($liste)}
{if !empty($liste)}
    <table class="list">
        <thead class="userOrder">
            <tr>
                {if $session->canAccess('membres', Garradin\Membres::DROIT_ADMIN)}<td class="check"><input type="checkbox" title="Tout cocher / décocher" /></td>{/if}
                {if $session->canAccess('membres', Membres::DROIT_ADMIN)}<td class="check"><input type="checkbox" title="Tout cocher / décocher" /></td>{/if}
                {foreach from=$champs key="c" item="champ"}
                    <td class="{if $order == $c} cur {if $desc}desc{else}asc{/if}{/if}">{if $c == "numero"}#{else}{$champ.title}{/if} <a href="?o={$c}&amp;a&amp;cat={$current_cat}" class="icn up">&uarr;</a><a href="?o={$c}&amp;d&amp;cat={$current_cat}" class="icn dn">&darr;</a></td>
                {/foreach}
                <td></td>
            </tr>
        </thead>
        <tbody>
            {foreach from=$liste item="membre"}
                <tr>
                    {if $session->canAccess('membres', Garradin\Membres::DROIT_ADMIN)}<td class="check"><input type="checkbox" name="selected[]" value="{$membre.id}" /></td>{/if}
                    {if $session->canAccess('membres', Membres::DROIT_ADMIN)}<td class="check"><input type="checkbox" name="selected[]" value="{$membre.id}" /></td>{/if}
                    {foreach from=$champs key="c" item="cfg"}
                        <td>
                            {if $c == $config.champ_identite}<a href="{$admin_url}membres/fiche.php?id={$membre.id}">{/if}
                            {$membre->$c|raw|display_champ_membre:$cfg}
                            {if $c == $config.champ_identite}</a>{/if}
                        </td>
                    {/foreach}
                    <td class="actions">
                        <a class="icn" href="{$admin_url}membres/fiche.php?id={$membre.id}" title="Fiche membre">👤</a>
                        <a class="icn" href="{$admin_url}membres/modifier.php?id={$membre.id}" title="Modifier la fiche membre">✎</a>
                        {if $session->canAccess('membres', Membres::DROIT_ECRITURE)}<a class="icn" href="{$admin_url}membres/modifier.php?id={$membre.id}" title="Modifier la fiche membre">✎</a>{/if}
                    </td>
                </tr>
            {/foreach}
        </tbody>
    </table>

    {if $session->canAccess('membres', Garradin\Membres::DROIT_ADMIN)}
    {if $session->canAccess('membres', Membres::DROIT_ADMIN)}
    <p class="actions">
        <em>Pour les membres cochés :</em>
        <input type="submit" name="move" value="Changer de catégorie" />
        <input type="submit" name="delete" value="Supprimer" />
        {csrf_field key="membres_action"}
    </p>
        {include file="admin/membres/_list_actions.tpl" colspan=count((array)$champs)+1}
    {/if}
    </table>

    {pagination url=$pagination_url page=$page bypage=$bypage total=$total}
    {else}
{else}
    <p class="alert">
        Aucun membre trouvé.
    </p>
    {/if}
{/if}

    </form>
</form>
{else}
    {if !empty($liste)}
    <table class="list">
        <thead>
            <th>Membre</th>
            <td></td>
        </thead>
        <tbody>
            {foreach from=$liste item="membre"}
                <tr>
                    <th>{$membre.identite}</th>
                    <td class="actions">
                        {if !empty($membre.email)}<a href="{$admin_url}membres/message.php?id={$membre.id}">Envoyer un message</a>{/if}
                    </td>
                </tr>
            {/foreach}
        </tbody>
    </table>

    {if !empty($pagination_url)}
        {pagination url=$pagination_url page=$page bypage=$bypage total=$total}
    {/if}

    {else}
    <p class="alert">
        Aucun membre trouvé.
    </p>
    {/if}
{/if}

{include file="admin/_foot.tpl"}

Modified src/templates/admin/membres/message.tpl from [6ceb73fe1d] to [3012105144].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
1
2
3
4
5
6
7
8
9
10



11
12
13
14
15
16
17










-
-
-







{include file="admin/_head.tpl" title="Contacter un membre" current="membres"}

{form_errors}

<form method="post" action="{$self_url}">
    <fieldset class="memberMessage">
        <legend>Message</legend>
        <dl>
            <dt>Expéditeur</dt>
            <dd>{$user.identite} &lt;{$user.email}&gt;</dd>
            <dd class="help">
                Votre adresse E-Mail apparaîtra dans le champ "expéditeur" du message reçu par le destinataire.
            </dd>
            <dt>Destinataire</dt>
            <dd>{$membre.identite} ({$categorie.nom})</dd>
            <dt><label for="f_sujet">Sujet</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
            <dd><input type="text" name="sujet" id="f_sujet" value="{form_field name=sujet}" required="required" /></dd>
            <dt><label for="f_message">Message</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
            <dd><textarea name="message" id="f_message" cols="72" rows="25" required="required">{form_field name=message}</textarea></dd>
            <dd>

Modified src/templates/admin/membres/message_collectif.tpl from [c3a52f70e1] to [873e17c7a4].

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

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
-
+



-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+

-
-
-
-
+
+
+
+




{include file="admin/_head.tpl" title="Envoyer un message collectif" current="membres/message_collectif"}
{include file="admin/_head.tpl" title="Envoyer un message collectif" current="membres/message"}

{form_errors}

<form method="post" action="{$self_url}" onsubmit="return confirm('Envoyer vraiment ce message collectif ?');">
    <fieldset class="memberMessage">
        <legend>Message</legend>
        <dl>
            <dt>Expéditeur</dt>
            <dd>{$config.nom_asso} &lt;{$config.email_asso}&gt;</dd>
            <dt><label for="f_dest">Membres destinataires</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
            <dd>
                <select name="dest">
                    <option value="0">Toutes les catégories qui ne sont pas cachées</option>
                {foreach from=$cats_liste key="id" item="nom"}
                    <option value="{$id}">{$nom} {if array_key_exists($id, $cats_cachees)}[cachée]{/if}</option>
                {/foreach}
                </select>
            </dd>
            <dd>
                <input type="checkbox" id="f_subscribed" name="subscribed" value="1" {form_field name="subscribed" default="1" checked="1"} />
                <label for="f_subscribed">Seulement les membres inscrits à la lettre d'information</label>
            </dd>
            <dt><label for="f_sujet">Sujet</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
<form method="post" action="{$self_url}">
	<fieldset class="memberMessage">
		<legend>Message</legend>
		<dl>
			<dt>Expéditeur</dt>
			<dd>{$config.nom_asso} &lt;{$config.email_asso}&gt;</dd>
			<dt>Destinataires</dt>
			<dd>
				<select name="recipients">
					<optgroup label="Catégorie de membres">
						{foreach from=$categories key="id" item="nom"}
						<option value="categorie_{$id}" {form_field name="recipients" selected="categorie_%d"|args:$id}>{$nom}</option>
						{/foreach}
					</optgroup>
					<optgroup label="Recherche de membres">
						{foreach from=$recherches item="r"}
						<option value="recherche_{$r.id}" {form_field name="recipients" selected="recherche_%d"|args:$r.qid}>{$r.intitule}</option>
						{/foreach}
					</optgroup>
				</select>
			</dd>
			{* FIXME : pas encore possible, en attente de refonte gestion cotisations
			<dd>
				<label><input type="checkbox" name="paid_members_only" value="1" {form_field name="paid_members_only" checked=1 default=1} />
					Seulement les membres à jour de cotisation
				</label>
			</dd>
			*}
			<dt><label for="f_sujet">Sujet</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
            <dd class="help">Sera automatiquement précédé de la mention [{$config.nom_asso}]</dd>
            <dd><input type="text" name="sujet" id="f_sujet" value="{form_field name=sujet}" required="required" /></dd>
            <dt><label for="f_message">Message</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
            <dd><textarea name="message" id="f_message" cols="72" rows="25" required="required">{form_field name=message}</textarea></dd>
        </dl>
    </fieldset>
			<dd><input type="text" name="sujet" id="f_sujet" value="{form_field name=sujet}" required="required" /></dd>
			<dt><label for="f_message">Message</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
			<dd><textarea name="message" id="f_message" cols="72" rows="25" required="required">{form_field name=message}</textarea></dd>
			<dd>
				<input type="checkbox" name="copie" id="f_copie" value="1" />
				<label for="f_copie">Recevoir par e-mail une copie du message envoyé</label>
			</dd>
		</dl>
	</fieldset>

    <p class="submit">
        {csrf_field key="send_message_collectif"}
        <input type="submit" name="save" value="Envoyer &rarr;" />
    </p>
	<p class="submit">
		{csrf_field key="send_message_co"}
		<input type="submit" name="send" value="Envoyer &rarr;" />
	</p>
</form>


{include file="admin/_foot.tpl"}

Modified src/templates/admin/membres/modifier.tpl from [f71297003d] to [fad59c5b3b].

1
2
3
4
5
6

7
8
9
10
11
12
13
1
2
3
4
5

6
7
8
9
10
11
12
13





-
+







{include file="admin/_head.tpl" title="Modifier un membre" current="membres" js=1}

<ul class="actions">
    <li><a href="{$admin_url}membres/fiche.php?id={$membre.id}"><b>{$membre.identite}</b></a></li>
    <li class="current"><a href="{$admin_url}membres/modifier.php?id={$membre.id}">Modifier</a></li>
    {if $session->canAccess('membres', Garradin\Membres::DROIT_ADMIN) && $user.id != $membre.id}
    {if $session->canAccess('membres', Membres::DROIT_ADMIN) && $user.id != $membre.id}
        <li><a href="{$admin_url}membres/supprimer.php?id={$membre.id}">Supprimer</a></li>
    {/if}
    <li><a href="{$admin_url}membres/cotisations.php?id={$membre.id}">Suivi des cotisations</a></li>
</ul>

{form_errors}

41
42
43
44
45
46
47
48

49
50
51
52
53
54
55
41
42
43
44
45
46
47

48
49
50
51
52
53
54
55







-
+







            </dd>
            <dd><input type="password" name="passe" id="f_passe" value="{form_field name=passe}" pattern=".{ldelim}6,{rdelim}" /></dd>
            <dt><label for="f_repasse">Encore le mot de passe</label> (vérification){if $champs.passe.mandatory} <b title="(Champ obligatoire)">obligatoire</b>{/if}</dt>
            <dd><input type="password" name="passe_confirmed" id="f_repasse" value="{form_field name=passe_confirmed}" pattern=".{ldelim}6,{rdelim}" /></dd>
        </dl>
    </fieldset>

    {if $session->canAccess('membres', Garradin\Membres::DROIT_ADMIN) && $user.id != $membre.id}
    {if $session->canAccess('membres', Membres::DROIT_ADMIN) && $user.id != $membre.id}
    <fieldset>
        <legend>Général</legend>
        <dl>
            <dt><label for="f_cat">Catégorie du membre</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
            <dd>
                <select name="id_categorie" id="f_cat">
                {foreach from=$membres_cats key="id" item="nom"}

Modified src/templates/admin/membres/recherche.tpl from [3886f956f8] to [3618a80c24].

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

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
-
+

-
-
-
-
-
-
-
-
+

-
-
-
+
+
+
-
-
-
-
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
+
-
-
-
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
+
-
-
-
-
-
+
+


-
-
+
-
+

-
+
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
+
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
-

-
-
-
-
-
-
+
-
-
+
-
-
-
+
-
-
+
-
-
-
+
+
+
+
-

-
-
+
+
+
+
+
+
+
-

-
-
+
+
+
+
-
-
-
-
-
+
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
+
-
-
+
+
-
-
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
-
-
+
+
-
-
-
-
-
-
-
-
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+

-
-
-
-
-


{include file="admin/_head.tpl" title="Recherche de membre" current="membres"}
{include file="admin/_head.tpl" title="Recherche de membre" current="membres" js=1 custom_js=['query_builder.min.js']}

{if $session->canAccess('membres', Garradin\Membres::DROIT_ADMIN)}
<ul class="actions">
    <li><a href="{$admin_url}membres/">Liste des membres</a></li>
    <li class="current"><a href="{$admin_url}membres/recherche.php">Recherche avancée</a></li>
    <li><a href="{$admin_url}membres/recherche_sql.php">Recherche par requête SQL</a></li>
</ul>
{/if}

{include file="admin/membres/_nav.tpl" current="recherche"}

<form method="get" action="{$admin_url}membres/recherche.php" class="shortFormLeft">
    <fieldset>
        <legend>Rechercher un membre</legend>
<form method="post" action="{$admin_url}membres/recherche.php" id="queryBuilderForm">
	<fieldset>
		<legend>Rechercher un membre</legend>
        <dl>
            <dt><label for="f_champ">Champ</label></dt>
            <dd>
                <select name="c" id="f_champ">
                    {foreach from=$champs_liste key="k" item="v"}
                    <option value="{$k}"{form_field name="c" default=$champ selected=$k}>{$v.title}</option>
                    {/foreach}
                </select>
            </dd>
		<div class="queryBuilder" id="queryBuilder"></div>
            <dt><label for="f_texte">Recherche</label></dt>
            <dd id="f_free"><input id="f_texte" type="text" name="r" value="{$recherche}" required="required" /></dd>
            {foreach from=$champs_liste key="k" item="v"}
                {if $v.type == 'select'}
                    <dd class="special" id="f_{$k}">
                        <select name="r" disabled="disabled">
                            {foreach from=$v.options item="opt"}
                            <option value="{$opt}"{form_field name="r" default=$recherche selected=$opt}>{$opt}</option>
                            {/foreach}
                        </select>
                    </dd>
		<p class="actions">
			<label>Trier par 
				<select name="order">
					{foreach from=$colonnes key="colonne" item="config"}
					<option value="{$colonne}"{form_field name="order" selected=$colonne}>{$config.label}</option>
					{/foreach}
				</select>
			</label>
                {elseif $v.type == 'multiple'}
                    <dd class="special" id="f_{$k}">
                        <select name="r" disabled="disabled">
                            {foreach from=$v.options key="opt_k" item="opt"}
                            <option value="{$opt_k}"{form_field name="r" default=$recherche selected=$opt_k}>{$opt}</option>
			<label><input type="checkbox" name="desc" value="1" {form_field name="desc" checked=1 default=$desc} /> Tri inversé</label>
                            {/foreach}
                        </select>
                    </dd>
			<label>Limiter à <input type="number" value="{$limit}" name="limit" size="5" /> résultats</label>
		</p>
                {elseif $v.type == 'checkbox'}
                    <dd class="special" id="f_{$k}">
                        <select name="r" disabled="disabled">
                            <option value="1"{form_field name="r" default=$recherche selected=1}>Oui</option>
		<p class="submit">
			<input type="submit" value="Chercher &rarr;" id="send" />
			<input type="hidden" name="q" id="jsonQuery" />
			<input type="hidden" name="id" value="{$id}" />
                            <option value="0"{form_field name="r" default=$recherche selected=0}>Non</option>
                        </select>
                    </dd>
                {/if}
            {/foreach}
			<input type="submit" name="save" value="{if $id}Enregistrer : {$recherche.intitule|truncate:40:"…":true}{else}Enregistrer cette recherche{/if}" class="minor" />
        </dl>
        <p class="submit">
            <input type="submit" value="Chercher &rarr;" />
        </p>
    </fieldset>
		</p>
	</fieldset>
</form>

{if $session->canAccess('membres', Garradin\Membres::DROIT_ECRITURE)}

<script type="text/javascript">
    <form method="post" action="{$admin_url}membres/action.php" class="memberList">
var colonnes = {$colonnes|escape:'json'};

    {if !empty($liste)}
{literal}
    <table class="list search">
        <thead>
var traductions = {
            {if $session->canAccess('membres', Garradin\Membres::DROIT_ADMIN)}<td class="check"><input type="checkbox" value="Tout cocher / décocher" onclick="checkUncheck();" /></td>{/if}
            {foreach from=$champs_entete key="c" item="cfg"}
                {if $champ == $c}
                    <th><strong>{$cfg.title}</strong></th>
                {else}
                    <td>{$cfg.title}</td>
                {/if}
            {/foreach}
            <td></td>
        </thead>
        <tbody>
            {foreach from=$liste item="membre"}
                <tr>
	"after": "après",
	"before": "avant",
	"is equal to": "est égal à",
	"is equal to one of": "est égal à une des ces options",
	"is not equal to one of": "n'est pas égal à une des ces options",
	"is not equal to": "n'est pas égal à",
	"is greater than": "est supérieur à",
	"is greater than or equal to": "est supérieur ou égal à",
	"is less than": "est inférieur à",
	"is less than or equal to": "est inférieur ou égal à",
	"is between": "est sitentre",
	"is not between": "n'est pas situé entre",
	"is null": "est nul",
	"is not null": "n'est pas nul",
	"begins with": "commence par",
	"doesn't begin with": "ne commence pas par",
	"ends with": "se termine par",
	"doesn't end with": "ne se termine pas par",
	"contains": "contient",
	"doesn't contain": "ne contient pas",
	"matches one of": "correspond à",
	"is true": "oui",
                    {if $session->canAccess('membres', Garradin\Membres::DROIT_ADMIN)}<td class="check"><input type="checkbox" name="selected[]" value="{$membre.id}" /></td>{/if}
                    {foreach from=$champs_entete key="c" item="cfg"}
                        {if $champ == $c}
                            <th><strong>{$membre->$c|raw|display_champ_membre:$cfg}</strong></th>
                        {else}
                            <td>{$membre->$c|raw|display_champ_membre:$cfg}</td>
                        {/if}
                    {/foreach}
                    <td class="actions">
	"is false": "non",
	"Matches ALL of the following conditions:": "Correspond à TOUS les critères suivants :",
	"Matches ANY of the following conditions:": "Correspond à UN SEUL des critères suivants :",
	"Add a new set of conditions below this one": "-- Ajouter un groupe de critères",
	"Remove this set of conditions": "-- Supprimer ce groupe de critères"
};

var q = new SQLQueryBuilder(colonnes);
q.__ = function (str) {
                    	<a class="icn" href="{$admin_url}membres/fiche.php?id={$membre.id}" title="Fiche membre">👤</a>
                        <a class="icn" href="{$admin_url}membres/modifier.php?id={$membre.id}" title="Modifier la fiche membre">✎</a>
                    </td>
                </tr>
            {/foreach}
        </tbody>
    </table>

	return traductions[str];
};
q.loadDefaultOperators();
q.buildInput = function (type, label, column) {
	if (label == '+')
	{
		label = '➕';
	}
    {if $session->canAccess('membres', Garradin\Membres::DROIT_ADMIN)}
    <p class="checkUncheck">
	else if (label == '-')
        <input type="button" value="Tout cocher / décocher" onclick="checkUncheck();" />
    </p>
    <p class="actions">
        <em>Pour les membres cochés :</em>
        <input type="submit" name="move" value="Changer de catégorie" />
	{
		label = '➖';
	}

	var i = document.createElement('input');
	i.type = type == 'integer' ? 'number' : type;
	i.value = label;
        <input type="submit" name="delete" value="Supprimer" />
        {csrf_field key="membres_action"}
    </p>
    {/if}

    {elseif $recherche != ''}
    <p class="alert">
        Aucun membre trouvé.
    </p>
    {/if}

	if (type == 'button')
    </form>

	{
    <script type="text/javascript">
    {literal}
    (function() {
		i.className = 'icn action';
        var checked = false;

	}
        window.checkUncheck = function()
        {
            var elements = document.getElementsByTagName('input');

	return i;
};
q.init(document.getElementById('queryBuilder'));
            var el_length = elements.length;

            for (i = 0; i < el_length; i++)
            {
$('#queryBuilderForm').onsubmit = function () {
	$('#jsonQuery').value = JSON.stringify(q.export());
};
{/literal}
q.import({$query|escape:'json'});
</script>

                var elm = elements[i];

                if (elm.type == 'checkbox')
                {
{if !empty($result)}
	{if $session->canAccess('membres', Membres::DROIT_ECRITURE)}
		<form method="post" action="{$admin_url}membres/action.php" class="memberList">
	{/if}
                    if (checked)
                        elm.checked = false;
                    else
                        elm.checked = true;
                }

            }

	<p class="help">{$result|count} membres trouvés pour cette recherche.</p>
            checked = checked ? false : true;
            return true;
        }
    }())
    {/literal}
    </script>
{else}
    {if !empty($liste)}
    <table class="list">
        <thead>
            <th>Membre</th>
            <td></td>
        </thead>
        <tbody>
            {foreach from=$liste item="membre"}
                <tr>
                    <th>{$membre.identite}</th>
                    <td class="actions">
                        {if !empty($membre.email)}<a href="{$admin_url}membres/message.php?id={$membre.id}">Envoyer un message</a>{/if}
                    </td>
                </tr>
            {/foreach}
        </tbody>
	<table class="list search">
		<thead>
			<tr>
				{if $session->canAccess('membres', Membres::DROIT_ADMIN)}<td class="check"><input type="checkbox" value="Tout cocher / décocher" /></td>{/if}
				{foreach from=$result_header key="c" item="cfg"}
					<td>{$cfg.title}</td>
				{/foreach}
				<td></td>
			</tr>
		</thead>
		<tbody>
			{foreach from=$result item="row"}
				<tr>
					{if $session->canAccess('membres', Membres::DROIT_ADMIN)}<td class="check"><input type="checkbox" name="selected[]" value="{$row.id}" /></td>{/if}
					{foreach from=$row key="key" item="value"}
						<?php $link = false; ?>
						{if isset($result_header[$key])}
							<td>
								{if !$link}
									<a href="{$admin_url}membres/fiche.php?id={$row.id}">
								{/if}

								{$value|raw|display_champ_membre:$result_header[$key]}

								{if !$link}
									<?php $link = true; ?>
									</a>
								{/if}
							</td>
						{/if}
					{/foreach}
					<td class="actions">
						<a class="icn" href="{$admin_url}membres/fiche.php?id={$row.id}" title="Fiche membre">👤</a>
						{if $session->canAccess('membres', Membres::DROIT_ECRITURE)}
						<a class="icn" href="{$admin_url}membres/modifier.php?id={$row.id}" title="Modifier la fiche membre">✎</a>
						{/if}
					</td>
				</tr>
			{/foreach}
		</tbody>
    </table>
    {else}
    <p class="info">
        Aucun membre trouvé.
	{if $session->canAccess('membres', Membres::DROIT_ADMIN)}
    </p>
    {/if}
		{include file="admin/membres/_list_actions.tpl" colspan=count($result_header)+1}
	{/if}
{/if}

	</table>
<script type="text/javascript">
{literal}
(function() {
    var current = false;

    var selectField = function(elm)
    {
        if (current)
        {
            document.getElementById('f_' + current).style.display = 'none';
            document.getElementById('f_' + current).querySelector('select').disabled = true;
            current = false;
        }
        

	{if $session->canAccess('membres', Membres::DROIT_ECRITURE)}
        if (document.getElementById('f_' + elm.value))
        {
		</form>
	{/if}
            document.getElementById('f_' + elm.value).style.display = 'block';
            document.getElementById('f_' + elm.value).querySelector('select').disabled = false;
            document.getElementById('f_free').style.display = 'none';
            document.getElementById('f_texte').disabled = true;
            current = elm.value;
        }
        else
        {

{elseif $result !== null}

            document.getElementById('f_texte').disabled = false;
            document.getElementById('f_free').style.display = 'block';
        }

        return true;
    }
	<p class="alert">
		Aucun membre trouvé.
	</p>

	</form>
{/if}

    document.getElementById('f_champ').onchange = function() { selectField(this); };
    window.onload = selectField(document.getElementById('f_champ'));
}())
{/literal}
</script>

{include file="admin/_foot.tpl"}

Modified src/templates/admin/membres/recherche_sql.tpl from [4ba2fc1f34] to [af64d8399c].

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

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
-
+

+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
-
-
-
+
-
-




+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-


-
-
-
+
+
+




-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-

{include file="admin/_head.tpl" title="Recherche par requête SQL" current="membres"}
{include file="admin/_head.tpl" title="Recherche par requête SQL" current="membres" js=1}

{include file="admin/membres/_nav.tpl" current="sql"}

{if $session->canAccess('membres', Membres::DROIT_ADMIN)}
<form method="get" action="{$admin_url}membres/recherche_sql.php">
    <fieldset>
        <legend>Schéma des tables SQL</legend>
        <pre class="sql_schema">{$schema.membres}</pre>
        <dl>
            <dt><label for="f_query">Requête SQL</label></dt>
            <dd class="help">Si aucune limite n'est précisée, une limite de 100 résultats sera appliquée.</dd>
            <dd><textarea name="query" id="f_query" cols="50" rows="7" required="required">{$query}</textarea></dd>
        </dl>
        <p class="submit">
            <input type="submit" value="Exécuter &rarr;" />
        </p>
    </fieldset>
</form>
	<form method="get" action="{$admin_url}membres/recherche_sql.php">
		<fieldset>
			<legend>Schéma des tables SQL</legend>
			<pre class="sql_schema">{$schema.membres}</pre>
			<dl>
				<dt><label for="f_query">Requête SQL</label></dt>
				<dd class="help">Si aucune limite n'est précisée, une limite de 100 résultats sera appliquée.</dd>
				<dd><textarea name="query" id="f_query" cols="70" rows="7" required="required">{$query}</textarea></dd>
			</dl>
			<p class="submit">
				<input type="submit" name="run" value="Exécuter &rarr;" />
				{if $query}
					{if $id}<input type="hidden" name="id" value="{$id}" />{/if}
					<input type="submit" name="save" value="{if $id}Enregistrer : {$recherche.intitule}{else}Enregistrer cette recherche{/if}" class="minor" />
				{/if}
			</p>
		</fieldset>
	</form>
{/if}

{if !empty($error)}
<p class="error">
    <strong>Erreur dans la requête SQL :</strong><br />
    {$error}
{form_errors}
</p>
{/if}

<form method="post" action="{$admin_url}membres/action.php" class="memberList">

{if !empty($result)}

<p class="alert">{$result|count} résultats renvoyés.</p>
<table class="list search">
    <thead>
        {if array_key_exists('id', $result[0])}
        <td class="check"><input type="checkbox" value="Tout cocher / décocher" onclick="checkUncheck();" /></td>
        {/if}
        {foreach from=$result[0] key="col" item="ignore"}
            <td>{$col}</td>
        {/foreach}
        {if array_key_exists('id', $result[0])}
        <td></td>
        {/if}
    </thead>
    <tbody>
        {foreach from=$result item="row"}
            <tr>
                {if array_key_exists('id', $result[0])}
                    <td class="check">{if !empty($row.id)}<input type="checkbox" name="selected[]" value="{$row.id}" />{/if}</td>
                {/if}
                {foreach from=$row item="col"}
                    <td>{$col}</td>
                {/foreach}
                {if array_key_exists('id', $result[0])}
                <td class="actions">
                    {if !empty($row.id)}
                    <a class="icn" href="{$admin_url}membres/fiche.php?id={$row.id}" title="Fiche membre">👤</a>
                    <a class="icn" href="{$admin_url}membres/modifier.php?id={$row.id}" title="Modifier ce membre">✎</a>
                    {/if}
                </td>
                {/if}
            </tr>
        {/foreach}
    </tbody>
</table>
	<p class="alert">{$result|count} résultats retournés.</p>

	<table class="list search">
		<thead>
			{if array_key_exists('id', $result[0])}
			<td class="check"><input type="checkbox" value="Tout cocher / décocher" onclick="g.checkUncheck();" /></td>
			{/if}
			{foreach from=$result[0] key="col" item="ignore"}
				<td>{$col}</td>
			{/foreach}
			{if array_key_exists('id', $result[0])}
			<td></td>
			{/if}
		</thead>
		<tbody>
			{foreach from=$result item="row"}
				<tr>
					{if $session->canAccess('membres', Membres::DROIT_ADMIN) && array_key_exists('id', $result[0])}
						<td class="check">{if !empty($row.id)}<input type="checkbox" name="selected[]" value="{$row.id}" />{/if}</td>
					{/if}
					{foreach from=$row item="col"}
						<td>{$col}</td>
					{/foreach}
					{if array_key_exists('id', $result[0])}
					<td class="actions">
						{if !empty($row.id)}
						<a class="icn" href="{$admin_url}membres/fiche.php?id={$row.id}" title="Fiche membre">👤</a>
						<a class="icn" href="{$admin_url}membres/modifier.php?id={$row.id}" title="Modifier ce membre">✎</a>
						{/if}
					</td>
					{/if}
				</tr>
			{/foreach}
		</tbody>
		{if $session->canAccess('membres', Membres::DROIT_ADMIN)}
			{include file="admin/membres/_list_actions.tpl" colspan=count((array)$result[0])+1}
		{/if}
	</table>

<p class="checkUncheck">
    <input type="button" value="Tout cocher / décocher" onclick="checkUncheck();" />
</p>
<p class="actions">
    <em>Pour les membres cochés :</em>
    <input type="submit" name="move" value="Changer de catégorie" />
    <input type="submit" name="delete" value="Supprimer" />
    {csrf_field key="membres_action"}
</p>

{else}
<p class="alert">
    Aucun membre trouvé.
</p>
	<p class="alert">
		Aucun membre trouvé.
	</p>
{/if}

</form>

<script type="text/javascript">
{literal}
(function() {
    var checked = false;

    window.checkUncheck = function()
    {
        var elements = document.getElementsByTagName('input');
        var el_length = elements.length;

        for (i = 0; i < el_length; i++)
        {
            var elm = elements[i];

            if (elm.type == 'checkbox')
            {
                if (checked)
                    elm.checked = false;
                else
                    elm.checked = true;
            }
        }

        checked = checked ? false : true;
        return true;
    }
}())
{/literal}
</script>

{include file="admin/_foot.tpl"}

Added src/templates/admin/membres/recherches.tpl version [3685bb7adf].













































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{include file="admin/_head.tpl" title="Recherches enregistrées" current="membres"}

{include file="admin/membres/_nav.tpl" current="recherches"}

{form_errors}

{if $mode == 'edit'}
	<form method="post" action="{$self_url}">
		<fieldset>
			<legend>Modifier une recherche enregistrée</legend>
			<dl>
				<dt><label for="f_intitule">Intitulé</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
				<dd><input type="text" name="intitule" id="f_intitule" value="{form_field name="intitule" data=$recherche}" size="80" required="required" /></dd>
				<dt>Statut</dt>
				<dd><label><input type="radio" name="prive" value="1" {if $recherche.id_membre}checked="checked"{/if} /> Recherche privée</label> — Visible seulement par moi-même</dd>
				<dd><label><input type="radio" name="prive" value="0" {if !$recherche.id_membre}checked="checked"{/if} /> Recherche publique</label> — Visible et exécutable par tous les membres ayant accès à la gestion des membres</dd>
				<dt>Type</dt>
				<dd>{if $recherche.type == Recherche::TYPE_JSON}Avancée{else}SQL{/if}</dd>
				<dt>Cible</dt>
				<dd>{$recherche.cible}</dd>
			</dl>
		</fieldset>

		<p class="submit">
			{csrf_field key="edit_recherche_%s"|args:$recherche.id}
			<input type="submit" name="save" value="Enregistrer &rarr;" />
		</p>
	</form>
{elseif $mode == 'delete'}

	<form method="post" action="{$self_url}">
		<fieldset>
			<legend>Supprimer une recherche enregistrée</legend>
			<h3 class="warning">
				Êtes-vous sûr de vouloir supprimer la recherche enregistrée
				{$recherche.intitule}&nbsp;?
			</h3>
		</fieldset>

		<p class="submit">
			{csrf_field key="del_recherche_%s"|args:$recherche.id}
			<input type="submit" name="delete" value="Supprimer &rarr;" />
		</p>
	</form>
{elseif count($liste) == 0}
	<p class="alert">Aucune recherche enregistrée. <a href="{$admin_url}membres/recherche.php">Faire une nouvelle recherche</a></p>
{else}
	<table class="list">
		<thead>
			<tr>
				<th>Recherche</th>
				<th>Type</th>
				<th>Statut</th>
				<th></th>
			</tr>
		</thead>
		<tbody>
			{foreach from=$liste item="recherche"}
			<tr>
				<th>{$recherche.intitule}</th>
				<td>{if $recherche.type == Recherche::TYPE_JSON}Avancée{else}SQL{/if}</td>
				<td>{if !$recherche.id_membre}Publique{else}Privée{/if}</td>
				<td class="actions">
					<a href="{$admin_url}membres/recherche{if $recherche.type == Recherche::TYPE_SQL}_sql{/if}.php?id={$recherche.id}" class="icn" title="Exécuter">𝍢</a>
					{if $recherche.id_membre || $session->canAccess('membres', Membres::DROIT_ADMIN)}
					<a href="{$admin_url}membres/recherches.php?edit={$recherche.id}" class="icn" title="Modifier">✎</a>
					<a href="{$admin_url}membres/recherches.php?delete={$recherche.id}" class="icn" title="Supprimer">✘</a>
					{/if}
				</td>
			</tr>
			{/foreach}
		</tbody>
	</table>
{/if}

{include file="admin/_foot.tpl"}

Modified src/templates/admin/membres/supprimer.tpl from [8d6ea3457e] to [3ee78004e1].

1
2
3
4
5
6

7
8
9
10
11
12
13
1
2
3
4
5

6
7
8
9
10
11
12
13





-
+







{include file="admin/_head.tpl" title="Supprimer un membre" current="membres"}

<ul class="actions">
    <li><a href="{$admin_url}membres/fiche.php?id={$membre.id}"><b>{$membre.identite}</b></a></li>
    <li><a href="{$admin_url}membres/modifier.php?id={$membre.id}">Modifier</a></li>
    {if $session->canAccess('membres', Garradin\Membres::DROIT_ADMIN)}
    {if $session->canAccess('membres', Membres::DROIT_ADMIN)}
        <li class="current"><a href="{$admin_url}membres/supprimer.php?id={$membre.id}">Supprimer</a></li>
    {/if}
    <li><a href="{$admin_url}membres/cotisations.php?id={$membre.id}">Suivi des cotisations</a></li>
</ul>

{form_errors}

Modified src/templates/admin/mes_infos_securite.tpl from [7c9c1d22c9] to [811d2ba17a].

63
64
65
66
67
68
69
70

71
72
73
74
75
76
77
63
64
65
66
67
68
69

70
71
72
73
74
75
76
77







-
+







    </form>
{else}

    <form method="post" action="{$self_url_no_qs}">

        <fieldset>
            <legend>Changer mon mot de passe</legend>
            {if $user.droit_membres < Garradin\Membres::DROIT_ADMIN && (!empty($champs.passe.private) || empty($champs.passe.editable))}
            {if $user.droit_membres < Membres::DROIT_ADMIN && (!empty($champs.passe.private) || empty($champs.passe.editable))}
                <p class="help">Vous devez contacter un administrateur pour changer votre mot de passe.</p>
            {else}
                <dl>
                    <dd>Vous avez déjà un mot de passe, ne remplissez les champs suivants que si vous souhaitez en changer.</dd>
                    <dt><label for="f_passe">Nouveau mot de passe</label></dt>
                    <dd class="help">
                        Astuce : un mot de passe de quatre mots choisis au hasard dans le dictionnaire est plus sûr 

Modified src/templates/admin/wiki/historique.tpl from [d5a6d6126a] to [7c0d2ecc52].

1
2
3
4

5
6
7
8
9
10
11
1
2
3

4
5
6
7
8
9
10
11



-
+







{include file="admin/_head.tpl" title="Historique : %s"|args:$page.titre current="wiki"}

<ul class="actions">
    <li><a href="{$admin_url}wiki/?{$page.uri}">Voir la page</a></li>
    <li><a href="{$admin_url}wiki/?{$page.uri}">Retour à la page</a></li>
</ul>

{if !empty($revisions)}
    <table class="list wikiRevisions">
    {foreach from=$revisions item="rev"}
        <tr>
            <td>
23
24
25
26
27
28
29
30

31
32
33
34
35
36
37
23
24
25
26
27
28
29

30
31
32
33
34
35
36
37







-
+







                    {else}
                        <a href="?id={$page.id}&amp;diff={$rev.revision-1}.{$rev.revision}">diff</a>
                    {/if}
                {/if}
            </td>
            <th>{$rev.date|date_fr:'d/m/Y à H:i'}</th>
            <td>
                {if $session->canAccess('membres', Garradin\Membres::DROIT_ACCES)}
                {if $session->canAccess('membres', Membres::DROIT_ACCES)}
                <a href="{$admin_url}membres/fiche.php?id={$rev.id_auteur}">{$rev.nom_auteur}</a>
                {/if}
            </td>
            <td class="length">
                {$rev.taille} octets
                {if $rev.revision > 1 && !$rev.chiffrement}
                    {if $rev.diff_taille > 0}
50
51
52
53
54
55
56
57

58
59
60
61
62
63
64
65
66

67
68
69
70
71
72
73
50
51
52
53
54
55
56

57
58
59
60
61
62
63
64
65

66
67
68
69
70
71
72
73







-
+








-
+







            </td>
        </tr>
    {/foreach}
    </table>
{elseif !empty($diff)}
    <div class="wikiRevision revisionLeft">
        <h3>Version du {$rev1.date|date_fr:'d/m/Y à H:i'}</h3>
        {if $session->canAccess('membres', Garradin\Membres::DROIT_ACCES)}
        {if $session->canAccess('membres', Membres::DROIT_ACCES)}
            <h4>De <a href="{$admin_url}membres/fiche.php?id={$rev1.id_auteur}">{$rev1.nom_auteur}</a></h4>
        {/if}
        {if $rev1.modification}
            <p><em>{$rev1.modification}</em></p>
        {/if}
    </div>
    <div class="wikiRevision revisionRight">
        <h3>Version {if $rev2.revision == $page.revision}actuelle en date{/if} du {$rev2.date|date_fr:'d/m/Y à H:i'}</h3>
        {if $session->canAccess('membres', Garradin\Membres::DROIT_ACCES)}
        {if $session->canAccess('membres', Membres::DROIT_ACCES)}
            <h4>De <a href="{$admin_url}membres/fiche.php?id={$rev2.id_auteur}">{$rev2.nom_auteur}</a></h4>
        {/if}
        {if $rev2.modification}
            <p><em>{$rev2.modification}</em></p>
        {/if}
    </div>
    {diff old=$rev1.contenu new=$rev2.contenu}

Modified src/templates/admin/wiki/page.tpl from [9618962284] to [c9d6b7be81].

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
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







-
+







-
+



-
+







{if !empty($page.titre) && $can_read}
    {include file="admin/_head.tpl" title=$page.titre current="wiki" js=1}
{else}
    {include file="admin/_head.tpl" title="Wiki" current="wiki"}
{/if}

<ul class="actions">
    {if $session->canAccess('wiki', Garradin\Membres::DROIT_ECRITURE)}
    {if $session->canAccess('wiki', Membres::DROIT_ECRITURE)}
        <li><a href="{$admin_url}wiki/creer.php?parent={if $config.accueil_wiki == $page.uri}0{else}{$page.id}{/if}"><strong>Créer une nouvelle page</strong></a></li>
    {/if}
    {if $can_edit}
        <li><a href="{$admin_url}wiki/editer.php?id={$page.id}">Éditer</a></li>
    {/if}
    {if $can_read && $page && $page.contenu}
        <li><a href="{$admin_url}wiki/historique.php?id={$page.id}">Historique</a>
        {if $page.droit_lecture == Garradin\Wiki::LECTURE_PUBLIC}
        {if $page.droit_lecture == Wiki::LECTURE_PUBLIC}
            <li><a href="{$www_url}{$page.uri}{if $has_public_children}/{/if}">Voir sur le site</a>
        {/if}
    {/if}
    {if $session->canAccess('wiki', Garradin\Membres::DROIT_ADMIN)}
    {if $session->canAccess('wiki', Membres::DROIT_ADMIN)}
        <li><a href="{$admin_url}wiki/supprimer.php?id={$page.id}">Supprimer</a></li>
    {/if}
</ul>

{if !$can_read}
    <p class="alert">Vous n'avez pas le droit de lire cette page.</p>
{else}
116
117
118
119
120
121
122
123

124
125
126
127
128
129
130
131
132
116
117
118
119
120
121
122

123
124
125
126
127
128
129
130
131
132







-
+









                </ul>
                {/if}
            </div>
            {/if}

            <p class="wikiFooter">
                Dernière modification le {$page.date_modification|date_fr:'d/m/Y à H:i'}
                {if $session->canAccess('membres', Garradin\Membres::DROIT_ACCES)}
                {if $session->canAccess('membres', Membres::DROIT_ACCES)}
                par <a href="{$admin_url}membres/fiche.php?id={$page.contenu.id_auteur}">{$auteur}</a>
                {/if}
            </p>
        {/if}
    {/if}
{/if}


{include file="admin/_foot.tpl"}

Modified src/templates/error.tpl from [f1a21eb4e1] to [52bccc1e68].

1
2
3
4
5

6
7
8
9
10
11
12
1
2
3
4

5
6
7
8
9
10
11
12




-
+







<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr" lang="fr">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Erreur</title>
    <title>{if empty($title)}Erreur{else}{$title}{/if}</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <style type="text/css">
    {literal}
    * { margin: 0; padding: 0; }

    html { width: 100%; height: 100%; }
    body {
27
28
29
30
31
32
33
34

35
36
37
38
39
40
41

42
43
44
45
27
28
29
30
31
32
33

34
35
36
37
38
39
40

41
42
43
44
45







-
+






-
+




    }
    {/literal}
    </style>
</head>

<body>

<h1>Erreur</h1>
<h1>{if empty($title)}Erreur{else}{$title}{/if}</h1>

<p class="error">
    {$error|escape|nl2br}
</p>

<p>
    <a href="{$www_url}" onclick="history.back(); return false;">&larr; Retour</a>
    <a href="{$www_url}" onclick="return history.back();">&larr; Retour</a>
</p>

</body>
</html>

Modified src/www/.htaccess from [9e40080631] to [8c789141ef].

1

2

3
4


5
6
7
8


9
10
11
12
13

14
1
2
3
4


5
6




7
8

9
10


11


+

+
-
-
+
+
-
-
-
-
+
+
-


-
-
+
-
Options -MultiViews -Indexes
DirectoryIndex index.php

# FallbackResource n'est dispo que depuis Apache 2.2.16, soit Debian Wheezy (2013)
<IfModule mod_rewrite.c>
    RewriteEngine on
<IfModule mod_version.c>
	<IfVersion >= 2.2.16>
    RewriteRule admin/plugin/(.*?)/(.*) admin/plugin.php?_p=$1&_u=$2 [QSA,L]
    RewriteRule f/([\d\w]+)/(.*) file.php?id=$1&file=$2 [QSA,L]
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
		FallbackResource /_route.php
	</IfVersion>
    RewriteRule .* index.php [QSA,L]
</IfModule>

<IfModule !mod_rewrite.c>
    ErrorDocument 404 /index.php
ErrorDocument 404 /_route.php
</IfModule>

Modified src/www/admin/_inc.php from [e0bbbc300b] to [d6f6a6d40f].

66
67
68
69
70
71
72
73
74
75

76

77


78
79



66
67
68
69
70
71
72

73

74
75
76

77
78
79

80
81
82







-

-
+

+
-
+
+

-
+
+
+

    $tpl->assign('is_logged', true);

    $user = $session->getUser();
    $tpl->assign('user', $user);

    $tpl->assign('current', '');
    $tpl->assign('plugins_menu', Plugin::listMenu());

    if ($session->canAccess('membres', Membres::DROIT_ACCES))
    if ($session->get('plugins_menu') === null)
    {
        // Construction de la liste de plugins pour le menu
        $tpl->assign('nb_membres', (new Membres)->countAllButHidden());
        // et stockage en session pour ne pas la recalculer à chaque page
        $session->set('plugins_menu', Plugin::listMenu($user));
    }
}

    $tpl->assign('plugins_menu', $session->get('plugins_menu'));
}

Modified src/www/admin/compta/import.php from [7e73141123] to [d14bd1f9b9].

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
1
2
3
4
5
6
7
8
9
10
11
12


13
14
15
16
17


18
19
20
21
22
23
24












-
-





-
-







<?php
namespace Garradin;

require_once __DIR__ . '/_inc.php';

$session->requireAccess('compta', Membres::DROIT_ADMIN);

$e = new Compta\Exercices;
$import = new Compta\Import;

if (qg('export') == 'csv')
{
    header('Content-type: application/csv');
    header('Content-Disposition: attachment; filename="Export comptabilité - ' . $config->get('nom_asso') . ' - ' . date('Y-m-d') . '.csv"');
    $import->toCSV($e->getCurrentId());
    exit;
}
elseif (qg('export') == 'ods')
{
    header('Content-type: application/vnd.oasis.opendocument.spreadsheet');
    header('Content-Disposition: attachment; filename="Export comptabilité - ' . $config->get('nom_asso') . ' - ' . date('Y-m-d') . '.ods"');
    $import->toODS($e->getCurrentId());
    exit;
}

if (f('import'))
{
    $form->check('compta_import', [

Added src/www/admin/config/categories/index.php version [c7ce3325e0].



























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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
<?php
namespace Garradin;

require_once __DIR__ . '/../_inc.php';

$cats = new Membres\Categories;

if (f('save'))
{
    $form->check('new_cat', [
        'nom' => 'required',
    ]);

    if (!$form->hasErrors())
    {
        $cats->add([
            'nom' => f('nom'),
        ]);

        Utils::redirect(ADMIN_URL . 'config/categories/');
    }
}

$tpl->assign('liste', $cats->listCompleteWithStats());

$tpl->display('admin/config/categories/index.tpl');

Added src/www/admin/config/categories/modifier.php version [6a75b928b9].



























































































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
<?php
namespace Garradin;

require_once __DIR__ . '/../_inc.php';

$cats = new Membres\Categories;

qv(['id' => 'required|numeric']);

$id = (int) qg('id');

$cat = $cats->get($id);

if (!$cat)
{
    throw new UserException("Cette catégorie n'existe pas.");
}

if (f('save'))
{
    $droits = implode(',', [
        Membres::DROIT_AUCUN,
        Membres::DROIT_ACCES,
        Membres::DROIT_ECRITURE,
        Membres::DROIT_ADMIN,
    ]);

    $form->check('edit_cat_' . $id, [
        'nom'                       => 'required',
        'droit_wiki'                => 'in:' . $droits,
        'droit_compta'              => 'in:' . $droits,
        'droit_membres'             => 'in:' . $droits,
        'droit_config'              => sprintf('in:%s,%s', Membres::DROIT_ADMIN, Membres::DROIT_AUCUN),
        'droit_connexion'           => sprintf('in:%s,%s', Membres::DROIT_ACCES, Membres::DROIT_AUCUN),
        'droit_inscription'         => sprintf('in:%s,%s', Membres::DROIT_ACCES, Membres::DROIT_AUCUN),
        'cacher'                    => 'boolean',
        'id_cotisation_obligatoire' => 'numeric',
    ]);

    if (!$form->hasErrors())
    {
        $data = [
            'nom'           =>  f('nom'),
            'droit_wiki'    =>  (int) f('droit_wiki'),
            'droit_compta'  =>  (int) f('droit_compta'),
            'droit_config'  =>  (int) f('droit_config'),
            'droit_membres' =>  (int) f('droit_membres'),
            'droit_connexion' => (int) f('droit_connexion'),
            'droit_inscription' => (int) f('droit_inscription'),
            'cacher'        =>  (int) f('cacher'),
            'id_cotisation_obligatoire' => (int) f('id_cotisation_obligatoire'),
        ];

        // Ne pas permettre de modifier la connexion, l'accès à la config et à la gestion des membres
        // pour la catégorie du membre qui édite les catégories, sinon il pourrait s'empêcher
        // de se connecter ou n'avoir aucune catégorie avec le droit de modifier les catégories !
        if ($cat->id == $user->id_categorie)
        {
            $data['droit_connexion'] = Membres::DROIT_ACCES;
            $data['droit_config'] = Membres::DROIT_ADMIN;
        }

        try {
            $cats->edit($id, $data);

            if ($id == $user->id_categorie)
            {
                // Mise à jour de la session courante
                $session->refresh();
            }

            Utils::redirect(ADMIN_URL . 'config/categories/');
        }
        catch (UserException $e)
        {
            $form->addError($e->getMessage());
        }
    }
}

$tpl->assign('cat', $cat);

$tpl->assign('readonly', $cat->id == $user->id_categorie ? 'disabled="disabled"' : '');

$cotisations = new Cotisations;
$tpl->assign('cotisations', $cotisations->listCurrent());

$tpl->assign('membres', new Membres);

$tpl->display('admin/config/categories/modifier.tpl');

Added src/www/admin/config/categories/supprimer.php version [93c453e7a7].












































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
<?php
namespace Garradin;

require_once __DIR__ . '/../_inc.php';

$cats = new Membres\Categories;

qv(['id' => 'required|numeric']);

$id = (int) qg('id');

$cat = $cats->get($id);

if (!$cat)
{
    throw new UserException("Cette catégorie n'existe pas.");
}

if ($cat->id == $user->id_categorie)
{
    throw new UserException("Vous ne pouvez pas supprimer votre catégorie.");
}

if (f('delete'))
{
    $form->check('delete_cat_' . $id);

    if (!$form->hasErrors())
    {
        try {
            $cats->remove($id);
            Utils::redirect(ADMIN_URL . 'config/categories/');
        }
        catch (UserException $e)
        {
            $form->addError($e->getMessage());
        }
    }
}

$tpl->assign('cat', $cat);

$tpl->display('admin/config/categories/supprimer.tpl');

Deleted src/www/admin/config/donnees.php version [37588fe983].

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

















































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
<?php
namespace Garradin;

require_once __DIR__ . '/_inc.php';

$s = new Sauvegarde;
$code = false;

if (f('config'))
{
    $form->check('backup_config', [
        'frequence_sauvegardes' => 'present|numeric|min:0|max:365',
        'nombre_sauvegardes' => 'present|numeric|min:1|max:90',
    ]);

    if (!$form->hasErrors())
    {
        try {
            $config->set('frequence_sauvegardes', f('frequence_sauvegardes'));
            $config->set('nombre_sauvegardes', f('nombre_sauvegardes'));
            $config->save();

            Utils::redirect(ADMIN_URL . 'config/donnees.php?ok=config');
        } catch (UserException $e) {
            $form->addError($e->getMessage());
        }
    }
}
elseif (f('create'))
{
    $form->check('backup_create');

    if (!$form->hasErrors())
    {
        try {
            $s->create();
            Utils::redirect(ADMIN_URL . 'config/donnees.php?ok=create');
        } catch (UserException $e) {
            $form->addError($e->getMessage());
        }
    }
}
elseif (f('download'))
{
    $form->check('backup_download');

    if (!$form->hasErrors())
    {
        header('Content-type: application/octet-stream');
        header('Content-Disposition: attachment; filename="' . $config->get('nom_asso') . ' - Sauvegarde données - ' . date('Y-m-d') . '.sqlite"');
        header('Content-Length: ' . $s->getDBSize(true));

        $s->dump();
        exit;
    }
}
elseif (f('restore'))
{
    $form->check('backup_manage');

    if (!$form->hasErrors())
    {
        try {
            $r = $s->restoreFromLocal(f('file'));
            Utils::redirect(ADMIN_URL . 'config/donnees.php?ok=restore&code=' . (int)$r);
        } catch (UserException $e) {
            $form->addError($e->getMessage());
        }
    }
}
elseif (f('remove'))
{
    $form->check('backup_manage');

    if (!$form->hasErrors())
    {
        try {
            $s->remove(f('file'));
            Utils::redirect(ADMIN_URL . 'config/donnees.php?ok=remove');
        } catch (UserException $e) {
            $form->addError($e->getMessage());
        }
    }
}
elseif (f('restore_file'))
{
    $form->check('backup_restore');

    if (!$form->hasErrors())
    {
        // Ignorer la vérification d'intégrité si autorisé et demandé
        $check = (ALLOW_MODIFIED_IMPORT && f('force_import')) ? false : true;

        try {
            $r = $s->restoreFromUpload($_FILES['file'], $user->id, $check);
            Utils::redirect(ADMIN_URL . 'config/donnees.php?ok=restore&code=' . (int)$r);
        } catch (UserException $e) {
            $form->addError($e->getMessage());
            $code = $e->getCode();
        }
    }
}

$tpl->assign('code', $code);
$tpl->assign('ok_code', qg('code'));
$tpl->assign('ok', qg('ok'));
$tpl->assign('liste', $s->getList());
$tpl->assign('max_file_size', Utils::getMaxUploadSize());

$tpl->assign('db_size', $s->getDBSize());
$tpl->assign('files_size', $s->getDBFilesSize());

$tpl->display('admin/config/donnees.tpl');

Added src/www/admin/config/donnees/automatique.php version [05a939d15b].



































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
<?php
namespace Garradin;

require_once __DIR__ . '/../_inc.php';

if (!ENABLE_AUTOMATIC_BACKUPS)
{
    throw new UserException('Les sauvegardes automatiques sont désactivées.');
}

if (f('config'))
{
    $form->check('backup_config', [
        'frequence_sauvegardes' => 'present|numeric|min:0|max:365',
        'nombre_sauvegardes' => 'present|numeric|min:1|max:90',
    ]);

    if (!$form->hasErrors())
    {
        try {
            $config->set('frequence_sauvegardes', f('frequence_sauvegardes'));
            $config->set('nombre_sauvegardes', f('nombre_sauvegardes'));
            $config->save();

            Utils::redirect(ADMIN_URL . 'config/donnees/automatique.php?ok=config');
        } catch (UserException $e) {
            $form->addError($e->getMessage());
        }
    }
}

$tpl->assign('ok', qg('ok'));

$tpl->display('admin/config/donnees/automatique.tpl');

Added src/www/admin/config/donnees/import.php version [f13c6c5a62].







1
2
3
4
5
6
+
+
+
+
+
+
<?php
namespace Garradin;

require_once __DIR__ . '/../_inc.php';

$tpl->display('admin/config/donnees/import.tpl');

Added src/www/admin/config/donnees/index.php version [a41ebc5c56].




















































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
<?php
namespace Garradin;

require_once __DIR__ . '/../_inc.php';

$s = new Sauvegarde;

if (f('download'))
{
    $form->check('backup_download');

    if (!$form->hasErrors())
    {
        header('Content-type: application/octet-stream');
        header('Content-Disposition: attachment; filename="' . $config->get('nom_asso') . ' - Sauvegarde données - ' . date('Y-m-d') . '.sqlite"');
        header('Content-Length: ' . $s->getDBSize(true));

        $s->dump();
        exit;
    }
}
elseif (f('restore_file'))
{
    $form->check('backup_restore');

    if (!$form->hasErrors())
    {
        // Ignorer la vérification d'intégrité si autorisé et demandé
        $check = (ALLOW_MODIFIED_IMPORT && f('force_import')) ? false : true;

        try {
            $r = $s->restoreFromUpload($_FILES['file'], $user->id, $check);
            Utils::redirect(ADMIN_URL . 'config/donnees/?ok=restore&code=' . (int)$r);
        } catch (UserException $e) {
            $form->addError($e->getMessage());
            $code = $e->getCode();
        }
    }
}

$tpl->assign('db_size', $s->getDBSize());
$tpl->assign('files_size', $s->getDBFilesSize());

$tpl->assign('code', isset($code) ? $code : null);
$tpl->assign('ok_code', qg('code'));
$tpl->assign('ok', qg('ok'));
$tpl->assign('now_date', date('Y-m-d'));

$tpl->assign('max_file_size', Utils::getMaxUploadSize());

$tpl->display('admin/config/donnees/index.tpl');

Added src/www/admin/config/donnees/local.php version [c67b3c1387].
























































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
<?php
namespace Garradin;

require_once __DIR__ . '/../_inc.php';

$s = new Sauvegarde;

if (f('create'))
{
    $form->check('backup_create');

    if (!$form->hasErrors())
    {
        try {
            $s->create();
            Utils::redirect(ADMIN_URL . 'config/donnees/local.php?ok=create');
        } catch (UserException $e) {
            $form->addError($e->getMessage());
        }
    }
}
if (f('restore'))
{
    $form->check('backup_manage');

    if (!$form->hasErrors())
    {
        try {
            $r = $s->restoreFromLocal(f('file'));
            Utils::redirect(ADMIN_URL . 'config/donnees/local.php?ok=restore&code=' . (int)$r);
        } catch (UserException $e) {
            $form->addError($e->getMessage());
        }
    }
}
elseif (f('remove'))
{
    $form->check('backup_manage');

    if (!$form->hasErrors())
    {
        try {
            $s->remove(f('file'));
            Utils::redirect(ADMIN_URL . 'config/donnees/local.php?ok=remove');
        } catch (UserException $e) {
            $form->addError($e->getMessage());
        }
    }
}

$tpl->assign('ok_code', qg('code'));
$tpl->assign('ok', qg('ok'));
$tpl->assign('liste', $s->getList());

$tpl->display('admin/config/donnees/local.tpl');

Added src/www/admin/config/donnees/reset.php version [3891123042].


























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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
<?php
namespace Garradin;

require_once __DIR__ . '/../_inc.php';

$s = new Sauvegarde;

if (f('reset_ok'))
{
    $form->check('reset');

    if (!$form->hasErrors())
    {
        try {
            Install::reset($session, f('passe_verif'));
            Utils::redirect(ADMIN_URL . 'config/donnees/reset.php?ok');
        } catch (UserException $e) {
            $form->addError($e->getMessage());
        }
    }
}

$tpl->assign('ok', qg('ok'));

$tpl->display('admin/config/donnees/reset.tpl');

Deleted src/www/admin/config/import.php version [0d90db7427].

1
2
3
4
5
6






-
-
-
-
-
-
<?php
namespace Garradin;

require_once __DIR__ . '/_inc.php';

$tpl->display('admin/config/import.tpl');

Modified src/www/admin/config/site.php from [6929129da8] to [dd99638235].

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
1
2
3
4
5

6
7





8
9



10
11



12
13
14
15

16
17
18
19
20
21
22
23





-
+

-
-
-
-
-
+
+
-
-
-
+
+
-
-
-
+
+
+
+
-
+







<?php
namespace Garradin;

require_once __DIR__ . '/_inc.php';

if (f('save') && $form->check('config_site'))
if (f('desactiver_site') && $form->check('config_site'))
{
    try {
        $config->set('champs_obligatoires', f('champs_obligatoires'));
        $config->set('champs_modifiables_membre', f('champs_modifiables_membre'));
        $config->set('categorie_membres', f('categorie_membres'));
        $config->save();
    $config->set('desactiver_site', true);
    $config->save();

        Utils::redirect(ADMIN_URL . 'config/site.php?ok');
    }
    Utils::redirect(ADMIN_URL . 'config/site.php');
}
    catch (UserException $e)
    {
        $form->addError($e->getMessage());
elseif (f('activer_site') && $form->check('config_site'))
{
    $config->set('desactiver_site', false);
    $config->save();
    }
    Utils::redirect(ADMIN_URL . 'config/site.php');
}

if (f('select') && f('reset') && $form->check('squelettes'))
{
    try {
        foreach (f('select') as $source)
        {

Added src/www/admin/email.php version [0a473d143d].



















1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
<?php

namespace Garradin;

require_once __DIR__ . '/../../include/init.php';

$tpl = Template::getInstance();

if (!empty($_GET['optout']))
{
    $email = new Email;
    $email->setRejectedStatus($_GET['optout'], $email::REJET_OPTOUT, 'Demande de désinscription');
    
    $tpl->assign('title', 'Confirmation');
    $tpl->assign('error', 'Votre adresse a bien été désinscrite, vous ne recevrez plus de messages de notre part.');
}

$tpl->display('error.tpl');

Modified src/www/admin/index.php from [dd9c88b1a0] to [5988e390c9].

25
26
27
28
29
30
31


32
33
34
35
36
37
38
39
40
41
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43







+
+










}
else
{
	$tpl->assign('cotisation', false);
}

$tpl->assign('custom_css', ['wiki.css']);

$tpl->assign('banniere', Plugin::fireSignal('accueil.banniere', ['user' => $user, 'session' => $session]));

$tpl->display('admin/index.tpl');
flush();

// Si pas de cron on réalise les tâches automatisées à ce moment-là
// c'est pas idéal mais mieux que rien
if (!USE_CRON)
{
	require_once ROOT . '/cron.php';
}

Modified src/www/admin/install.php from [0d0c1e0547] to [29602daaba].

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
1
2
3




























































4
5
6
7
8
9
10
11
12
13



-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-


+







<?php
namespace Garradin;

/*
 * Tests : vérification que les conditions pour s'exécuter sont remplies
 */

function test_requis($condition, $message)
{
    if ($condition)
    {
        return true;
    }

    if (PHP_SAPI != 'cli')
    {
        header('Content-Type: text/html; charset=utf-8');
        echo "<!DOCTYPE html>\n<html>\n<head>\n<title>Erreur</title>\n<meta charset=\"utf-8\" />\n";
        echo '<style type="text/css">body { font-family: sans-serif; } ';
        echo '.error { color: darkred; padding: .5em; margin: 1em; border: 3px double red; background: yellow; }</style>';
        echo "\n</head>\n<body>\n<h2>Erreur</h2>\n<h3>Le problème suivant empêche Garradin de fonctionner :</h3>\n";
        echo '<p class="error">' . htmlspecialchars($message, ENT_QUOTES, 'UTF-8') . '</p>';
        echo '<hr /><p>Pour plus d\'informations consulter ';
        echo '<a href="http://dev.kd2.org/garradin/Probl%C3%A8mes%20fr%C3%A9quents">l\'aide sur les problèmes à l\'installation</a>.</p>';
        echo "\n</body>\n</html>";
    }
    else
    {
        echo "[ERREUR] Le problème suivant empêche Garradin de fonctionner :\n";
        echo $message . "\n";
        echo "Pour plus d'informations consulter http://dev.kd2.org/garradin/Probl%C3%A8mes%20fr%C3%A9quents\n";
    }

    exit;
}

test_requis(
    version_compare(phpversion(), '5.6', '>='),
    'PHP 5.6 ou supérieur requis. PHP version ' . phpversion() . ' installée.'
);

test_requis(
    defined('CRYPT_BLOWFISH') && CRYPT_BLOWFISH,
    'L\'algorithme de hashage de mot de passe Blowfish n\'est pas présent (pas installé ou pas compilé).'
);

test_requis(
    class_exists('SQLite3'),
    'Le module de base de données SQLite3 n\'est pas disponible.'
);

$v = \SQLite3::version();

test_requis(
    version_compare($v['versionString'], '3.7.4', '>='),
    'SQLite3 version 3.7.4 ou supérieur requise. Version installée : ' . $v['versionString']
);

test_requis(
    file_exists(__DIR__ . '/../../include/lib/KD2'),
    'Librairie KD2 non disponible.'
);

const INSTALL_PROCESS = true;

require_once __DIR__ . '/../../include/test_required.php';
require_once __DIR__ . '/../../include/init.php';

Install::checkAndCreateDirectories();

if (!file_exists(DB_FILE))
{
    // Renommage du fichier sqlite à la version 0.5.0

Modified src/www/admin/membres/action.php from [c6d0a5fc14] to [d0b4b091d1].

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
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












+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+

-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-







<?php
namespace Garradin;

require_once __DIR__ . '/_inc.php';

$session->requireAccess('membres', Membres::DROIT_ADMIN);

if (!f('selected') || !is_array(f('selected')) || !count(f('selected')))
{
    throw new UserException("Aucun membre sélectionné.");
}

$action = f('action');
$list = f('selected');

if (!$action)
{
    throw new UserException('Aucune action sélectionnée.');
}

if ($action == 'ods' || $action == 'csv')
{
    $import = new Membres\Import;

    if ($action == 'ods')
    {
        $import->toODS($list);
    }
    else
    {
        $import->toCSV($list);
    }

    exit;
}
elseif ($action == 'move' || $action == 'delete')
{
foreach (f('selected') as &$id)
{
    $id = (int) $id;
    foreach (f('selected') as &$id)
    {
        $id = (int) $id;

    // On ne permet pas d'action collective sur l'utilisateur courant pour éviter les risques
    // d'erreur genre "oh je me suis supprimé du coup j'ai plus accès à rien"
    if ($id == $user->id)
    {
        throw new UserException("Il n'est pas possible de se modifier ou supprimer soi-même.");
    }
}

        // On ne permet pas d'action collective sur l'utilisateur courant pour éviter les risques
        // d'erreur genre "oh je me suis supprimé du coup j'ai plus accès à rien"
        if ($id == $user->id)
        {
            throw new UserException("Il n'est pas possible de se modifier ou supprimer soi-même.");
        }
    }
}
$action = f('action') ?: (f('move') ? 'move' : (f('delete') ? 'delete' : ''));

if (!$action)
{
    throw new UserException('Aucune action sélectionnée.');
}

if ($action == 'move' && f('confirm'))
{
    $form->check('membres_action', [
        'selected' => 'required|array',
        'id_categorie' => 'required|numeric',
    ]);
51
52
53
54
55
56
57
58
59


60
61
62
63
64
65
66
67
68
69
70
70
71
72
73
74
75
76


77
78
79
80
81
82
83
84
85
86
87
88
89







-
-
+
+











    if (!$form->hasErrors())
    {
        $membres->delete(f('selected'));
        Utils::redirect(ADMIN_URL . 'membres/');
    }
}

$tpl->assign('selected', f('selected'));
$tpl->assign('nb_selected', count(f('selected')));
$tpl->assign('selected', $list);
$tpl->assign('nb_selected', count($list));

if ($action == 'move')
{
    $cats = new Membres\Categories;

    $tpl->assign('membres_cats', $cats->listSimple());
}

$tpl->assign('action', $action);

$tpl->display('admin/membres/action.tpl');

Deleted src/www/admin/membres/categories/index.php version [a435abf714].

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




























-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
<?php
namespace Garradin;

require_once __DIR__ . '/../_inc.php';

$session->requireAccess('membres', Membres::DROIT_ADMIN);

$cats = new Membres\Categories;

if (f('save'))
{
    $form->check('new_cat', [
        'nom' => 'required',
    ]);

    if (!$form->hasErrors())
    {
        $cats->add([
            'nom' => f('nom'),
        ]);

        Utils::redirect(ADMIN_URL . 'membres/categories/');
    }
}

$tpl->assign('liste', $cats->listCompleteWithStats());

$tpl->display('admin/membres/categories/index.tpl');

Deleted src/www/admin/membres/categories/modifier.php version [624c7118d6].

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






























































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
<?php
namespace Garradin;

require_once __DIR__ . '/../_inc.php';

$session->requireAccess('membres', Membres::DROIT_ADMIN);

$cats = new Membres\Categories;

qv(['id' => 'required|numeric']);

$id = (int) qg('id');

$cat = $cats->get($id);

if (!$cat)
{
    throw new UserException("Cette catégorie n'existe pas.");
}

if (f('save'))
{
    $droits = implode(',', [
        Membres::DROIT_AUCUN,
        Membres::DROIT_ACCES,
        Membres::DROIT_ECRITURE,
        Membres::DROIT_ADMIN,
    ]);

    $form->check('edit_cat_' . $id, [
        'nom'                       => 'required',
        'droit_wiki'                => 'in:' . $droits,
        'droit_compta'              => 'in:' . $droits,
        'droit_membres'             => 'in:' . $droits,
        'droit_config'              => sprintf('in:%s,%s', Membres::DROIT_ADMIN, Membres::DROIT_AUCUN),
        'droit_connexion'           => sprintf('in:%s,%s', Membres::DROIT_ACCES, Membres::DROIT_AUCUN),
        'droit_inscription'         => sprintf('in:%s,%s', Membres::DROIT_ACCES, Membres::DROIT_AUCUN),
        'cacher'                    => 'boolean',
        'id_cotisation_obligatoire' => 'numeric',
    ]);

    if (!$form->hasErrors())
    {
        $data = [
            'nom'           =>  f('nom'),
            'description'   =>  f('description'),
            'droit_wiki'    =>  (int) f('droit_wiki'),
            'droit_compta'  =>  (int) f('droit_compta'),
            'droit_config'  =>  (int) f('droit_config'),
            'droit_membres' =>  (int) f('droit_membres'),
            'droit_connexion' => (int) f('droit_connexion'),
            'droit_inscription' => (int) f('droit_inscription'),
            'cacher'        =>  (int) f('cacher'),
            'id_cotisation_obligatoire' => (int) f('id_cotisation_obligatoire'),
        ];

        // Ne pas permettre de modifier la connexion, l'accès à la config et à la gestion des membres
        // pour la catégorie du membre qui édite les catégories, sinon il pourrait s'empêcher
        // de se connecter ou n'avoir aucune catégorie avec le droit de modifier les catégories !
        if ($cat->id == $user->id_categorie)
        {
            $data['droit_connexion'] = Membres::DROIT_ACCES;
            $data['droit_config'] = Membres::DROIT_ADMIN;
            $data['droit_membres'] = Membres::DROIT_ADMIN;
        }

        try {
            $cats->edit($id, $data);

            if ($id == $user->id_categorie)
            {
                // Mise à jour de la session courante
                $session->refresh();
            }

            Utils::redirect(ADMIN_URL . 'membres/categories/');
        }
        catch (UserException $e)
        {
            $form->addError($e->getMessage());
        }
    }
}

$tpl->assign('cat', $cat);

$tpl->assign('readonly', $cat->id == $user->id_categorie ? 'disabled="disabled"' : '');

$cotisations = new Cotisations;
$tpl->assign('cotisations', $cotisations->listCurrent());

$tpl->assign('membres', $membres);

$tpl->display('admin/membres/categories/modifier.tpl');

Deleted src/www/admin/membres/categories/supprimer.php version [43f9a12d7b].

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













































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
<?php
namespace Garradin;

require_once __DIR__ . '/../_inc.php';

$session->requireAccess('membres', Membres::DROIT_ADMIN);

$cats = new Membres\Categories;

qv(['id' => 'required|numeric']);

$id = (int) qg('id');

$cat = $cats->get($id);

if (!$cat)
{
    throw new UserException("Cette catégorie n'existe pas.");
}

if ($cat->id == $user->id_categorie)
{
    throw new UserException("Vous ne pouvez pas supprimer votre catégorie.");
}

if (f('delete'))
{
    $form->check('delete_cat_' . $id);

    if (!$form->hasErrors())
    {
        try {
            $cats->remove($id);
            Utils::redirect(ADMIN_URL . 'membres/categories/');
        }
        catch (UserException $e)
        {
            $form->addError($e->getMessage());
        }
    }
}

$tpl->assign('cat', $cat);

$tpl->display('admin/membres/categories/supprimer.tpl');

Modified src/www/admin/membres/cotisations.php from [32cdb67ed5] to [d156e1e5bb].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1
2
3
4
5


6
7
8
9
10
11
12





-
-







<?php
namespace Garradin;

require_once __DIR__ . '/_inc.php';

$session->requireAccess('membres', Membres::DROIT_ECRITURE);

qv(['id' => 'required|numeric']);

$id = (int) qg('id');

$membre = $membres->get($id);

if (!$membre)

Modified src/www/admin/membres/fiche.php from [173904eaef] to [890dac5864].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
1
2
3
4
5


6
7
8
9
10
11
12





-
-







<?php
namespace Garradin;

require_once __DIR__ . '/_inc.php';

$session->requireAccess('membres', Membres::DROIT_ECRITURE);

qv(['id' => 'required|numeric']);

$id = (int) qg('id');

$membre = $membres->get($id);

if (!$membre)

Modified src/www/admin/membres/import.php from [414da74c86] to [b710d208b2].

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
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













-
-





-
-







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+



-
+
-






-
+

-
+
+

-
+

-
+





-
-










+
+
+

-
-


<?php
namespace Garradin;

require_once __DIR__ . '/_inc.php';

$session->requireAccess('membres', Membres::DROIT_ADMIN);

$import = new Membres\Import;

$tpl->assign('tab', null !== qg('export') ? 'export' : 'import');

if (qg('export') == 'csv')
{
    header('Content-type: application/csv');
    header('Content-Disposition: attachment; filename="Export membres - ' . $config->get('nom_asso') . ' - ' . date('Y-m-d') . '.csv"');
    $import->toCSV();
    exit;
}
elseif (qg('export') == 'ods')
{
    header('Content-type: application/vnd.oasis.opendocument.spreadsheet');
    header('Content-Disposition: attachment; filename="Export membres - ' . $config->get('nom_asso') . ' - ' . date('Y-m-d') . '.ods"');
    $import->toODS();
    exit;
}

$champs = $config->get('champs_membres')->getAll();
$champs->date_inscription = (object) ['title' => 'Date inscription', 'type' => 'date'];

$csv_file = false;

if (f('csv_encoded'))
{
    $form->check('membres_import', [
        'csv_encoded'     => 'required|json',
        'csv_translate'   => 'required|array',
        'skip_first_line' => 'boolean',
    ]);

    $csv_file = json_decode(f('csv_encoded'), true);

    if (!$form->hasErrors())
    {
        try
        {
            $import->fromArray($csv_file, f('csv_translate'), f('skip_first_line') ? 1 : 0);
            Utils::redirect(ADMIN_URL . 'membres/import.php?ok');
        }
        catch (UserException $e)
        {
            $form->addError($e->getMessage());
        }
    }
}
if (f('import'))
elseif (f('import'))
{
    $form->check('membres_import', [
        'upload' => 'file|required',
        'type'   => 'required|in:galette,garradin',
        'type'   => 'required|in:csv,garradin',
        'galette_translate' => 'array',
    ]);

    if (!$form->hasErrors())
    {
        try
        {
            if (f('type') == 'galette')
            if (f('type') == 'garradin')
            {
                $import->fromGalette($_FILES['upload']['tmp_name'], f('galette_translate'));
                $import->fromGarradinCSV($_FILES['upload']['tmp_name'], $user->id);
                Utils::redirect(ADMIN_URL . 'membres/import.php?ok');
            }
            elseif (f('type') == 'garradin')
            elseif (f('type') == 'csv')
            {
                $import->fromCSV($_FILES['upload']['tmp_name'], $user->id);
                $csv_file = $import->getCSVAsArray($_FILES['upload']['tmp_name']);
            }
            else
            {
                throw new UserException('Import inconnu.');
            }

            Utils::redirect(ADMIN_URL . 'membres/import.php?ok');
        }
        catch (UserException $e)
        {
            $form->addError($e->getMessage());
        }
    }
}

$tpl->assign('ok', null !== qg('ok') ? true : false);

$tpl->assign('csv_file', $csv_file);
$tpl->assign('csv_first_line', $csv_file ? reset($csv_file) : null);

$tpl->assign('garradin_champs', $champs);
$tpl->assign('galette_champs', $import->galette_fields);
$tpl->assign('translate', f('galette_translate'));

$tpl->display('admin/membres/import.tpl');

Modified src/www/admin/membres/index.php from [e45d2413f7] to [33e9d88bdd].

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
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





-
-
-
-
-
-
-
-
-
-
-
-
-
+
+

-
-
+
+

-
-
+
+

-
-
-
-
-
-
-
+
+
+
+
+
+
+

-
-
-
-
+
+
+
+

-
-
-
+
+
+

-
-
+
+

-
-
+
+

-
+

-
-
-
-
-
-
+
+
+
+
+
+

-
-
+
+

-
+

-
-
+
+

-
+

-
-
-
-
-
-
+
+
+
+
+
+

-
-
-
+
+
+

-
-
+
+
-
-




<?php
namespace Garradin;

require_once __DIR__ . '/_inc.php';

// Recherche de membre (pour ceux qui n'ont qu'un accès à la liste des membres)
if (qg('r'))
{
	$recherche = trim(qg('r'));

	$result = $membres->search($config->get('champ_identite'), $recherche);
    $tpl->assign('liste', $result);
	$tpl->assign('recherche', $recherche);
}
else
{
	$cats = new Membres\Categories;
	$champs = $config->get('champs_membres');
$cats = new Membres\Categories;
$champs = $config->get('champs_membres');

	$membres_cats = $cats->listSimple();
	$membres_cats_cachees = $cats->listHidden();
$membres_cats = $cats->listSimple();
$membres_cats_cachees = $cats->listHidden();

	$cat_id = (int) qg('cat') ?: 0;
	$page = (int) qg('p') ?: 1;
$cat_id = (int) qg('cat') ?: 0;
$page = (int) qg('p') ?: 1;

	if ($cat_id)
	{
	    if (!$session->canAccess('membres', Membres::DROIT_ECRITURE) && array_key_exists($cat_id, $membres_cats_cachees))
	    {
	    	$cat_id = 0;
	    }
	}
if ($cat_id)
{
    if (!$session->canAccess('membres', Membres::DROIT_ECRITURE) && array_key_exists($cat_id, $membres_cats_cachees))
    {
    	$cat_id = 0;
    }
}

	if (!$cat_id)
	{
	    $cat_id = array_diff(array_keys((array) $membres_cats), array_keys((array) $membres_cats_cachees));
	}
if (!$cat_id)
{
    $cat_id = array_diff(array_keys((array) $membres_cats), array_keys((array) $membres_cats_cachees));
}

	// Par défaut le champ de tri c'est l'identité
	$order = $config->get('champ_identite');
	$desc = false;
// Par défaut le champ de tri c'est l'identité
$order = $config->get('champ_identite');
$desc = false;

	if (qg('o'))
	    $order = qg('o');
if (qg('o'))
    $order = qg('o');

	if (null !== qg('d'))
	    $desc = true;
if (null !== qg('d'))
    $desc = true;

	$fields = $champs->getListedFields();
$fields = $champs->getListedFields();

	// Vérifier que le champ de tri existe bien dans la table
	if (!isset($fields->$order))
	{
		// Sinon par défaut c'est le premier champ de la table qui fait le tri
		$order = $champs->getFirstListed();
	}
// Vérifier que le champ de tri existe bien dans la table
if (!isset($fields->$order))
{
	// Sinon par défaut c'est le premier champ de la table qui fait le tri
	$order = $champs->getFirstListed();
}

	$tpl->assign('order', $order);
	$tpl->assign('desc', $desc);
$tpl->assign('order', $order);
$tpl->assign('desc', $desc);

	$tpl->assign('champs', $fields);
$tpl->assign('champs', $fields);

	$tpl->assign('liste', $membres->listByCategory($cat_id, array_keys((array) $fields), $page, $order, $desc));
	$tpl->assign('total', $membres->countByCategory($cat_id));
$tpl->assign('liste', $membres->listByCategory($cat_id, array_keys((array) $fields), $page, $order, $desc));
$tpl->assign('total', $membres->countByCategory($cat_id));

	$cat_id = is_array($cat_id) ? 0 : $cat_id;
$cat_id = is_array($cat_id) ? 0 : $cat_id;

	$tpl->assign('pagination_url', Utils::getSelfUrl([
		'p' => '[ID]',
		'o' => $order,
		($desc ? 'd' : 'a') => '',
		'cat' => $cat_id,
	]));
$tpl->assign('pagination_url', Utils::getSelfUrl([
	'p' => '[ID]',
	'o' => $order,
	($desc ? 'd' : 'a') => '',
	'cat' => $cat_id,
]));

	$tpl->assign('membres_cats', $membres_cats);
	$tpl->assign('membres_cats_cachees', $membres_cats_cachees);
	$tpl->assign('current_cat', $cat_id);
$tpl->assign('membres_cats', $membres_cats);
$tpl->assign('membres_cats_cachees', $membres_cats_cachees);
$tpl->assign('current_cat', $cat_id);

	$tpl->assign('page', $page);
	$tpl->assign('bypage', Membres::ITEMS_PER_PAGE);
$tpl->assign('page', $page);
$tpl->assign('bypage', Membres::ITEMS_PER_PAGE);

}

$tpl->assign('sent', null !== qg('sent'));

$tpl->display('admin/membres/index.tpl');

Modified src/www/admin/membres/message_collectif.php from [2bd7d9d11b] to [528d973d74].

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
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





-
+
+

-
+

-
+


-
+
-

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+




+
-
+
+









-
-
-
-
+
+


<?php
namespace Garradin;

require_once __DIR__ . '/_inc.php';

$session->requireAccess('membres', Membres::DROIT_ADMIN);
$cats = new Membres\Categories;
$recherche = new Recherche;

if (f('save'))
if (f('send'))
{
    $form->check('send_message_collectif', [
    $form->check('send_message_co', [
        'sujet'      => 'required|string',
        'message'    => 'required|string',
        'dest'       => 'numeric',
        'recipients' => 'required|string',
        'subscribed' => 'boolean',
    ]);

    if (preg_match('/^(categorie|recherche)_(\d+)$/', f('recipients'), $match))
    {
        if ($match[1] == 'categorie')
        {
            $recipients = $membres->listAllByCategory($match[2]);
        }
        else
        {
            $recipients = $recherche->search($match[2], 'id, email');
        }

        if (!count($recipients) || !isset($recipients[0]->email))
        {
            $form->addError('Aucun membre dans la liste.');
        }
    }
    else
    {
        throw new UserException('Destinataires invalides : ' . f('recipients'));
    }

    if (!$form->hasErrors())
    {
        try {
            $membres->sendMessage($recipients, f('sujet'),
            $membres->sendMessageToCategory(f('dest'), f('sujet'), f('message'), (bool) f('subscribed'));
                f('message'), (bool) f('copie'));

            Utils::redirect(ADMIN_URL . 'membres/?sent');
        }
        catch (UserException $e)
        {
            $form->addError($e->getMessage());
        }
    }
}

$cats = new Membres\Categories;

$tpl->assign('cats_liste', $cats->listSimple());
$tpl->assign('cats_cachees', $cats->listHidden());
$tpl->assign('categories', $cats->listNotHidden());
$tpl->assign('recherches', $recherche->getList($user->id, 'membres'));

$tpl->display('admin/membres/message_collectif.tpl');

Modified src/www/admin/membres/recherche.php from [f11a643a77] to [5423db5485].

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
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





-
-
-
+
-


+
+
+
+
+
+
+

+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
-
-
-
-
+
+
+
+
+
+
+
+
+

-
-
-
+
+
+
+
+
+
+

-
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-

+
-
+

-
+



-
-
-
+
+
+
+
-
+
+
+
+
+

-
-
+
+
-
-
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-
+

+
+
+
+
+
+
+
+
+
+
-
+


-
+
+
+
+
+
+
+
+


<?php
namespace Garradin;

require_once __DIR__ . '/_inc.php';

$session->requireAccess('membres', Membres::DROIT_ECRITURE);

$recherche = trim(qg('r'));
$recherche = new Recherche;
$champ = trim(qg('c'));

$champs = $config->get('champs_membres');
$text_query = trim(qg('qt'));
$query = null;
$limit = f('limit') ?: 100;
$order = f('order');
$desc = (bool) f('desc');
$sql_query = null;
$id = f('id') ?: qg('id');

// Recherche simple
$auto = false;

// On détermine magiquement quel champ on recherche
if (!$champ)
{
    $auto = true;
if ($text_query !== '')
{
    $operator = 'LIKE %?%';

    if (is_numeric(trim($text_query)))
    {
        $column = 'numero';
        $operator = '= ?';
    }
    elseif (strpos($text_query, '@') !== false)
    {
        $column = 'email';
    }
    else
    {
        $column = $config->get('champ_identite');
    }

    $query = [[
        'operator' => 'AND',
        'conditions' => [
            [
                'column'   => $column,
                'operator' => $operator,
                'values'   => [$text_query],
            ],
        ],
    ]];

    if (is_numeric(trim($recherche))) {
        $champ = 'numero';
    }
    elseif (strpos($recherche, '@') !== false) {
        $champ = 'email';
    $order = $column;
}
elseif ($id)
{
    $r = $recherche->get($id);

    if (!$r || $r->type != Recherche::TYPE_JSON)
    {
        throw new UserException('Recherche inconnue ou invalide');
    }
    else {
        $champ = $config->get('champ_identite');
    }

    $query = $r->query;
    $order = $r->order;
    $desc = $r->desc;
    $limit = $r->limit;

    $tpl->assign('recherche', $r);
}
else
{
    if ($champ != 'numero' && !$champs->get($champ))
    {

if (f('q') !== null)
{
        throw new UserException('Le champ demandé n\'existe pas.');
    }
}

    $query = json_decode(f('q'), true);
}

if ($query)
if ($recherche != '')
{
    $sql_query = $recherche->buildQuery('membres', $query, $order, $desc, $limit);
    $result = $membres->search($champ, $recherche);
    $result = $recherche->searchSQL('membres', $sql_query);

    if (count($result) == 1 && $auto)
    if (count($result) == 1 && $text_query !== '')
    {
        Utils::redirect(ADMIN_URL . 'membres/fiche.php?id=' . (int)$result[0]->id);
    }
}

$champs_liste = $champs->getList();

    if (f('save'))
    {
        $query = [
$champs_entete = $champs->getListedFields();
            'query' => $query,
            'order' => $order,
            'limit' => $limit,
            'desc'  => $desc,
        ];

if (!isset($champs_entete->$champ))
{
        if ($id)
        {
    $champs_entete = array_merge(
        [$champ => $champs_liste->$champ],
            $recherche->edit($id, [
                'type'    => Recherche::TYPE_JSON,
                'contenu' => $query,
        (array)$champs_entete
    );
}

$tpl->assign('champs_entete', $champs_entete);
$tpl->assign('champs_liste', $champs_liste);
$tpl->assign('champ', $champ);

            ]);
        }
        else
        {
            $id = $recherche->add('Recherche avancée du ' . date('d/m/Y H:i:s'), $user->id, $recherche::TYPE_JSON, 'membres', $query);
        }

        Utils::redirect('/admin/membres/recherches.php?id=' . $id);
    }

    $tpl->assign('result_header', $membres->getSearchHeaderFields($result));
}
if ($recherche != '')
else
{
    $query = [[
        'operator' => 'AND',
        'conditions' => [
            [
                'column'   => $config->get('champ_identite'),
                'operator' => '= ?',
                'values'   => ['Souad Massi'],
            ],
        ],
    ]];
    $tpl->assign('liste', $result);
    $result = null;
}

$tpl->assign('recherche', $recherche);
$tpl->assign('id', $id);
$tpl->assign('query', $query);
$tpl->assign('sql_query', $sql_query);
$tpl->assign('result', $result);
$tpl->assign('order', $order);
$tpl->assign('desc', $desc);
$tpl->assign('limit', $limit);
$tpl->assign('colonnes', $recherche->getColumns('membres'));

$tpl->display('admin/membres/recherche.tpl');

Modified src/www/admin/membres/recherche_sql.php from [d0f26422b0] to [b02dad85af].

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
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





-
+


+
+

+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+




-
-
-
-
-
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+


+
+

<?php
namespace Garradin;

require_once __DIR__ . '/_inc.php';

$session->requireAccess('membres', Membres::DROIT_ADMIN);
$recherche = new Recherche;

$query = trim(qg('query'));
$result = null;
$id = (int) qg('id');

if ($id)
{
	$r = $recherche->get($id);

	if (!$r || $r->type != Recherche::TYPE_SQL)
	{
		throw new UserException('Recherche inconnue');
	}

	if (!$session->canAccess('membres', Membres::DROIT_ADMIN) || !$query)
	{
		$query = $r->contenu;
	}

	$tpl->assign('recherche', $r);
}
else
{
	$session->requireAccess('membres', Membres::DROIT_ADMIN);
}

$tpl->assign('schema', $membres->schemaSQL());
$tpl->assign('schema', $recherche->schema('membres'));
$tpl->assign('query', $query);

if ($query != '')
{
    try {
        $tpl->assign('result', $membres->searchSQL($query));
    }
    catch (\Exception $e)
    {
	try {
		$result = $recherche->searchSQL('membres', $query);
	}
	catch (\Exception $e)
	{
        $tpl->assign('result', null);
        $tpl->assign('error', $e->getMessage());
    }
}
else
{
    $tpl->assign('result', null);
		$form->addError($e->getMessage());
	}

	if (!$form->hasErrors() && qg('save'))
	{
		if ($id)
		{
			$recherche->edit($id, [
				'type'    => Recherche::TYPE_SQL,
				'contenu' => $query,
			]);
		}
		else
		{
			$id = $recherche->add('Recherche SQL du ' . date('d/m/Y H:i:s'), $user->id, $recherche::TYPE_SQL, 'membres', $query);
		}

		Utils::redirect('/admin/membres/recherches.php?id=' . $id);
	}
}

$tpl->assign('result', $result);
$tpl->assign('id', $id);
$tpl->display('admin/membres/recherche_sql.tpl');

Added src/www/admin/membres/recherches.php version [7efc82999d].
























































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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
<?php
namespace Garradin;

require_once __DIR__ . '/_inc.php';

$recherche = new Recherche;
$mode = null;

if (qg('edit') || qg('delete'))
{
	$r = $recherche->get(qg('edit') ?: qg('delete'));

	if (!$r)
	{
		throw new UserException('Recherche non trouvée');
	}

	if ($r->id_membre !== null && $r->id_membre != $user->id)
	{
		throw new UserException('Recherche privée appartenant à un autre membre.');
	}

	$tpl->assign('recherche', $r);

	$mode = qg('edit') ? 'edit' : 'delete';
}

if ($mode == 'edit' && f('save') && $form->check('edit_recherche_' . $r->id))
{
	try {
		$recherche->edit($r->id, [
			'intitule'  => f('intitule'),
			'id_membre' => f('prive') ? $user->id : null,
		]);

		Utils::redirect('/admin/membres/recherches.php');
	}
	catch (UserException $e) {
		$form->addError($e->getMessage());
	}
}
elseif ($mode == 'delete' && f('delete') && $form->check('del_recherche_' . $r->id))
{
	$recherche->remove($r->id);
	Utils::redirect('/admin/membres/recherches.php');
}

$tpl->assign('mode', $mode);

if (!$mode)
{
	$tpl->assign('liste', $recherche->getList($user->id, 'membres'));
}

$tpl->display('admin/membres/recherches.tpl');

Modified src/www/admin/static/admin.css from [903a1b66a2] to [c451b4a60c].

1
2
3
4
5
6
7
8
9





10
11
12
13
14
15
16
1
2
3
4





5
6
7
8
9
10
11
12
13
14
15
16




-
-
-
-
-
+
+
+
+
+







@charset "UTF-8";

@font-face {
    font-family: 'gicon';
    src: url('font/garradin.eot?36341436');
    src: url('font/garradin.eot?36341436#iefix') format('embedded-opentype'),
        url('font/garradin.woff?36341436') format('woff'),
        url('font/garradin.ttf?36341436') format('truetype'),
        url('font/garradin.svg?36341436#garradin') format('svg');
    src: url('font/garradin.eot?2018');
    src: url('font/garradin.eot?2018#iefix') format('embedded-opentype'),
        url('font/garradin.woff?2018') format('woff'),
        url('font/garradin.ttf?2018') format('truetype'),
        url('font/garradin.svg?2018#garradin') format('svg');
    font-weight: normal;
    font-style: normal;
}

body, div, dl, dt, dd, ul, ol, li, h1, h2, h3, h4, h5, h6, 
pre, form, fieldset, input, textarea, p, blockquote, th, td,
figure, article, aside, section, header, footer { 
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
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







-
-
+
+
-
-
+

+
+
+
+
+
+
+
+



-
-
+
+
-
-



-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







    color: #090;
}

span.alert, b.alert {
    color: #990;
}

p.error, div.error {
    border: 1px solid #c00;
p.alert, div.alert, p.error, div.error, p.confirm, div.confirm {
    border: 1px solid #ccc;
    background: #fcc;
    padding: 0.5em;
    padding: .5em;
    margin-bottom: 1em;
    border-radius: .3em;
    padding-left: 3em;
    position: relative;
}

p.error, div.error {
    border-color: #c00;
    background-color: #fcc;
}

p.confirm, div.confirm {
    border: 1px solid #0c0;
    background: #cfc;
    border-color: #0c0;
    background-color: #cfc;
    padding: 0.5em;
    margin-bottom: 1em;
}

p.alert, div.alert {
    border: 1px solid #cc0;
    background: #ffc;
    padding: 0.5em;
    margin-bottom: 1em;
    overflow: auto;
    border-color: #cc0;
    background-color: #ffc;
}

p.confirm::before, div.confirm::before, p.alert::before, div.alert::before, p.error::before, div.error::before {
    font-family: "gicon";
    left: .5em;
    top: .2em;
    position: absolute;
    font-size: 1.5em;
    text-shadow: 2px 2px 5px #666;
}    

p.confirm::before, div.confirm::before {
    content: "☑";
    color: green;
}

p.alert::before, div.alert::before {
    content: "⚠";
    color: yellow;
}

p.error::before, div.error::before {
    content: "⚠";
    color: red;
}

p.help {
    margin: 1em;
    color: #666;
}

274
275
276
277
278
279
280




281
282
283
284
285
286
287
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317







+
+
+
+







input[type=text], textarea, input[type=password], input[type=email],
input[type=url], input[type=tel], select {
    padding: 0.2em 0.4em;
    font-family: Sans-serif;
    min-width: 20em;
    max-width: 100%;
}

input[type=password], input.clearTextPassword {
    font-family: monospace;
}

select, input[size] {
    min-width: 0;
}

input.time {
    text-align: center;
300
301
302
303
304
305
306










307
308
309
310
311
312
313
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353







+
+
+
+
+
+
+
+
+
+







    transition: opacity .5s ease;
}

input.resetButton {
    padding: .1em;
    margin-left: 1em;
}

input[type=button].showPassword {
    margin-left: -2em;
    margin-right: 1em;
    background: none;
}

input[type=button].showPassword:hover {
    background: none;
}

.loader {
    width: 100%;
    min-height: 32px;
    display: block;
    position: relative;
}
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
441
442
443
444
445
446

447
448
449
450
451
452
453
445
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







-
+



+
+
+
+
+
+
+
+















+















-
+







    float: right;
}

ul.actions {
    list-style-type: none;
    margin: 1em 0;
    border-bottom: .1em solid #9c4f15;
    border-bottom: .1em solid rgb(var(--gMainColor));
    border-bottom-color: rgb(var(--gMainColor));
    padding: 0 1em;
    z-index: 100;
}

ul.actions.sub {
    margin: -1em 2em 1em 0;
    padding-top: 1em;
    border-right: .1em solid #9c4f15;
    border-right-color: rgb(var(--gMainColor));
    border-bottom-right-radius: .5em;
}

ul.actions li {
    display: inline-block;
    margin: 0 0.2em;
}

ul.actions li a, ul.actions li label {
    display: inline-block;
    background: rgb(217, 134, 40);
    background: rgba(217, 134, 40, .5);
    background: rgba(var(--gSecondColor), .5);
    border-radius: .5em .5em 0 0;
    padding: .1em .5em;
    color: #000;
    text-decoration: none;
    transition: background-color .2s, color .2s;
}

ul.actions li input {
    display: none;
}

ul.actions li.current a, ul.actions li input:checked + label {
    background: #9c4f15;
    background: rgb(var(--gMainColor));
    color: #fff;
    color: rgb(var(--gBgColor));
}

ul.actions li a:hover, ul.actions li label:hover {
    color: #fff;
    color: rgb(var(--gBgColor));
    background-color: rgb(var(--gMainColor));
    text-decoration: underline;
    border-bottom: none;
}

h3.warning {
    margin: 1em;
    color: red;
564
565
566
567
568
569
570


























571
572
573
574
575
576
577
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







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







}

table.search th {
    background: rgb(217, 134, 40);
    background: rgba(217, 134, 40, 0.5);
    background: rgba(var(--gSecondColor), 0.5);
}

table.list .disabled {
    background: #eee;
    color: #999;
}

#queryBuilder .column select, #queryBuilderForm .actions select {
    max-width: 15em;
}

#queryBuilder table td {
    vertical-align: top;
    padding: .1em .2em;
}

#queryBuilder input[type=button], #queryBuilder .values input {
    margin: .1em;
}

#queryBuilderForm .actions label {
    margin: 0 .5em;
}

#queryBuilderForm input[type=number] {
    width: 4em;
}

.userOrder .cur {
    background: rgb(217, 134, 40);
    background: rgba(var(--gSecondColor), 1.0);
    color: #fff;
    color: rgb(var(--gBgColor));
}
676
677
678
679
680
681
682



























683
684
685
686
687
688
689
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791







+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







    padding-bottom: .5em;
    border-bottom: 1pt solid #999;
}

#rapport h1 {
    text-align: center;
}

h2.ruler {
    margin: .5em;
    text-align: center;
    color: #333;
    overflow: hidden;
}

h2.ruler:before, h2.ruler:after {
    background-color: #000;
    content: "";
    display: inline-block;
    height: 1px;
    position: relative;
    vertical-align: middle;
    width: 50%;
}

h2.ruler:before {
    right: 0.5em;
    margin-left: -50%;
}

h2.ruler:after {
    left: 0.5em;
    margin-right: -50%;
}

.icn, .icnl {
    font-family: "gicon", sans-serif;
    font-style: normal;
    font-weight: normal;
    speak: none;
    font-variant: normal;

Modified src/www/admin/static/font/garradin.css from [2361fa015f] to [d980f71e5b].

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
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




-
-
-
-
-
+
+
+
+
+
+









-
+




















-
+
+
+

+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
-
+
+
+
+

-

+
-
-
+
+
+
+
+

-
-
-
-
-
+
+
+
+
+
+

@charset "UTF-8";

 @font-face {
  font-family: 'garradin';
  src: url('../font/garradin.eot?36341436');
  src: url('../font/garradin.eot?36341436#iefix') format('embedded-opentype'),
       url('../font/garradin.woff?36341436') format('woff'),
       url('../font/garradin.ttf?36341436') format('truetype'),
       url('../font/garradin.svg?36341436#garradin') format('svg');
  src: url('../font/garradin.eot?31180986');
  src: url('../font/garradin.eot?31180986#iefix') format('embedded-opentype'),
       url('../font/garradin.woff2?31180986') format('woff2'),
       url('../font/garradin.woff?31180986') format('woff'),
       url('../font/garradin.ttf?31180986') format('truetype'),
       url('../font/garradin.svg?31180986#garradin') format('svg');
  font-weight: normal;
  font-style: normal;
}
/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
/*
@media screen and (-webkit-min-device-pixel-ratio:0) {
  @font-face {
    font-family: 'garradin';
    src: url('../font/garradin.svg?36341436#garradin') format('svg');
    src: url('../font/garradin.svg?31180986#garradin') format('svg');
  }
}
*/
 
 [class^="icn-"]:before, [class*=" icn-"]:before {
  font-family: "garradin";
  font-style: normal;
  font-weight: normal;
  speak: none;
 
  display: inline-block;
  text-decoration: inherit;
  width: 1em;
  margin-right: .2em;
  text-align: center;
  /* opacity: .8; */
 
  /* For safety - reset parent styles, that can break glyph codes*/
  font-variant: normal;
  text-transform: none;
}
 
  /* fix buttons height, for twitter bootstrap */
  line-height: 1em;
 
  /* Animation center compensation - margins should be symmetric */
  /* remove if not needed */
  margin-left: .2em;
.icn-search:before { content: '🔍'; } /* '\1f50d' */
.icn-user:before { content: '👤'; } /* '\1f464' */
.icn-users:before { content: '👪'; } /* '\1f46a' */
.icn-delete:before { content: '\2718'; } /* '✘' */
.icn-plus:before { content: '\2795'; } /* '' */
.icn-minus:before { content: '\2796'; } /* '' */
.icn-help:before { content: '\2753'; } /* '' */
 
  /* you can be more comfortable with increased icons size */
  /* font-size: 120%; */
 
  /* Font smoothing. That was taken from TWBS */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
 
  /* Uncomment for 3D effect */
  /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
}
 
.icn-up:before { content: '\2191'; } /* '' */
.icn-down:before { content: '\2193'; } /* '' */
.icn-download:before { content: '\21d3'; } /* '' */
.icn-home:before { content: '\2302'; } /* '⌂' */
.icn-attach:before { content: '📎'; } /* '\1f4ce' */
.icn-lock:before { content: '🔒'; } /* '\1f512' */
.icn-print:before { content: '\2399'; } /* '' */
.icn-check:before { content: '\2611'; } /* '' */
.icn-settings:before { content: '\2638'; } /* '☸' */
.icn-alert:before { content: '\26a0'; } /* '⚠' */
.icn-mail:before { content: '\2709'; } /* '✉' */
.icn-download:before { content: '\21d3'; } /* '⇓' */
.icn-edit:before { content: '\270e'; } /* '✎' */
.icn-delete:before { content: '\2718'; } /* '✘' */
.icn-print:before { content: '\2399'; } /* '' */
.icn-alert:before { content: '\26a0'; } /* '' */
.icn-help:before { content: '\2753'; } /* '' */
.icn-plus:before { content: '\2795'; } /* '➕' */
.icn-minus:before { content: '\2796'; } /* '➖' */
.icn-logout:before { content: '\291d'; } /* '' */
.icn-eye-off:before { content: '\292b'; } /* '⤫' */
.icn-menu:before { content: '𝍢'; } /* '\1d362' */
.icn-settings:before { content: '\2638'; } /* '' */
.icn-down:before { content: '\2193'; } /* '' */
.icn-up:before { content: '\2191'; } /* '' */
.icn-logout:before { content: '\291d'; } /* '' */
.icn-check:before { content: '\2611'; } /* '' */
.icn-eye:before { content: '👁'; } /* '\1f441' */
.icn-user:before { content: '👤'; } /* '\1f464' */
.icn-users:before { content: '👪'; } /* '\1f46a' */
.icn-attach:before { content: '📎'; } /* '\1f4ce' */
.icn-search:before { content: '🔍'; } /* '\1f50d' */
.icn-lock:before { content: '🔒'; } /* '\1f512' */
.icn-unlock:before { content: '🔓'; } /* '\1f513' */

Modified src/www/admin/static/font/garradin.eot from [7ef839bdc8] to [4de801b16e].

cannot compute difference between binary files

Modified src/www/admin/static/font/garradin.svg from [49157899c4] to [5bcfb6f494].

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
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



-
+




+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+



<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Copyright (C) 2014 by original authors @ fontello.com</metadata>
<metadata>Copyright (C) 2018 by original authors @ fontello.com</metadata>
<defs>
<font id="garradin" horiz-adv-x="1000" >
<font-face font-family="garradin" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
<missing-glyph horiz-adv-x="1000" />
<glyph glyph-name="up" unicode="&#x2191;" d="M571 171q0-14-10-25t-25-10h-500q-15 0-25 10t-11 25 11 26l250 250q10 10 25 10t25-10l250-250q10-11 10-26z" horiz-adv-x="571.4" />
<glyph glyph-name="search" unicode="&#x1f50d;" d="m643 386q0 103-74 176t-176 74-177-74-73-176 73-177 177-73 176 73 74 177z m286-465q0-29-22-50t-50-21q-30 0-50 21l-191 191q-100-69-223-69-80 0-153 31t-125 84-84 125-31 153 31 152 84 126 125 84 153 31 152-31 126-84 84-126 31-152q0-123-69-223l191-191q21-21 21-51z" horiz-adv-x="928.6" />
<glyph glyph-name="check" unicode="&#x2611;" d="m786 331v-177q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h464q35 0 65-14 9-4 10-13 2-10-5-16l-27-28q-6-5-13-5-2 0-5 1-13 3-25 3h-464q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v141q0 8 5 13l36 35q6 6 13 6 3 0 7-2 11-4 11-16z m129 273l-455-454q-13-14-31-14t-32 14l-240 240q-14 13-14 31t14 32l61 62q14 13 32 13t32-13l147-147 361 361q13 13 31 13t32-13l62-61q13-14 13-32t-13-32z" horiz-adv-x="928.6" />
<glyph glyph-name="user" unicode="&#x1f464;" d="m786 66q0-67-41-106t-108-39h-488q-67 0-108 39t-41 106q0 30 2 58t8 61 15 60 24 55 34 45 48 30 62 11q5 0 24-12t41-27 60-27 75-12 74 12 61 27 41 27 24 12q34 0 62-11t48-30 34-45 24-55 15-60 8-61 2-58z m-179 498q0-88-63-151t-151-63-152 63-62 151 62 152 152 63 151-63 63-152z" horiz-adv-x="785.7" />
<glyph glyph-name="users" unicode="&#x1f46a;" d="m331 350q-90-3-148-71h-75q-45 0-77 22t-31 66q0 197 69 197 4 0 25-11t54-24 66-12q38 0 75 13-3-21-3-37 0-78 45-143z m598-356q0-66-41-105t-108-39h-488q-68 0-108 39t-41 105q0 30 2 58t8 61 14 61 24 54 35 45 48 30 62 11q6 0 24-12t41-26 59-27 76-12 75 12 60 27 41 26 23 12q35 0 63-11t47-30 35-45 24-54 15-61 8-61 2-58z m-572 713q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z m393-214q0-89-63-152t-151-62-152 62-63 152 63 151 152 63 151-63 63-151z m321-126q0-43-31-66t-77-22h-75q-57 68-147 71 45 65 45 143 0 16-3 37 37-13 74-13 33 0 67 12t54 24 24 11q69 0 69-197z m-71 340q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z" horiz-adv-x="1071.4" />
<glyph glyph-name="delete" unicode="&#x2718;" d="m724 112q0-22-15-38l-76-76q-16-15-38-15t-38 15l-164 165-164-165q-16-15-38-15t-38 15l-76 76q-16 16-16 38t16 38l164 164-164 164q-16 16-16 38t16 38l76 76q16 16 38 16t38-16l164-164 164 164q16 16 38 16t38-16l76-76q15-15 15-38t-15-38l-164-164 164-164q15-15 15-38z" horiz-adv-x="785.7" />
<glyph glyph-name="plus" unicode="&#x2795;" d="m786 439v-107q0-22-16-38t-38-15h-232v-233q0-22-16-37t-38-16h-107q-22 0-38 16t-15 37v233h-232q-23 0-38 15t-16 38v107q0 23 16 38t38 16h232v232q0 22 15 38t38 16h107q23 0 38-16t16-38v-232h232q22 0 38-16t16-38z" horiz-adv-x="785.7" />

<glyph glyph-name="down" unicode="&#x2193;" d="M571 457q0-14-10-25l-250-250q-11-11-25-11t-25 11l-250 250q-11 11-11 25t11 25 25 11h500q14 0 25-11t10-25z" horiz-adv-x="571.4" />

<glyph glyph-name="download" unicode="&#x21d3;" d="M714 100q0 15-10 25t-25 11-26-11-10-25 10-25 26-11 25 11 10 25z m143 0q0 15-10 25t-26 11-25-11-10-25 10-25 25-11 26 11 10 25z m72 125v-179q0-22-16-37t-38-16h-821q-23 0-38 16t-16 37v179q0 22 16 38t38 16h259l75-76q33-32 76-32t76 32l76 76h259q22 0 38-16t16-38z m-182 318q10-23-8-40l-250-250q-10-10-25-10t-25 10l-250 250q-17 17-8 40 10 21 33 21h143v250q0 15 11 25t25 11h143q14 0 25-11t10-25v-250h143q24 0 33-21z" horiz-adv-x="928.6" />

<glyph glyph-name="home" unicode="&#x2302;" d="M786 296v-267q0-15-11-26t-25-10h-214v214h-143v-214h-214q-15 0-25 10t-11 26v267q0 1 0 2t0 2l321 264 321-264q1-1 1-4z m124 39l-34-41q-5-5-12-6h-2q-7 0-12 3l-386 322-386-322q-7-4-13-4-7 2-12 7l-35 41q-4 5-3 13t6 12l401 334q18 15 42 15t43-15l136-114v109q0 8 5 13t13 5h107q8 0 13-5t5-13v-227l122-102q5-5 6-12t-4-13z" horiz-adv-x="928.6" />

<glyph glyph-name="print" unicode="&#x2399;" d="M214-7h500v143h-500v-143z m0 357h500v214h-89q-22 0-38 16t-16 38v89h-357v-357z m643-36q0 15-10 25t-26 11-25-11-10-25 10-25 25-10 26 10 10 25z m72 0v-232q0-7-6-12t-12-6h-125v-89q0-22-16-38t-38-16h-536q-22 0-37 16t-16 38v89h-125q-7 0-13 6t-5 12v232q0 44 32 76t75 31h36v304q0 22 16 38t37 16h375q23 0 50-12t42-26l85-85q15-16 27-43t11-49v-143h35q45 0 76-31t32-76z" horiz-adv-x="928.6" />

<glyph glyph-name="check" unicode="&#x2611;" d="M786 331v-177q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h464q35 0 65-14 9-4 10-13 2-10-5-16l-27-28q-6-5-13-5-2 0-5 1-13 3-25 3h-464q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v141q0 8 5 13l36 35q6 6 13 6 3 0 7-2 11-4 11-16z m129 273l-455-454q-13-14-31-14t-32 14l-240 240q-14 13-14 31t14 32l61 62q14 13 32 13t32-13l147-147 361 361q13 13 31 13t32-13l62-61q13-14 13-32t-13-32z" horiz-adv-x="928.6" />

<glyph glyph-name="settings" unicode="&#x2638;" d="M571 350q0 59-41 101t-101 42-101-42-42-101 42-101 101-42 101 42 41 101z m286 61v-124q0-7-4-13t-11-7l-104-16q-10-30-21-51 19-27 59-77 6-6 6-13t-5-13q-15-21-55-61t-53-39q-7 0-14 5l-77 60q-25-13-51-21-9-76-16-104-4-16-20-16h-124q-8 0-14 5t-6 12l-16 103q-27 9-50 21l-79-60q-6-5-14-5-8 0-14 6-70 64-92 94-4 5-4 13 0 6 5 12 8 12 28 37t30 40q-15 28-23 55l-102 15q-7 1-11 7t-5 13v124q0 7 5 13t10 7l104 16q8 25 22 51-23 32-60 77-6 7-6 14 0 5 5 12 15 20 55 60t53 40q7 0 15-5l77-60q24 13 50 21 9 76 17 104 3 15 20 15h124q7 0 13-4t7-12l15-103q28-9 50-21l80 60q5 5 13 5 7 0 14-5 72-67 92-95 4-5 4-13 0-6-4-12-9-12-29-38t-30-39q14-28 23-55l102-15q7-1 12-7t4-13z" horiz-adv-x="857.1" />

<glyph glyph-name="alert" unicode="&#x26a0;" d="M571 83v106q0 8-5 13t-12 5h-108q-7 0-12-5t-5-13v-106q0-8 5-13t12-6h108q7 0 12 6t5 13z m-1 208l10 257q0 6-5 10-7 6-14 6h-122q-7 0-14-6-5-4-5-12l9-255q0-5 6-9t13-3h103q8 0 13 3t6 9z m-7 522l428-786q20-35-1-70-10-17-26-26t-35-10h-858q-18 0-35 10t-26 26q-21 35-1 70l429 786q9 17 26 27t36 10 36-10 27-27z" horiz-adv-x="1000" />

<glyph glyph-name="mail" unicode="&#x2709;" d="M1000 454v-443q0-37-26-63t-63-27h-822q-36 0-63 27t-26 63v443q25-28 56-49 202-137 278-192 32-24 51-37t53-27 61-13h2q28 0 61 13t53 27 51 37q95 68 278 192 32 22 56 49z m0 164q0-44-27-84t-68-69q-210-146-262-181-5-4-23-17t-30-22-29-18-33-15-27-5h-2q-12 0-27 5t-33 15-29 18-30 22-23 17q-51 35-147 101t-114 80q-35 23-65 64t-31 77q0 43 23 72t66 29h822q36 0 62-26t27-63z" horiz-adv-x="1000" />

<glyph glyph-name="edit" unicode="&#x270e;" d="M203-7l50 51-131 131-51-51v-60h72v-71h60z m291 518q0 12-12 12-5 0-9-4l-303-302q-4-4-4-10 0-12 13-12 5 0 9 4l303 302q3 4 3 10z m-30 107l232-232-464-465h-232v233z m381-54q0-29-20-50l-93-93-232 233 93 92q20 21 50 21 29 0 51-21l131-131q20-22 20-51z" horiz-adv-x="857.1" />

<glyph glyph-name="delete" unicode="&#x2718;" d="M724 112q0-22-15-38l-76-76q-16-15-38-15t-38 15l-164 165-164-165q-16-15-38-15t-38 15l-76 76q-16 16-16 38t16 38l164 164-164 164q-16 16-16 38t16 38l76 76q16 16 38 16t38-16l164-164 164 164q16 16 38 16t38-16l76-76q15-15 15-38t-15-38l-164-164 164-164q15-15 15-38z" horiz-adv-x="785.7" />

<glyph glyph-name="minus" unicode="&#x2796;" d="m786 439v-107q0-22-16-38t-38-15h-678q-23 0-38 15t-16 38v107q0 23 16 38t38 16h678q22 0 38-16t16-38z" horiz-adv-x="785.7" />
<glyph glyph-name="help" unicode="&#x2753;" d="m393 149v-134q0-9-7-16t-15-6h-134q-9 0-16 6t-7 16v134q0 9 7 16t16 6h134q8 0 15-6t7-16z m176 335q0-30-8-56t-20-43-31-33-32-25-34-19q-23-13-38-37t-15-37q0-10-7-18t-16-9h-134q-8 0-14 10t-6 21v26q0 46 37 87t79 60q33 15 47 32t14 42q0 23-26 41t-60 18q-36 0-60-16-20-14-60-64-7-9-17-9-7 0-14 4l-91 70q-8 6-9 14t3 16q89 148 259 148 45 0 90-17t81-46 59-72 23-88z" horiz-adv-x="571.4" />
<glyph glyph-name="home" unicode="&#x2302;" d="m786 296v-267q0-15-11-26t-25-10h-214v214h-143v-214h-214q-15 0-25 10t-11 26v267q0 1 0 2t0 2l321 264 321-264q1-1 1-4z m124 39l-34-41q-5-5-12-6h-2q-7 0-12 3l-386 322-386-322q-7-4-13-4-7 2-12 7l-35 41q-4 5-3 13t6 12l401 334q18 15 42 15t43-15l136-114v109q0 8 5 13t13 5h107q8 0 13-5t5-13v-227l122-102q5-5 6-12t-4-13z" horiz-adv-x="928.6" />
<glyph glyph-name="attach" unicode="&#x1f4ce;" d="m783 77q0-65-44-109t-109-44q-75 0-131 55l-434 434q-63 64-63 151 0 88 62 150t150 62q88 0 152-63l338-338q5-5 5-12 0-9-17-26t-26-17q-7 0-13 5l-338 339q-44 43-101 43-59 0-100-42t-40-101q0-58 42-101l433-433q35-35 81-35 36 0 59 23t24 59q0 46-36 81l-324 324q-14 14-33 14-16 0-27-11t-11-27q0-18 14-33l229-228q6-6 6-13 0-9-18-26t-26-17q-7 0-12 5l-229 229q-35 34-35 83 0 46 32 78t77 32q49 0 83-36l325-324q55-54 55-131z" horiz-adv-x="785.7" />
<glyph glyph-name="lock" unicode="&#x1f512;" d="m179 421h285v108q0 59-42 101t-101 41-101-41-41-101v-108z m464-53v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v108q0 102 74 176t176 74 177-74 73-176v-108h18q23 0 38-15t16-38z" horiz-adv-x="642.9" />
<glyph glyph-name="mail" unicode="&#x2709;" d="m1000 454v-443q0-37-26-63t-63-27h-822q-36 0-63 27t-26 63v443q25-28 56-49 202-137 278-192 32-24 51-37t53-27 61-13h2q28 0 61 13t53 27 51 37q95 68 278 192 32 22 56 49z m0 164q0-44-27-84t-68-69q-210-146-262-181-5-4-23-17t-30-22-29-18-33-15-27-5h-2q-12 0-27 5t-33 15-29 18-30 22-23 17q-51 35-147 101t-114 80q-35 23-65 64t-31 77q0 43 23 72t66 29h822q36 0 62-26t27-63z" horiz-adv-x="1000" />
<glyph glyph-name="download" unicode="&#x21d3;" d="m714 100q0 15-10 25t-25 11-26-11-10-25 10-25 26-11 25 11 10 25z m143 0q0 15-10 25t-26 11-25-11-10-25 10-25 25-11 26 11 10 25z m72 125v-179q0-22-16-37t-38-16h-821q-23 0-38 16t-16 37v179q0 22 16 38t38 16h259l75-76q33-32 76-32t76 32l76 76h259q22 0 38-16t16-38z m-182 318q10-23-8-40l-250-250q-10-10-25-10t-25 10l-250 250q-17 17-8 40 10 21 33 21h143v250q0 15 11 25t25 11h143q14 0 25-11t10-25v-250h143q24 0 33-21z" horiz-adv-x="928.6" />
<glyph glyph-name="edit" unicode="&#x270e;" d="m203-7l50 51-131 131-51-51v-60h72v-71h60z m291 518q0 12-12 12-5 0-9-4l-303-302q-4-4-4-10 0-12 13-12 5 0 9 4l303 302q3 4 3 10z m-30 107l232-232-464-465h-232v233z m381-54q0-29-20-50l-93-93-232 233 93 92q20 21 50 21 29 0 51-21l131-131q20-22 20-51z" horiz-adv-x="857.1" />
<glyph glyph-name="print" unicode="&#x2399;" d="m214-7h500v143h-500v-143z m0 357h500v214h-89q-22 0-38 16t-16 38v89h-357v-357z m643-36q0 15-10 25t-26 11-25-11-10-25 10-25 25-10 26 10 10 25z m72 0v-232q0-7-6-12t-12-6h-125v-89q0-22-16-38t-38-16h-536q-22 0-37 16t-16 38v89h-125q-7 0-13 6t-5 12v232q0 44 32 76t75 31h36v304q0 22 16 38t37 16h375q23 0 50-12t42-26l85-85q15-16 27-43t11-49v-143h35q45 0 76-31t32-76z" horiz-adv-x="928.6" />
<glyph glyph-name="alert" unicode="&#x26a0;" d="m571 83v106q0 8-5 13t-12 5h-108q-7 0-12-5t-5-13v-106q0-8 5-13t12-6h108q7 0 12 6t5 13z m-1 208l10 257q0 6-5 10-7 6-14 6h-122q-7 0-14-6-5-4-5-12l9-255q0-5 6-9t13-3h103q8 0 13 3t6 9z m-7 522l428-786q20-35-1-70-10-17-26-26t-35-10h-858q-18 0-35 10t-26 26q-21 35-1 70l429 786q9 17 26 27t36 10 36-10 27-27z" horiz-adv-x="1000" />
<glyph glyph-name="menu" unicode="&#x1d362;" d="m857 100v-71q0-15-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 25t25 11h785q15 0 26-11t10-25z m0 286v-72q0-14-10-25t-26-10h-785q-15 0-25 10t-11 25v72q0 14 11 25t25 10h785q15 0 26-10t10-25z m0 285v-71q0-15-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 26t25 10h785q15 0 26-10t10-26z" horiz-adv-x="857.1" />
<glyph glyph-name="settings" unicode="&#x2638;" d="m571 350q0 59-41 101t-101 42-101-42-42-101 42-101 101-42 101 42 41 101z m286 61v-124q0-7-4-13t-11-7l-104-16q-10-30-21-51 19-27 59-77 6-6 6-13t-5-13q-15-21-55-61t-53-39q-7 0-14 5l-77 60q-25-13-51-21-9-76-16-104-4-16-20-16h-124q-8 0-14 5t-6 12l-16 103q-27 9-50 21l-79-60q-6-5-14-5-8 0-14 6-70 64-92 94-4 5-4 13 0 6 5 12 8 12 28 37t30 40q-15 28-23 55l-102 15q-7 1-11 7t-5 13v124q0 7 5 13t10 7l104 16q8 25 22 51-23 32-60 77-6 7-6 14 0 5 5 12 15 20 55 60t53 40q7 0 15-5l77-60q24 13 50 21 9 76 17 104 3 15 20 15h124q7 0 13-4t7-12l15-103q28-9 50-21l80 60q5 5 13 5 7 0 14-5 72-67 92-95 4-5 4-13 0-6-4-12-9-12-29-38t-30-39q14-28 23-55l102-15q7-1 12-7t4-13z" horiz-adv-x="857.1" />
<glyph glyph-name="down" unicode="&#x2193;" d="m571 457q0-14-10-25l-250-250q-11-11-25-11t-25 11l-250 250q-11 11-11 25t11 25 25 11h500q14 0 25-11t10-25z" horiz-adv-x="571.4" />
<glyph glyph-name="up" unicode="&#x2191;" d="m571 171q0-14-10-25t-25-10h-500q-15 0-25 10t-11 25 11 26l250 250q10 10 25 10t25-10l250-250q10-11 10-26z" horiz-adv-x="571.4" />
<glyph glyph-name="help" unicode="&#x2753;" d="M393 149v-134q0-9-7-16t-15-6h-134q-9 0-16 6t-7 16v134q0 9 7 16t16 6h134q8 0 15-6t7-16z m176 335q0-30-8-56t-20-43-31-33-32-25-34-19q-23-13-38-37t-15-37q0-10-7-18t-16-9h-134q-8 0-14 10t-6 21v26q0 46 37 87t79 60q33 15 47 32t14 42q0 23-26 41t-60 18q-36 0-60-16-20-14-60-64-7-9-17-9-7 0-14 4l-91 70q-8 6-9 14t3 16q89 148 259 148 45 0 90-17t81-46 59-72 23-88z" horiz-adv-x="571.4" />

<glyph glyph-name="plus" unicode="&#x2795;" d="M786 439v-107q0-22-16-38t-38-15h-232v-233q0-22-16-37t-38-16h-107q-22 0-38 16t-15 37v233h-232q-23 0-38 15t-16 38v107q0 23 16 38t38 16h232v232q0 22 15 38t38 16h107q23 0 38-16t16-38v-232h232q22 0 38-16t16-38z" horiz-adv-x="785.7" />

<glyph glyph-name="minus" unicode="&#x2796;" d="M786 439v-107q0-22-16-38t-38-15h-678q-23 0-38 15t-16 38v107q0 23 16 38t38 16h678q22 0 38-16t16-38z" horiz-adv-x="785.7" />

<glyph glyph-name="logout" unicode="&#x291d;" d="M857 350q0-87-34-166t-91-137-137-92-166-34-167 34-136 92-92 137-34 166q0 102 45 191t126 151q24 18 54 14t46-28q18-23 14-53t-28-47q-54-41-84-101t-30-127q0-58 22-111t62-91 91-61 111-23 110 23 92 61 61 91 22 111q0 68-30 127t-84 101q-24 18-28 47t14 53q17 24 47 28t53-14q81-61 126-151t45-191z m-357 429v-358q0-29-21-50t-50-21-51 21-21 50v358q0 29 21 50t51 21 50-21 21-50z" horiz-adv-x="857.1" />

<glyph glyph-name="eye-off" unicode="&#x292b;" d="M0 326q6 49 64 110 79 80 176 128 129 61 260 61 29 0 59-2l74 129q10 16 23 18 4 0 8-2l51-32q17-7 2-33l-57-101-47-79-41-72-144-250-41-72-47-80-57-100q-15-25-31-15l-53 31q-15 8 0 33l49 86-8 4q-103 53-176 129-64 72-64 109z m264 0q0-74 47-133l48 84q-9 24-9 49 0 51 34 91t85 50l49 84-18 0q-98 0-167-66t-69-159z m177-295l41 71 18 0q98 0 167 65t69 159q0 74-47 133l63 109q2-2 4-3t4-1q103-52 176-128 64-73 64-110-6-49-64-109-79-80-176-129-129-61-260-61-25 0-59 4z m90 155l110 189q9-25 9-49 0-51-34-90t-85-50z" horiz-adv-x="1000" />

<glyph glyph-name="menu" unicode="&#x1d362;" d="M857 100v-71q0-15-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 25t25 11h785q15 0 26-11t10-25z m0 286v-72q0-14-10-25t-26-10h-785q-15 0-25 10t-11 25v72q0 14 11 25t25 10h785q15 0 26-10t10-25z m0 285v-71q0-15-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 26t25 10h785q15 0 26-10t10-26z" horiz-adv-x="857.1" />

<glyph glyph-name="eye" unicode="&#x1f441;" d="M0 350q6 49 64 110 79 80 176 129 129 60 260 60 137-2 260-60 103-53 176-129 64-73 64-110-6-49-64-109-79-80-176-129-129-61-260-61-137 2-260 61-103 53-176 129-64 72-64 109z m264 0q0-94 69-159t167-65 167 65 69 159-69 159-167 66-167-66-69-159z m86 1q0 60 44 102t106 42 106-42 44-102-44-102-106-43-106 43-44 102z" horiz-adv-x="1000" />

<glyph glyph-name="user" unicode="&#x1f464;" d="M786 66q0-67-41-106t-108-39h-488q-67 0-108 39t-41 106q0 30 2 58t8 61 15 60 24 55 34 45 48 30 62 11q5 0 24-12t41-27 60-27 75-12 74 12 61 27 41 27 24 12q34 0 62-11t48-30 34-45 24-55 15-60 8-61 2-58z m-179 498q0-88-63-151t-151-63-152 63-62 151 62 152 152 63 151-63 63-152z" horiz-adv-x="785.7" />

<glyph glyph-name="users" unicode="&#x1f46a;" d="M331 350q-90-3-148-71h-75q-45 0-77 22t-31 66q0 197 69 197 4 0 25-11t54-24 66-12q38 0 75 13-3-21-3-37 0-78 45-143z m598-356q0-66-41-105t-108-39h-488q-68 0-108 39t-41 105q0 30 2 58t8 61 14 61 24 54 35 45 48 30 62 11q6 0 24-12t41-26 59-27 76-12 75 12 60 27 41 26 23 12q35 0 63-11t47-30 35-45 24-54 15-61 8-61 2-58z m-572 713q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z m393-214q0-89-63-152t-151-62-152 62-63 152 63 151 152 63 151-63 63-151z m321-126q0-43-31-66t-77-22h-75q-57 68-147 71 45 65 45 143 0 16-3 37 37-13 74-13 33 0 67 12t54 24 24 11q69 0 69-197z m-71 340q0-59-42-101t-101-42-101 42-42 101 42 101 101 42 101-42 42-101z" horiz-adv-x="1071.4" />

<glyph glyph-name="attach" unicode="&#x1f4ce;" d="M783 77q0-65-44-109t-109-44q-75 0-131 55l-434 434q-63 64-63 151 0 88 62 150t150 62q88 0 152-63l338-338q5-5 5-12 0-9-17-26t-26-17q-7 0-13 5l-338 339q-44 43-101 43-59 0-100-42t-40-101q0-58 42-101l433-433q35-35 81-35 36 0 59 23t24 59q0 46-36 81l-324 324q-14 14-33 14-16 0-27-11t-11-27q0-18 14-33l229-228q6-6 6-13 0-9-18-26t-26-17q-7 0-12 5l-229 229q-35 34-35 83 0 46 32 78t77 32q49 0 83-36l325-324q55-54 55-131z" horiz-adv-x="785.7" />

<glyph glyph-name="search" unicode="&#x1f50d;" d="M643 386q0 103-74 176t-176 74-177-74-73-176 73-177 177-73 176 73 74 177z m286-465q0-29-22-50t-50-21q-30 0-50 21l-191 191q-100-69-223-69-80 0-153 31t-125 84-84 125-31 153 31 152 84 126 125 84 153 31 152-31 126-84 84-126 31-152q0-123-69-223l191-191q21-21 21-51z" horiz-adv-x="928.6" />

<glyph glyph-name="lock" unicode="&#x1f512;" d="M179 421h285v108q0 59-42 101t-101 41-101-41-41-101v-108z m464-53v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h17v108q0 102 74 176t176 74 177-74 73-176v-108h18q23 0 38-15t16-38z" horiz-adv-x="642.9" />

<glyph glyph-name="logout" unicode="&#x291d;" d="m857 350q0-87-34-166t-91-137-137-92-166-34-167 34-136 92-92 137-34 166q0 102 45 191t126 151q24 18 54 14t46-28q18-23 14-53t-28-47q-54-41-84-101t-30-127q0-58 22-111t62-91 91-61 111-23 110 23 92 61 61 91 22 111q0 68-30 127t-84 101q-24 18-28 47t14 53q17 24 47 28t53-14q81-61 126-151t45-191z m-357 429v-358q0-29-21-50t-50-21-51 21-21 50v358q0 29 21 50t51 21 50-21 21-50z" horiz-adv-x="857.1" />
<glyph glyph-name="unlock" unicode="&#x1f513;" d="m929 529v-143q0-15-11-25t-25-11h-36q-14 0-25 11t-11 25v143q0 59-41 101t-101 41-101-41-42-101v-108h53q23 0 38-15t16-38v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h375v108q0 103 73 176t177 74 176-74 74-176z" horiz-adv-x="928.6" />
<glyph glyph-name="unlock" unicode="&#x1f513;" d="M929 529v-143q0-15-11-25t-25-11h-36q-14 0-25 11t-11 25v143q0 59-41 101t-101 41-101-41-42-101v-108h53q23 0 38-15t16-38v-322q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v322q0 22 16 38t38 15h375v108q0 103 73 176t177 74 176-74 74-176z" horiz-adv-x="928.6" />
</font>
</defs>
</svg>

Modified src/www/admin/static/font/garradin.ttf from [4452fdd794] to [2aa8a2bf32].

cannot compute difference between binary files

Modified src/www/admin/static/font/garradin.woff from [c939b61e60] to [ccddd0b63a].

cannot compute difference between binary files

Modified src/www/admin/static/scripts/global.js from [bab9903c66] to [7039561d92].

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
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







-
+

+

-
+


-
-
-
-
+

-
-
+
+
+





+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







	};

	// From KD2fw/js/xhr.js
	g.load = function(b,d,f,e){var a=new XMLHttpRequest();if(!a||!b)return false;if(a.overrideMimeType)a.overrideMimeType('text/xml');b+=(b.indexOf('?')+1?'&':'?')+(+(new Date));a.onreadystatechange=function(){if(a.readyState!=4)return;if((s=a.status)==200){if(!d)return true;var c=a.responseText;if(f=='json'){return((j=window.JSON)&&j.parse)?j.parse(c):eval('('+c.replace(/[\n\r]/g,'')+')')}d(c)}else if(e){e(s)}};a.open('GET',b,true);a.send(null)};

	g.checkUncheck = function()
	{
		var elements = this.form.getElementsByTagName('input');
		var elements = this.form.querySelectorAll('input[type=checkbox]');
		var el_length = elements.length;
		var checked = this.checked;

		for (i = 0; i < el_length; i++)
		for (var i = 0; i < el_length; i++)
		{
			var elm = elements[i];

			if (elm.type == 'checkbox' && elm.name)
			{
				elm.checked = this.checked;
			elm.checked = checked;

				if (elm.onchange)
					elm.onchange({target: elm});
			if (elm.onchange && elm.name)
			{
				elm.onchange({target: elm});
			}
		}

		return true;
	};

	g.enhancePasswordField = function (field, repeat_field = null)
	{
		var show_password = document.createElement('input');
		show_password.type = 'button';
		show_password.className = 'icn action showPassword';
		show_password.title = 'Voir/cacher le mot de passe';
		show_password.value = '👁';
		show_password.onclick = function (e) {
			var pos = field.selectionStart;
			var hidden = field.type.match(/pass/i);
			field.type = hidden ? 'text' : 'password';
			this.value = !hidden ? '👁' : '⤫';
			field.classList.toggle('clearTextPassword');

			if (null !== repeat_field)
			{
				repeat_field.type = field.type;
				repeat_field.classList.toggle('clearTextPassword');
			}

			// Remettre le focus sur le champ mot de passe
			// on ne peut pas vraiment remettre le focus sur le champ
			// précis qui était utilisé avant de cliquer sur le bouton 
			// car il faudrait enregistrer les actions onfocus de tous
			// les champs de la page
			field.focus();
			field.selectionStart = field.selectionEnd = pos;
		};

		field.parentNode.insertBefore(show_password, field.nextSibling);
	};

	var dateInputFallback = function ()
	{
		/*
		// Firefox dit implémenter date, mais ne l'implémente pas, aucun moyen de détecter ce cas
		// donc on force l'utilisation du custom datepicker de Garradin…
		var input = document.createElement('input');
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
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







-
+

+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
-
-
-
+
+
+
+

-
+

-
-
-
-
+
+
+
+

-
-
+
+

-
-
-
-
+
+
+
+
+

-
+

-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+

-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-

		{
			document.body.removeChild(input);
		}*/
	};

	g.onload(dateInputFallback);

	if (document.querySelectorAll)
	if (!document.querySelectorAll)
	{
		return;
	}

		g.onload(function () {
			var checkTables = document.querySelectorAll('table thead input[type=checkbox]');
			var l = checkTables.length;
	g.onload(function () {
		var tableActions = document.querySelectorAll('form table tfoot .actions select');

		for (var i = 0; i < tableActions.length; i++)
		{
			tableActions[i].onchange = function () {
				if (!this.form.querySelector('table tbody input[type=checkbox]:checked'))
				{
					return !window.alert("Aucune ligne sélectionnée !");
				}

				this.form.submit();
			};
		}

		// Ajouter action check/uncheck sur les checkbox de raccourci dans les tableaux
		var checkTables = document.querySelectorAll('table thead input[type=checkbox], table tfoot input[type=checkbox]');
		var l = checkTables.length;

			for (var i = 0; i < l; i++)
			{
				var masterCheck = checkTables[i];
				masterCheck.onchange = g.checkUncheck;
		for (var i = 0; i < l; i++)
		{
			var masterCheck = checkTables[i];
			masterCheck.onchange = g.checkUncheck;

				var parent = masterCheck.parentNode;
			var parent = masterCheck.parentNode;

				while (parent.nodeType != Node.ELEMENT_NODE || parent.tagName != 'TABLE')
				{
					parent = parent.parentNode;
				}
			while (parent.nodeType != Node.ELEMENT_NODE || parent.tagName != 'TABLE')
			{
				parent = parent.parentNode;
			}

				var checkBoxes = parent.querySelectorAll('tbody input[type=checkbox]');
				var ll = checkBoxes.length;
			var checkBoxes = parent.querySelectorAll('tbody tr input[type=checkbox]');
			var ll = checkBoxes.length;

				for (var j = 0; j < ll; j++)
				{
					checkBoxes[j].onchange = function (e) {
						var elm = e.target || this;
			for (var j = 0; j < ll; j++)
			{
				checkBoxes[j].onchange = function (e) {
					var elm = e.target || this;
					var checked = elm.checked ? true : false;

						var parent = elm.parentNode;
					var parent = elm.parentNode;

						while (parent.nodeType != Node.ELEMENT_NODE || parent.tagName != 'TR')
						{
							parent = parent.parentNode;
						}
						
						if (elm.checked)
							parent.className = parent.className.replace(/ checked$|$/, ' checked');
						else
							parent.className = parent.className.replace(/ checked/, '');
					};
					while (parent.nodeType != Node.ELEMENT_NODE || parent.tagName != 'TR')
					{
						parent = parent.parentNode;
					}
					
					if (checked)
						parent.className = parent.className.replace(/ checked$|$/, ' checked');
					else
						parent.className = parent.className.replace(/ checked/, '');
				};

					if (checkBoxes[j].checked)
					{
						checkBoxes[j].onchange({target: checkBoxes[j]});
					}
				}
			}
		});
				if (checkBoxes[j].checked)
				{
					checkBoxes[j].onchange({target: checkBoxes[j]});
				}
			}
		}
	});
	}

})();

Modified src/www/admin/static/scripts/password.js from [14f90c76a4] to [c16716738c].

1
2
3
4
5
6
7
8
9
10
11
12



13
14
15
16
17
18
19
1
2
3
4
5
6
7
8
9

10
11
12
13
14
15
16
17
18
19
20
21









-


+
+
+







(function () {
	var strength_elm, match_elm, pw_elm, pw2_elm, suggest_elm;

	RegExp.quote = function(str) {
	    return (str+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
	};

	window.initPasswordField = function(suggest, password, password2)
	{
		suggest_elm = (typeof suggest == 'string') ? document.getElementById(suggest) : suggest;
		pw_elm = (typeof password == 'string') ? document.getElementById(password) : password;
		pw2_elm = (typeof password2 == 'string') ? document.getElementById(password2) : password2;
		suggest_elm = (typeof suggest == 'string') ? document.getElementById(suggest) : suggest;

		g.enhancePasswordField(pw_elm, pw2_elm);

		suggest_elm.size = suggest_elm.value.length;

		suggest_elm.onclick = function () {
	        pw_elm.value = this.value;
	        pw2_elm.value = this.value;
	        this.select();

Added src/www/admin/static/scripts/query_builder.min.js version [0e11953bac].


1
+
!function(){var e=function(e){this.columns=e};(window.SQLQueryBuilder=e).prototype.loadDefaultOperators=function(){for(var e in this.operators={"= ?":this.__("is equal to"),"!= ?":this.__("is not equal to"),"IN (??)":this.__("is equal to one of"),"NOT IN (??)":this.__("is not equal to one of"),"> ?":this.__("is greater than"),">= ?":this.__("is greater than or equal to"),"< ?":this.__("is less than"),"<= ?":this.__("is less than or equal to"),"BETWEEN ? AND ?":this.__("is between"),"NOT BETWEEN ? AND ?":this.__("is not between"),"IS NULL":this.__("is null"),"IS NOT NULL":this.__("is not null"),"LIKE ?%":this.__("begins with"),"NOT LIKE ?%":this.__("doesn't begin with"),"LIKE %?":this.__("ends with"),"NOT LIKE %?":this.__("doesn't end with"),"LIKE %?%":this.__("contains"),"NOT LIKE %?%":this.__("doesn't contain"),"&":this.__("matches one of"),"= 1":this.__("is true"),"= 0":this.__("is false")},this.types_operators={integer:["= ?","!= ?","IN (??)","NOT IN (??)","> ?",">= ?","< ?","<= ?","BETWEEN ? AND ?","NOT BETWEEN ? AND ?"],enum:["= ?","!= ?","IN (??)","NOT IN (??)"],boolean:["= 1","= 0"],text:["= ?","!= ?","IN (??)","NOT IN (??)","LIKE ?%","NOT LIKE ?%","LIKE %?","NOT LIKE %?","LIKE %?%","NOT LIKE %?%"],bitwise:["&"]},this.types_operators){var t={};for(var i in this.types_operators[e]){var o=this.types_operators[e][i];t[o]=this.operators[o]}this.types_operators[e]=t}this.types_operators.date=JSON.parse(JSON.stringify(this.types_operators.integer)),delete this.types_operators.date["<= ?"],delete this.types_operators.date[">= ?"],this.types_operators.date["< ?"]=this.__("before"),this.types_operators.date["> ?"]=this.__("after"),this.types_operators.datetime=this.types_operators.date},e.prototype.__=function(e){return e},e.prototype.init=function(e){this.parent=e;var t={"":"---"};for(column in this.columns)t[column]=this.columns[column].label;this.columnSelect=this.buildSelect(t)},e.prototype.addGroup=function(t,e){var i=document.createElement("fieldset"),o=document.createElement("legend"),n=this.buildSelect({AND:this.__("Matches ALL of the following conditions:"),OR:this.__("Matches ANY of the following conditions:"),ADD:this.__("Add a new set of conditions below this one"),DEL:this.__("Remove this set of conditions")});n.onfocus=function(){this.oldValue=this.value},n.value=e;var r=this;n.onchange=function(){if("DEL"==this.value){if(1==t.childNodes.length)return void(this.value=this.oldValue);t.removeChild(i)}else if("ADD"==this.value){var e=r.addGroup(t,"AND");r.addRow(e),this.value=this.oldValue}},o.appendChild(n),i.appendChild(o);var s=document.createElement("table");return i.appendChild(s),t.appendChild(i),i},e.prototype.addRow=function(e,t){var i=e.getElementsByTagName("table")[0],o=document.createElement("tr");(s=document.createElement("td")).className="buttons";var n,r=this;(n=this.buildInput("button","+")).onclick=function(){r.addRow(function(e,t){for(;(e=e.parentElement)&&!(e.matches||e.matchesSelector).call(e,t););return e}(this,"fieldset"),this.parentNode.parentNode)},s.appendChild(n),(n=this.buildInput("button","-")).onclick=function(){r.deleteRow(this.parentNode.parentNode)},s.appendChild(n),o.appendChild(s),(s=document.createElement("td")).className="column";var s,a=this.columnSelect.cloneNode(!0);return a.onchange=function(){return r.switchColumn(this)},s.appendChild(a),o.appendChild(s),(s=document.createElement("td")).className="operator",o.appendChild(s),(s=document.createElement("td")).className="values",o.appendChild(s),void 0===t?i.appendChild(o):i.insertBefore(o,t.nextSibling),o},e.prototype.deleteRow=function(e){e.parentNode.childNodes.length<=1||e.parentNode.removeChild(e)},e.prototype.switchColumn=function(e){var t=e.parentNode.parentNode;t.childNodes[2].innerHTML="",t.childNodes[3].innerHTML="",this.addOperator(t,this.columns[e.value])},e.prototype.addOperator=function(e,t){var i=this.types_operators[t.type],o={"":"---"};for(var n in i)o[n]=i[n];var r=this.buildSelect(o),s=this;return r.onchange=function(){return s.switchOperator(this)},e.childNodes[2].appendChild(r),r},e.prototype.switchOperator=function(e,t){var i=e.parentNode.parentNode;i.childNodes[3].innerHTML="";var o=i.childNodes[3],n=i.childNodes[1].firstChild,r=e.value,s=this.columns[n.value];if(r){var a=1,l=!1,d=null,h=r.match(/\?/g);if(h){r.match(/\?\?/)?(a=t?t.length:3,l=!0):1<h.length&&(a=h.length);for(var p=0;p<a;p++)d=this.addMatchField(o,d,s,r),t&&(d.value=t[p]);if(l){(u=this.buildInput("button","-")).onclick=function(){this.parentNode.childNodes.length<=3||(this.parentNode.removeChild(this.previousSibling),this.parentNode.removeChild(this.previousSibling))},o.appendChild(u);var u=this.buildInput("button","+"),c=this;u.onclick=function(){c.addMatchField(o,this.previousSibling.previousSibling,s,r)},o.appendChild(u)}}}},e.prototype.addMatchField=function(e,t,i,o){if("enum"==i.type)var n=this.buildSelect(i.values);else if("bitwise"==i.type){n=document.createElement("span");for(var r in i.values){var s=this.buildInput("checkbox",r),a=document.createElement("label");a.appendChild(s),a.appendChild(document.createTextNode(" "+i.values[r])),n.appendChild(a.cloneNode(!0))}}else n=this.buildInput(i.type,"",i);return n=e.insertBefore(n,t?t.nextSibling:null),t&&e.insertBefore(document.createElement("br"),n),n},e.prototype.buildInput=function(e,t,i){var o=document.createElement("input");return o.type="integer"==e?"number":e,o.value=t,o},e.prototype.buildSelect=function(e){var t=document.createElement("select");for(var i in e){var o=document.createElement("option");o.value=i,o.innerHTML=e[i],t.appendChild(o)}return t},e.prototype.import=function(e){for(var t in e)if(0!=e[t].conditions.length){var i=this.addGroup(this.parent,e[t].operator);for(var o in e[t].conditions){var n=e[t].conditions[o],r=this.addRow(i);r.childNodes[1].firstChild.value=n.column;var s=this.addOperator(r,this.columns[n.column]);s.value=n.operator,this.switchOperator(s,n.values)}}},e.prototype.export=function(){var e=this.parent.querySelectorAll("table"),t=[];for(var i in e)if(e.hasOwnProperty(i)){for(var o=(i=e[i]).rows,n=[],r=0;r<o.length;r++){var s=o[r];if(s.getElementsByTagName("select")[0].value){var a=Array.prototype.slice.call(s.cells[3].querySelectorAll("input, select")).map(function(e){if("button"!=e.type)return e.value}),l={column:s.cells[1].firstChild.value,operator:s.cells[2].firstChild.value,values:a};l.operator&&(l.operator.match(/\?\?/)&&(l.values=l.values.slice(0,-2)),n.push(l))}}t.push({operator:i.parentNode.firstChild.firstChild.value,conditions:n})}return t}}();

Modified src/www/admin/static/scripts/wiki_editor.js from [2dad10610c] to [e1b49b3d06].

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
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



+
-
+

-
-
-
+
+
+

-
-
+
+

-
-
-
+
+
+

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+

-
-
-
-
+
+
+
+

-
-
-
-
+
+
+
+

-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+

-
-
-
+
+
+

-
-
+
+

-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+

-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+

-
-
+
+

-
-
-
-
-
-
-
+
+
+
+
+
+
+

-
-
-
-
+
+
+
+

-
-
-
-
-
+
+
+
+
+


-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+

-
-
-
+
+
+

-
-
-
-
-
+
+
+
+
+

-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+

-
+

-
-
-
-
-
+
+
+
+
+

-
-
-
-
+
+
+
+

-
-
-
-
-
-
+
+
+
+
+
+
+

(function () {
	var wiki_id = window.location.search.match(/id=(\d+)/)[1];

	g.onload(function () {
	g.style('scripts/wiki_editor.css');
		g.style('scripts/wiki_editor.css');

	g.script('scripts/text_editor.min.js').onload = function () {
		var t = new textEditor('f_contenu');
		t.parent = t.textarea.parentNode;
		g.script('scripts/text_editor.min.js').onload = function () {
			var t = new textEditor('f_contenu');
			t.parent = t.textarea.parentNode;

		var toolbar = document.createElement('nav');
		toolbar.className = 'te';
			var toolbar = document.createElement('nav');
			toolbar.className = 'te';

		var toggleFullscreen = function (e)
		{
			var classes = t.parent.className.split(' ');
			var toggleFullscreen = function (e)
			{
				var classes = t.parent.className.split(' ');

			for (var i = 0; i < classes.length; i++)
			{
				if (classes[i] == 'fullscreen')
				{
					classes.splice(i, 1);
					t.parent.className = classes.join(' ');
					t.fullscreen = false;
					return true;
				}
			}
			
			classes.push('fullscreen');
			t.parent.className = classes.join(' ');
			t.fullscreen = true;
			return true;
		};
				for (var i = 0; i < classes.length; i++)
				{
					if (classes[i] == 'fullscreen')
					{
						classes.splice(i, 1);
						t.parent.className = classes.join(' ');
						t.fullscreen = false;
						return true;
					}
				}
				
				classes.push('fullscreen');
				t.parent.className = classes.join(' ');
				t.fullscreen = true;
				return true;
			};

		var openPreview = function ()
		{
			openIFrame('');
			var form = document.createElement('form');
			form.appendChild(t.textarea.cloneNode(true));
			form.firstChild.value = t.textarea.value;
			form.target = 'editorFrame';
			form.action = g.admin_url + 'wiki/_preview.php?id=' + wiki_id;
			form.style.display = 'none';
			form.method = 'post';
			document.body.appendChild(form);
			form.submit();
			//document.body.removeChild(form);
		};
			var openPreview = function ()
			{
				openIFrame('');
				var form = document.createElement('form');
				form.appendChild(t.textarea.cloneNode(true));
				form.firstChild.value = t.textarea.value;
				form.target = 'editorFrame';
				form.action = g.admin_url + 'wiki/_preview.php?id=' + wiki_id;
				form.style.display = 'none';
				form.method = 'post';
				document.body.appendChild(form);
				form.submit();
				//document.body.removeChild(form);
			};

		var openSyntaxHelp = function ()
		{
			openIFrame(g.admin_url + 'wiki/_syntaxe.html');
		};
			var openSyntaxHelp = function ()
			{
				openIFrame(g.admin_url + 'wiki/_syntaxe.html');
			};

		var openFileInsert = function ()
		{
			openIFrame(g.admin_url + 'wiki/_fichiers.php?page=' + wiki_id);
		};
			var openFileInsert = function ()
			{
				openIFrame(g.admin_url + 'wiki/_fichiers.php?page=' + wiki_id);
			};

		window.te_insertFile = function (file)
		{
			var tag = '<<fichier|'+file+'>>';
			
			t.insertAtPosition(t.getSelection().start, tag);
			
			closeIFrame();
		};
			window.te_insertFile = function (file)
			{
				var tag = '<<fichier|'+file+'>>';
				
				t.insertAtPosition(t.getSelection().start, tag);
				
				closeIFrame();
			};

		window.te_insertImage = function (file, position, caption)
		{
			var tag = '<<image|' + file;
			window.te_insertImage = function (file, position, caption)
			{
				var tag = '<<image|' + file;

			if (position)
				tag += '|' + position;
				if (position)
					tag += '|' + position;

			if (caption)
				tag += '|' + caption;
			
			tag += '>>';
			
			t.insertAtPosition(t.getSelection().start, tag);
			
			closeIFrame();
		};
				if (caption)
					tag += '|' + caption;
				
				tag += '>>';
				
				t.insertAtPosition(t.getSelection().start, tag);
				
				closeIFrame();
			};

		var openIFrame = function(url)
		{
			if (t.iframe && t.iframe.src == t.base_url + url)
			{
				t.iframe.className = '';
				t.parent.className += ' iframe';
				return true;
			}
			else if (t.iframe)
			{
				t.parent.removeChild(t.iframe);
				t.iframe = null;
			}
			var openIFrame = function(url)
			{
				if (t.iframe && t.iframe.src == t.base_url + url)
				{
					t.iframe.className = '';
					t.parent.className += ' iframe';
					return true;
				}
				else if (t.iframe)
				{
					t.parent.removeChild(t.iframe);
					t.iframe = null;
				}

			var w = t.textarea.offsetWidth,
				h = t.textarea.offsetHeight;
				var w = t.textarea.offsetWidth,
					h = t.textarea.offsetHeight;

			var iframe = document.createElement('iframe');
			iframe.width = w;
			iframe.height = h;
			iframe.src = url;
			iframe.name = 'editorFrame';
			iframe.frameborder = '0';
			iframe.scrolling = 'yes';
				var iframe = document.createElement('iframe');
				iframe.width = w;
				iframe.height = h;
				iframe.src = url;
				iframe.name = 'editorFrame';
				iframe.frameborder = '0';
				iframe.scrolling = 'yes';

			t.parent.appendChild(iframe);
			t.parent.className += ' iframe';
			t.iframe = iframe;
		};
				t.parent.appendChild(iframe);
				t.parent.className += ' iframe';
				t.iframe = iframe;
			};

		var closeIFrame = function ()
		{
			t.parent.className = t.parent.className.replace(/ iframe$/, '');
			t.iframe.className = 'hidden';
		};
			var closeIFrame = function ()
			{
				t.parent.className = t.parent.className.replace(/ iframe$/, '');
				t.iframe.className = 'hidden';
			};


		var appendButton = function (name, title, action, altTitle)
		{
			var btn = document.createElement('input');
			btn.type = 'button';
			btn.title = altTitle ? altTitle : title;
			btn.value = title;
			btn.className = name;
			btn.onclick = function () { action.call(); return false; };
			var appendButton = function (name, title, action, altTitle)
			{
				var btn = document.createElement('input');
				btn.type = 'button';
				btn.title = altTitle ? altTitle : title;
				btn.value = title;
				btn.className = name;
				btn.onclick = function () { action.call(); return false; };

			toolbar.appendChild(btn);
			return btn;
		};
				toolbar.appendChild(btn);
				return btn;
			};

		var wrapTags = function (left, right)
		{
			t.wrapSelection(t.getSelection(), left, right);
			return true;
		};
			var wrapTags = function (left, right)
			{
				t.wrapSelection(t.getSelection(), left, right);
				return true;
			};

		appendButton('title', "== Titre", function () { wrapTags("== ", ""); } );
		appendButton('bold', '**gras**', function () { wrapTags('**', '**'); } );
		appendButton('italic', "''italique''", function () { wrapTags("''", "''"); } );
		appendButton('link', "[[lien|http://]]", function () { 
			if (url = window.prompt('Adresse URL ?')) 
				wrapTags("[[", "|" + url + ']]'); 
		} );
		appendButton('icnl file', "📎", openFileInsert, 'Insérer fichier / image');
			appendButton('title', "== Titre", function () { wrapTags("== ", ""); } );
			appendButton('bold', '**gras**', function () { wrapTags('**', '**'); } );
			appendButton('italic', "''italique''", function () { wrapTags("''", "''"); } );
			appendButton('link', "[[lien|http://]]", function () { 
				if (url = window.prompt('Adresse URL ?')) 
					wrapTags("[[", "|" + url + ']]'); 
			} );
			appendButton('icnl file', "📎", openFileInsert, 'Insérer fichier / image');

		appendButton('ext icnl preview', '⎙', openPreview, 'Prévisualiser');
			appendButton('ext icnl preview', '⎙', openPreview, 'Prévisualiser');

		appendButton('ext icnl help', '❓', openSyntaxHelp, 'Aide sur la syntaxe');
		appendButton('ext fullscreen', 'Plein écran', toggleFullscreen, 'Plein écran');
		appendButton('ext close', 'Fermer', closeIFrame);
		
		t.parent.insertBefore(toolbar, t.parent.firstChild);
			appendButton('ext icnl help', '❓', openSyntaxHelp, 'Aide sur la syntaxe');
			appendButton('ext fullscreen', 'Plein écran', toggleFullscreen, 'Plein écran');
			appendButton('ext close', 'Fermer', closeIFrame);
			
			t.parent.insertBefore(toolbar, t.parent.firstChild);

		t.shortcuts.push({key: 'F11', callback: toggleFullscreen});
		t.shortcuts.push({ctrl: true, key: 'b', callback: function () { return wrapTags('**', '**'); } });
		t.shortcuts.push({ctrl: true, key: 'g', callback: function () { return wrapTags('**', '**'); } });
		t.shortcuts.push({ctrl: true, key: 'i', callback: function () { return wrapTags("''", "''"); } });
			t.shortcuts.push({key: 'F11', callback: toggleFullscreen});
			t.shortcuts.push({ctrl: true, key: 'b', callback: function () { return wrapTags('**', '**'); } });
			t.shortcuts.push({ctrl: true, key: 'g', callback: function () { return wrapTags('**', '**'); } });
			t.shortcuts.push({ctrl: true, key: 'i', callback: function () { return wrapTags("''", "''"); } });

		if (window.location.hash.match(/fullscreen/))
		{
			t.toggleFullscreen();
			window.location.hash = '';
		}
	};
			if (window.location.hash.match(/fullscreen/))
			{
				t.toggleFullscreen();
				window.location.hash = '';
			}
		};
	});
}());

Modified src/www/admin/upgrade.php from [4dda8f7462] to [16f61c74de].

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
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

+

+
+



+










+
+
+
+
+
+










+
+
+
+
+







<?php

namespace Garradin;

use Garradin\Membres\Session;

const UPGRADE_PROCESS = true;

require_once __DIR__ . '/../../include/test_required.php';
require_once __DIR__ . '/../../include/init.php';

$config = Config::getInstance();

$v = $config->getVersion();

if (version_compare($v, garradin_version(), '>='))
{
    throw new UserException("Pas de mise à jour à faire.");
}

// versions pré-0.7.0: démerdez-vous !
if (!$v || version_compare($v, '0.7.0', '<'))
{
    throw new UserException("Votre version de Garradin est trop ancienne pour être mise à jour. Mettez à jour vers Garradin 0.8.5 avant de faire la mise à jour vers cette version.");
}

Install::checkAndCreateDirectories();

if (Static_Cache::exists('upgrade'))
{
    $path = Static_Cache::getPath('upgrade');
    throw new UserException('Une mise à jour est déjà en cours.'
        . PHP_EOL . 'Si celle-ci a échouée et que vous voulez ré-essayer, supprimez le fichier suivant:'
        . PHP_EOL . $path);
}

// Voir si l'utilisateur est loggé, on le fait ici pour le cas où
// il y aurait déjà eu des entêtes envoyés au navigateur plus bas
$session = new Session;
$user_is_logged = $session->isLogged(true);

Static_Cache::store('upgrade', 'Mise à jour en cours.');

$db = DB::getInstance();
$redirect = true;

// Créer une sauvegarde automatique
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
65
66
67
68
69
70
71



































































































































































72
73
74
75
76
77
78







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-







<div id="loader" class="loader" style="margin: 2em 0; height: 50px;"></div>
<script>
animatedLoader(document.getElementById("loader"), 5);
</script>';

flush();

// versions pré-0.3.0
if (!$v)
{
    $db->exec('ALTER TABLE membres ADD COLUMN lettre_infos INTEGER DEFAULT 0;');
    $v = '0.3.0';
}

if (version_compare($v, '0.4.0', '<'))
{
    $config->set('monnaie', '€');
    $config->set('pays', 'FR');
    $config->save();

    $db->exec(file_get_contents(ROOT . '/include/data/0.4.0.sql'));

    // Mise en place compta
    $comptes = new Compta\Comptes;
    $comptes->importPlan();

    $comptes = new Compta\Categories;
    $comptes->importCategories();
}

if (version_compare($v, '0.4.3', '<'))
{
    $db->exec(file_get_contents(ROOT . '/include/data/0.4.3.sql'));
}

if (version_compare($v, '0.4.5', '<'))
{
    // Mise à jour plan comptable
    $comptes = new Compta\Comptes;
    $comptes->importPlan();

    // Création page wiki connexion
    $wiki = new Wiki;
    $page = Wiki::transformTitleToURI('Bienvenue');
    $config->set('accueil_connexion', $page);

    if (!$wiki->getByUri($page))
    {
        $id_page = $wiki->create([
            'titre' =>  'Bienvenue',
            'uri'   =>  $page,
        ]);

        $wiki->editRevision($id_page, 0, [
            'id_auteur' =>  null,
            'contenu'   =>  "Bienvenue dans l'administration de ".$config->get('nom_asso')." !\n\n"
                .   "Utilisez le menu à gauche pour accéder aux différentes rubriques.",
        ]);
    }

    $config->set('accueil_connexion', $page);
    $config->save();
}

if (version_compare($v, '0.5.0', '<'))
{
    // Récupération de l'ancienne config
    $champs_modifiables_membre = $db->firstColumn('SELECT valeur FROM config WHERE cle = "champs_modifiables_membre";');
    $champs_modifiables_membre = !empty($champs_modifiables_membre) ? explode(',', $champs_modifiables_membre) : [];

    $champs_obligatoires = $db->firstColumn('SELECT valeur FROM config WHERE cle = "champs_obligatoires";');
    $champs_obligatoires = !empty($champs_obligatoires) ? explode(',', $champs_obligatoires) : [];

    // Import des champs membres par défaut
    $champs = Membres\Champs::importInstall();

    // Application de l'ancienne config aux nouveaux champs membres
    foreach ($champs_obligatoires as $name)
    {
        if ($champs->get($name) !== null)
            $champs->set($name, 'mandatory', true);
    }

    foreach ($champs_modifiables_membre as $name)
    {
        if ($champs->get($name) !== null)
            $champs->set($name, 'editable', true);
    }

    $champs->save();

    $config->set('champs_membres', $champs);
    $config->save();

    // Suppression de l'ancienne config
    $db->exec('DELETE FROM config WHERE cle IN ("champs_obligatoires", "champs_modifiables_membre");');
}

if (version_compare($v, '0.6.0-rc1', '<'))
{
    $categories = new Membres\Categories;
    $list = $categories->listComplete();

    $db->exec('PRAGMA foreign_keys = OFF; BEGIN;');

    // Mise à jour base de données
    $db->exec(file_get_contents(ROOT . '/include/data/0.6.0.sql'));

    $id_cat_cotisation = $db->firstColumn('SELECT id FROM compta_categories WHERE compte = 756 LIMIT 1;');

    // Conversion des cotisations de catégories en cotisations indépendantes
    foreach ($list as $cat)
    {
        $db->insert('cotisations', [
            'id_categorie_compta'   =>  null,
            'intitule'              =>  $cat->nom,
            'montant'               =>  (float) $cat->montant_cotisation,
            // Convertir un nombre de mois en nombre de jours
            'duree'                 =>  round($cat->duree_cotisation * 30.44),
            'description'           =>  'Créé automatiquement depuis les catégories de membres (version 0.5.x)',
        ]);

        $args = [
            'id_cotisation' =>  (int)$db->lastInsertRowId(),
            'id_categorie'  =>  (int)$cat->id,
        ];

        // import des dates de cotisation existantes comme paiements
        $db->preparedQuery('INSERT INTO cotisations_membres 
            (id_membre, id_cotisation, date)
            SELECT id, :id_cotisation, date(date_cotisation) FROM membres
            WHERE date_cotisation IS NOT NULL AND date_cotisation != \'\' AND id_categorie = :id_categorie;',
            $args);

        // Mais on ne crée pas d'écriture comptable, car elles existent probablement déjà
    }

    // Déplacement des squelettes dans le répertoire public
    if (!file_exists(ROOT . '/www/squelettes'))
    {
        mkdir(ROOT . '/www/squelettes');
    }

    if (file_exists(ROOT . '/squelettes'))
    {
        $dir = dir(ROOT . '/squelettes');

        while ($file = $dir->read())
        {
            if ($file == '.' || $file == '..')
                continue;

            rename(ROOT . '/squelettes/' . $file, ROOT . '/www/squelettes/' . $file);
        }

        $dir->close();

        @rmdir(ROOT . '/squelettes');
    }

    $db->exec('END; PRAGMA foreign_keys = ON;');

    // Mise à jour de la table membres, suppression du champ date_cotisation notamment
    $config->get('champs_membres')->save();

    // Possibilité de choisir l'identité et l'identifiant d'un membre
    $config->set('champ_identite', 'nom');
    $config->set('champ_identifiant', 'email');
    $config->save();
}

if (version_compare($v, '0.7.0', '<'))
{
    $db->exec('PRAGMA foreign_keys = OFF; BEGIN;');

    // Mise à jour base de données
    $db->exec(file_get_contents(ROOT . '/include/data/0.7.0.sql'));
324
325
326
327
328
329
330
331


































































332
333
334
335
336




























337
338
339
340
341
342
343
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








+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+





+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







{
    $db->begin();

    $db->import(ROOT . '/include/data/0.8.4.sql');

    $db->commit();
}

if (version_compare($v, '0.9.0-rc1', '<'))
{
    $db->exec('PRAGMA foreign_keys = OFF;');
    $db->begin();

    $db->import(ROOT . '/include/data/0.9.0.sql');

    // Correction des ID parents des comptes qui ont été mal renseignés
    // exemple : compte 512A avec "5" comme parent (c'était permis,
    // par erreur, par le formulaire d'ajout de compte dans le plan)
    // Serait probablement possible en 3-4 lignes de SQL avec
    // WITH RECURSIVE mais c'est au delà de mes compétences
    $comptes = $db->iterate('SELECT id FROM compta_comptes WHERE parent != length(id) - 1;');

    foreach ($comptes as $compte)
    {
        $parent = false;
        $id = $compte->id;

        while (!$parent && strlen($id))
        {
            // On enlève un caractère à la fin jusqu'à trouver un compte parent correspondant
            $id = substr($id, 0, -1);
            $parent = $db->firstColumn('SELECT id FROM compta_comptes WHERE id = ?;', $id);
        }

        if (!$parent)
        {
            // Situation normalement impossible !
            throw new \LogicException(sprintf('Le compte %s est invalide et n\'a pas de compte parent possible !', $compte->id));
        }

        $db->update('compta_comptes', ['parent' => $parent], 'id = :id', ['id' => $compte->id]);
    }

    $champs = $config->get('champs_membres');

    if ($champs->get('lettre_infos'))
    {
        // Ajout d'une recherche avancée en exemple
        $query = [
            'query' => [[
                'operator' => 'AND',
                'conditions' => [
                    [
                        'column'   => 'lettre_infos',
                        'operator' => '= 1',
                        'values'   => [],
                    ],
                ],
            ]],
            'order' => 'numero',
            'desc' => true,
            'limit' => '10000',
        ];

        $recherche = new Recherche;
        $recherche->add('Membres inscrits à la lettre d\'information', null, $recherche::TYPE_JSON, 'membres', $query);
    }

    $db->commit();

    $config->set('desactiver_site', false);
    $config->save();
}

Utils::clearCaches();

$config->setVersion(garradin_version());

Static_Cache::remove('upgrade');

// Réinstaller les plugins système si nécessaire
Plugin::checkAndInstallSystemPlugins();

// Mettre à jour les plugins si nécessaire
foreach (Plugin::listInstalled() as $id=>$infos)
{
    // Ne pas tenir compte des plugins dont le code n'est pas dispo
    if ($infos->disabled)
    {
        continue;
    }

    $plugin = new Plugin($id);

    if ($plugin->needUpgrade())
    {
        $plugin->upgrade();
    }

    unset($plugin);
}

// Forcer à rafraîchir les données de la session si elle existe
if ($user_is_logged)
{
    $session->refresh();
}

echo '<h2>Mise à jour terminée.</h2>
<p><a href="'.ADMIN_URL.'">Retour</a></p>';

if ($redirect)
{
    echo '

Modified src/www/index.php from [36cb4b20f1] to [f59003a96f].

1
2
3
4
5





6
7
8
1
2
3
4
5
6
7
8
9
10
11
12
13





+
+
+
+
+



<?php

namespace Garradin;

require __DIR__ . '/_inc.php';

if (Config::getInstance()->get('desactiver_site'))
{
	Utils::redirect(ADMIN_URL);
}

$squelette = new Squelette;
$squelette->dispatchURI();