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: |
541af2ee19e297c4ffc346753688865f |
User & Date: | bohwaz on 2021-05-26 21:41:05 |
Other Links: | manifest | tags |
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 | |
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 | </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} | | | | | 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 & 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 | <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> | | | 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 | {include file="admin/_head.tpl" title="Bonjour %s !"|args:$user.identite current="home"} {$banner|raw} <nav class="tabs"> <ul> | | | | 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="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 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="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 :</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 : <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 ? 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Ă© : 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 : <code>{$clef_pgp_fingerprint}</code></dd>{/if} </dl> <p class="block alert"> Attention : 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="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 | <?php namespace Garradin; | | | | | 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 | <?php namespace Garradin; | | > > | | | 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 | <?php namespace Garradin; | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | 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 | <?php namespace Garradin; use Garradin\Services\Services_User; | | | | 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'); |