Overview
SHA1:52c8d1e88bfb946fa823a50f129dc2b7c97a1c6f
Date: 2017-04-28 07:38:47
User: bohwaz
Comment:Merge changements du trunk avec branche de dév
Timelines: family | ancestors | descendants | both | dev
Downloads: Tarball | ZIP archive
Other Links: files | file ages | folders | manifest
Tags And Properties
Context
2017-05-01
04:45
[b82b088636] Rendre getArgType public car utilisé dans Squelette (user: bohwaz, tags: dev)
2017-04-28
07:38
[52c8d1e88b] Merge changements du trunk avec branche de dév (user: bohwaz, tags: dev)
07:28
[2f39cf54bf] Implémentation de l'utilisation d'un SMTP externe (optionnel) pour l'envoi de mails (user: bohwaz, tags: trunk, stable)
2017-04-27
05:27
[29880c37a5] Les fonctions get* doivent toujours retourner un tableau (user: bohwaz, tags: dev)
Changes

Modified src/.htaccess from [f39f249836] to [2b3e1adfe6].

3
4
5
6
7
8
9

10


11
12
13
14
15
16
17
18
19
20
21

# Au cas où 
<IfModule mod_alias.c>
	RedirectMatch 403 /include/
	RedirectMatch 403 /cache/
	RedirectMatch 403 /plugins/
	RedirectMatch 403 /templates/

	RedirectMatch 403 /*.sqlite


</IfModule>

# Redirection dynamique, pour les installations sans vhost dédié
# Objectif: supprimer le /www/ de l'URL
<IfModule mod_rewrite.c>
	RewriteEngine on
	RewriteBase /
	RewriteCond %{REQUEST_URI}::$1 ^(.*?/)(.*)::\2$
	RewriteRule ^(.*)$ - [E=BASE:%1]
	RewriteRule (.*) %{ENV:BASE}/www/$1 [QSA]
</IfModule>







>
|
>
>











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

# Au cas où 
<IfModule mod_alias.c>
	RedirectMatch 403 /include/
	RedirectMatch 403 /cache/
	RedirectMatch 403 /plugins/
	RedirectMatch 403 /templates/
	RedirectMatch 403 /.*\.sqlite
	RedirectMatch 403 /.*\.log
	RedirectMatch 403 /(README|VERSION|COPYING)
	RedirectMatch 403 /config\.(.*)\.php
</IfModule>

# Redirection dynamique, pour les installations sans vhost dédié
# Objectif: supprimer le /www/ de l'URL
<IfModule mod_rewrite.c>
	RewriteEngine on
	RewriteBase /
	RewriteCond %{REQUEST_URI}::$1 ^(.*?/)(.*)::\2$
	RewriteRule ^(.*)$ - [E=BASE:%1]
	RewriteRule (.*) %{ENV:BASE}/www/$1 [QSA]
</IfModule>

Modified src/config.dist.php from [71b83249aa] to [73cd358136].

1
2
3
4
5





6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
..
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
..
75
76
77
78
79
80
81























<?php

/**
 * Ce fichier représente un exemple des constantes de configuration
 * disponibles pour Garradin.





 */

// Nécessaire pour situer les constantes dans le bon namespace
namespace Garradin;

// Connexion automatique à l'administration avec l'adresse e-mail donnée
#const LOCAL_LOGIN = 'president@association.net';

// Connexion automatique avec le numéro de membre indiqué
// Défaut : false (connexion automatique désactivée)
const LOCAL_LOGIN = 1;

// Répertoire où est le code source de Garradin
const ROOT = '/usr/share/garradin';

................................................................................
// Adresse URI de la racine du site Garradin
// (doit se terminer par un slash)
// Défaut : découverte automatique à partir de SCRIPT_NAME
const WWW_URI = '/garradin/';

// Adresse URL HTTP(S) de Garradin
// Défaut : découverte à partir de HTTP_HOST ou SERVER_NAME + WWW_URI
define('Garradin\WWW_URL', 'http://garradin.net' . WWW_URI);

// Doit-on suggérer à l'utilisateur d'utiliser la version chiffrée du site ?
// 1 ou true = affiche un message de suggestion sur l'écran de connexion invitant à utiliser le site chiffré
// (conseillé si vous avez un certificat auto-signé ou peu connu type CACert)
// 2 = rediriger automatiquement sur la version chiffrée pour l'administration
// 3 = rediriger automatiquement sur la version chiffrée pour administration et site public
// false ou 0 = aucune version chiffrée disponible, donc ne rien proposer ni rediriger
const PREFER_HTTPS = false;

// Emplacement de stockage des plugins
define('Garradin\PLUGINS_ROOT', DATA_ROOT . '/plugins');

// Plugins fixes qui ne peuvent être désinstallés (séparés par une virgule)
const PLUGINS_SYSTEM = 'email,web';

// Affichage des erreurs
// Si "true" alors un message expliquant l'erreur et comment rapporter le bug s'affiche
// en cas d'erreur. Sinon rien ne sera affiché.
................................................................................
// Supporte les serveurs web suivants :
// - Apache avec mod_xsendfile (paquet libapache2-mod-xsendfile)
// - Lighttpd
// N'activer que si vous êtes sûr que le module est installé et activé.
// Nginx n'est PAS supporté, car X-Accel-Redirect ne peut gérer que des fichiers
// qui sont *dans* le document root du vhost, ce qui n'est pas le cas ici.
const ENABLE_XSENDFILE = false;




























>
>
>
>
>





<
<
<







 







|










|







 







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



16
17
18
19
20
21
22
..
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
..
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
<?php

/**
 * Ce fichier représente un exemple des constantes de configuration
 * disponibles pour Garradin.
 *
 * NE PAS MODIFIER CE FICHIER!
 *
 * Pour configurer Garradin, copiez ce fichier en 'config.local.php'
 * et modifiez ce dont vous avez besoin.
 */

// Nécessaire pour situer les constantes dans le bon namespace
namespace Garradin;




// Connexion automatique avec le numéro de membre indiqué
// Défaut : false (connexion automatique désactivée)
const LOCAL_LOGIN = 1;

// Répertoire où est le code source de Garradin
const ROOT = '/usr/share/garradin';

................................................................................
// Adresse URI de la racine du site Garradin
// (doit se terminer par un slash)
// Défaut : découverte automatique à partir de SCRIPT_NAME
const WWW_URI = '/garradin/';

// Adresse URL HTTP(S) de Garradin
// Défaut : découverte à partir de HTTP_HOST ou SERVER_NAME + WWW_URI
const WWW_URL = 'http://garradin.net' . WWW_URI;

// Doit-on suggérer à l'utilisateur d'utiliser la version chiffrée du site ?
// 1 ou true = affiche un message de suggestion sur l'écran de connexion invitant à utiliser le site chiffré
// (conseillé si vous avez un certificat auto-signé ou peu connu type CACert)
// 2 = rediriger automatiquement sur la version chiffrée pour l'administration
// 3 = rediriger automatiquement sur la version chiffrée pour administration et site public
// false ou 0 = aucune version chiffrée disponible, donc ne rien proposer ni rediriger
const PREFER_HTTPS = false;

// Emplacement de stockage des plugins
const PLUGINS_ROOT = DATA_ROOT . '/plugins';

// Plugins fixes qui ne peuvent être désinstallés (séparés par une virgule)
const PLUGINS_SYSTEM = 'email,web';

// Affichage des erreurs
// Si "true" alors un message expliquant l'erreur et comment rapporter le bug s'affiche
// en cas d'erreur. Sinon rien ne sera affiché.
................................................................................
// Supporte les serveurs web suivants :
// - Apache avec mod_xsendfile (paquet libapache2-mod-xsendfile)
// - Lighttpd
// N'activer que si vous êtes sûr que le module est installé et activé.
// Nginx n'est PAS supporté, car X-Accel-Redirect ne peut gérer que des fichiers
// qui sont *dans* le document root du vhost, ce qui n'est pas le cas ici.
const ENABLE_XSENDFILE = false;

// Hôte du serveur SMTP, mettre à false (défaut) pour utiliser la fonction
// mail() de PHP
const SMTP_HOST = false;

// Port du serveur SMTP
// 25 = port standard pour connexion non chiffrée (465 pour Gmail)
// 587 = port standard pour connexion SSL
const SMTP_PORT = 587;

// Login utilisateur pour le server SMTP
// mettre à null pour utiliser un serveur local ou anonyme
const SMTP_USER = 'garradin@monserveur.com';

// Mot de passe pour le serveur SMTP
// mettre à null pour utiliser un serveur local ou anonyme
const SMTP_PASSWORD = 'abcd';

// Sécurité du serveur SMTP
// NONE = pas de chiffrement
// SSL = connexion SSL (le plus sécurisé)
// STARTTLS = utilisation de STARTTLS (moyennement sécurisé)
const SMTP_SECURITY = 'STARTTLS';

Modified src/include/data/0.8.0.sql from [70e25e73cc] to [f8987d594b].

1
2
3
4
5












-- Ajouter champ pour OTP
ALTER TABLE membres ADD COLUMN secret_otp TEXT NULL;

-- Ajouter champ clé PGP
ALTER TABLE membres ADD COLUMN clef_pgp TEXT NULL;

















>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- Ajouter champ pour OTP
ALTER TABLE membres ADD COLUMN secret_otp TEXT NULL;

-- Ajouter champ clé PGP
ALTER TABLE membres ADD COLUMN clef_pgp TEXT NULL;

-- Sessions
CREATE TABLE membres_sessions
(
	selecteur TEXT NOT NULL,
	token TEXT NOT NULL,
	id_membre INTEGER NOT NULL,
	expire TEXT NOT NULL,

	FOREIGN KEY (id_membre) REFERENCES membres (id),
	PRIMARY KEY (selecteur, id_membre)
);

Modified src/include/init.php from [530e87cdde] to [17ebcf2ead].

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

// Configuration externalisée
if (file_exists(__DIR__ . '/../config.local.php'))
{
    require __DIR__ . '/../config.local.php';
}



if (!defined('Garradin\ROOT'))
{
    define('Garradin\ROOT', dirname(__DIR__));
}

if (!defined('Garradin\DATA_ROOT'))
{
    define('Garradin\DATA_ROOT', ROOT);
}

if (!defined('Garradin\CACHE_ROOT'))
{
    define('Garradin\CACHE_ROOT', DATA_ROOT . '/cache');
}

if (!defined('Garradin\DB_FILE'))
{
    define('Garradin\DB_FILE', DATA_ROOT . '/association.sqlite');
}

if (!defined('Garradin\DB_SCHEMA'))
{
    define('Garradin\DB_SCHEMA', ROOT . '/include/data/schema.sql');
}

if (!defined('Garradin\WWW_URI'))
{
    // Automagic URL discover
    $path = str_replace(ROOT . '/www', '', getcwd());
    $path = str_replace($path, '', dirname($_SERVER['SCRIPT_NAME']));
    $path = (!empty($path[0]) && $path[0] != '/') ? '/' . $path : $path;
    $path = (substr($path, -1) != '/') ? $path . '/' : $path;
................................................................................

    // Pour installations sans vhost
    $path = str_replace('/www/', '', $path);

    define('Garradin\WWW_URI', $path);
}

if (!defined('Garradin\PREFER_HTTPS'))
{
    define('Garradin\PREFER_HTTPS', false);
}

if (!defined('Garradin\WWW_URL'))
{
    $host = isset($_SERVER['HTTP_HOST']) 
        ? $_SERVER['HTTP_HOST'] 
        : (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'localhost');
    define('Garradin\WWW_URL', 'http' . (!empty($_SERVER['HTTPS']) ? 's' : '') . '://' . $host . WWW_URI);
}

if (!defined('Garradin\PLUGINS_ROOT'))
{
    define('Garradin\PLUGINS_ROOT', DATA_ROOT . '/plugins');
}

if (!defined('Garradin\PLUGINS_SYSTEM'))
{
    define('Garradin\PLUGINS_SYSTEM', '');
}

// Affichage des erreurs par défaut
if (!defined('Garradin\SHOW_ERRORS'))
{
    define('Garradin\SHOW_ERRORS', false);
}

if (!defined('Garradin\MAIL_ERRORS'))
{
    define('Garradin\MAIL_ERRORS', false);
}

// Utilisation de cron pour les tâches automatiques
if (!defined('Garradin\USE_CRON'))
{
    define('Garradin\USE_CRON', false);
}

// Activation de X-SendFile
if (!defined('Garradin\ENABLE_XSENDFILE'))
{
    define('Garradin\ENABLE_XSENDFILE', false);
}

define('Garradin\WEBSITE', 'http://garradin.eu/');
define('Garradin\PLUGINS_URL', 'https://garradin.eu/plugins/list.json');

// PHP devrait être assez intelligent pour chopper la TZ système mais nan
// il sait pas faire (sauf sur Debian qui a le bon patch pour ça), donc pour 







>
>










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







 







<
<
<
<
<








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







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

// Configuration externalisée
if (file_exists(__DIR__ . '/../config.local.php'))
{
    require __DIR__ . '/../config.local.php';
}

// Configuration par défaut, si les constantes ne sont pas définies dans config.local.php
// (fallback)
if (!defined('Garradin\ROOT'))
{
    define('Garradin\ROOT', dirname(__DIR__));
}

if (!defined('Garradin\DATA_ROOT'))
{
    define('Garradin\DATA_ROOT', ROOT);
}
















if (!defined('Garradin\WWW_URI'))
{
    // Automagic URL discover
    $path = str_replace(ROOT . '/www', '', getcwd());
    $path = str_replace($path, '', dirname($_SERVER['SCRIPT_NAME']));
    $path = (!empty($path[0]) && $path[0] != '/') ? '/' . $path : $path;
    $path = (substr($path, -1) != '/') ? $path . '/' : $path;
................................................................................

    // Pour installations sans vhost
    $path = str_replace('/www/', '', $path);

    define('Garradin\WWW_URI', $path);
}






if (!defined('Garradin\WWW_URL'))
{
    $host = isset($_SERVER['HTTP_HOST']) 
        ? $_SERVER['HTTP_HOST'] 
        : (isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : 'localhost');
    define('Garradin\WWW_URL', 'http' . (!empty($_SERVER['HTTPS']) ? 's' : '') . '://' . $host . WWW_URI);
}

static $default_config = [
    'CACHE_ROOT'       => DATA_ROOT . '/cache',
    'DB_FILE'          => DATA_ROOT . '/association.sqlite',
    'DB_SCHEMA'        => ROOT . '/include/data/schema.sql',
    'PLUGINS_ROOT'     => DATA_ROOT . '/plugins',
    'PREFER_HTTPS'     => false,
    'PLUGINS_SYSTEM'   => '',
    'SHOW_ERRORS'      => false,
    'MAIL_ERRORS'      => false,
    'USE_CRON'         => false,
    'ENABLE_XSENDFILE' => false,
    'SMTP_HOST'        => false,
    'SMTP_USER'        => null,
    'SMTP_PASSWORD'    => null,
    'SMTP_PORT'        => 587,
    'SMTP_SECURITY'    => 'STARTTLS',
];

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

    if (!defined($const))
    {
        define($const, $value);
    }





}

define('Garradin\WEBSITE', 'http://garradin.eu/');
define('Garradin\PLUGINS_URL', 'https://garradin.eu/plugins/list.json');

// PHP devrait être assez intelligent pour chopper la TZ système mais nan
// il sait pas faire (sauf sur Debian qui a le bon patch pour ça), donc pour 

Modified src/include/lib/Garradin/Membres/Cotisations.php from [d8153a14b1] to [c27ad66fb9].

278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
			CASE WHEN c.duree IS NOT NULL THEN date(cm.date, \'+\'||c.duree||\' days\') >= date()
			WHEN c.fin IS NOT NULL THEN c.fin >= date() ELSE 1 END AS a_jour
			FROM cotisations_membres AS cm
				INNER JOIN cotisations AS c ON c.id = cm.id_cotisation
				INNER JOIN membres AS m ON m.id = cm.id_membre
			WHERE
				cm.id_cotisation = ?
				AND m.id_categorie NOT IN (SELECT id FROM membres_categories WHERE cacher = 1)
			GROUP BY cm.id_membre ORDER BY '.$order.' '.$desc.' LIMIT ?,?;',
			\SQLITE3_ASSOC, (int)$id, $begin, self::ITEMS_PER_PAGE);
	}

	/**
	 * Liste des événements d'un membre
	 * @param  integer $id Numéro de membre







|







278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
			CASE WHEN c.duree IS NOT NULL THEN date(cm.date, \'+\'||c.duree||\' days\') >= date()
			WHEN c.fin IS NOT NULL THEN c.fin >= date() ELSE 1 END AS a_jour
			FROM cotisations_membres AS cm
				INNER JOIN cotisations AS c ON c.id = cm.id_cotisation
				INNER JOIN membres AS m ON m.id = cm.id_membre
			WHERE
				cm.id_cotisation = ?
				AND m.id_categorie NOT IN (SELECT mc.id FROM membres_categories AS mc WHERE mc.cacher = 1)
			GROUP BY cm.id_membre ORDER BY '.$order.' '.$desc.' LIMIT ?,?;',
			\SQLITE3_ASSOC, (int)$id, $begin, self::ITEMS_PER_PAGE);
	}

	/**
	 * Liste des événements d'un membre
	 * @param  integer $id Numéro de membre

Modified src/include/lib/Garradin/Sauvegarde.php from [4833dc42ef] to [e15be831b5].

232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
			throw new UserException('Le fichier fourni ne semble pas contenir de données liées à Garradin.');
		}

		if ($user_id)
		{
			// Empêchons l'admin de se tirer une balle dans le pied
			$is_still_admin = $db->querySingle('SELECT 1 FROM membres_categories 
				WHERE id = (SELECT id_categorie FROM membres WHERE id = ?)
				AND droit_config >= ' . Membres::DROIT_ADMIN . '
				AND droit_connexion >= ' . Membres::DROIT_ACCES);

			if (!$is_still_admin)
			{
				throw new UserException('Vous n\'êtes pas administrateur dans le fichier de sauvegarde fourni.');
			}







|







232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
			throw new UserException('Le fichier fourni ne semble pas contenir de données liées à Garradin.');
		}

		if ($user_id)
		{
			// Empêchons l'admin de se tirer une balle dans le pied
			$is_still_admin = $db->querySingle('SELECT 1 FROM membres_categories 
				WHERE id = (SELECT id_categorie FROM membres WHERE id = ' . (int) $user_id . ')
				AND droit_config >= ' . Membres::DROIT_ADMIN . '
				AND droit_connexion >= ' . Membres::DROIT_ACCES);

			if (!$is_still_admin)
			{
				throw new UserException('Vous n\'êtes pas administrateur dans le fichier de sauvegarde fourni.');
			}

Modified src/include/lib/Garradin/Utils.php from [ce60202b4f] to [9c5200104c].

466
467
468
469
470
471
472
473
474






















475
476
477
478
479
480
481

        $subject = '=?UTF-8?B?'.base64_encode($subject).'?=';

        if (is_array($to))
        {
            foreach ($to as $t)
            {
                return mail($t, $suject, $content, $headers);
            }






















        }
        else
        {
            return mail($to, $subject, $content, $headers);
        }
    }








|

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







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

        $subject = '=?UTF-8?B?'.base64_encode($subject).'?=';

        if (is_array($to))
        {
            foreach ($to as $t)
            {
                return self::_sendMail($t, $suject, $content, $headers);
            }
        }
        else
        {
            return self::_sendMail($to, $subject, $content, $headers);
        }
    }

    static protected function _sendMail($to, $subject, $content, $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, 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
        {
            return mail($to, $subject, $content, $headers);
        }
    }

Modified src/include/lib/Garradin/Wiki.php from [f4f8b19ea5] to [004bc8f304].

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
...
491
492
493
494
495
496
497


498
499
500
501
502
503
504
...
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
    public function listBackBreadCrumbs($id)
    {
        if ($id == 0)
            return [];

        $db = DB::getInstance();
        $flat = [];


        while ($id > 0)
        {
            $res = $db->simpleQuerySingle('SELECT parent, titre, uri
                FROM wiki_pages WHERE id = ? LIMIT 1;', true, (int)$id);

            $flat[] = [
                'id'        =>  $id,
                'titre'     =>  $res['titre'],
                'uri'       =>  $res['uri'],
            ];






            $id = (int)$res['parent'];
        }

        return array_reverse($flat);
    }

................................................................................
                'titre' => 'Racine',
                'children' => $db->simpleStatementFetchAssocKey('SELECT id, parent, titre FROM wiki_pages
                    WHERE parent = ? ORDER BY transliterate_to_ascii(titre) COLLATE NOCASE;',
                    SQLITE3_ASSOC, 0)
            ]
        ];



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

            $flat[$id] = [
                'id'        =>  $id,
                'parent'    =>  $id ? (int)$parent : null,
................................................................................
                'children'  =>  $db->simpleStatementFetchAssocKey('SELECT id, parent, titre FROM wiki_pages
                    WHERE parent = ? ORDER BY transliterate_to_ascii(titre) COLLATE NOCASE;',
                    SQLITE3_ASSOC, (int)$id)
            ];

            $id = (int)$parent;
        }
        while ($id != 0);

        $tree = [];
        foreach ($flat as $id=>&$node)
        {
            if (is_null($node['parent']))
            {
                $tree[$id] = &$node;







>

|









>
>
>
>
>







 







>
>







 







|







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
...
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
...
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
    public function listBackBreadCrumbs($id)
    {
        if ($id == 0)
            return [];

        $db = DB::getInstance();
        $flat = [];
        $max = 0;

        while ($id > 0 && $max++ < 10)
        {
            $res = $db->simpleQuerySingle('SELECT parent, titre, uri
                FROM wiki_pages WHERE id = ? LIMIT 1;', true, (int)$id);

            $flat[] = [
                'id'        =>  $id,
                'titre'     =>  $res['titre'],
                'uri'       =>  $res['uri'],
            ];

            if ($id == $res['parent'])
            {
                throw new Exception('Parent! ' . $id . '/' . $res['parent']);
            }

            $id = (int)$res['parent'];
        }

        return array_reverse($flat);
    }

................................................................................
                'titre' => 'Racine',
                'children' => $db->simpleStatementFetchAssocKey('SELECT id, parent, titre FROM wiki_pages
                    WHERE parent = ? ORDER BY transliterate_to_ascii(titre) COLLATE NOCASE;',
                    SQLITE3_ASSOC, 0)
            ]
        ];

        $max = 0;

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

            $flat[$id] = [
                'id'        =>  $id,
                'parent'    =>  $id ? (int)$parent : null,
................................................................................
                'children'  =>  $db->simpleStatementFetchAssocKey('SELECT id, parent, titre FROM wiki_pages
                    WHERE parent = ? ORDER BY transliterate_to_ascii(titre) COLLATE NOCASE;',
                    SQLITE3_ASSOC, (int)$id)
            ];

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

        $tree = [];
        foreach ($flat as $id=>&$node)
        {
            if (is_null($node['parent']))
            {
                $tree[$id] = &$node;

Modified src/templates/admin/install.tpl from [5c3879e9f8] to [fa74b55a08].

40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
            <dt><label for="f_passe_membre">Mot de passe</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
            <dd class="help">
                Astuce : un mot de passe de quatre mots choisis au hasard dans le dictionnaire est plus sûr 
                et plus simple à retenir qu'un mot de passe composé de 10 lettres et chiffres.
            </dd>
            <dd class="help">
                Pas d'idée&nbsp;? Voici une suggestion choisie au hasard :
                <input type="text" readonly="readonly" title="Cliquer pour utiliser cette suggestion comme mot de passe" id="pw_suggest" value="{$passphrase}" />
            </dd>
            <dd><input type="password" name="passe_membre" id="f_passe_membre" value="{form_field name=passe_membre}" pattern=".{ldelim}5,{rdelim}" required="required" /></dd>
            <dt><label for="f_repasse_membre">Encore le mot de passe</label> (vérification) <b title="(Champ obligatoire)">obligatoire</b></dt>
            <dd><input type="password" name="repasse_membre" id="f_repasse_membre" value="{form_field name=repasse_membre}" pattern=".{ldelim}5,{rdelim}" required="required" /></dd>
        </dl>
    </fieldset>








|







40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
            <dt><label for="f_passe_membre">Mot de passe</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
            <dd class="help">
                Astuce : un mot de passe de quatre mots choisis au hasard dans le dictionnaire est plus sûr 
                et plus simple à retenir qu'un mot de passe composé de 10 lettres et chiffres.
            </dd>
            <dd class="help">
                Pas d'idée&nbsp;? Voici une suggestion choisie au hasard :
                <input type="text" readonly="readonly" title="Cliquer pour utiliser cette suggestion comme mot de passe" id="pw_suggest" value="{$passphrase}" autocomplete="off" />
            </dd>
            <dd><input type="password" name="passe_membre" id="f_passe_membre" value="{form_field name=passe_membre}" pattern=".{ldelim}5,{rdelim}" required="required" /></dd>
            <dt><label for="f_repasse_membre">Encore le mot de passe</label> (vérification) <b title="(Champ obligatoire)">obligatoire</b></dt>
            <dd><input type="password" name="repasse_membre" id="f_repasse_membre" value="{form_field name=repasse_membre}" pattern=".{ldelim}5,{rdelim}" required="required" /></dd>
        </dl>
    </fieldset>

Modified src/templates/admin/membres/ajouter.tpl from [6ac67980df] to [f16619648e].

23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
            <dt><label for="f_passe">Mot de passe</label>{if $champs.passe.mandatory} <b title="(Champ obligatoire)">obligatoire</b>{/if}</dt>
            <dd class="help">
                Astuce : un mot de passe de quatre mots choisis au hasard dans le dictionnaire est plus sûr 
                et plus simple à retenir qu'un mot de passe composé de 10 lettres et chiffres.
            </dd>
            <dd class="help">
                Pas d'idée&nbsp;? Voici une suggestion choisie au hasard :
                <input type="text" readonly="readonly" title="Cliquer pour utiliser cette suggestion comme mot de passe" id="pw_suggest" value="{$passphrase}" />
            </dd>
            <dd><input type="password" name="passe" id="f_passe" value="{form_field name=passe}" pattern=".{ldelim}5,{rdelim}" /></dd>
            <dt><label for="f_repasse">Encore le mot de passe</label> (vérification)</dt>
            <dd><input type="password" name="repasse" id="f_repasse" value="{form_field name=repasse}" pattern=".{ldelim}5,{rdelim}" /></dd>
        </dl>
    </fieldset>








|







23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
            <dt><label for="f_passe">Mot de passe</label>{if $champs.passe.mandatory} <b title="(Champ obligatoire)">obligatoire</b>{/if}</dt>
            <dd class="help">
                Astuce : un mot de passe de quatre mots choisis au hasard dans le dictionnaire est plus sûr 
                et plus simple à retenir qu'un mot de passe composé de 10 lettres et chiffres.
            </dd>
            <dd class="help">
                Pas d'idée&nbsp;? Voici une suggestion choisie au hasard :
                <input type="text" readonly="readonly" title="Cliquer pour utiliser cette suggestion comme mot de passe" id="pw_suggest" value="{$passphrase}" autocomplete="off" />
            </dd>
            <dd><input type="password" name="passe" id="f_passe" value="{form_field name=passe}" pattern=".{ldelim}5,{rdelim}" /></dd>
            <dt><label for="f_repasse">Encore le mot de passe</label> (vérification)</dt>
            <dd><input type="password" name="repasse" id="f_repasse" value="{form_field name=repasse}" pattern=".{ldelim}5,{rdelim}" /></dd>
        </dl>
    </fieldset>

Modified src/templates/admin/membres/modifier.tpl from [55fc8eab86] to [7878a5d47d].

41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
            <dt><label for="f_passe">Nouveau mot de passe</label>{if $champs.passe.mandatory} <b title="(Champ obligatoire)">obligatoire</b>{/if}</dt>
            <dd class="help">
                Astuce : un mot de passe de quatre mots choisis au hasard dans le dictionnaire est plus sûr 
                et plus simple à retenir qu'un mot de passe composé de 10 lettres et chiffres.
            </dd>
            <dd class="help">
                Pas d'idée&nbsp;? Voici une suggestion choisie au hasard :
                <input type="text" readonly="readonly" title="Cliquer pour utiliser cette suggestion comme mot de passe" id="pw_suggest" value="{$passphrase}" />
            </dd>
            <dd><input type="password" name="passe" id="f_passe" value="{form_field name=passe}" pattern=".{ldelim}5,{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="repasse" id="f_repasse" value="{form_field name=repasse}" pattern=".{ldelim}5,{rdelim}" /></dd>
        </dl>
    </fieldset>








|







41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
            <dt><label for="f_passe">Nouveau mot de passe</label>{if $champs.passe.mandatory} <b title="(Champ obligatoire)">obligatoire</b>{/if}</dt>
            <dd class="help">
                Astuce : un mot de passe de quatre mots choisis au hasard dans le dictionnaire est plus sûr 
                et plus simple à retenir qu'un mot de passe composé de 10 lettres et chiffres.
            </dd>
            <dd class="help">
                Pas d'idée&nbsp;? Voici une suggestion choisie au hasard :
                <input type="text" readonly="readonly" title="Cliquer pour utiliser cette suggestion comme mot de passe" id="pw_suggest" value="{$passphrase}" autocomplete="off" />
            </dd>
            <dd><input type="password" name="passe" id="f_passe" value="{form_field name=passe}" pattern=".{ldelim}5,{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="repasse" id="f_repasse" value="{form_field name=repasse}" pattern=".{ldelim}5,{rdelim}" /></dd>
        </dl>
    </fieldset>

Modified src/templates/admin/mes_infos_securite.tpl from [f70adeab6b] to [31aa919cb6].

73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
                    <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 
                        et plus simple à retenir qu'un mot de passe composé de 10 lettres et chiffres.
                    </dd>
                    <dd class="help">
                        Pas d'idée&nbsp;? Voici une suggestion choisie au hasard :
                        <input type="text" readonly="readonly" title="Cliquer pour utiliser cette suggestion comme mot de passe" id="pw_suggest" value="{$passphrase}" />
                    </dd>
                    <dd><input type="password" name="passe" id="f_passe" value="{form_field name=passe}" pattern=".{ldelim}5,{rdelim}" /></dd>
                    <dt><label for="f_repasse">Encore le mot de passe</label> (vérification)</dt>
                    <dd><input type="password" name="repasse" id="f_repasse" value="{form_field name=repasse}" pattern=".{ldelim}5,{rdelim}" /></dd>
                </dl>
            {/if}
        </fieldset>







|







73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
                    <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 
                        et plus simple à retenir qu'un mot de passe composé de 10 lettres et chiffres.
                    </dd>
                    <dd class="help">
                        Pas d'idée&nbsp;? Voici une suggestion choisie au hasard :
                        <input type="text" readonly="readonly" title="Cliquer pour utiliser cette suggestion comme mot de passe" id="pw_suggest" value="{$passphrase}" autocomplete="off" />
                    </dd>
                    <dd><input type="password" name="passe" id="f_passe" value="{form_field name=passe}" pattern=".{ldelim}5,{rdelim}" /></dd>
                    <dt><label for="f_repasse">Encore le mot de passe</label> (vérification)</dt>
                    <dd><input type="password" name="repasse" id="f_repasse" value="{form_field name=repasse}" pattern=".{ldelim}5,{rdelim}" /></dd>
                </dl>
            {/if}
        </fieldset>

Modified src/www/admin/index.php from [7c054dec30] to [fe9c328e0e].

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
..
32
33
34
35
36
37
38
39
40
<?php

namespace Garradin;

require_once __DIR__ . '/_inc.php';

$cats = new Membres\Categories;
$categorie = $cats->get($user['id_categorie']);

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

$wiki = new Wiki;
$page = $wiki->getByURI($config->get('accueil_connexion'));
$tpl->assign('page', $page);

$cats = new Membres\Categories;

$categorie = $cats->get($user['id_categorie']);

$cotisations = new Membres\Cotisations;

if (!empty($categorie['id_cotisation_obligatoire']))
{
	$tpl->assign('cotisation', $cotisations->isMemberUpToDate($user['id'], $categorie['id_cotisation_obligatoire']));
}
else
{
	$tpl->assign('cotisation', false);
}

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

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

?>

>





|









|





|







 







<
<
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
..
33
34
35
36
37
38
39


<?php

namespace Garradin;

require_once __DIR__ . '/_inc.php';

$cats = new Membres\Categories;
$categorie = $cats->get($user->id_categorie);

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

$wiki = new Wiki;
$page = $wiki->getByURI($config->get('accueil_connexion'));
$tpl->assign('page', $page);

$cats = new Membres\Categories;

$categorie = $cats->get($user->id_categorie);

$cotisations = new Membres\Cotisations;

if (!empty($categorie['id_cotisation_obligatoire']))
{
	$tpl->assign('cotisation', $cotisations->isMemberUpToDate($user->id, $categorie['id_cotisation_obligatoire']));
}
else
{
	$tpl->assign('cotisation', false);
}

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

// 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/membres/index.php from [243f82dc01] to [32c91fd704].

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

	if (isset($_GET['d']))
	    $desc = true;

	$fields = $champs->getListedFields();

	// Vérifier que le champ de tri existe bien dans la table
	if (!array_key_exists($order, $fields))
	{
		// Sinon par défaut c'est le premier champ de la table qui fait le tri
		$order = key($fields);
	}

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







|







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

	if (isset($_GET['d']))
	    $desc = true;

	$fields = $champs->getListedFields();

	// Vérifier que le champ de tri existe bien dans la table
	if ($order != 'id' && !array_key_exists($order, $fields))
	{
		// Sinon par défaut c'est le premier champ de la table qui fait le tri
		$order = key($fields);
	}

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

Modified src/www/admin/mes_infos.php from [16b21a0d0d] to [7c839aa4bc].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
..
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
<?php
namespace Garradin;

require_once __DIR__ . '/_inc.php';

$membre = $membres->getLoggedUser();

if (!$membre)
{
    throw new UserException("Ce membre n'existe pas.");
}

$error = false;

if (!empty($_POST['save']))
{
    if (!Utils::CSRF_check('edit_me'))
    {
        $error = 'Une erreur est survenue, merci de renvoyer le formulaire.';
................................................................................
            {
                if (!empty($c['editable']))
                {
                    $data[$key] = Utils::post($key);
                }
            }

            $membres->edit($membre['id'], $data, false);
            $membres->updateSessionData();

            Utils::redirect('/admin/');
        }
        catch (UserException $e)
        {
            $error = $e->getMessage();
        }
................................................................................
    }
}

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

$tpl->assign('champs', $config->get('champs_membres')->getAll());

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

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





<
<
<
<
<
<
<







 







|
<







 







|


1
2
3
4
5







6
7
8
9
10
11
12
..
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
<?php
namespace Garradin;

require_once __DIR__ . '/_inc.php';








$error = false;

if (!empty($_POST['save']))
{
    if (!Utils::CSRF_check('edit_me'))
    {
        $error = 'Une erreur est survenue, merci de renvoyer le formulaire.';
................................................................................
            {
                if (!empty($c['editable']))
                {
                    $data[$key] = Utils::post($key);
                }
            }

            $session->editUser($data);


            Utils::redirect('/admin/');
        }
        catch (UserException $e)
        {
            $error = $e->getMessage();
        }
................................................................................
    }
}

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

$tpl->assign('champs', $config->get('champs_membres')->getAll());

$tpl->assign('membre', $session->getUser());

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

Modified src/www/admin/wiki/index.php from [8a0b8d439a] to [eb50176436].

20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    $tpl->assign('can_edit', $wiki->canWritePage(Wiki::ECRITURE_NORMAL));
    $tpl->assign('can_read', true);
}
else
{
    $tpl->assign('can_read', $wiki->canReadPage($page['droit_lecture']));
    $tpl->assign('can_edit', $wiki->canWritePage($page['droit_ecriture']));
    $tpl->assign('children', $wiki->getList($page['uri'] == $config->get('accueil_wiki') ? 0 : $page['id'], true));
    $tpl->assign('breadcrumbs', $wiki->listBackBreadCrumbs($page['id']));
    $tpl->assign('auteur', $membres->getNom($page['contenu']['id_auteur']));

    $images = Fichiers::listLinkedFiles(Fichiers::LIEN_WIKI, $page['id'], true);

    if ($images && !$page['contenu']['chiffrement'])
    {







|







20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    $tpl->assign('can_edit', $wiki->canWritePage(Wiki::ECRITURE_NORMAL));
    $tpl->assign('can_read', true);
}
else
{
    $tpl->assign('can_read', $wiki->canReadPage($page['droit_lecture']));
    $tpl->assign('can_edit', $wiki->canWritePage($page['droit_ecriture']));
    $tpl->assign('children', $wiki->getList($page_uri == '' ? 0 : $page['id'], true));
    $tpl->assign('breadcrumbs', $wiki->listBackBreadCrumbs($page['id']));
    $tpl->assign('auteur', $membres->getNom($page['contenu']['id_auteur']));

    $images = Fichiers::listLinkedFiles(Fichiers::LIEN_WIKI, $page['id'], true);

    if ($images && !$page['contenu']['chiffrement'])
    {

Added tests/run.sh version [476c017c61].











>
>
>
>
>
1
2
3
4
5
#!/bin/bash

DIR="$(dirname "${BASH_SOURCE[0]}")"

find "$DIR" -type f -name '*.php' -exec php "{}" \;