Overview
SHA1:07c00c16ddfde3e303856cdb65fd4ce3d2737cdd
Date: 2018-10-27 22:56:48
User: bohwaz
Comment:Merge version développement, sortie 0.9.0
Timelines: family | ancestors | descendants | both | trunk | stable | 0.9.0
Downloads: Tarball | ZIP archive
Other Links: files | file ages | folders | manifest
Tags And Properties
Context
2018-10-27
23:12
[c70c29a6de] Mise à jour dépendances (user: bohwaz, tags: trunk)
22:56
[07c00c16dd] Merge version développement, sortie 0.9.0 (user: bohwaz, tags: trunk, stable, 0.9.0)
22:05
[52e2bcee25] Ne vider le cache statique que lors d'événements type mise à jour (user: bohwaz, tags: dev)
2018-09-22
09:59
[933b330272] Utilisation de safe_unlink/safe_mkdir partout (user: bohwaz, tags: trunk, stable)
Changes

Modified src/Makefile from [c2bdd03499] to [78b3f7812f].


1
2
3
4
5
6
7

KD2_FILE := https://fossil.kd2.org/kd2fw/uv/KD2-5.6.zip

deps:
	$(eval TMP_KD2=$(shell mktemp -d))
	#cd ${TMP_KD2}

	wget ${KD2_FILE} -O ${TMP_KD2}/kd2.zip
>







1
2
3
4
5
6
7
8
.PHONY: dev-server release deps publish check-dependencies
KD2_FILE := https://fossil.kd2.org/kd2fw/uv/KD2-5.6.zip

deps:
	$(eval TMP_KD2=$(shell mktemp -d))
	#cd ${TMP_KD2}

	wget ${KD2_FILE} -O ${TMP_KD2}/kd2.zip

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

4
5
6
7
8
9
10
11
12
13
14
15
16
17
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
  Licence : GNU AGPL v3







<
<
<
<

|

4
5
6
7
8
9
10




11
12
13
Inclus les bibliothèques suivantes :

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





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

Modified src/VERSION from [d1cc680d2c] to [37225f3c32].

1
0.8.5
|
1
0.9.0

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
 * STARTTLS = utilisation de STARTTLS (moyennement sécurisé)
 *
 * Défaut : STARTTLS
 */
const SMTP_SECURITY = 'STARTTLS';

/**
 * Forcer la valeur de l'expéditeur des emails
 * 
 * false, null ou vide = désactivé
 * chaîne = adresse email qui sera utilisé dans le champ From
 * 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
 * 
 * Défaut : false
 */
const FORCE_EMAIL_FROM = false;







|

|
|
<

>
|
<
>
|

|

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

/**
 * Activer les sauvegardes automatiques
 * 
 * Utile à désactiver si vous avez déjà des sauvegardes effectuées
 * automatiquement au niveau du système.

 * 
 * 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 : true
 */
const ENABLE_AUTOMATIC_BACKUPS = true;

Modified src/cron.php from [d4a8acbd51] to [8c598487d9].

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

namespace Garradin;

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

// Exécution des tâches automatiques

if ($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();







|




<








<
<
<
2
3
4
5
6
7
8
9
10
11
12
13

14
15
16
17
18
19
20
21




namespace Garradin;

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

// Exécution des tâches automatiques

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();
}



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
...
315
316
317
318
319
320
321

322
323
324
325
326
327
328
...
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
...
382
383
384
385
386
387
388












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

    config TEXT NULL
);

CREATE TABLE IF NOT EXISTS plugins_signaux
-- Association entre plugins et signaux (hooks)
(
    signal TEXT NOT NULL,
................................................................................
);

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

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)
................................................................................
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)
);



















<







 







>







 







|







 







>
>
>
>
>
>
>
>
>
>
>
>
9
10
11
12
13
14
15

16
17
18
19
20
21
22
...
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
...
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
...
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
-- 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,


    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,
................................................................................
    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,
................................................................................
);

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) 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)
................................................................................
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
    '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,
];

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

    if (!defined($const))







|







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',
    'ENABLE_AUTOMATIC_BACKUPS' => true,
];

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

    if (!defined($const))

Modified src/include/lib/Garradin/Compta/Comptes.php from [0fc00e722e] to [8786b38bca].

172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
...
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
...
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
    public function delete($id)
    {
        $db = DB::getInstance();

        $id = trim($id);

        // Ne pas supprimer un compte qui est utilisé !
        if ($db->firstColumn('SELECT 1 FROM compta_journal WHERE compte_debit = ? OR compte_debit = ? LIMIT 1;', $id, $id))
        {
            throw new UserException('Ce compte ne peut être supprimé car des opérations comptables y sont liées.');
        }

        if ($db->test('compta_comptes_bancaires', $db->where('id', $id)))
        {
            throw new UserException('Ce compte ne peut être supprimé car il est lié à un compte bancaire.');
................................................................................
    public function canDelete($id)
    {
        $db = DB::getInstance();

        $id = trim($id);

        if ($db->firstColumn('SELECT 1 FROM compta_journal
                WHERE compte_debit = ? OR compte_debit = ? LIMIT 1;', $id, $id))
        {
            return false;
        }

        if ($db->test('compta_categories', $db->where('compte', $id)))
        {
            return false;
................................................................................
    {
        $db = DB::getInstance();

        $id = trim($id);

        if ($db->firstColumn('SELECT 1 FROM compta_journal
                WHERE id_exercice = (SELECT id FROM compta_exercices WHERE cloture = 0 LIMIT 1) 
                AND (compte_debit = ? OR compte_debit = ?) LIMIT 1;', $id, $id))
        {
            $code = 1;
            return false;
        }

        if ($db->test('compta_categories', $db->where('compte', $id)))
        {







|







 







|







 







|







172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
...
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
...
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
    public function delete($id)
    {
        $db = DB::getInstance();

        $id = trim($id);

        // Ne pas supprimer un compte qui est utilisé !
        if ($db->test('compta_journal', 'compte_debit = ? OR compte_credit = ?', $id, $id))
        {
            throw new UserException('Ce compte ne peut être supprimé car des opérations comptables y sont liées.');
        }

        if ($db->test('compta_comptes_bancaires', $db->where('id', $id)))
        {
            throw new UserException('Ce compte ne peut être supprimé car il est lié à un compte bancaire.');
................................................................................
    public function canDelete($id)
    {
        $db = DB::getInstance();

        $id = trim($id);

        if ($db->firstColumn('SELECT 1 FROM compta_journal
                WHERE compte_debit = ? OR compte_credit = ? LIMIT 1;', $id, $id))
        {
            return false;
        }

        if ($db->test('compta_categories', $db->where('compte', $id)))
        {
            return false;
................................................................................
    {
        $db = DB::getInstance();

        $id = trim($id);

        if ($db->firstColumn('SELECT 1 FROM compta_journal
                WHERE id_exercice = (SELECT id FROM compta_exercices WHERE cloture = 0 LIMIT 1) 
                AND (compte_debit = ? OR compte_credit = ?) LIMIT 1;', $id, $id))
        {
            $code = 1;
            return false;
        }

        if ($db->test('compta_categories', $db->where('compte', $id)))
        {

Modified src/include/lib/Garradin/Compta/Comptes_Bancaires.php from [5768c2e53a] to [c2ddfadd03].

80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
        {
            throw new UserException('Ce compte n\'est pas un compte bancaire.');
        }

        // Ne pas supprimer/désactiver un compte qui est utilisé dans l'exercice courant
        if ($db->firstColumn('SELECT 1 FROM compta_journal
                WHERE id_exercice = (SELECT id FROM compta_exercices WHERE cloture = 0 LIMIT 1) 
                AND (compte_debit = ? OR compte_debit = ?) LIMIT 1;', $id, $id))
        {
            throw new UserException('Ce compte ne peut être supprimé car des écritures y sont liées sur l\'exercice courant. '
                . 'Il faut supprimer ou ré-attribuer ces écritures avant de pouvoir supprimer le compte.');
        }

        // Il n'est pas possible de supprimer ou désactiver un compte qui est lié à des catégories
        if ($db->test('compta_categories', $db->where('compte', $id)))







|







80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
        {
            throw new UserException('Ce compte n\'est pas un compte bancaire.');
        }

        // Ne pas supprimer/désactiver un compte qui est utilisé dans l'exercice courant
        if ($db->firstColumn('SELECT 1 FROM compta_journal
                WHERE id_exercice = (SELECT id FROM compta_exercices WHERE cloture = 0 LIMIT 1) 
                AND (compte_debit = ? OR compte_credit = ?) LIMIT 1;', $id, $id))
        {
            throw new UserException('Ce compte ne peut être supprimé car des écritures y sont liées sur l\'exercice courant. '
                . 'Il faut supprimer ou ré-attribuer ces écritures avant de pouvoir supprimer le compte.');
        }

        // Il n'est pas possible de supprimer ou désactiver un compte qui est lié à des catégories
        if ($db->test('compta_categories', $db->where('compte', $id)))

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
..
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
..
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
<?php

namespace Garradin\Compta;

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

use KD2\ODSWriter;


class Import
{
	protected $header = [
		'Numéro mouvement',
		'Date',
		'Type de mouvement',
................................................................................
		'Numéro de chèque',
		'Numéro de pièce',
		'Remarques'
	];

	protected function export($exercice)
	{
		return DB::getInstance()->prepare('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,
................................................................................
			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)
	{
		$res = $this->export($exercice);

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

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

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

		fclose($fp);

		return true;
	}

    public function toODS($exercice)
    {
    	$result = $this->export($exercice);
        $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);
		}




|
|
|
<
<
>







 







|







 







|
>
>
>
>
>




|
|
<

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







1
2
3
4
5
6
7


8
9
10
11
12
13
14
15
..
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
..
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
<?php

namespace Garradin\Compta;

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


use Garradin\Config;

class Import
{
	protected $header = [
		'Numéro mouvement',
		'Date',
		'Type de mouvement',
................................................................................
		'Numéro de chèque',
		'Numéro de pièce',
		'Remarques'
	];

	protected function export($exercice)
	{
		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,
................................................................................
			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;
		');
	}

	protected function exportName()
	{
		return sprintf('Export comptabilité - %s - %s', Config::getInstance()->get('nom_asso'), date('Y-m-d'));
	}

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














	public function toODS($exercice)
	{
		return Utils::toODS($this->exportName(), $this->export($exercice), $this->header);


	}











	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
..
65
66
67
68
69
70
71


72
73
74
75
76
77
78
     * Singleton simple
     * @return Config
     */
    static public function getInstance()
    {
        return self::$_instance ?: self::$_instance = new Config;
    }






    /**
     * Empêche de cloner l'objet
     * @return void
     */
    private function __clone()
    {
................................................................................
            'champ_identite'          =>  $string,
            
            'version'                 =>  $string,
            
            'couleur1'                =>  $string,
            'couleur2'                =>  $string,
            'image_fond'              =>  $string,


        ];

        $db = DB::getInstance();

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

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







>
>
>
>
>







 







>
>







16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
..
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
     * 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()
    {
................................................................................
            '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
..
45
46
47
48
49
50
51





52
53
54
55
56
57
58
    private function __clone()
    {
        // Désactiver le clonage, car on ne veut qu'une seule instance
    }

    public function __construct($create = false)
    {





        $flags = \SQLITE3_OPEN_READWRITE;

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

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

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







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







>
>
>
>
>







 







>
>
>
>
>







22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
..
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
    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;
        }

................................................................................
            // 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 [d6545e900e] to [dc1e4df9ed].

4
5
6
7
8
9
10




























11
12
13
14
15
16
17
..
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
...
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
...
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166

/**
 * 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 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;');
................................................................................
		$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);

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





















		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)
		{
			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.');
		    }

		    // 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)
	{
................................................................................
		$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;
			}







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







|







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>












|
|
|
|

|
|
|
|
|







 







|







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
..
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
...
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
...
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214

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

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

		if ($ok)
		{
			// Force l'installation de plugin système
			Plugin::checkAndInstallSystemPlugins();
		}

		return $ok;
	}

	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;');
................................................................................
		$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);

................................................................................
		$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)
		{
			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.');
			}

			// 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)
	{
................................................................................
		$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 [845a014e33] to [3d4bebf63e].

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
...
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472

473

474
475
476
477
478
479
480


481
482
483
484
485
486
487
488
489
490

491
492
493
494
495
496
497
498
499
500
501
502
503


504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
    }

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

    public function search($field, $query)
    {
        $db = DB::getInstance();
        $config = Config::getInstance();

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


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


        }


        $champ = $champs->get($field);

        if ($champ->type == 'multiple')
        {
            $where = 'WHERE '.$field.' & (1 << '.(int)$query.')';
            $order = false;
        }
        elseif ($champ->type == 'tel')
        {
            $query = Utils::normalizePhoneNumber($query);
            $query = preg_replace('!^0+!', '', $query);

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

            $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 . '%';

            }

            $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());


        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();
................................................................................
    }

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

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







|

|
<
|
<
>
|
<
|
<
>
>
|
>
|
|
|
|
<
<
<
|
<
<
<
<
|
<
|
|
|

<
<
<
<
>
|
<
<
>
|
<
>

<
<
<
<
<
>
|

<
<
<
>
|
<
>
|
<
|
<
>
|

<
>
|
<
<
<
<
<
<
<
<
<
<
<
<
<
>







 







|
<
<
<
<
<
|
<
>
|
>
|
<
<
<
<
<
<
>
>
|
<
|
<
<
<
<

<
<
>
|
<
<
<
<
<
<
<
<

<

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






















































    }

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

    public function getSearchHeaderFields(array $result)
    {
        if (!count($result))

        {

            return false;
        }



        $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();


        foreach ($recipients as $recipient)
        {





            Utils::sendEmail(Utils::EMAIL_CONTEXT_BULK, $recipient->email, $subject, $message, $recipient->id);
        }




        if ($send_copy)
        {

            Utils::sendEmail(Utils::EMAIL_CONTEXT_BULK, $config->get('email_asso'), $subject, $message);
        }



        return true;
    }


    public function listAllByCategory($id_categorie)
    {













        return DB::getInstance()->get('SELECT id, email FROM membres WHERE id_categorie = ?;', (int)$id_categorie);
    }

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

        $db = DB::getInstance();
................................................................................
    }

    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));
    }



}






















































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

    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];
        }







<
<
<
<
<







45
46
47
48
49
50
51





52
53
54
55
56
57
58
        }
    }

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






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

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

    	// 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',
            'secret_otp TEXT NULL, -- Code secret pour TOTP',
            'clef_pgp TEXT NULL, -- Clé publique PGP'
    	];


        $create_keys = [
            'FOREIGN KEY (id_categorie) REFERENCES membres_categories (id)'
        ];

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







|







 







|
|

|
|
|
|
|
|


|

>







248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
...
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
            }
            elseif ($config->type == 'number' || $config->type == 'multiple')
            {
                $rules[] = 'numeric';
            }
            elseif ($config->type == 'select')
            {
                $rules[] = 'in:' . range(0, count($config->options) - 1);
            }
            elseif ($config->type == 'checkbox')
            {
                $rules[] = 'boolean';
            }

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

        // 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/Cotisations.php from [92bb62c99b] to [ec784bf9dc].

259
260
261
262
263
264
265
266
267
268
269
270



271
272
273
274
275
276
277
		$champ_id = Config::getInstance()->get('champ_identite');

		if (empty($order))
			$order = 'date';

		switch ($order)
		{
			case 'date':
				$order = 'cm.date';
				break;
			case 'a_jour':
				break;



			case 'identite':
				$order = 'transliterate_to_ascii(m.'.$champ_id.') COLLATE NOCASE';
				break;
			default:
				$order = 'cm.id_membre';
				break;
		}







<




>
>
>







259
260
261
262
263
264
265

266
267
268
269
270
271
272
273
274
275
276
277
278
279
		$champ_id = Config::getInstance()->get('champ_identite');

		if (empty($order))
			$order = 'date';

		switch ($order)
		{

				$order = 'cm.date';
				break;
			case 'a_jour':
				break;
			case 'date':
				$order = 'cm.date';
				break;
			case 'identite':
				$order = 'transliterate_to_ascii(m.'.$champ_id.') COLLATE NOCASE';
				break;
			default:
				$order = 'cm.id_membre';
				break;
		}

Modified src/include/lib/Garradin/Membres/Import.php from [4f9e17fd62] to [f63328115c].

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

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)

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

		$columns = array_flip($this->galette_fields);

		$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);
		Utils::skip_bom($fp);



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

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






			if (count($row) != count($columns))









































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

			$data = [];

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

					continue;


				// Concaténer plusieurs champs


				if (isset($data[$garradin]))
					$data[$garradin] .= "\n" . $col($galette);











				else






					$data[$garradin] = $col($galette);

			}

			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  int    $current_user_id
	 * @return boolean          TRUE en cas de succès
	 */
	public function fromCSV($path, $current_user_id)
	{
		if (!file_exists($path) || !is_readable($path))
		{
			throw new \RuntimeException('Fichier inconnu : '.$path);
		}

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

		$db->commit();

		fclose($fp);
		return true;
	}

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

        $champs = Config::getInstance()->get('champs_membres')->getKeys();
        $champs_sql = 'm.' . implode(', m.', $champs);


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



        $champs[] = 'categorie_membre';
        return [$champs, $res];




	}

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

        $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;
            }

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

        fclose($fp);

        return true;
    }

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


        $ods->add($champs);

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

        $ods->output();
    }
}







<
<


<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
>













|
|
<

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

<
<
>
>











>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







|


|
>

|
>
|
>
>
|
<
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
|
>













<
<





|



|







 







|

|

|
|
>

|
|
>
>

<
|
>
>
>
>


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










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



class Import
{





















































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

		$delim = Utils::find_csv_delim($fp);
		Utils::skip_bom($fp);















		$line = 0;


		$out = [];
		$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) != $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 $column_index => $garradin_field)
			{
				// Champs qu'on ne veut pas importer
				if (empty($garradin_field))
				{
					continue;
				}

				// 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();


		return true;
	}

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

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

		$db->commit();

		fclose($fp);
		return true;
	}

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

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

		$res = $db->iterate('SELECT ' . $champs_sql . ', c.nom AS "Catégorie membre" FROM membres AS m 
			INNER JOIN membres_categories AS c ON m.id_categorie = c.id
			' . $where . '
			ORDER BY c.id;');


		return [
			array_keys((array) $res->current()),
			$res,
			sprintf('Export membres - %s - %s', Config::getInstance()->get('nom_asso'), date('Y-m-d')),
		];
	}

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






		return Utils::toCSV($name, $result, $champs);
	}














	public function toODS(array $list = null)
	{
		list($champs, $result, $name) = $this->export($list);


		return Utils::toODS($name, $result, $champs);
	}

}









Modified src/include/lib/Garradin/Membres/Session.php from [b9cd12a177] to [76f4ec5c54].

7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
...
108
109
110
111
112
113
114
115
116
117
118
119
120
121


122

123
124
125
126
127
128
129
...
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
...
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
...
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
...
339
340
341
342
343
344
345
346
347
348
349
350
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
{
................................................................................

	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()
	{
		$logged = parent::isLogged();

		if (!$logged && defined('\Garradin\LOCAL_LOGIN')
			&& is_int(\Garradin\LOCAL_LOGIN) && \Garradin\LOCAL_LOGIN > 0)
		{


			$logged = $this->create(\Garradin\LOCAL_LOGIN);

		}

		return $logged;
	}

	// Ici checkOTP utilise NTP en second recours
	public function checkOTP($secret, $code)
................................................................................
		$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 true;
	}

	static public function recoverPasswordConfirm($code)
	{
		if (substr_count($code, '.') !== 2)
		{
			return false;
................................................................................
		$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);
	}

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

		return true;
	}

	public function canAccess($category, $permission)
	{
		if (!$this->user)
................................................................................

		return $out;
	}

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


		$from = sprintf('"%s" <%s>', sprintf('=?UTF-8?B?%s?=', base64_encode($user->identite)), FORCE_EMAIL_FROM ?: $config->get('email_asso'));


		$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.";

		if ($copie)
		{
			Utils::mail($from, $sujet, $message);
		}

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

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

		foreach ($data as $key=>$value)
................................................................................
			{
				throw new UserException('Clé PGP invalide : impossible d\'extraire l\'empreinte.');
			}
		}

		$db = DB::getInstance();
		$db->update('membres', $data, $db->where('id', (int)$this->user->id));
		$this->refresh();

		return true;
	}
}







<







 







|



|


>
>
|
>







 







|
<







 







|





|







 







<

>
|
<
>
|
<



|


|







 







|




7
8
9
10
11
12
13

14
15
16
17
18
19
20
...
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
...
191
192
193
194
195
196
197
198

199
200
201
202
203
204
205
...
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
...
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
...
339
340
341
342
343
344
345
346
347
348
349
350
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 KD2\Security;
use KD2\Security_OTP;
use KD2\QRCode;

class Session extends \KD2\UserSession
{
................................................................................

	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($disable_local_login = false)
	{
		$logged = parent::isLogged();

		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);
			}
		}

		return $logged;
	}

	// Ici checkOTP utilise NTP en second recours
	public function checkOTP($secret, $code)
................................................................................
		$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é.";

		return Utils::sendEmail(Utils::EMAIL_CONTEXT_SYSTEM, $membre->email, 'Mot de passe perdu ?', $message, $membre->id, $membre->clef_pgp);

	}

	static public function recoverPasswordConfirm($code)
	{
		if (substr_count($code, '.') !== 2)
		{
			return false;
................................................................................
		$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::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(false);

		return true;
	}

	public function canAccess($category, $permission)
	{
		if (!$this->user)
................................................................................

		return $out;
	}

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


		$content = "Ce message vous a été envoyé par :\n";
		$content.= sprintf("%s\n%s\n\n", $user->identite, $user->email);

		$content.= str_repeat('=', 70) . "\n\n";
		$content.= $message;


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

		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)
................................................................................
			{
				throw new UserException('Clé PGP invalide : impossible d\'extraire l\'empreinte.');
			}
		}

		$db = DB::getInstance();
		$db->update('membres', $data, $db->where('id', (int)$this->user->id));
		$this->refresh(false);

		return true;
	}
}

Modified src/include/lib/Garradin/Plugin.php from [d5458d89d2] to [45828504b6].

1
2
3
4
5
6


7
8
9
10
11
12
13
..
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
..
58
59
60
61
62
63
64



65
66
67
68
69
70
71
...
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
...
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
...
280
281
282
283
284
285
286
287




288
289


290
291
292
293
294
295
296
297
298
...
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
...
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
...
375
376
377
378
379
380
381
382
383


384
385














































386
387
388
389
390
391
392
...
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
...
635
636
637
638
639
640
641

642
643
644
645
646
647
648
...
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
<?php

namespace Garradin;

class Plugin
{


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

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



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



	}

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

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




		$this->id = $id;
	}

	/**
	 * Enregistrer les changements dans la config
	 */
................................................................................
		$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'];

		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))
................................................................................
	 * 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;
	}

	/**
................................................................................
		{
			$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],
			'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
................................................................................
	{
		$callable_name = '';

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







		$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.'"');
			}
		}



		$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();
	}
................................................................................
		$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);

		}

		return $plugins;
	}

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





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

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

................................................................................
		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()
	{


		$db = DB::getInstance();
		return $db->getAssoc('SELECT id, nom FROM plugins WHERE menu = 1 ORDER BY nom;');














































	}

	/**
	 * Liste les plugins téléchargés mais non installés
	 * @return array Liste des plugins téléchargés
	 */
	static public function listDownloaded()
................................................................................
		$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))
			{
				// Sélectionner les archives PHAR
				$file = $match[1];
			}
			elseif (is_dir(PLUGINS_ROOT . '/' . $file)
				&& preg_match('!^([a-zA-Z0-9_.-]+)$!i', $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
................................................................................
			'officiel' 	=> 	(int)(bool)$official,
			'nom'		=>	$infos->nom,
			'description'=>	$infos->description,
			'auteur'	=>	$infos->auteur,
			'url'		=>	$infos->url,
			'version'	=>	$infos->version,
			'menu'		=>	(int)(bool)$infos->menu,

			'config'	=>	$config,
		]);

		if (file_exists($path . '/install.php'))
		{
			$plugin = new Plugin($id);
			require $plugin->path() . '/install.php';
................................................................................

	/**
	 * 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)
	{
		$list = DB::getInstance()->get('SELECT * FROM plugins_signaux WHERE signal = ?;', $signal);


		foreach ($list as $row)
		{


			if (!in_array($row->plugin, self::$signal_files))
			{
				require_once self::getPath($row->plugin) . '/signals.php';

			}

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

			if ($return)

				return $return;
		}


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






>
>







 







<
<
|










>
>
|
>
>
>







 







>
>
>







 







|







 







|







 







|
>
>
>
>
|
<
>
>
|
<







 







>
>
>
>
>
>













>
>







 







>











>
>
>
>
>







 







|

>
>

|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







|





|







 







>







 







|


>



>
>
|

<
>


|


>

|
|
>
|


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
..
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
..
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
...
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
...
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
...
288
289
290
291
292
293
294
295
296
297
298
299
300

301
302
303

304
305
306
307
308
309
310
...
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
...
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
...
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
...
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
...
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
...
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
<?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',
................................................................................
		'pdf' => 'application/pdf',
		'png' => 'image/png',
		'swf' => 'application/shockwave-flash',
		'xml' => 'text/xml',
		'svg' => 'image/svg+xml',
	];



	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));
		}

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

		$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
	 */
................................................................................
		$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'];

		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))
................................................................................
	 * 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;
	}

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

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

		return DB::getInstance()->update('plugins', [
			'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,
		], '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
................................................................................
	{
		$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();
	}
................................................................................
		$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;
		}

................................................................................
		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($user)
	{
		self::checkAndInstallSystemPlugins();

		$db = DB::getInstance();
		$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()
................................................................................
		$dir = dir(PLUGINS_ROOT);

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

			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('!^' . 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
................................................................................
			'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';
................................................................................

	/**
	 * 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, &$callback_return = null)
	{
		$list = DB::getInstance()->get('SELECT * FROM plugins_signaux WHERE signal = ?;', $signal);
		$system = explode(',', PLUGINS_SYSTEM);

		foreach ($list as $row)
		{
			// Ne pas appeler les plugins dont le code n'existe pas/plus,
			// SAUF si c'est un plugin système (auquel cas ça fera une erreur)
			if (!self::getPath($row->plugin, in_array($row->plugin, $system)))
			{

				continue;
			}

			$return = call_user_func_array('Garradin\\Plugin\\' . $row->callback, [&$params, &$callback_return]);

			if ($return)
			{
				return $return;
			}
		}

		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
		$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);

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







|







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::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 [0f45eebdb7].



































































































































































































































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
442
443
444
445
446
447
448
449
<?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'     => $config->mandatory ? false : true,
				];

				if ($config->type == 'checkbox')
				{
					$column->type = 'boolean';
				}
				elseif ($config->type == 'select')
				{
					$column->type = 'enum';
					$column->values = array_combine($config->options, $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 = [];

		static $no_transform_operators = ['IS NULL', 'IS NOT NULL', '= 0', '= 1', '&'];

		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' && !in_array($condition['operator'], $no_transform_operators))
				{
					$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);
			}
		}

		if (!count($query_groups))
		{
			throw new UserException('Aucune clause trouvée dans la recherche.');
		}

		// 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'));
		}

		$query_columns[] = $order;

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

		$query_columns = array_unique($query_columns);
		$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 [3200010089] to [66b6709354].

11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
..
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
...
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
	const NOT_A_DB = 42;
	const NO_APP_ID = 43;

	/**
	 * Renvoie la liste des fichiers SQLite sauvegardés
	 * @param  boolean $auto Si true ne renvoie que la liste des sauvegardes automatiques
	 * @return array 		 Liste des fichiers
	 */	
	public function getList($auto = false)
	{
		$ext = $auto ? 'auto\.\d+\.sqlite' : 'sqlite';

		$out = [];
		$dir = dir(DATA_ROOT);

................................................................................
	 * @return string Le nom de fichier de la sauvegarde ainsi créée
	 */
	public function create($auto = false)
	{
		$suffix = is_string($auto) ? $auto : ($auto ? 'auto.1' : date('Y-m-d-His'));

		$backup = str_replace('.sqlite', sprintf('.%s.sqlite', $suffix), DB_FILE);
		
		// Acquire a shared lock before copying
		$db = DB::getInstance();
		$db->exec('BEGIN IMMEDIATE TRANSACTION;');
		
		copy(DB_FILE, $backup);
		
		$db->exec('END TRANSACTION;');
		return basename($backup);
	}

	/**
	 * Effectue une rotation des sauvegardes automatiques
	 * association.auto.1.sqlite deviendra association.auto.2.sqlite par exemple
................................................................................


		$db->close();

		$backup = str_replace('.sqlite', date('.Y-m-d-His') . '.avant_restauration.sqlite', DB_FILE);

		DB::getInstance()->close();
		
		if (!rename(DB_FILE, $backup))
		{
			throw new \RuntimeException('Unable to backup current DB file.');
		}

		if (!copy($file, DB_FILE))
		{







|







 







|



|

|







 







|







11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
..
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
...
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
	const NOT_A_DB = 42;
	const NO_APP_ID = 43;

	/**
	 * Renvoie la liste des fichiers SQLite sauvegardés
	 * @param  boolean $auto Si true ne renvoie que la liste des sauvegardes automatiques
	 * @return array 		 Liste des fichiers
	 */
	public function getList($auto = false)
	{
		$ext = $auto ? 'auto\.\d+\.sqlite' : 'sqlite';

		$out = [];
		$dir = dir(DATA_ROOT);

................................................................................
	 * @return string Le nom de fichier de la sauvegarde ainsi créée
	 */
	public function create($auto = false)
	{
		$suffix = is_string($auto) ? $auto : ($auto ? 'auto.1' : date('Y-m-d-His'));

		$backup = str_replace('.sqlite', sprintf('.%s.sqlite', $suffix), DB_FILE);

		// Acquire a shared lock before copying
		$db = DB::getInstance();
		$db->exec('BEGIN IMMEDIATE TRANSACTION;');

		copy(DB_FILE, $backup);

		$db->exec('END TRANSACTION;');
		return basename($backup);
	}

	/**
	 * Effectue une rotation des sauvegardes automatiques
	 * association.auto.1.sqlite deviendra association.auto.2.sqlite par exemple
................................................................................


		$db->close();

		$backup = str_replace('.sqlite', date('.Y-m-d-His') . '.avant_restauration.sqlite', DB_FILE);

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

		if (!rename(DB_FILE, $backup))
		{
			throw new \RuntimeException('Unable to backup current DB file.');
		}

		if (!copy($file, DB_FILE))
		{

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

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







|
|
<
<
<







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 = 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/Template.php from [0415690ed3] 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
...
359
360
361
362
363
364
365



366
367
368
369
370
371
372

	private function __clone()
	{
	}

	public function __construct()
	{


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


		self::setCompileDir(CACHE_ROOT . '/compiled');
		self::setTemplateDir(ROOT . '/templates');


		parent::__construct();








		$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);

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

				$value = $binary;
			}




			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 . '/> ' 







>
>





>
|
<
>

<
>
>
>
>
>
>
>







 







>
>
>







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
...
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384

	private function __clone()
	{
	}

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

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

		$this->setTemplatesDir(ROOT . '/templates');
		$this->setCompiledDir(CACHE_ROOT . '/compiled');

		$this->setNamespace('Garradin');


		// 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);

................................................................................
					{
						$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 [df46f1372d] to [b08ae6096e].

2
3
4
5
6
7
8

9
10
11




12
13
14
15
16
17
18
...
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
...
758
759
760
761
762
763
764
765

















































































































































namespace Garradin;

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


class Utils
{




    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',
................................................................................
        $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;
................................................................................

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

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























































































































































>



>
>
>
>







 







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







 







|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
...
376
377
378
379
380
381
382


























































































383
384
385
386
387
388
389
...
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

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',
................................................................................
        $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 clearCaches($path = false)
    {
        if (!$path)
        {
            self::clearCaches('compiled');
            self::clearCaches('static');
            return true;
................................................................................

        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 ($context == 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
<h1>Erreur</h1>
<p>Garradin n'est pas installé sur un sous-domaine dédié.</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>


>


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 [20f8c32d13] 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
..
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
<!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-09-14" media="all" />
    {if isset($js) || isset($custom_js)}
        <script type="text/javascript" src="{$admin_url}static/scripts/global.js"></script>
    {/if}
    {if isset($custom_js)}
        {foreach from=$custom_js item="js"}
            <script type="text/javascript" src="{$admin_url}static/scripts/{$js}"></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" />
        {/foreach}
    {/if}
    {if isset($plugin_css)}
        {foreach from=$plugin_css item="css"}
            <link rel="stylesheet" type="text/css" href="{plugin_url file=$css}" />
        {/foreach}
    {/if}
    {if isset($plugin_js)}
        {foreach from=$plugin_js item="js"}
            <script type="text/javascript" src="{plugin_url file=$js}"></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)" />
    {if isset($config)}
        {custom_colors config=$config}
    {/if}
</head>

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

................................................................................
    <?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}
                </ul>
            {/if}
        </li>
        {if $session->canAccess('membres', Garradin\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)}
            <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>
                {/if}
            </ul>
            {/if}
            </li>
        {/if}
        {if $session->canAccess('compta', Garradin\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)}
                <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)}
                <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)}
            <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)}
            <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}
        <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>







|

|



|




|




|




|


|
|







 







|
|




|

|



<
<
|
<




|


|




|






|









|







|











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
..
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
<!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?{$version_hash}1" media="all" />
    {if isset($js) || isset($custom_js)}
        <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}?{$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}?{$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}?{$version_hash}" />
        {/foreach}
    {/if}
    {if isset($plugin_js)}
        {foreach from=$plugin_js item="js"}
            <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?{$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}">

................................................................................
    <?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="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', 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', 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>


                <li class="member message{if $current == 'membres/message'} current{/if}"><a href="{$admin_url}membres/message_collectif.php">Message collectif</a></li>

            </ul>
            {/if}
            </li>
        {/if}
        {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', 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', 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', 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', 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') || !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
                <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)}
                        <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)}
                        <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)}
    {form_errors}

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

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







|


|











|







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', Membres::DROIT_ECRITURE)}
                        <a class="icn" href="{$admin_url}compta/banques/rapprocher.php?id={$compte.id}" title="Rapprocher">☑</a>
                    {/if}
                    {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', 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
..
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

<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>
            </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>
            <input type="hidden" name="id" value="{$compte.id}" />
            <input type="submit" value="Afficher" />
        </p>
    </fieldset>
</form>

{form_errors}
................................................................................
                <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)}
                    <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>







|
|











|







 







|







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
..
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

<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 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 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}
................................................................................
                <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', 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
        </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)}
                <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>







|







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', 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
        </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)}
        <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>







|







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', 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
{include file="admin/_head.tpl" title="Comptabilité" current="compta"}

{if $session->canAccess('compta', Garradin\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">


|







1
2
3
4
5
6
7
8
9
10
{include file="admin/_head.tpl" title="Comptabilité" current="compta"}

{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
..
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
{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)}
        <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>
................................................................................
        </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)}
                <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>





|







 







|







1
2
3
4
5
6
7
8
9
10
11
12
13
..
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
{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', 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>
................................................................................
        </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', 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
{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><a href="{$admin_url}compta/operations/recherche.php">Recherche d'opération</a></li>*}
    {if $session->canAccess('compta', Garradin\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}
<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();">
            <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>



|
|
|

|




|



|







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 == 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', Membres::DROIT_ADMIN)}
        <li><a href="{$admin_url}compta/operations/recherche_sql.php">Recherche par requête SQL</a></li>
    {/if}
</ul>

{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 == 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
..
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
{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)}
        <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>
................................................................................
        </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)}
                <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>





|







 







|







1
2
3
4
5
6
7
8
9
10
11
12
13
..
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
{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', 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>
................................................................................
        </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', 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
            </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>
                {/foreach}
                {if ($type == Garradin\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}








|

|







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 == Compta\Categories::DEPENSES && $compte.id == $operation.compte_credit) || $compte.id == $operation.compte_debit} selected="selected"{/if}>{$compte.libelle} - {$compte.banque}</option>
                {/foreach}
                {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
..
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
..
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
{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}
<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">
................................................................................
        {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/?cat={$operation.id_categorie}">{$categorie.intitule}</a>
        </dd>
    {/if}

    <dt>Exercice</dt>
    <dd>
        <a href="{$admin_url}compta/exercices/">{$exercice.libelle}</a>
................................................................................
            <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)}
                <a href="{$admin_url}membres/fiche.php?id={$operation.id_auteur}">{$nom_auteur}</a>
            {else}
                {$nom_auteur}
            {/if}
        {else}
            <em>membre supprimé</em>
        {/if}


|







 







|







 







|







1
2
3
4
5
6
7
8
9
10
..
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
..
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
{include file="admin/_head.tpl" title="Opération n°%d"|args:$operation.id current="compta/gestion"}

{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">
................................................................................
        {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 == 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>
................................................................................
            <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', 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
            <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)}
            <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)}
    <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>







|









|







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', 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', 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
<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 == '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>
</ul>

|
>
|
|
|
<
|

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 == '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 == 'plugins'} class="current"{/if}><a href="{$admin_url}config/plugins.php">Extensions</a></li>
</ul>

Modified src/templates/admin/config/categories/index.tpl from [9c762d5e02] to [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
{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>

|
>
>




|












>
|

|







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

Modified src/templates/admin/config/categories/modifier.tpl from [206fa39a37] to [7b22c4c7fb].

1


2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
..
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
{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>
                <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} />
|
>
>










<
<







 







<
<
<
<
<

|



|



|



|







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


14
15
16
17
18
19
20
..
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
{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>
                <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} />

Modified src/templates/admin/config/categories/supprimer.tpl from [145753eeb5] to [9ebed2a419].

1


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







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

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

Modified src/templates/admin/config/donnees/import.tpl from [e2b6c250be] to [79252f8a0f].

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


|
>
>




|
>
|
|
|
>
|




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

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

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}
    <p class="confirm">
        La configuration a bien été enregistrée.
    </p>
{/if}

{form_errors}





|







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 && !$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
{include file="admin/_head.tpl" current="config" js=1}

{include file="admin/config/_menu.tpl" current="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">


|







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="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
..
52
53
54
55
56
57
58

59
60
61
62
63
64
65
    </form>
{else}
    {if !empty($liste_installes)}
        <table class="list">
            <thead>
                <tr>
                    <th>Extension</th>
                    <td>Auteur</td>
                    <td>Version installée</td>
                    <td></td>
                </tr>
            </thead>
            <tbody>
                {foreach from=$liste_installes item="plugin"}
                <tr>
                    <th>
                        <h4>{$plugin.nom}</h4>
                        <small>{$plugin.description}</small>
                    </th>






                    <td>
                        <a href="{$plugin.url}" onclick="return !window.open(this.href);">{$plugin.auteur}</a>
                    </td>
                    <td>
                        {$plugin.version}
                    </td>
                    <td class="actions">
................................................................................
                            <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>

                </tr>
                {/foreach}
            </tbody>
        </table>
    {else}
        <p class="help">
            Aucune extension n'est installée.







|






|




>
>
>
>
>
>







 







>







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
..
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></td>
                    <td>Version installée</td>
                    <td></td>
                </tr>
            </thead>
            <tbody>
                {foreach from=$liste_installes item="plugin"}
                <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">
................................................................................
                            <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
..
33
34
35
36
37
38
39
40
41


















42
43
44
45
46
47
48
{include file="admin/_head.tpl" title="Configuration — Site public" current="config" js=1}

{form_errors}

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












{if isset($edit)}
    <form method="post" action="{$self_url}">
        <h3>Éditer un squelette</h3>

        {if $ok}
        <p class="confirm">
            Modifications enregistrées.
        </p>
................................................................................
    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>


















    </fieldset>

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

        {if $reset_ok}






>
>
>
>
>
>
>
>
>
>
>
|







 







|
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







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
..
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
{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>
{elseif isset($edit)}
    <form method="post" action="{$self_url}">
        <h3>Éditer un squelette</h3>

        {if $ok}
        <p class="confirm">
            Modifications enregistrées.
        </p>
................................................................................
    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>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
{include file="admin/_head.tpl" title="Bonjour %s !"|args:$user.identite current="home"}



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

>
>







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
..
43
44
45
46
47
48
49
50




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

{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 />
................................................................................
    </p>

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

</form>





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







 








>
>
>
>

1
2
3
4
5
6
7
8
..
43
44
45
46
47
48
49
50
51
52
53
54
55
{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 />
................................................................................
    </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 [0b734d6ba4].







































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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 Office</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
{include file="admin/_head.tpl" title="Action collective sur les membres" current="membres"}





{form_errors}

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

>
>
>
>







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
            </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)}
    <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"}







|







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', 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"}

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

1
2
3
4
5
6
7
8
9
10
11
12
13
..
51
52
53
54
55
56
57

58

59
60
61
62
63
64
65
..
78
79
80
81
82
83
84
85
86
87
88
89
90

91

92
93
94
95
96
97
98
99
{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}
        <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}
................................................................................
        {else}
            <span class="error">En retard</span>
            — <a href="{$admin_url}membres/cotisations/rappels.php?id={$membre.id}">Suivi des rappels</a>
        {/if}
    </dd>
    {/foreach}
{/if}

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

</dl>

{if !empty($cotisations)}
<table class="list">
    <thead>
        <th>Date</th>
        <td>Cotisation</td>
................................................................................
                        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)}
                        <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>
            </tr>
        {/foreach}
    </tbody>
</table>
{/if}

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




|
|







 







>

>







 







|





>

>








1
2
3
4
5
6
7
8
9
10
11
12
13
..
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
..
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
{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>
    {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}
................................................................................
        {else}
            <span class="error">En retard</span>
            — <a href="{$admin_url}membres/cotisations/rappels.php?id={$membre.id}">Suivi des rappels</a>
        {/if}
    </dd>
    {/foreach}
{/if}
{if $session->canAccess('membres', Membres::DROIT_ECRITURE)}
    <dt><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></dt>
{/if}
</dl>

{if !empty($cotisations)}
<table class="list">
    <thead>
        <th>Date</th>
        <td>Cotisation</td>
................................................................................
                        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', 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>
                    {if $session->canAccess('membres', Membres::DROIT_ECRITURE)}
                    <a class="icn" href="{$admin_url}membres/cotisations/supprimer.php?id={$c.id}" title="Supprimer cette cotisation pour ce membre">✘</a>
                    {/if}
                </td>
            </tr>
        {/foreach}
    </tbody>
</table>
{/if}

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

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
{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}
            <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)}
            <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)}
    <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}">






|










|







|







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', 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', 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', 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
{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)}
        <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}">





|







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', 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
{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)}
        <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}">





|







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', 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 [2f3ec3b601].

1
2
3
4

5

6
7
8
9
10
11
12
13
..
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="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)}
        <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>
................................................................................
                    {/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)}
                        <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)}

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




>
|
>
|







 







|









|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
..
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
{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>
    {if $session->canAccess('membres', Membres::DROIT_ECRITURE)}
        <li><a href="{$admin_url}membres/cotisations/ajout.php">Saisie d'une cotisation</a></li>
    {/if}
    {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>
................................................................................
                    {/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', 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', 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 [6fd6dc7342].

1
2
3
4

5

6
7
8
9
10
11

12
13
14
15
16
17
18
..
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
{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}
        <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>
        <legend>Enregistrer un rappel fait à ce membre</legend>
        <dl>
            <dt><label for="f_id_cotisation">Cotisation</label></dt>
            <dd>
                <select id="f_id_cotisation" name="id_cotisation">
................................................................................
        </dl>
        <p class="submit">
            {csrf_field key="add_rappel_%s"|args:$membre.id}
            <input type="submit" name="save" value="Enregistrer le rappel &rarr;" />
        </p>
    </fieldset>
</form>


{if !empty($rappels)}
<table class="list">
    <thead>
        <th>Date du rappel</th>
        <td>Moyen de communication</td>
        <td>Cotisation</td>
................................................................................
        <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}
                        Autre
                    {elseif $r.media == Garradin\Rappels_envoyes::MEDIA_COURRIER}
                        Courrier
                    {elseif $r.media == Garradin\Rappels_envoyes::MEDIA_TELEPHONE}
                        Téléphone
                    {else}
                        E-Mail
                    {/if}
                </td>
                <td>
                    {$r.intitule} — 




>
|
>
|





>







 







>







 







|

|

|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
..
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
{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>
    {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>

{if $session->canAccess('membres', Membres::DROIT_ECRITURE)}
<form method="post" action="{$self_url}">
    <fieldset>
        <legend>Enregistrer un rappel fait à ce membre</legend>
        <dl>
            <dt><label for="f_id_cotisation">Cotisation</label></dt>
            <dd>
                <select id="f_id_cotisation" name="id_cotisation">
................................................................................
        </dl>
        <p class="submit">
            {csrf_field key="add_rappel_%s"|args:$membre.id}
            <input type="submit" name="save" value="Enregistrer le rappel &rarr;" />
        </p>
    </fieldset>
</form>
{/if}

{if !empty($rappels)}
<table class="list">
    <thead>
        <th>Date du rappel</th>
        <td>Moyen de communication</td>
        <td>Cotisation</td>
................................................................................
        <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 == Rappels_envoyes::MEDIA_AUTRE}
                        Autre
                    {elseif $r.media == Rappels_envoyes::MEDIA_COURRIER}
                        Courrier
                    {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
..
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
{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}
        <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}

................................................................................
                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"}





|







 







<










1
2
3
4
5
6
7
8
9
10
11
12
13
..
24
25
26
27
28
29
30

31
32
33
34
35
36
37
38
39
40
{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', 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}

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

    <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 [3ab24d7fdd].

1
2
3
4

5

6
7
8
9
10
11
12
13
..
42
43
44
45
46
47
48

49

50
51
52
53
54
55
56
57
58
59
60
61
62
{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)}
        <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} — 
................................................................................
            {foreach from=$liste item="co"}
                <tr>
                    <td class="num"><a href="{$admin_url}membres/fiche.php?id={$co.id_membre}">{$co.numero}</a></td>
                    <th>{$co.nom}</th>
                    <td>{if $co.a_jour}<b class="confirm">À jour</b>{else}<b class="error">En retard</b>{/if}</td>
                    <td>{$co.date|format_sqlite_date_to_french}</td>
                    <td class="actions">

                        <a class="icn" href="{$admin_url}membres/cotisations/ajout.php?id={$co.id_membre}&amp;cotisation={$cotisation.id}" title="Saisir une cotisation">➕</a>

                        <a class="icn" href="{$admin_url}membres/cotisations.php?id={$co.id_membre}" title="Voir toutes les cotisations de ce membre">𝍢</a>
                        <a class="icn" href="{$admin_url}membres/cotisations/rappels.php?id={$co.id_membre}" title="Rappels envoyés à ce membre">⚠</a>
                    </td>
                </tr>
            {/foreach}
        </tbody>
    </table>

    {pagination url=$pagination_url page=$page bypage=$bypage total=$total}
{/if}


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




>
|
>
|







 







>

>













1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
..
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
{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>
    {if $session->canAccess('membres', Membres::DROIT_ECRITURE)}
        <li><a href="{$admin_url}membres/cotisations/ajout.php">Saisie d'une cotisation</a></li>
    {/if}
    {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} — 
................................................................................
            {foreach from=$liste item="co"}
                <tr>
                    <td class="num"><a href="{$admin_url}membres/fiche.php?id={$co.id_membre}">{$co.numero}</a></td>
                    <th>{$co.nom}</th>
                    <td>{if $co.a_jour}<b class="confirm">À jour</b>{else}<b class="error">En retard</b>{/if}</td>
                    <td>{$co.date|format_sqlite_date_to_french}</td>
                    <td class="actions">
                        {if $session->canAccess('membres', Membres::DROIT_ECRITURE)}
                        <a class="icn" href="{$admin_url}membres/cotisations/ajout.php?id={$co.id_membre}&amp;cotisation={$cotisation.id}" title="Saisir une cotisation">➕</a>
                        {/if}
                        <a class="icn" href="{$admin_url}membres/cotisations.php?id={$co.id_membre}" title="Voir toutes les cotisations de ce membre">𝍢</a>
                        <a class="icn" href="{$admin_url}membres/cotisations/rappels.php?id={$co.id_membre}" title="Rappels envoyés à ce membre">⚠</a>
                    </td>
                </tr>
            {/foreach}
        </tbody>
    </table>

    {pagination url=$pagination_url page=$page bypage=$bypage total=$total}
{/if}


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

Modified src/templates/admin/membres/fiche.tpl from [184a802b43] to [afe9db5157].

1
2
3
4
5
6
7
8
9
10
11
12
13
..
42
43
44
45
46
47
48

49
50
51
52
53
54
55

56
57
58
59
60
61
62
..
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
{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}
        <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}
................................................................................
        {else}
            Aucune cotisation enregistrée
        {/if} 
    </dt>
    <dd>
        <a href="{$admin_url}membres/cotisations.php?id={$membre.id}">Voir l'historique</a>
    </dd>

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

</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>
................................................................................
				{/if}
		{/if}
		</dd>
	</dl>
</aside>

<dl class="describe">
    {foreach from=$champs key="c" item="config"}
    <dt>{$config.title}</dt>
    <dd>
        {if $config.type == 'checkbox'}
            {if $membre->$c}Oui{else}Non{/if}
        {elseif empty($membre->$c)}
            <em>(Non renseigné)</em>
        {elseif $c == 'nom'}
            <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'}
            <a href="mailto:{$membre->$c}">{$membre->$c}</a>
        {elseif $config.type == 'tel'}
            <a href="tel:{$membre->$c}">{$membre->$c|format_tel}</a>
        {elseif $config.type == 'country'}
            {$membre->$c|get_country_name}
        {elseif $config.type == 'date' || $config.type == 'datetime'}
            {$membre->$c|format_sqlite_date_to_french}
        {elseif $config.type == 'password'}
            *******
        {elseif $config.type == 'multiple'}
            <ul>
            {foreach from=$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"}




|
|







 







>
|
|
|
|
|
|
|
>







 







|
|

|



|

|
|
>
|
<
|
|

|

|

|

|

|













1
2
3
4
5
6
7
8
9
10
11
12
13
..
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
..
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
{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>
    {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}
................................................................................
        {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}
    {/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>
................................................................................
				{/if}
		{/if}
		</dd>
	</dl>
</aside>

<dl class="describe">
    {foreach from=$champs key="c" item="c_config"}
    <dt>{$c_config.title}</dt>
    <dd>
        {if $c_config.type == 'checkbox'}
            {if $membre->$c}Oui{else}Non{/if}
        {elseif empty($membre->$c)}
            <em>(Non renseigné)</em>
        {elseif $c == $c_config.champ_identite}
            <strong>{$membre->$c}</strong>
        {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}
        {elseif $c_config.type == 'tel'}
            <a href="tel:{$membre->$c}">{$membre->$c|format_tel}</a>
        {elseif $c_config.type == 'country'}
            {$membre->$c|get_country_name}
        {elseif $c_config.type == 'date' || $c_config.type == 'datetime'}
            {$membre->$c|format_sqlite_date_to_french}
        {elseif $c_config.type == 'password'}
            *******
        {elseif $c_config.type == 'multiple'}
            <ul>
            {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
{include file="admin/_head.tpl" title="Import & export des membres" current="membres" js=1}



<ul class="actions">
    <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">







































    <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>
            </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.
            </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>
            </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>
            <dd class="galette">
                <table class="list auto">
                    <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>



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


>
>
|







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>









|



|
|


|
|


|
|

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


>
>








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

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 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">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 (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_csv" value="csv" {form_field name=type checked="csv"} />
                <label for="f_type_csv">Fichier CSV générique</label>
            </dd>
            <dd class="help">
                Vous pourrez choisir la correspondance entre colonnes du CSV et champs des fiches membres
                dans le prochain écran.
            </dd>





















        </dl>
    </fieldset>

    {/if}

    <p class="submit">
        {csrf_field key="membres_import"}
        <input type="submit" name="import" value="Importer &rarr;" />
    </p>

</form>















{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
{include file="admin/_head.tpl" title="Liste des membres" current="membres" js=1}

{if $session->canAccess('membres', Garradin\Membres::DROIT_ECRITURE)}
<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)
                || !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">
    <fieldset>
        <legend>Rechercher un membre</legend>
        <input type="text" name="r" 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">

    {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}
                {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}
                    {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>
                    </td>
                </tr>
            {/foreach}
        </tbody>
    </table>

    {if $session->canAccess('membres', Garradin\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>

    {/if}


    {pagination url=$pagination_url page=$page bypage=$bypage total=$total}
    {else}
    <p class="alert">
        Aucun membre trouvé.
    </p>
    {/if}

    </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"}


|
<
<
<
<
<
<
<
<
<












|










|


|




<
<
|

|



|









|









|




<
<
|
<
<
<
<
<
<
>

>


|



|

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

<
<
<
<
<
<
<
<
<
<
<

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}

{include file="admin/membres/_nav.tpl" current="index"}










{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', 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/recherche.php" class="shortFormLeft">
    <fieldset>
        <legend>Rechercher un membre</legend>
        <input type="text" name="qt" value="" />
        <input type="submit" value="Chercher &rarr;" />
    </fieldset>
</form>



<form method="post" action="action.php" class="memberList">

{if !empty($liste)}
    <table class="list">
        <thead class="userOrder">
            <tr>
                {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', 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>
                        {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>


    {if $session->canAccess('membres', Membres::DROIT_ADMIN)}






        {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}
    <p class="alert">
        Aucun membre trouvé.
    </p>
{/if}

</form>






























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

Modified src/templates/admin/membres/message.tpl from [6ceb73fe1d] to [3012105144].

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

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







<
<
<







4
5
6
7
8
9
10



11
12
13
14
15
16
17

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



            <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
{include file="admin/_head.tpl" title="Envoyer un message collectif" current="membres/message_collectif"}

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

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


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



|
|
|
|
|
|
|
|
|
|
|
|
|
>
>
>
>
>
>
|
|
>
|
<
<
>
>
>
|
>
|
<
|
|
|
>
>
>
>
|
|

|
|
|
|




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

{form_errors}

<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><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_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
..
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
{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}
        <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}

................................................................................
            </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}
    <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"}





|







 







|







1
2
3
4
5
6
7
8
9
10
11
12
13
..
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
{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', 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}

................................................................................
            </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', 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 [f88de1f28b].

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
{include file="admin/_head.tpl" title="Recherche de membre" current="membres"}

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



<form method="get" action="{$admin_url}membres/recherche.php" class="shortFormLeft">
    <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>
            <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>
                {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>
                            {/foreach}
                        </select>
                    </dd>
                {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>
                            <option value="0"{form_field name="r" default=$recherche selected=0}>Non</option>
                        </select>
                    </dd>
                {/if}
            {/foreach}
        </dl>
        <p class="submit">
            <input type="submit" value="Chercher &rarr;" />



        </p>
    </fieldset>
</form>

{if $session->canAccess('membres', Garradin\Membres::DROIT_ECRITURE)}






























    <form method="post" action="{$admin_url}membres/action.php" class="memberList">



    {if !empty($liste)}
    <table class="list search">
        <thead>
            {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>
                    {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">
                    	<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>

    {if $session->canAccess('membres', Garradin\Membres::DROIT_ADMIN)}
    <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>
    {/if}


    {elseif $recherche != ''}
    <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>
{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>
    {else}
    <p class="info">
        Aucun membre trouvé.


    </p>
    {/if}
{/if}

<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 (document.getElementById('f_' + elm.value))
        {
            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
        {
            document.getElementById('f_texte').disabled = false;
            document.getElementById('f_free').style.display = 'block';
        }

        return true;
    }

    document.getElementById('f_champ').onchange = function() { selectField(this); };
    window.onload = selectField(document.getElementById('f_champ'));
}())
{/literal}
</script>

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

|
<
<
<
<
<
<

>

|
|
|
<
<
<
>
>
>
|
<
<
>
>
|
|
>
>
>
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
|
>
>
>
|
|


<
>
>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>

<
<
|
|
|
|
|
|
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
>
|
<
<
<
<
<
>
|
<
>
|
<
<
<
<

<
>
>
>
|
<
<
>
|
<
<
<
>
|
<
|
<
<
<
<
>
|
>
|
<
<
<
>
>
|
<
|
>
|
<
>
>
|
>
>
>
>
>
|
|
<
>
>
>
>
>
|
>
|
|
|
|
<
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
<
>
>
>
>
|
|
|
|
>
>
>
|
<
<
<
>
>
|
|
<

<
<
<
<
>

<
>
>
>
|
<
>
>
|
<
<
<
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<

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
{include file="admin/_head.tpl" title="Recherche de membre" current="membres" js=1 custom_js=['query_builder.min.js']}

{include file="admin/membres/_nav.tpl" current="recherche"}







{form_errors}

<form method="post" action="{$admin_url}membres/recherche.php" id="queryBuilderForm">
	<fieldset>
		<legend>Rechercher un membre</legend>



		<div class="queryBuilder" id="queryBuilder"></div>
		<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>
			<label><input type="checkbox" name="desc" value="1" {form_field name="desc" checked=1 default=$desc} /> Tri inversé</label>
			<label>Limiter à <input type="number" value="{$limit}" name="limit" size="5" /> résultats</label>
		</p>





























		<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}" />
			<input type="submit" name="save" value="{if $id}Enregistrer : {$recherche.intitule|truncate:40:"…":true}{else}Enregistrer cette recherche{/if}" class="minor" />
		</p>
	</fieldset>
</form>


<script type="text/javascript">
var colonnes = {$colonnes|escape:'json'};

{literal}
var traductions = {
	"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 situé entre",
	"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",
	"is false": "non",
	"Matches ALL of the following conditions:": "Correspond à TOUS les critères suivants :",
	"Matches ANY of the following conditions:": "Correspond à UN 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) {
	return traductions[str];
};
q.loadDefaultOperators();
q.buildInput = function (type, label, column) {
	if (label == '+')























	{











		label = '➕';
	}





	else if (label == '-')
	{

		label = '➖';
	}






	var i = document.createElement('input');
	i.type = type == 'integer' ? 'number' : type;
	i.value = label;



	if (type == 'button')
	{



		i.className = 'icn action';
	}






	return i;
};
q.init(document.getElementById('queryBuilder'));




$('#queryBuilderForm').onsubmit = function () {
	$('#jsonQuery').value = JSON.stringify(q.export());
};

{/literal}
q.import({$query|escape:'json'});
</script>



{if !empty($result)}
	{if $session->canAccess('membres', Membres::DROIT_ECRITURE)}
		<form method="post" action="{$admin_url}membres/action.php" class="memberList">
	{/if}

	<p class="help">{$result|count} membres trouvés pour cette recherche.</p>
	<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>
	{if $session->canAccess('membres', Membres::DROIT_ADMIN)}
		{include file="admin/membres/_list_actions.tpl" colspan=count($result_header)+1}
	{/if}
	</table>




	{if $session->canAccess('membres', Membres::DROIT_ECRITURE)}
		</form>
	{/if}






{elseif $result !== null}


	<p class="alert">
		Aucun membre trouvé.
	</p>


	</form>
{/if}





























{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
{include file="admin/_head.tpl" title="Recherche par requête SQL" current="membres"}




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


{if !empty($error)}
<p class="error">
    <strong>Erreur dans la requête SQL :</strong><br />
    {$error}
</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="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>
{/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"}
|

>
>
>
|
|
|
|
|
|
|
|
|
|
|
>
>
>
>
|
|
|
>

<
<
<
|
<
<




>
|
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>
>
>
|

<
<
<
<
<
<
<
<
<
<

|
|
|




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

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" 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="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}




{form_errors}



<form method="post" action="{$admin_url}membres/action.php" class="memberList">

{if !empty($result)}

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











{else}
	<p class="alert">
		Aucun membre trouvé.
	</p>
{/if}

</form>































{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
{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)}
        <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}






|







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', 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
    </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))}
                <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 







|







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 < 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
..
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
..
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
{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>
</ul>

{if !empty($revisions)}
    <table class="list wikiRevisions">
    {foreach from=$revisions item="rev"}
        <tr>
            <td>
................................................................................
                    {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)}
                <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}
................................................................................
            </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)}
            <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)}
            <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}



|







 







|







 







|








|







1
2
3
4
5
6
7
8
9
10
11
..
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
..
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
{include file="admin/_head.tpl" title="Historique : %s"|args:$page.titre current="wiki"}

<ul class="actions">
    <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>
................................................................................
                    {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', 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}
................................................................................
            </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', 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', 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
...
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
{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)}
        <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}
            <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)}
        <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}
................................................................................
                </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)}
                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"}







|







|



|







 







|









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
...
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
{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', 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 == 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', 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}
................................................................................
                </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', 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
..
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<!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>
    <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 {
................................................................................
    }
    {/literal}
    </style>
</head>

<body>

<h1>Erreur</h1>

<p class="error">
    {$error|escape|nl2br}
</p>

<p>
    <a href="{$www_url}" onclick="history.back(); return false;">&larr; Retour</a>
</p>

</body>
</html>




|







 







|






|




1
2
3
4
5
6
7
8
9
10
11
12
..
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<!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>{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 {
................................................................................
    }
    {/literal}
    </style>
</head>

<body>

<h1>{if empty($title)}Erreur{else}{$title}{/if}</h1>

<p class="error">
    {$error|escape|nl2br}
</p>

<p>
    <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
Options -MultiViews -Indexes



<IfModule mod_rewrite.c>
    RewriteEngine on
    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
    RewriteRule .* index.php [QSA,L]
</IfModule>

<IfModule !mod_rewrite.c>
    ErrorDocument 404 /index.php
</IfModule>

>

>
|
<
<
|
|
|
<


<
|
<
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_version.c>


	<IfVersion >= 2.2.16>
		FallbackResource /_route.php
	</IfVersion>

</IfModule>


ErrorDocument 404 /_route.php

Modified src/www/admin/_inc.php from [e0bbbc300b] to [d6f6a6d40f].

66
67
68
69
70
71
72
73
74
75

76

77

78
79



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

    {

        $tpl->assign('nb_membres', (new Membres)->countAllButHidden());

    }
}









<

<
>

>
|
>

|
>
>
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', '');



    if ($session->get('plugins_menu') === null)
    {
        // Construction de la liste de plugins pour le menu
        // 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].

6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
$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', [







<
<





<
<







6
7
8
9
10
11
12


13
14
15
16
17


18
19
20
21
22
23
24
$session->requireAccess('compta', Membres::DROIT_ADMIN);

$e = new Compta\Exercices;
$import = new Compta\Import;

if (qg('export') == 'csv')
{


    $import->toCSV($e->getCurrentId());
    exit;
}
elseif (qg('export') == 'ods')
{


    $import->toODS($e->getCurrentId());
    exit;
}

if (f('import'))
{
    $form->check('compta_import', [

Modified src/www/admin/config/categories/index.php from [a435abf714] to [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
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');





<
<







 







|





|
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');

Modified src/www/admin/config/categories/modifier.php from [624c7118d6] to [6a75b928b9].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
..
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
..
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
..
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);
................................................................................
        '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'),
................................................................................
        // 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');





<
<







 







<







 







<











|







 







|

|
1
2
3
4
5


6
7
8
9
10
11
12
..
37
38
39
40
41
42
43

44
45
46
47
48
49
50
..
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
..
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);
................................................................................
        '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'),
................................................................................
        // 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');

Modified src/www/admin/config/categories/supprimer.php from [43f9a12d7b] to [93c453e7a7].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
..
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);
................................................................................
{
    $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');





<
<







 







|










|
1
2
3
4
5


6
7
8
9
10
11
12
..
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);
................................................................................
{
    $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');

Modified src/www/admin/config/donnees/import.php from [0d90db7427] to [f13c6c5a62].

1
2
3
4
5
6
<?php
namespace Garradin;

require_once __DIR__ . '/_inc.php';

$tpl->display('admin/config/import.tpl');



|

|
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');

Modified src/www/admin/config/plugins.php from [e71350c226] to [3d32d903c2].

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
        'plugin' => 'required',
    ]);

    if (!$form->hasErrors())
    {
        try {
            Plugin::install(f('plugin'), false);

            Utils::redirect(ADMIN_URL . 'config/plugins.php');
        }
        catch (UserException $e)
        {
            $form->addError($e->getMessage());
        }
    }
}

if (f('delete'))
{
    $form->check('delete_plugin_' . qg('delete'), [
        'plugin' => 'required',
    ]);

    if (!$form->hasErrors())
    {
        try {
            $plugin = new Plugin(qg('delete'));
            $plugin->uninstall();

            
            Utils::redirect(ADMIN_URL . 'config/plugins.php');
        }
        catch (UserException $e)
        {
            $form->addError($e->getMessage());
        }
    }







>











|
<
<






>
|







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
        'plugin' => 'required',
    ]);

    if (!$form->hasErrors())
    {
        try {
            Plugin::install(f('plugin'), false);
            $session->set('plugins_menu', null);
            Utils::redirect(ADMIN_URL . 'config/plugins.php');
        }
        catch (UserException $e)
        {
            $form->addError($e->getMessage());
        }
    }
}

if (f('delete'))
{
    $form->check('delete_plugin_' . qg('delete'));



    if (!$form->hasErrors())
    {
        try {
            $plugin = new Plugin(qg('delete'));
            $plugin->uninstall();
            $session->set('plugins_menu', null);

            Utils::redirect(ADMIN_URL . 'config/plugins.php');
        }
        catch (UserException $e)
        {
            $form->addError($e->getMessage());
        }
    }

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
<?php
namespace Garradin;

require_once __DIR__ . '/_inc.php';

if (f('save') && $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();

        Utils::redirect(ADMIN_URL . 'config/site.php?ok');
    }
    catch (UserException $e)

    {
        $form->addError($e->getMessage());
    }



}

if (f('select') && f('reset') && $form->check('squelettes'))
{
    try {
        foreach (f('select') as $source)
        {





|

|
<
<
<
|
<
|
|
<
>
|
<
<
>
>
>







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('desactiver_site') && $form->check('config_site'))
{
    $config->set('desactiver_site', true);



    $config->save();

    Utils::redirect(ADMIN_URL . 'config/site.php');
}

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
}
else
{
	$tpl->assign('cotisation', false);
}

$tpl->assign('custom_css', ['wiki.css']);



$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';
}







>
>










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
<?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/init.php';

Install::checkAndCreateDirectories();

if (!file_exists(DB_FILE))
{
    // Renommage du fichier sqlite à la version 0.5.0



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


>







1
2
3




























































4
5
6
7
8
9
10
11
12
13
<?php
namespace Garradin;





























































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

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
..
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
$session->requireAccess('membres', Membres::DROIT_ADMIN);

if (!f('selected') || !is_array(f('selected')) || !count(f('selected')))
{
    throw new UserException("Aucun membre sélectionné.");
}


























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.");
    }
}

$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',
    ]);
................................................................................
    if (!$form->hasErrors())
    {
        $membres->delete(f('selected'));
        Utils::redirect(ADMIN_URL . 'membres/');
    }
}

$tpl->assign('selected', f('selected'));
$tpl->assign('nb_selected', count(f('selected')));

if ($action == 'move')
{
    $cats = new Membres\Categories;

    $tpl->assign('membres_cats', $cats->listSimple());
}

$tpl->assign('action', $action);

$tpl->display('admin/membres/action.tpl');







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
|

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







 







|
|











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
..
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
$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;

        // 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.");
        }
    }
}







if ($action == 'move' && f('confirm'))
{
    $form->check('membres_action', [
        'selected' => 'required|array',
        'id_categorie' => 'required|numeric',
    ]);
................................................................................
    if (!$form->hasErrors())
    {
        $membres->delete(f('selected'));
        Utils::redirect(ADMIN_URL . 'membres/');
    }
}

$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');

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





<
<







1
2
3
4
5


6
7
8
9
10
11
12
<?php
namespace Garradin;

require_once __DIR__ . '/_inc.php';



qv(['id' => 'required|numeric']);

$id = (int) qg('id');

$membre = $membres->get($id);

if (!$membre)

Modified src/www/admin/membres/cotisations/index.php from [b198d992d0] to [960a734ef2].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
namespace Garradin;

require_once __DIR__ . '/../_inc.php';

$session->requireAccess('membres', Membres::DROIT_ECRITURE);

$cotisations = new Cotisations;

if ($session->canAccess('membres', Membres::DROIT_ADMIN))
{
	$cats = new Compta\Categories;

	if (f('save'))





<
<







1
2
3
4
5


6
7
8
9
10
11
12
<?php
namespace Garradin;

require_once __DIR__ . '/../_inc.php';



$cotisations = new Cotisations;

if ($session->canAccess('membres', Membres::DROIT_ADMIN))
{
	$cats = new Compta\Categories;

	if (f('save'))

Modified src/www/admin/membres/cotisations/rappels.php from [3c87990373] to [c3e2281ab7].

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


24
25
26
27
28
29
30
<?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)
................................................................................
}

$re = new Rappels_Envoyes;
$cm = new Membres\Cotisations;

if (f('save'))
{


	$medias = implode(',', [$re::MEDIA_EMAIL, $re::MEDIA_COURRIER, $re::MEDIA_TELEPHONE, $re::MEDIA_AUTRE]);

	$form->check('add_rappel_' . $membre->id, [
		'id_cotisation' => 'numeric|required',
		'media'         => 'numeric|required|in:' . $medias,
		'date'          => 'required|date_format:Y-m-d'
	]);





<
<







 







>
>







1
2
3
4
5


6
7
8
9
10
11
12
..
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php
namespace Garradin;

require_once __DIR__ . '/../_inc.php';



qv(['id' => 'required|numeric']);

$id = (int) qg('id');

$membre = $membres->get($id);

if (!$membre)
................................................................................
}

$re = new Rappels_Envoyes;
$cm = new Membres\Cotisations;

if (f('save'))
{
	$session->requireAccess('membres', Membres::DROIT_ECRITURE);

	$medias = implode(',', [$re::MEDIA_EMAIL, $re::MEDIA_COURRIER, $re::MEDIA_TELEPHONE, $re::MEDIA_AUTRE]);

	$form->check('add_rappel_' . $membre->id, [
		'id_cotisation' => 'numeric|required',
		'media'         => 'numeric|required|in:' . $medias,
		'date'          => 'required|date_format:Y-m-d'
	]);

Modified src/www/admin/membres/cotisations/voir.php from [5e91281c68] to [db337019a9].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
namespace Garradin;

require_once __DIR__ . '/../_inc.php';

$session->requireAccess('membres', Membres::DROIT_ECRITURE);

qv(['id' => 'required|numeric']);

$id = (int) qg('id');

$cotisations = new Cotisations;
$m_cotisations = new Membres\Cotisations;






<
<







1
2
3
4
5


6
7
8
9
10
11
12
<?php
namespace Garradin;

require_once __DIR__ . '/../_inc.php';



qv(['id' => 'required|numeric']);

$id = (int) qg('id');

$cotisations = new Cotisations;
$m_cotisations = new Membres\Cotisations;

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





<
<







1
2
3
4
5


6
7
8
9
10
11
12
<?php
namespace Garradin;

require_once __DIR__ . '/_inc.php';



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

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

$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'];


























if (f('import'))
{
    $form->check('membres_import', [
        'upload' => 'file|required',
        'type'   => 'required|in:galette,garradin',
        'galette_translate' => 'array',
    ]);

    if (!$form->hasErrors())
    {
        try
        {
            if (f('type') == 'galette')
            {
                $import->fromGalette($_FILES['upload']['tmp_name'], f('galette_translate'));

            }
            elseif (f('type') == 'garradin')
            {
                $import->fromCSV($_FILES['upload']['tmp_name'], $user->id);
            }
            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('garradin_champs', $champs);
$tpl->assign('galette_champs', $import->galette_fields);
$tpl->assign('translate', f('galette_translate'));

$tpl->display('admin/membres/import.tpl');







<
<





<
<







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|



|
<






|

|
>

|

|





<
<










>
>
>

<
<


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

$import = new Membres\Import;

$tpl->assign('tab', null !== qg('export') ? 'export' : 'import');

if (qg('export') == 'csv')
{


    $import->toCSV();
    exit;
}
elseif (qg('export') == '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());
        }
    }
}
elseif (f('import'))
{
    $form->check('membres_import', [
        'upload' => 'file|required',
        'type'   => 'required|in:csv,garradin',

    ]);

    if (!$form->hasErrors())
    {
        try
        {
            if (f('type') == 'garradin')
            {
                $import->fromGarradinCSV($_FILES['upload']['tmp_name'], $user->id);
                Utils::redirect(ADMIN_URL . 'membres/import.php?ok');
            }
            elseif (f('type') == 'csv')
            {
                $csv_file = $import->getCSVAsArray($_FILES['upload']['tmp_name']);
            }
            else
            {
                throw new UserException('Import inconnu.');
            }


        }
        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->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
<?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');

	$membres_cats = $cats->listSimple();
	$membres_cats_cachees = $cats->listHidden();

	$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)
	{
	    $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;

	if (qg('o'))
	    $order = qg('o');

	if (null !== qg('d'))
	    $desc = true;

	$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();
	}

	$tpl->assign('order', $order);
	$tpl->assign('desc', $desc);

	$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));

	$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('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('sent', null !== qg('sent'));

$tpl->display('admin/membres/index.tpl');





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

|
|

|
|

|
|
|
|
|
|
|

|
|
|
|

|
|
|

|
|

|
|

|

|
|
|
|
|
|

|
|

|

|
|

|

|
|
|
|
|
|

|
|
|

|
|

<
<



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












$cats = new Membres\Categories;
$champs = $config->get('champs_membres');

$membres_cats = $cats->listSimple();
$membres_cats_cachees = $cats->listHidden();

$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)
{
    $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;

if (qg('o'))
    $order = qg('o');

if (null !== qg('d'))
    $desc = true;

$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();
}

$tpl->assign('order', $order);
$tpl->assign('desc', $desc);

$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));

$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('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('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
<?php
namespace Garradin;

require_once __DIR__ . '/_inc.php';

$session->requireAccess('membres', Membres::DROIT_ADMIN);


if (f('save'))
{
    $form->check('send_message_collectif', [
        'sujet'      => 'required|string',
        'message'    => 'required|string',
        'dest'       => 'numeric',
        'subscribed' => 'boolean',
    ]);






















    if (!$form->hasErrors())
    {
        try {

            $membres->sendMessageToCategory(f('dest'), f('sujet'), f('message'), (bool) f('subscribed'));

            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->display('admin/membres/message_collectif.tpl');





|
>

|

|


<
|

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>




>
|
>









|
>

<
<
<

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

$cats = new Membres\Categories;
$recherche = new Recherche;

if (f('send'))
{
    $form->check('send_message_co', [
        'sujet'      => 'required|string',
        'message'    => 'required|string',

        'recipients' => 'required|string',
    ]);

    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'),
                f('message'), (bool) f('copie'));

            Utils::redirect(ADMIN_URL . 'membres/?sent');
        }
        catch (UserException $e)
        {
            $form->addError($e->getMessage());
        }
    }
}

$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 [d259486866].

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
<?php
namespace Garradin;

require_once __DIR__ . '/_inc.php';

$session->requireAccess('membres', Membres::DROIT_ECRITURE);

$recherche = trim(qg('r'));
$champ = trim(qg('c'));

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








$auto = false;



// On détermine magiquement quel champ on recherche
if (!$champ)

{
    $auto = true;


    if (is_numeric(trim($recherche))) {
        $champ = 'numero';

    }
    elseif (strpos($recherche, '@') !== false) {

        $champ = 'email';
    }
    else {

        $champ = $config->get('champ_identite');
    }
}













else
{
    if ($champ != 'numero' && !$champs->get($champ))

    {


        throw new UserException('Le champ demandé n\'existe pas.');
    }
}





if ($recherche != '')


{
    $result = $membres->search($champ, $recherche);



















    if (count($result) == 1 && $auto)
    {
        Utils::redirect(ADMIN_URL . 'membres/fiche.php?id=' . (int)$result[0]->id);
    }
}


$champs_liste = $champs->getList();
$champs_entete = $champs->getListedFields();







if (!isset($champs_entete->$champ))

{
    $champs_entete = array_merge(
        [$champ => $champs_liste->$champ],
        (array)$champs_entete



    );
}


$tpl->assign('champs_entete', $champs_entete);
$tpl->assign('champs_liste', $champs_liste);
$tpl->assign('champ', $champ);


if ($recherche != '')
{
    $tpl->assign('liste', $result);

}


















$tpl->assign('recherche', $recherche);








$tpl->display('admin/membres/recherche.tpl');





<
<
|
<


>
>
>
>
>
>
>

<
>
>
|
<
<
>
|
<
>
|
<
|
>

|
>
|

|
>
|

|
>
>
>
>
>
>
>
>
>
>
>
>
>
|

<
>
|
>
>
|

|
>
>
>
>

<
>
>
|
<
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|



|
>
|
<
<
>
>
>
>
>
>

<
>
|
<
<
<
>
>
>
|
|
>
|
<
<
<
>
|
<
|
<
>
|

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>


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
<?php
namespace Garradin;

require_once __DIR__ . '/_inc.php';



$recherche = new Recherche;


$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
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],
            ],
        ],
    ]];

    $order = $column;
}
elseif ($id)
{

    $r = $recherche->get($id);

    if (!$r || $r->type != Recherche::TYPE_JSON)
    {
        throw new UserException('Recherche inconnue ou invalide');
    }

    $query = $r->query;
    $order = $r->order;
    $desc = $r->desc;
    $limit = $r->limit;


    $tpl->assign('recherche', $r);
}


if (f('q') !== null)
{
    $query = json_decode(f('q'), true);
}

if ($query)
{
    try {
        $sql_query = $recherche->buildQuery('membres', $query, $order, $desc, $limit);
        $result = $recherche->searchSQL('membres', $sql_query);
    }
    catch (UserException $e) {
        $form->addError($e->getMessage());
        $query = null;
    }
}

if ($query)
{
    if (count($result) == 1 && $text_query !== '')
    {
        Utils::redirect(ADMIN_URL . 'membres/fiche.php?id=' . (int)$result[0]->id);
    }

    if (f('save') && !$form->hasErrors())
    {


        $query = [
            'query' => $query,
            'order' => $order,
            'limit' => $limit,
            'desc'  => $desc,
        ];


        if ($id)
        {



            $recherche->edit($id, [
                'type'    => Recherche::TYPE_JSON,
                'contenu' => $query,
            ]);
        }
        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));
}
else
{
    $query = [[
        'operator' => 'AND',
        'conditions' => [
            [
                'column'   => $config->get('champ_identite'),
                'operator' => '= ?',
                'values'   => [''],
            ],
        ],
    ]];
    $result = null;
}

$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
<?php
namespace Garradin;

require_once __DIR__ . '/_inc.php';

$session->requireAccess('membres', Membres::DROIT_ADMIN);

$query = trim(qg('query'));
























$tpl->assign('schema', $membres->schemaSQL());
$tpl->assign('query', $query);

if ($query != '')
{
    try {
        $tpl->assign('result', $membres->searchSQL($query));

    }
    catch (\Exception $e)
    {
        $tpl->assign('result', null);
        $tpl->assign('error', $e->getMessage());
    }
}









else
{
    $tpl->assign('result', null);

}







$tpl->display('admin/membres/recherche_sql.tpl');





|


>
>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|




|
<
>
|
|
|
<
|
|
|
>
>
>
>
>
>
>
>
>
|
|
<
>
|

>
>
>
>
>
>

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

$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', $recherche->schema('membres'));
$tpl->assign('query', $query);

if ($query != '')
{
	try {

		$result = $recherche->searchSQL('membres', $query);
	}
	catch (\Exception $e)
	{

		$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 [d2377de7ca] to [2dd14f5041].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
...
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
...
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
...
405
406
407
408
409
410
411
412
413
414
415








416
417
418
419
420
421
422
...
424
425
426
427
428
429
430

431
432
433
434
435
436
437
...
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
...
564
565
566
567
568
569
570


























571
572
573
574
575
576
577
...
676
677
678
679
680
681
682



























683
684
685
686
687
688
689
@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');
    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 { 
................................................................................
    color: #090;
}

span.alert, b.alert {
    color: #990;
}

p.error, div.error {
    border: 1px solid #c00;
    background: #fcc;
    padding: 0.5em;
    margin-bottom: 1em;








}

p.confirm, div.confirm {
    border: 1px solid #0c0;
    background: #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;



















}

p.help {
    margin: 1em;
    color: #666;
}

................................................................................
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%;
}





select, input[size] {
    min-width: 0;
}

input.time {
    text-align: center;
................................................................................
    transition: opacity .5s ease;
}

input.resetButton {
    padding: .1em;
    margin-left: 1em;
}











.loader {
    width: 100%;
    min-height: 32px;
    display: block;
    position: relative;
}
................................................................................
    float: right;
}

ul.actions {
    list-style-type: none;
    margin: 1em 0;
    border-bottom: .1em solid #9c4f15;
    border-bottom: .1em solid rgb(var(--gMainColor));
    padding: 0 1em;
    z-index: 100;
}









ul.actions li {
    display: inline-block;
    margin: 0 0.2em;
}

ul.actions li a, ul.actions li label {
................................................................................
    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;

}

ul.actions li input {
    display: none;
}

ul.actions li.current a, ul.actions li input:checked + label {
................................................................................
    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));
    text-decoration: underline;
    border-bottom: none;
}

h3.warning {
    margin: 1em;
    color: red;
................................................................................
}

table.search th {
    background: rgb(217, 134, 40);
    background: rgba(217, 134, 40, 0.5);
    background: rgba(var(--gSecondColor), 0.5);
}



























.userOrder .cur {
    background: rgb(217, 134, 40);
    background: rgba(var(--gSecondColor), 1.0);
    color: #fff;
    color: rgb(var(--gBgColor));
}
................................................................................
    padding-bottom: .5em;
    border-bottom: 1pt solid #999;
}

#rapport h1 {
    text-align: center;
}




























.icn, .icnl {
    font-family: "gicon", sans-serif;
    font-style: normal;
    font-weight: normal;
    speak: none;
    font-variant: normal;




|
|
|
|
|







 







|
|
<
|

>
>
>
>
>
>
>
>



|
|
<
<



<
>
|
>
>
>
>
|
<
<
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>
>
>
>







 







>
>
>
>
>
>
>
>
>
>







 







|



>
>
>
>
>
>
>
>







 







>







 







|







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
...
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
...
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
...
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
...
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
...
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
...
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
...
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
...
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
@charset "UTF-8";

@font-face {
    font-family: 'gicon';
    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 { 
................................................................................
    color: #090;
}

span.alert, b.alert {
    color: #990;
}

p.alert, div.alert, p.error, div.error, p.confirm, div.confirm {
    border: 1px solid #ccc;

    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-color: #0c0;
    background-color: #cfc;


}

p.alert, div.alert {

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

................................................................................
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;
................................................................................
    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;
}
................................................................................
    float: right;
}

ul.actions {
    list-style-type: none;
    margin: 1em 0;
    border-bottom: .1em solid #9c4f15;
    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 {
................................................................................
    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: rgb(var(--gMainColor));
    color: #fff;
    color: rgb(var(--gBgColor));
}

ul.actions li a:hover, ul.actions li label:hover {
    color: #fff;
    background-color: rgb(var(--gMainColor));
    text-decoration: underline;
    border-bottom: none;
}

h3.warning {
    margin: 1em;
    color: red;
................................................................................
}

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));
}
................................................................................
    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
..
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
@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');
  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');
  }
}
*/
 
 [class^="icn-"]:before, [class*=" icn-"]:before {
  font-family: "garradin";
  font-style: normal;
................................................................................
  margin-right: .2em;
  text-align: center;
  /* opacity: .8; */
 
  /* For safety - reset parent styles, that can break glyph codes*/
  font-variant: normal;
  text-transform: none;
}


 















.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'; } /* '❓' */
.icn-home:before { content: '\2302'; } /* '⌂' */
.icn-attach:before { content: '📎'; } /* '\1f4ce' */
.icn-lock:before { content: '🔒'; } /* '\1f512' */
.icn-mail:before { content: '\2709'; } /* '✉' */
.icn-download:before { content: '\21d3'; } /* '⇓' */
.icn-edit:before { content: '\270e'; } /* '✎' */
.icn-print:before { content: '\2399'; } /* '⎙' */
.icn-alert:before { content: '\26a0'; } /* '⚠' */

.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-unlock:before { content: '🔓'; } /* '\1f513' */




|
|
>
|
|
|









|







 







|
>
>

>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
>

|
|
|
|
>
|

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
..
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?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?31180986#garradin') format('svg');
  }
}
*/
 
 [class^="icn-"]:before, [class*=" icn-"]:before {
  font-family: "garradin";
  font-style: normal;
................................................................................
  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;
 
  /* 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-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-edit:before { content: '\270e'; } /* '✎' */
.icn-delete:before { content: '\2718'; } /* '✘' */
.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-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
<?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>
<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="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="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="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" />
</font>
</defs>
</svg>



|




|
>
>
>
>
>
>
>
>
>
|
<
<
>
>
>
>
>
>
>
>
>
|
>
>
>
|
>
|
<
<
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
|
<
<
<
<
<
<
<
<
<
<
>
|



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) 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="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="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="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
...
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
	};

	// 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 el_length = elements.length;


		for (i = 0; i < el_length; i++)
		{
			var elm = elements[i];

			if (elm.type == 'checkbox' && elm.name)
			{
				elm.checked = this.checked;

				if (elm.onchange)
					elm.onchange({target: elm});
			}
		}

		return true;
	};

































	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');
................................................................................
		{
			document.body.removeChild(input);
		}*/
	};

	g.onload(dateInputFallback);

	if (document.querySelectorAll)
	{



		g.onload(function () {















			var checkTables = document.querySelectorAll('table thead input[type=checkbox]');
			var l = checkTables.length;

			for (var i = 0; i < l; i++)
			{
				var masterCheck = checkTables[i];
				masterCheck.onchange = g.checkUncheck;

				var parent = masterCheck.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;

				for (var j = 0; j < ll; j++)
				{
					checkBoxes[j].onchange = function (e) {
						var elm = e.target || this;


						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/, '');
					};

					if (checkBoxes[j].checked)
					{
						checkBoxes[j].onchange({target: checkBoxes[j]});
					}
				}
			}
		});
	}

})();







|

>

|


<
|
|
|
|
<
|





>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







 







|

>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|

|
|
|
|

|

|
|
|
|

|
|

|
|
|
|
>

|

|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
<
<

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
...
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
	};

	// 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.querySelectorAll('input[type=checkbox]');
		var el_length = elements.length;
		var checked = this.checked;

		for (var i = 0; i < el_length; i++)
		{
			var elm = elements[i];

			elm.checked = checked;

			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');
................................................................................
		{
			document.body.removeChild(input);
		}*/
	};

	g.onload(dateInputFallback);

	if (!document.querySelectorAll)
	{
		return;
	}

	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;

			var parent = masterCheck.parentNode;

			while (parent.nodeType != Node.ELEMENT_NODE || parent.tagName != 'TABLE')
			{
				parent = parent.parentNode;
			}

			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;
					var checked = elm.checked ? true : false;

					var parent = elm.parentNode;

					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]});
				}
			}
		}
	});


})();

Modified src/www/admin/static/scripts/password.js from [14f90c76a4] to [c16716738c].

3
4
5
6
7
8
9
10
11
12



13
14
15
16
17
18
19

	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.size = suggest_elm.value.length;

		suggest_elm.onclick = function () {
	        pw_elm.value = this.value;
	        pw2_elm.value = this.value;
	        this.select();







<


>
>
>







3
4
5
6
7
8
9

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

	RegExp.quote = function(str) {
	    return (str+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
	};

	window.initPasswordField = function(suggest, password, password2)
	{

		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 [6daac3a8d1].



>
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 t.null&&(i["IS NULL"]=this.operators["IS NULL"],i["IS NOT NULL"]=this.operators["IS NOT NULL"]),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
(function () {
	var wiki_id = window.location.search.match(/id=(\d+)/)[1];


	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;

		var toolbar = document.createElement('nav');
		toolbar.className = 'te';

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

		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 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_insertImage = function (file, position, caption)
		{
			var tag = '<<image|' + file;

			if (position)
				tag += '|' + position;

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

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

		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('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);

		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 = '';
		}
	};

}());



>
|

|
|
|

|
|

|
|
|

|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
|
|
|
|

|
|
|
|

|
|
|
|

|
|
|
|
|
|
|
|

|
|
|

|
|

|
|
|
|
|
|
|
|
|

|
|
|
|
|
|
|
|
|
|
|
|
|

|
|

|
|
|
|
|
|
|

|
|
|
|

|
|
|
|
|


|
|
|
|
|
|
|
|

|
|
|

|
|
|
|
|

|
|
|
|
|
|
|
|

|

|
|
|
|
|

|
|
|
|

|
|
|
|
|
|
>

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.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 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;
			};

			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 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_insertImage = function (file, position, caption)
			{
				var tag = '<<image|' + file;

				if (position)
					tag += '|' + position;

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

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

			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('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);

			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 = '';
			}
		};
	});
}());

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
..
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
...
324
325
326
327
328
329
330
331


































































332
333
334
335
336




























337
338
339
340
341
342
343
<?php

namespace Garradin;



const UPGRADE_PROCESS = true;


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.");
}







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);
}






Static_Cache::store('upgrade', 'Mise à jour en cours.');

$db = DB::getInstance();
$redirect = true;

// Créer une sauvegarde automatique
................................................................................
<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'));
................................................................................
{
    $db->begin();

    $db->import(ROOT . '/include/data/0.8.4.sql');

    $db->commit();
}



































































Utils::clearCaches();

$config->setVersion(garradin_version());

Static_Cache::remove('upgrade');





























echo '<h2>Mise à jour terminée.</h2>
<p><a href="'.ADMIN_URL.'">Retour</a></p>';

if ($redirect)
{
    echo '

>

>
>



>










>
>
>
>
>
>










>
>
>
>
>







 







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







 








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>





>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







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
..
65
66
67
68
69
70
71



































































































































































72
73
74
75
76
77
78
...
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
<?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
................................................................................
<div id="loader" class="loader" style="margin: 2em 0; height: 50px;"></div>
<script>
animatedLoader(document.getElementById("loader"), 5);
</script>';

flush();





































































































































































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'));
................................................................................
{
    $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
<?php

namespace Garradin;

require __DIR__ . '/_inc.php';






$squelette = new Squelette;
$squelette->dispatchURI();





>
>
>
>
>



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();