Overview
Comment:Move user info and services pages to admin/me/ tree
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk | stable
Files: files | file ages | folders
SHA3-256: 541af2ee19e297c4ffc346753688865f42fc08a85a5f8b2e4eaed84592870968
User & Date: bohwaz on 2021-05-26 21:41:05
Other Links: manifest | tags
Context
2021-05-26
21:41
Implement export of user data as ZIP file check-in: d483a6d163 user: bohwaz tags: trunk, stable
21:41
Move user info and services pages to admin/me/ tree check-in: 541af2ee19 user: bohwaz tags: trunk, stable
20:01
Add zip download button check-in: d8a1ce4e8a user: bohwaz tags: trunk, stable
Changes

Modified src/include/lib/Garradin/DynamicList.php from [2a8411f96b] to [4a558ce327].

103
104
105
106
107
108
109











110
111
112
113
114
115
116
		else if ('ods' == $format) {
			CSV::toODS($name, $this->iterate(false), $this->getHeaderColumns(true), $this->export_callback);
		}
		else {
			throw new UserException('Invalid export format');
		}
	}












	public function paginationURL()
	{
		return Utils::getModifiedURL('?p=[ID]');
	}

	public function orderURL(string $order, bool $desc)







>
>
>
>
>
>
>
>
>
>
>







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
		else if ('ods' == $format) {
			CSV::toODS($name, $this->iterate(false), $this->getHeaderColumns(true), $this->export_callback);
		}
		else {
			throw new UserException('Invalid export format');
		}
	}

	public function asArray(): array
	{
		$out = [];

		foreach ($this->iterate(true) as $row) {
			$out[] = $row;
		}

		return $out;
	}

	public function paginationURL()
	{
		return Utils::getModifiedURL('?p=[ID]');
	}

	public function orderURL(string $order, bool $desc)

Modified src/templates/admin/_head.tpl from [35b3bf84ba] to [cd5caa4701].

100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
            </li>
        {/if}

        {if $session->canAccess($session::SECTION_CONFIG, $session::ACCESS_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="{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{if $current == 'my_services'}  class="current"{/if}><a href="{$admin_url}my_services.php">Mes activités &amp; 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}







|
|

|







100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
            </li>
        {/if}

        {if $session->canAccess($session::SECTION_CONFIG, $session::ACCESS_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="{if $current == 'me'} current{elseif $current_parent == 'me'} current_parent{/if}">
            <a href="{$admin_url}me/"><b class="icn">đŸ‘€</b><i> Mes infos personnelles</i></a>
            <ul>
                <li{if $current == 'me/services'}  class="current"{/if}><a href="{$admin_url}me/services.php">Mes activités &amp; 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}

Modified src/templates/admin/config/membres.tpl from [d986be9230] to [e15825a3fa].

127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
                <dd><input type="hidden" name="champs[{$nom}][type]" value="{$champ.type}" />{$champ.type|get_type}</dd>
                <dt><label for="f_{$nom}_title">Titre</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
                <dd><input type="text" name="champs[{$nom}][title]" id="f_{$nom}_title" value="{form_field data=$champ name=title}" required="required" /></dd>
                <dt><label for="f_{$nom}_help">Aide</label></dt>
                <dd><input type="text" name="champs[{$nom}][help]" id="f_{$nom}_help" value="{form_field data=$champ name=help}" /></dd>

                <dt><input type="checkbox" name="champs[{$nom}][private]" value="1" {form_field data=$champ name=private checked="1"} id="f_{$nom}_private"/> <label for="f_{$nom}_private">Caché pour les membres</label></dt>
                <dd class="help">Si coché, ce champ ne sera pas visible par les membres dans leur espace personnel.</dd>
                <dt><input type="checkbox" name="champs[{$nom}][editable]" value="1" {form_field data=$champ name=editable checked="1"} id="f_{$nom}_editable" /> <label for="f_{$nom}_editable">Modifiable par les membres</label></dt>
                <dd class="help">Si coché, les membres pourront changer cette information depuis leur espace personnel.</dd>
                <dt><label><input type="checkbox" name="champs[{$nom}][mandatory]" value="1" {form_field data=$champ name=mandatory checked="1"} id="f_{$nom}_mandatory" /> <label for="f_{$nom}_mandatory">Champ obligatoire</label></dt>
                <dd class="help">Si coché, ce champ ne pourra rester vide.</dd>

                {if $champ.type == 'select' || $champ.type == 'multiple'}
                    <dt><label>Options disponibles</label></dt>







|







127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
                <dd><input type="hidden" name="champs[{$nom}][type]" value="{$champ.type}" />{$champ.type|get_type}</dd>
                <dt><label for="f_{$nom}_title">Titre</label> <b title="(Champ obligatoire)">obligatoire</b></dt>
                <dd><input type="text" name="champs[{$nom}][title]" id="f_{$nom}_title" value="{form_field data=$champ name=title}" required="required" /></dd>
                <dt><label for="f_{$nom}_help">Aide</label></dt>
                <dd><input type="text" name="champs[{$nom}][help]" id="f_{$nom}_help" value="{form_field data=$champ name=help}" /></dd>

                <dt><input type="checkbox" name="champs[{$nom}][private]" value="1" {form_field data=$champ name=private checked="1"} id="f_{$nom}_private"/> <label for="f_{$nom}_private">Caché pour les membres</label></dt>
                <dd class="help">Si cochĂ©, ce champ ne sera pas visible par les membres dans leur espace personnel. Attention, il apparaĂźtra quand mĂȘme sur l'export de donnĂ©es RGPD que le membre peut tĂ©lĂ©charger, et qui contiendra toutes les donnĂ©es concernant ce membre.</dd>
                <dt><input type="checkbox" name="champs[{$nom}][editable]" value="1" {form_field data=$champ name=editable checked="1"} id="f_{$nom}_editable" /> <label for="f_{$nom}_editable">Modifiable par les membres</label></dt>
                <dd class="help">Si coché, les membres pourront changer cette information depuis leur espace personnel.</dd>
                <dt><label><input type="checkbox" name="champs[{$nom}][mandatory]" value="1" {form_field data=$champ name=mandatory checked="1"} id="f_{$nom}_mandatory" /> <label for="f_{$nom}_mandatory">Champ obligatoire</label></dt>
                <dd class="help">Si coché, ce champ ne pourra rester vide.</dd>

                {if $champ.type == 'select' || $champ.type == 'multiple'}
                    <dt><label>Options disponibles</label></dt>

Modified src/templates/admin/index.tpl from [8fc4b88474] to [39b0508d91].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{include file="admin/_head.tpl" title="Bonjour %s !"|args:$user.identite current="home"}

{$banner|raw}

<nav class="tabs">
	<ul>
		<li><a href="{$admin_url}mes_infos.php">Modifier mes informations personnelles</a></li>
		<li><a href="{$admin_url}my_services.php">Suivi de mes activités et cotisations</a></li>
	</ul>
</nav>

<aside class="describe">
	<h3>{$config.nom_asso}</h3>
	{if !empty($config.adresse_asso)}
	<p>






|
|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{include file="admin/_head.tpl" title="Bonjour %s !"|args:$user.identite current="home"}

{$banner|raw}

<nav class="tabs">
	<ul>
		<li><a href="{$admin_url}me/">Modifier mes informations personnelles</a></li>
		<li><a href="{$admin_url}me/services.php">Suivi de mes activités et cotisations</a></li>
	</ul>
</nav>

<aside class="describe">
	<h3>{$config.nom_asso}</h3>
	{if !empty($config.adresse_asso)}
	<p>

Modified src/templates/admin/membres/_details.tpl from [824eeb6d7f] to [059439d1b5].

1
2
3
4
5
6
7
8










9
10
11
12
13
14
15
<?php
assert(isset($data, $champs, $show_message_button));
$user_files_path = (new Membres)->getAttachementsDirectory($data->id);
?>

<dl class="describe">
	{foreach from=$champs key="c" item="c_config"}
	<?php










	$value = $data->$c ?? null;
	?>
	<dt>{$c_config.title}</dt>
	<dd>
		{if $c_config.type == 'checkbox'}
			{if $value}Oui{else}Non{/if}
		{elseif $c_config.type == 'file'}








>
>
>
>
>
>
>
>
>
>







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
assert(isset($data, $champs, $show_message_button));
$user_files_path = (new Membres)->getAttachementsDirectory($data->id);
?>

<dl class="describe">
	{foreach from=$champs key="c" item="c_config"}
	<?php
	// Skip private fields from "my info" page
	if ($mode == 'user' && $c_config->private) {
		continue;
	}

	// Skip files from export
	if ($mode == 'export' && $c_config->type == 'file') {
		continue;
	}

	$value = $data->$c ?? null;
	?>
	<dt>{$c_config.title}</dt>
	<dd>
		{if $c_config.type == 'checkbox'}
			{if $value}Oui{else}Non{/if}
		{elseif $c_config.type == 'file'}

Modified src/templates/me/edit.tpl from [a8c6d019fb] to [30ffe59eb4].

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
{include file="admin/_head.tpl" title="Mes informations personnelles" current="mes_infos"}

<nav class="tabs">
	<ul>
		<li class="current"><a href="{$admin_url}mes_infos.php">Mes informations personnelles</a></li>
		<li><a href="{$admin_url}mes_infos_securite.php">Mot de passe et options de sécurité</a></li>
	</ul>
</nav>

{form_errors membre=1}

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

	<fieldset>
		<legend>Informations personnelles</legend>
		<dl>
			{foreach from=$champs item="champ" key="nom"}
			{if empty($champ.private) && $nom != 'passe'}
				{html_champ_membre config=$champ name=$nom data=$data user_mode=true}
			{/if}
			{/foreach}
		</dl>
	</fieldset>

	<fieldset>
		<legend>Changer mon mot de passe</legend>
		<p><a href="{$admin_url}mes_infos_securite.php">Modifier mon mot de passe ou autres informations de sécurité.</a></p>
	</fieldset>

	<p class="submit">
		{csrf_field key=$csrf_key}
		{button type="submit" name="save" label="Enregistrer" shape="right" class="main"}
	</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
{include file="admin/_head.tpl" title="Mes informations personnelles" current="me"}

<nav class="tabs">
	<ul>
		<li class="current"><a href="{$admin_url}me/">Mes informations personnelles</a></li>
		<li><a href="{$admin_url}me/security.php">Mot de passe et options de sécurité</a></li>
	</ul>
</nav>

{form_errors membre=1}

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

	<fieldset>
		<legend>Informations personnelles</legend>
		<dl>
			{foreach from=$champs item="champ" key="nom"}
			{if empty($champ.private) && $nom != 'passe'}
				{html_champ_membre config=$champ name=$nom data=$data user_mode=true}
			{/if}
			{/foreach}
		</dl>
	</fieldset>

	<fieldset>
		<legend>Changer mon mot de passe</legend>
		<p><a href="{$admin_url}me/security.php">Modifier mon mot de passe ou autres informations de sécurité.</a></p>
	</fieldset>

	<p class="submit">
		{csrf_field key=$csrf_key}
		{button type="submit" name="save" label="Enregistrer" shape="right" class="main"}
	</p>

</form>

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

Modified src/templates/me/index.tpl from [9470f7e272] to [40ad0c52b7].

1
2
3
4
5
6
7
8
9






10
11
12
13
14
15
16
17




18
{include file="admin/_head.tpl" title="Mes informations personnelles" current="mes_infos"}

<nav class="tabs">
    <ul>
        <li class="current"><a href="{$admin_url}mes_infos.php">Mes informations personnelles</a></li>
        <li><a href="{$admin_url}mes_infos_securite.php">Mot de passe et options de sécurité</a></li>
    </ul>
</nav>







<dl class="describe">
    <dd>
        {linkbutton href="mes_infos_modifier.php" label="Modifier mes informations" shape="edit"}
    </dd>
</dl>

{include file="admin/membres/_details.tpl" champs=$champs data=$data show_message_button=false mode="user"}





{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
{include file="admin/_head.tpl" title="Mes informations personnelles" current="me"}

<nav class="tabs">
	<ul>
		<li class="current"><a href="{$admin_url}me/">Mes informations personnelles</a></li>
		<li><a href="{$admin_url}me/security.php">Mot de passe et options de sécurité</a></li>
	</ul>
</nav>

{if $ok !== null}
<p class="confirm block">
	Les modifications ont bien été enregistrées.
</p>
{/if}

<dl class="describe">
	<dd>
		{linkbutton href="!me/edit.php" label="Modifier mes informations" shape="edit"}
	</dd>
</dl>

{include file="admin/membres/_details.tpl" champs=$champs data=$data show_message_button=false mode="user"}

<p>
	{linkbutton href="!me/export.php" label="Télécharger toutes les données détenues sur moi" shape="download"}
</p>

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

Modified src/templates/me/security.tpl from [f732fceed2] to [c9bb6e985d].

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
{include file="admin/_head.tpl" title="Mes informations de connexion et sécurité" current="mes_infos"}

<nav class="tabs">
    <ul>
        <li><a href="{$admin_url}mes_infos.php">Mes informations personnelles</a></li>
        <li class="current"><a href="{$admin_url}mes_infos_securite.php">Mot de passe et options de sécurité</a></li>
    </ul>
</nav>

{if $ok}
<p class="block confirm">
    Changements enregistrés.
</p>
{/if}

{form_errors}

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

    {if !empty($otp) && $otp == 'disable'}
        <p class="block alert">
            Confirmez la désactivation de l'authentification à double facteur TOTP.
        </p>
    {elseif !empty($otp)}
        <p class="block alert">
            Confirmez l'activation de l'authentification Ă  double facteur TOTP en l'utilisant une premiĂšre fois.
        </p>

        <fieldset>
            <legend>Confirmer l'activation de l'authentification Ă  double facteur (2FA)</legend>
            <img class="qrcode" src="{$otp.qrcode}" alt="" />
            <dl>
                <dt>Votre clé secrÚte est&nbsp;:</dt>
                <dd><code>{$otp.secret_display}</code></dd>
                <dd class="help">Recopiez la clé secrÚte ou scannez le QR code pour configurer votre application TOTP (par exemple <a href="https://freeotp.github.io/">FreeOTP</a>), puis utilisez celle-ci pour générer un code d'accÚs et confirmer l'activation.</dd>
                <dd class="help">Pour configurer une autre application, vous pouvez utiliser ces paramĂštres&nbsp;: <tt>{$otp.url}</tt></dd>
                <dt><label for="f_code">Code TOTP</label></dt>
                <dd class="help">Entrez ici le code donné par l'application d'authentification double facteur.</dd>
                <dd><input type="text" name="code" id="f_code" value="{form_field name=code}" autocomplete="off" /></dd>
            </dl>
        </fieldset>
    {/if}

    <fieldset>
        <legend>Confirmer les changements</legend>
        <dl>
            <dt><label for="f_passe_confirm">Mot de passe actuel</label></dt>
            <dd class="help">Entrez votre mot de passe actuel pour confirmer les changements demandés.</dd>
            <dd><input type="password" name="passe_check" autocomplete="current-password" /></dd>
        </dl>
    </fieldset>

    <p class="submit">
        {csrf_field key="edit_me_security"}
        <input type="hidden" name="passe" value="{form_field name="passe"}" />
        <input type="hidden" name="passe_confirmed" value="{form_field name="passe_confirmed"}" />
        <input type="hidden" name="clef_pgp" value="{form_field name="clef_pgp"}" />
        {if !empty($otp)}
        <input type="hidden" name="otp_secret" value="{$otp.secret}" />
        {/if}
        {button type="submit" name="confirm" label="Confirmer" shape="right" class="main"}
    </p>

    </form>
{else}

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

        <fieldset>
            <legend>Changer mon mot de passe</legend>
            {if $user.droit_membres < $session::ACCESS_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> (minimum {$password_length} caractĂšres)</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="{$password_pattern}" autocomplete="new-password" /></dd>
                    <dt><label for="f_repasse">Encore le mot de passe</label> (vérification)</dt>
                    <dd><input type="password" name="passe_confirmed" id="f_passe_confirmed" value="{form_field name=passe_confirmed}" pattern="{$password_pattern}" autocomplete="new-password" /></dd>
                </dl>
            {/if}
        </fieldset>

        <fieldset>
            <legend>Authentification Ă  double facteur (2FA)</legend>
            <p class="help">Pour renforcer la sécurité de votre connexion en cas de vol de votre mot de passe, vous pouvez activer
                l'authentification à double facteur. Cela nécessite d'installer une application comme <a href="https://freeotp.github.io/">FreeOTP</a>
                sur votre téléphone.</p>
            <dl>
                <dt>Authentification Ă  double facteur (TOTP)</dt>
            {if $membre.secret_otp}
                {input type="radio" name="otp" value="" default="" label="Activée"}
                {input type="radio" name="otp" value="generate" label="Re-générer une nouvelle clé secrÚte" help="Si la clé a été compromise ou perdue"}
                {input type="radio" name="otp" value="disable" label="DĂ©sactiver l'authentification Ă  double facteur"}
            {else}
                <dd><em>Désactivée</em></dd>
                {input type="checkbox" name="otp" value="generate" label="Activer"}
            {/if}
            </dl>
        </fieldset>

        {if $pgp_disponible}
        <fieldset>
            <legend>Protéger mes mails personnels par chiffrement PGP/GnuPG</legend>
            <dl>
                <dt><label for="f_clef_pgp">Ma clé publique PGP</label></dt>
                <dd class="help">En inscrivant ici votre clé publique, tous les emails personnels (non collectifs) qui vous
                    sont envoyés seront chiffrés (cryptés) avec cette clé&nbsp;: messages envoyés par les membres, rappels de cotisation,
                    procédure de récupération de mot de passe, etc.</dd>
                <dd><textarea name="clef_pgp" id="f_clef_pgp" cols="90" rows="5">{form_field name="clef_pgp" data=$user}</textarea></dd>
                {if $clef_pgp_fingerprint}<dd class="help">L'empreinte de la clé est&nbsp;: <code>{$clef_pgp_fingerprint}</code></dd>{/if}
            </dl>
            <p class="block alert">
                Attention&nbsp;: en inscrivant ici votre clé PGP, les emails de récupération de mot de passe perdu vous seront envoyés chiffrés
                et ne pourront ĂȘtre lus sans utiliser le mot de passe protĂ©geant votre clĂ© privĂ©e correspondante.
            </p>
        </fieldset>
        {/if}

        <p class="submit">
            {csrf_field key="edit_me_security"}
            {button type="submit" name="save" label="Enregistrer" shape="right" class="main"}
        </p>

    </form>

    <script type="text/javascript">
    {literal}
    g.script('scripts/password.js', () => {
        initPasswordField('pw_suggest', 'f_passe', 'f_passe_confirmed');
    });
    {/literal}
    </script>
{/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
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
{include file="admin/_head.tpl" title="Mes informations de connexion et sécurité" current="me"}

<nav class="tabs">
	<ul>
		<li><a href="{$admin_url}me/">Mes informations personnelles</a></li>
		<li class="current"><a href="{$admin_url}me/security.php">Mot de passe et options de sécurité</a></li>
	</ul>
</nav>

{if $ok}
<p class="block confirm">
	Changements enregistrés.
</p>
{/if}

{form_errors}

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

	{if !empty($otp) && $otp == 'disable'}
		<p class="block alert">
			Confirmez la désactivation de l'authentification à double facteur TOTP.
		</p>
	{elseif !empty($otp)}
		<p class="block alert">
			Confirmez l'activation de l'authentification Ă  double facteur TOTP en l'utilisant une premiĂšre fois.
		</p>

		<fieldset>
			<legend>Confirmer l'activation de l'authentification Ă  double facteur (2FA)</legend>
			<img class="qrcode" src="{$otp.qrcode}" alt="" />
			<dl>
				<dt>Votre clé secrÚte est&nbsp;:</dt>
				<dd><code>{$otp.secret_display}</code></dd>
				<dd class="help">Recopiez la clé secrÚte ou scannez le QR code pour configurer votre application TOTP (par exemple <a href="https://getaegis.app/" target="_blank">Aegis</a>), puis utilisez celle-ci pour générer un code d'accÚs et confirmer l'activation.</dd>
				<dd class="help">Pour configurer une autre application, vous pouvez utiliser ces paramĂštres&nbsp;: <tt>{$otp.url}</tt></dd>
				<dt><label for="f_code">Code TOTP</label></dt>
				<dd class="help">Entrez ici le code donné par l'application d'authentification double facteur.</dd>
				<dd><input type="text" name="code" id="f_code" value="{form_field name=code}" autocomplete="off" /></dd>
			</dl>
		</fieldset>
	{/if}

	<fieldset>
		<legend>Confirmer les changements</legend>
		<dl>
			<dt><label for="f_passe_confirm">Mot de passe actuel</label></dt>
			<dd class="help">Entrez votre mot de passe actuel pour confirmer les changements demandés.</dd>
			<dd><input type="password" name="passe_check" autocomplete="current-password" /></dd>
		</dl>
	</fieldset>

	<p class="submit">
		{csrf_field key="edit_me_security"}
		<input type="hidden" name="passe" value="{form_field name="passe"}" />
		<input type="hidden" name="passe_confirmed" value="{form_field name="passe_confirmed"}" />
		<input type="hidden" name="clef_pgp" value="{form_field name="clef_pgp"}" />
		{if !empty($otp)}
		<input type="hidden" name="otp_secret" value="{$otp.secret}" />
		{/if}
		{button type="submit" name="confirm" label="Confirmer" shape="right" class="main"}
	</p>

	</form>
{else}

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

		<fieldset>
			<legend>Changer mon mot de passe</legend>
			{if $user.droit_membres < $session::ACCESS_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> (minimum {$password_length} caractĂšres)</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="{$password_pattern}" autocomplete="new-password" /></dd>
					<dt><label for="f_repasse">Encore le mot de passe</label> (vérification)</dt>
					<dd><input type="password" name="passe_confirmed" id="f_passe_confirmed" value="{form_field name=passe_confirmed}" pattern="{$password_pattern}" autocomplete="new-password" /></dd>
				</dl>
			{/if}
		</fieldset>

		<fieldset>
			<legend>Authentification Ă  double facteur (2FA)</legend>
			<p class="help">Pour renforcer la sécurité de votre connexion en cas de vol de votre mot de passe, vous pouvez activer
				l'authentification à double facteur. Cela nécessite d'installer une application comme <a href="https://getaegis.app/" target="_blank">Aegis</a>
				sur votre téléphone.</p>
			<dl>
				<dt>Authentification Ă  double facteur (TOTP)</dt>
			{if $membre.secret_otp}
				{input type="radio" name="otp" value="" default="" label="Activée"}
				{input type="radio" name="otp" value="generate" label="Re-générer une nouvelle clé secrÚte" help="Si la clé a été compromise ou perdue"}
				{input type="radio" name="otp" value="disable" label="DĂ©sactiver l'authentification Ă  double facteur"}
			{else}
				<dd><em>Désactivée</em></dd>
				{input type="checkbox" name="otp" value="generate" label="Activer"}
			{/if}
			</dl>
		</fieldset>

		{if $pgp_disponible}
		<fieldset>
			<legend>Protéger mes mails personnels par chiffrement PGP/GnuPG</legend>
			<dl>
				<dt><label for="f_clef_pgp">Ma clé publique PGP</label></dt>
				<dd class="help">En inscrivant ici votre clé publique, tous les emails personnels (non collectifs) qui vous
					sont envoyés seront chiffrés (cryptés) avec cette clé&nbsp;: messages envoyés par les membres, rappels de cotisation,
					procédure de récupération de mot de passe, etc.</dd>
				<dd><textarea name="clef_pgp" id="f_clef_pgp" cols="90" rows="5">{form_field name="clef_pgp" data=$user}</textarea></dd>
				{if $clef_pgp_fingerprint}<dd class="help">L'empreinte de la clé est&nbsp;: <code>{$clef_pgp_fingerprint}</code></dd>{/if}
			</dl>
			<p class="block alert">
				Attention&nbsp;: en inscrivant ici votre clé PGP, les emails de récupération de mot de passe perdu vous seront envoyés chiffrés
				et ne pourront ĂȘtre lus sans utiliser le mot de passe protĂ©geant votre clĂ© privĂ©e correspondante.
			</p>
		</fieldset>
		{/if}

		<p class="submit">
			{csrf_field key="edit_me_security"}
			{button type="submit" name="save" label="Enregistrer" shape="right" class="main"}
		</p>

	</form>

	<script type="text/javascript">
	{literal}
	g.script('scripts/password.js', () => {
		initPasswordField('pw_suggest', 'f_passe', 'f_passe_confirmed');
	});
	{/literal}
	</script>
{/if}

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

Modified src/templates/me/services.tpl from [79bef75320] to [4bbaabc663].

1
2
3
4
5
6
7
8
{include file="admin/_head.tpl" title="Mes activités & cotisations" current="my_services"}

<dl class="cotisation">
	<dt>Mes activités et cotisations</dt>
	{foreach from=$services item="service"}
	<dd>
		{$service.label}
		{if $service.status == -1 && $service.end_date} — terminĂ©e
|







1
2
3
4
5
6
7
8
{include file="admin/_head.tpl" title="Mes activités & cotisations" current="me/services"}

<dl class="cotisation">
	<dt>Mes activités et cotisations</dt>
	{foreach from=$services item="service"}
	<dd>
		{$service.label}
		{if $service.status == -1 && $service.end_date} — terminĂ©e

Modified src/www/admin/me/edit.php from [37640b7c31] to [80d70aeee3].

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

require_once __DIR__ . '/_inc.php';

$csrf_key = 'edit_my_info';

$form->runIf('save', function () use ($session) {
	$data = [];
	$config = Config::getInstance();
	$champs = Config::getInstance()->get('champs_membres');

	foreach ($champs->getAll() as $key=>$c) {
		if (!empty($c->editable)) {
			$data[$key] = f($key);
		}
	}

	if (isset($data[$config->get('champ_identifiant')]) && !trim($data[$config->get('champ_identifiant')]) && $session->canAccess($session::SECTION_CONFIG, $session::ACCESS_ADMIN)) {
		throw new UserException("Le champ identifiant ne peut ĂȘtre vide pour un administrateur, sinon vous ne pourriez plus vous connecter.");
	}

	$session->editUser($data);
}, $csrf_key, '!mes_infos.php');

$data = $session->getUser();
$champs = Config::getInstance()->get('champs_membres')->getAll();

$tpl->assign(compact('csrf_key', 'champs', 'data'));

$tpl->display('admin/mes_infos_modifier.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
<?php
namespace Garradin;

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

$csrf_key = 'edit_my_info';

$form->runIf('save', function () use ($session) {
	$data = [];
	$config = Config::getInstance();
	$champs = Config::getInstance()->get('champs_membres');

	foreach ($champs->getAll() as $key=>$c) {
		if (!empty($c->editable)) {
			$data[$key] = f($key);
		}
	}

	if (isset($data[$config->get('champ_identifiant')]) && !trim($data[$config->get('champ_identifiant')]) && $session->canAccess($session::SECTION_CONFIG, $session::ACCESS_ADMIN)) {
		throw new UserException("Le champ identifiant ne peut ĂȘtre vide pour un administrateur, sinon vous ne pourriez plus vous connecter.");
	}

	$session->editUser($data);
}, $csrf_key, '!me/?ok');

$data = $session->getUser();
$champs = Config::getInstance()->get('champs_membres')->getAll();

$tpl->assign(compact('csrf_key', 'champs', 'data'));

$tpl->display('me/edit.tpl');

Modified src/www/admin/me/index.php from [56acaec485] to [cd4ab73468].

1
2
3
4
5
6
7
8


9
10
11
<?php
namespace Garradin;

require_once __DIR__ . '/_inc.php';

$data = $session->getUser();
$champs = Config::getInstance()->get('champs_membres')->getList();



$tpl->assign(compact('champs', 'data'));

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



|




>
>
|

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

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

$data = $session->getUser();
$champs = Config::getInstance()->get('champs_membres')->getList();

$ok = qg('ok');

$tpl->assign(compact('champs', 'data', 'ok'));

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

Modified src/www/admin/me/security.php from [07c69221f1] to [52a874b9dd].

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

$confirm = false;

if (f('confirm'))
{
    $form->check('edit_me_security', [
        'passe'       => 'confirmed|min:6',
        'passe_check' => 'required',
    ]);

    if (f('passe_check') && !$session->checkPassword(f('passe_check'), $user->passe))
    {
        $form->addError('Le mot de passe fourni ne correspond pas au mot de passe actuel. Merci de bien vouloir renseigner votre mot de passe courant pour confirmer les changements.');
    }
    elseif (f('otp_secret') && f('otp_secret') != 'disable' && !f('code')) {
        $form->addError('Le code OTP est obligatoire');
    }
    elseif (f('code') && !$session->checkOTP(f('otp_secret'), f('code')))
    {
        $form->addError('Le code TOTP entré n\'est pas valide.');
    }

    if (!$form->hasErrors())
    {
        try {
            $data = [
                'clef_pgp' => f('clef_pgp'),
            ];

            if (f('passe') && !empty($config->get('champs_membres')->get('passe')->editable))
            {
                $data['passe'] = f('passe');
            }

            if (f('otp_secret') == 'disable')
            {
                $data['secret_otp'] = null;
            }
            elseif (f('otp_secret') !== null)
            {
                $data['secret_otp'] = f('otp_secret');
            }

            $session->editSecurity($data);
            Utils::redirect(ADMIN_URL . 'mes_infos_securite.php?ok');
        }
        catch (UserException $e)
        {
            $form->addError($e->getMessage());
        }
    }

    $confirm = true;
}
elseif (f('save'))
{
    $form->check('edit_me_security', [
        'passe'       => 'confirmed|min:6',
    ]);

    if (f('clef_pgp') && !$session->getPGPFingerprint(f('clef_pgp')))
    {
        $form->addError('Clé PGP invalide : impossible de récupérer l\'empreinte de la clé.');
    }
    
    if (!$form->hasErrors())
    {
        $confirm = true;
    }
}

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

if (f('otp') == 'generate')
{
    $tpl->assign('otp', $session->getNewOTPSecret());
}
elseif (f('otp') == 'disable')
{
    $tpl->assign('otp', 'disable');
}
elseif (f('otp_secret'))
{
    $tpl->assign('otp', $session->getOTPSecret(f('otp_secret')));
}
else
{
    $tpl->assign('otp', false);
}

$tpl->assign('pgp_disponible', \KD2\Security::canUseEncryption());

$fingerprint = '';

if ($user->clef_pgp)
{
    $fingerprint = $session->getPGPFingerprint($user->clef_pgp, true);
}

$tpl->assign('clef_pgp_fingerprint', $fingerprint);

$tpl->assign('passphrase', Utils::suggestPassword());
$tpl->assign('champs', $config->get('champs_membres')->getAll());

$tpl->assign('membre', $user);
$tpl->assign('ok', qg('ok') !== null);

$tpl->display('admin/mes_infos_securite.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
<?php

namespace Garradin;

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

$confirm = false;

if (f('confirm'))
{
	$form->check('edit_me_security', [
		'passe'       => 'confirmed|min:6',
		'passe_check' => 'required',
	]);

	if (f('passe_check') && !$session->checkPassword(f('passe_check'), $user->passe))
	{
		$form->addError('Le mot de passe fourni ne correspond pas au mot de passe actuel. Merci de bien vouloir renseigner votre mot de passe courant pour confirmer les changements.');
	}
	elseif (f('otp_secret') && f('otp_secret') != 'disable' && !f('code')) {
		$form->addError('Le code OTP est obligatoire');
	}
	elseif (f('code') && !$session->checkOTP(f('otp_secret'), f('code')))
	{
		$form->addError('Le code TOTP entré n\'est pas valide.');
	}

	if (!$form->hasErrors())
	{
		try {
			$data = [
				'clef_pgp' => f('clef_pgp'),
			];

			if (f('passe') && !empty($config->get('champs_membres')->get('passe')->editable))
			{
				$data['passe'] = f('passe');
			}

			if (f('otp_secret') == 'disable')
			{
				$data['secret_otp'] = null;
			}
			elseif (f('otp_secret') !== null)
			{
				$data['secret_otp'] = f('otp_secret');
			}

			$session->editSecurity($data);
			Utils::redirect(ADMIN_URL . 'me/security.php?ok');
		}
		catch (UserException $e)
		{
			$form->addError($e->getMessage());
		}
	}

	$confirm = true;
}
elseif (f('save'))
{
	$form->check('edit_me_security', [
		'passe'       => 'confirmed|min:6',
	]);

	if (f('clef_pgp') && !$session->getPGPFingerprint(f('clef_pgp')))
	{
		$form->addError('Clé PGP invalide : impossible de récupérer l\'empreinte de la clé.');
	}
	
	if (!$form->hasErrors())
	{
		$confirm = true;
	}
}

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

if (f('otp') == 'generate')
{
	$tpl->assign('otp', $session->getNewOTPSecret());
}
elseif (f('otp') == 'disable')
{
	$tpl->assign('otp', 'disable');
}
elseif (f('otp_secret'))
{
	$tpl->assign('otp', $session->getOTPSecret(f('otp_secret')));
}
else
{
	$tpl->assign('otp', false);
}

$tpl->assign('pgp_disponible', \KD2\Security::canUseEncryption());

$fingerprint = '';

if ($user->clef_pgp)
{
	$fingerprint = $session->getPGPFingerprint($user->clef_pgp, true);
}

$tpl->assign('clef_pgp_fingerprint', $fingerprint);

$tpl->assign('passphrase', Utils::suggestPassword());
$tpl->assign('champs', $config->get('champs_membres')->getAll());

$tpl->assign('membre', $user);
$tpl->assign('ok', qg('ok') !== null);

$tpl->display('me/security.tpl');

Modified src/www/admin/me/services.php from [395c086295] to [29032889e2].

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

use Garradin\Services\Services_User;

require_once __DIR__ . '/_inc.php';

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

$list = Services_User::perUserList($user->id);
$list->loadFromQueryString();

$tpl->assign(compact('list'));

$tpl->assign('services', Services_User::listDistinctForUser($user->id));

$tpl->display('my_services.tpl');





|










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

use Garradin\Services\Services_User;

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

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

$list = Services_User::perUserList($user->id);
$list->loadFromQueryString();

$tpl->assign(compact('list'));

$tpl->assign('services', Services_User::listDistinctForUser($user->id));

$tpl->display('me/services.tpl');