Adapts Xorg MLs and aliases handling to new mail chain.
authorStéphane Jacob <sj@m4x.org>
Wed, 23 Feb 2011 15:08:16 +0000 (16:08 +0100)
committerStéphane Jacob <sj@m4x.org>
Sun, 27 Feb 2011 22:19:01 +0000 (23:19 +0100)
Signed-off-by: Stéphane Jacob <sj@m4x.org>
configs/platal.ini
include/emails.inc.php
include/validations/listes.inc.php
modules/lists.php
modules/xnetlists.php
templates/xnetlists/alias-admin.tpl
templates/xnetlists/index.tpl

index c6ae06f..4764c71 100644 (file)
@@ -256,6 +256,10 @@ vhost_sep = "_"
 ; Maximum number of mails an instance of the moderation cron accepts to deliver.
 max_mail_per_min = 400
 
+; $globals->lists->redirect_domain
+; Domain where mailing list emails are redirected.
+redirect_domain = ""
+
 
 ; The mail section contains parameters used to interacts with email routing
 [Mail]
index 4561329..3af0096 100644 (file)
@@ -24,6 +24,103 @@ define('ERROR_INACTIVE_REDIRECTION', 2);
 define('ERROR_INVALID_EMAIL', 3);
 define('ERROR_LOOP_EMAIL', 4);
 
+function add_to_list_alias(User $user, $local_part, $domain)
+{
+    Platal::assert($user !== null);
+
+    XDB::execute('INSERT IGNORE INTO  email_virtual (email, domain, redirect, type)
+                              SELECT  {?}, id, {?}, {?}
+                                FROM  email_virtual_domains
+                               WHERE  name = {?}',
+                 $local_part, $user->forlifeEmail(), $type, $domain);
+}
+
+function delete_from_list_alias(User $user, $local_part, $domain)
+{
+    Platal::assert($user !== null);
+
+    XDB::execute('DELETE  v
+                    FROM  email_virtual         AS v
+              INNER JOIN  email_virtual_domains AS m ON (v.domain = m.id)
+              INNER JOIN  email_virtual_domains AS d ON (d.aliasing = m.id)
+                   WHERE  v.email = {?} AND d.name = {?} AND v.redirect = {?} AND type = {?}',
+                 $local_part, $domain, $user->forlifeEmail(), $type);
+}
+
+function list_alias_members($local_part, $domain)
+{
+    $emails = XDB::fetchColumn('SELECT  DISTINCT(redirect)
+                                  FROM  email_virtual         AS v
+                            INNER JOIN  email_virtual_domains AS m ON (v.domain = m.id)
+                            INNER JOIN  email_virtual_domains AS d ON (d.aliasing = m.id)
+                                 WHERE  v.email = {?} AND d.name = {?} AND type = \'user\'',
+                               $local_part, $domain);
+
+    $members = array();
+    foreach ($emails as $email) {
+        $members[] = User::getSilent($email);
+    }
+
+    return $members;
+}
+
+function delete_list_alias($local_part, $domain)
+{
+    XDB::execute('DELETE  v
+                    FROM  email_virtual         AS v
+              INNER JOIN  email_virtual_domains AS m ON (v.domain = m.id)
+              INNER JOIN  email_virtual_domains AS d ON (d.aliasing = m.id)
+                   WHERE  v.email = {?} AND d.name = {?} AND type = \'user\'',
+                 $local_part, $domain);
+}
+
+function iterate_list_alias($domain)
+{
+    return XDB::fetchColumn('SELECT  CONCAT(v.email, \'@\', m.name)
+                               FROM  email_virtual         AS v
+                         INNER JOIN  email_virtual_domains AS m ON (v.domain = m.id)
+                              WHERE  m.name = {?} AND v.type = \'user\'
+                           GROUP BY  v.email',
+                            $domain);
+}
+
+function create_list($local_part, $domain)
+{
+    global $globals;
+
+    $redirect = $domain . '_' . $local_part . '+';
+    foreach(array('post', 'owner', 'admin', 'bounces', 'unsubscribe') as $suffix) {
+        XDB::execute('INSERT IGNORE INTO  email_virtual (email, domain, redirect, type)
+                                  SELECT  {?}, id, {?}, \'list\'
+                                    FROM  email_virtual_domains
+                                   WHERE  name = {?}',
+                     ($suffix == 'post') ? $local_part : $local_part . '-' . $suffix,
+                     $redirect . $suffix . '@' . $globals->lists->redirect_domain, $domain);
+    }
+}
+
+function delete_list($local_part, $domain)
+{
+    global $globals;
+
+    $redirect = $domain . '_' . $local_part . '+';
+    foreach(array('post', 'owner', 'admin', 'bounces', 'unsubscribe') as $suffix) {
+        XDB::execute('DELETE  email_virtual
+                       WHERE  redirect = {?} AND type = \'list\'',
+                     $redirect . $suffix . '@' . $globals->lists->redirect_domain);
+    }
+}
+
+function list_exist($local_part, $domain)
+{
+    return XDB::fetchOneCell('SELECT  COUNT(*)
+                                FROM  email_virtual         AS v
+                          INNER JOIN  email_virtual_domains AS m ON (v.domain = m.id)
+                          INNER JOIN  email_virtual_domains AS d ON (m.id = d.aliasing)
+                               WHERE  v.email = {?} AND d.name = {?}',
+                             $local_part, $domain);
+}
+
 // function mark_broken_email() {{{1
 function mark_broken_email($email, $admin = false)
 {
index 806ceae..197d187 100644 (file)
@@ -43,7 +43,7 @@ class ListeReq extends Validate
     // {{{ constructor
 
     public function __construct(User $_user, $_asso, $_liste, $_domain, $_desc, $_advertise,
-                                $_modlevel, $_inslevel, $_owners, $_members, $_stamp=0)
+                                $_modlevel, $_inslevel, $_owners, $_members, $_stamp = 0)
     {
         parent::__construct($_user, false, 'liste', $_stamp);
 
@@ -127,48 +127,23 @@ class ListeReq extends Validate
 
     public function commit()
     {
-        global $globals;
+        require_once 'emails.inc.php';
 
-        if ($this->asso == "alias") {
-            $new = $this->liste . '@' . $this->domain;
-            XDB::query('INSERT INTO virtual (alias, type) VALUES({?}, "user")', $new);
+        if ($this->asso == 'alias') {
             foreach ($this->members as $member) {
                 $user = User::get($member);
-                if ($user != null) {
-                    XDB::query(
-                        "INSERT INTO  virtual_redirect (vid, redirect)
-                              SELECT  vid, {?}
-                                FROM  virtual
-                               WHERE  alias = {?}", $user->forlifeEmail(), $new);
-                }
+                add_to_list_alias($user, $this->liste, $this->domain);
             }
-            return 1;
-        }
-
-        $list = new MMList(S::user(), $this->domain);
-        $ret = $list->create_list($this->liste, utf8_decode($this->desc), $this->advertise,
-                                  $this->modlevel, $this->inslevel,
-                                  $this->owners, $this->members);
-        $liste = strtolower($this->liste);
-        if ($ret && !$this->asso) {
-            foreach(Array($liste, $liste . "-owner", $liste . "-admin", $liste . "-bounces", $liste . "-unsubscribe") as $l) {
-                XDB::execute("INSERT INTO aliases (alias, type) VALUES({?}, 'liste')", $l);
-            }
-        } elseif ($ret) {
-            foreach (Array('', 'owner', 'admin', 'bounces', 'unsubscribe') as $app) {
-                $mdir = $app == '' ? '+post' : '+' . $app;
-                if (!empty($app)) {
-                    $app  = '-' . $app;
-                }
-                $red = $this->domain . '_' . $liste;
-                XDB::execute('INSERT INTO virtual (alias, type)
-                                        VALUES({?}, {?})', $liste . $app . '@' . $this->domain, 'list');
-                XDB::execute('INSERT INTO virtual_redirect (vid, redirect)
-                                        VALUES ({?}, {?})', XDB::insertId(),
-                                       $red . $mdir . '@listes.polytechnique.org');
+        } else {
+            $list = new MMList(S::user(), $this->domain);
+            $success = $list->create_list($this->liste, utf8_decode($this->desc), $this->advertise,
+                                          $this->modlevel, $this->inslevel,
+                                          $this->owners, $this->members);
+            if ($success) {
+                create_list($this->liste, $this->domain);
             }
+            return $success;
         }
-        return $ret;
     }
 
     // }}}
index 60ca0ee..5c4ec4d 100644 (file)
@@ -239,17 +239,17 @@ class ListsModule extends PLModule
             S::assert_xsrf_token();
         }
 
-        $asso = Post::v('asso');
-        $liste = Post::v('liste');
+        $asso = Post::t('asso');
+        $list = strtolower(Post::t('liste'));
 
-        if (empty($liste)) {
+        if (empty($list)) {
             $page->trigError('Le champ «&nbsp;adresse souhaitée&nbsp;» est vide.');
         }
-        if (!preg_match("/^[a-zA-Z0-9\-]*$/", $liste)) {
+        if (!preg_match("/^[a-zA-Z0-9\-]*$/", $list)) {
             $page->trigError('Le nom de la liste ne doit contenir que des lettres non accentuées, chiffres et tirets.');
         }
 
-        if (($asso == "binet") || ($asso == "alias")) {
+        if (($asso == 'binet') || ($asso == 'alias')) {
             $promo = Post::i('promo');
             $domain = $promo . '.' . $globals->mail->domain;
 
@@ -257,35 +257,25 @@ class ListsModule extends PLModule
                 $page->trigError('La promotion est mal renseignée, elle doit être du type&nbsp;: 2004.');
             }
 
-            $new = $liste . '@' . $domain;
-            $res = XDB::query('SELECT COUNT(*) FROM virtual WHERE alias={?}', $new);
-
-        } else {
-            if ($asso == "groupex") {
-                $groupex_name = Post::v('groupex_name');
-
-                $res_groupe = XDB::query('SELECT mail_domain FROM groups WHERE nom={?}', $groupex_name);
-                $domain = $res_groupe->fetchOneCell();
+        } elseif ($asso == 'groupex') {
+                $domain = XDB::fetchOneCell('SELECT  mail_domain
+                                               FROM  groups
+                                              WHERE  nom = {?}',
+                                            Post::t('groupex_name'));
 
                 if (!$domain) {
                     $page->trigError('Il n\'y a aucun groupe de ce nom sur Polytechnique.net.');
                 }
-
-                $new = $liste . '@' . $domain;
-                $res = XDB::query('SELECT COUNT(*) FROM virtual WHERE alias={?}', $new);
-            } else {
-                $res = XDB::query("SELECT COUNT(*) FROM aliases WHERE alias={?}", $liste);
-                $domain = $globals->mail->domain;
-            }
+        } else {
+            $domain = $globals->mail->domain;
         }
 
-        $n = $res->fetchOneCell();
-
-        if ($n) {
+        require_once 'emails.inc.php';
+        if (list_exist($list, $domain)) {
             $page->trigError("L'«&nbsp;adresse souhaitée&nbsp;» est déjà prise.");
         }
 
-        if (!Post::v('desc')) {
+        if (!Post::t('desc')) {
             $page->trigError('Le sujet est vide.');
         }
 
@@ -293,15 +283,15 @@ class ListsModule extends PLModule
             $page->trigError('Il n\'y a pas de gestionnaire.');
         }
 
-        if (count($members)<4) {
+        if (count($members) < 4) {
             $page->trigError('Il n\'y a pas assez de membres.');
         }
 
         if (!$page->nb_errs()) {
             $page->trigSuccess('Demande de création envoyée&nbsp;!');
             $page->assign('created', true);
-            $req = new ListeReq(S::user(), $asso, $liste, $domain,
-                                Post::v('desc'), Post::i('advertise'),
+            $req = new ListeReq(S::user(), $asso, $list, $domain,
+                                Post::t('desc'), Post::i('advertise'),
                                 Post::i('modlevel'), Post::i('inslevel'),
                                 $owners, $members);
             $req->submit();
@@ -805,26 +795,14 @@ class ListsModule extends PLModule
         }
 
         $domain = $this->prepare_client($page);
-        if ($domain == $globals->mail->domain || $domain == $globals->mail->domain2) {
-            $domain = '';
-            $table  = 'aliases';
-            $type   = 'liste';
-        } else {
-            $domain = '@' . $domain;
-            $table  = 'virtual';
-            $type   = 'list';
-        }
-
         $page->changeTpl('lists/delete.tpl');
         if (Post::v('valid') == 'OUI') {
             S::assert_xsrf_token();
 
             if ($this->client->delete_list($liste, Post::b('del_archive'))) {
-                foreach (array('', '-owner', '-admin', '-bounces', '-unsubscribe') as $app) {
-                    XDB::execute("DELETE FROM  $table
-                                        WHERE  type={?} AND alias={?}",
-                                 $type, $liste.$app.$domain);
-                }
+                require_once 'emails.inc.php';
+
+                delete_list($liste, $domain);
                 $page->assign('deleted', true);
                 $page->trigSuccess('La liste a été détruite&nbsp;!');
             } else {
index a7a2ff0..3f0e792 100644 (file)
@@ -73,6 +73,7 @@ class XnetListsModule extends ListsModule
     function handler_lists($page)
     {
         global $globals;
+        require_once 'emails.inc.php';
 
         if (!$globals->asso('mail_domain')) {
             return PL_NOT_FOUND;
@@ -94,28 +95,15 @@ class XnetListsModule extends ListsModule
         if (Post::has('del_alias') && may_update()) {
             S::assert_xsrf_token();
 
-            $alias = Post::v('del_alias');
-            // prevent group admin from erasing aliases from other groups
-            $alias = substr($alias, 0, strpos($alias, '@')).'@'.$globals->asso('mail_domain');
-            XDB::query(
-                    'DELETE FROM  r, v
-                           USING  virtual AS v
-                       LEFT JOIN  virtual_redirect AS r USING(vid)
-                           WHERE  v.alias={?}', $alias);
-            $page->trigSuccess(Post::v('del_alias')." supprimé&nbsp;!");
+            $alias = Post::t('del_alias');
+            list($local_part, ) = explode('@', $alias);
+            delete_list_alias($local_part, $globals->asso('mail_domain'));
+            $page->trigSuccess($alias . ' supprimé&nbsp;!');
         }
 
         $listes = $this->client->get_lists();
         $page->assign('listes', $listes);
-
-        $alias  = XDB::iterator(
-                'SELECT  alias,type
-                   FROM  virtual
-                  WHERE  alias
-                   LIKE  {?} AND type="user"
-               ORDER BY  alias', '%@'.$globals->asso('mail_domain'));
-        $page->assign('alias', $alias);
-
+        $page->assign('aliases', iterate_list_alias($globals->asso('mail_domain')));
         $page->assign('may_update', may_update());
 
         if (count($listes) > 0 && !$globals->asso('has_ml')) {
@@ -142,61 +130,44 @@ class XnetListsModule extends ListsModule
             S::assert_xsrf_token();
         }
 
-        if (!Post::has('liste') || !Post::v('liste')) {
+        if (!Post::has('liste') || !Post::t('liste')) {
             $page->trigError('Le champs «&nbsp;adresse souhaitée&nbsp;» est vide.');
             return;
         }
 
-        $liste = strtolower(Post::v('liste'));
-
-        if (!preg_match("/^[a-zA-Z0-9\-]*$/", $liste)) {
+        $list = strtolower(Post::t('liste'));
+        if (!preg_match("/^[a-zA-Z0-9\-]*$/", $list)) {
             $page->trigError('le nom de la liste ne doit contenir que des lettres non accentuées, chiffres et tirets');
             return;
         }
 
-        $new = $liste.'@'.$globals->asso('mail_domain');
-        $res = XDB::query('SELECT alias FROM virtual WHERE alias={?}', $new);
-
-        if ($res->numRows()) {
-            $page->trigError('cet alias est déjà pris');
+        require_once 'emails.inc.php';
+        if (list_exist($list, $globals->asso('mail_domain'))) {
+            $page->trigError('Cet alias est déjà pris.');
             return;
         }
-        if (!Post::v('desc')) {
-            $page->trigError('le sujet est vide');
+        if (!Post::t('desc')) {
+            $page->trigError('Le sujet est vide.');
             return;
         }
 
-        $ret = $this->client->create_list(
-                    $liste, utf8_decode(Post::v('desc')), Post::v('advertise'),
-                    Post::v('modlevel'), Post::v('inslevel'),
-                    array(S::user()->forlifeEmail()), array(S::user()->forlifeEmail()));
-
-        $dom = strtolower($globals->asso("mail_domain"));
-        $red = $dom.'_'.$liste;
+        $success = $this->client->create_list($list, utf8_decode(Post::t('desc')), Post::t('advertise'),
+                                              Post::t('modlevel'), Post::t('inslevel'),
+                                              array(S::user()->forlifeEmail()), array(S::user()->forlifeEmail()));
 
-        if (!$ret) {
+        if (!$success) {
             $page->kill("Un problème est survenu, contacter "
                         ."<a href='mailto:support@m4x.org'>support@m4x.org</a>");
             return;
         }
-        foreach (array('', 'owner', 'admin', 'bounces', 'unsubscribe') as $app) {
-            $mdir = $app == '' ? '+post' : '+' . $app;
-            if (!empty($app)) {
-                $app  = '-' . $app;
-            }
-            XDB::execute('INSERT INTO virtual (alias,type)
-                                    VALUES({?},{?})', $liste. $app . '@'.$dom, 'list');
-            XDB::execute('INSERT INTO virtual_redirect (vid,redirect)
-                                    VALUES ({?}, {?})', XDB::insertId(),
-                                   $red . $mdir . '@listes.polytechnique.org');
-        }
+        create_list($list, $globals->asso('mail_domain'));
 
         XDB::execute("UPDATE  groups
                          SET  flags = CONCAT_WS(',', IF(flags = '', NULL, flags), 'has_ml')
                        WHERE  id = {?}",
                      $globals->asso('id'));
 
-        pl_redirect('lists/admin/'.$liste);
+        pl_redirect('lists/admin/' . $list);
     }
 
     function handler_sync($page, $liste = null)
@@ -242,53 +213,30 @@ class XnetListsModule extends ListsModule
         }
         $page->changeTpl('xnetlists/alias-admin.tpl');
 
+        require_once 'emails.inc.php';
+        list($local_part, $domain) = explode('@', $lfull);
         if (Env::has('add_member')) {
             S::assert_xsrf_token();
 
-            $add = Env::t('add_member');
-            $user = User::getSilent($add);
+            $email = Env::t('add_member');
+            $user = User::getSilent($email);
             if ($user) {
-                $add = $user->forlifeEmail();
-            } else if (!User::isForeignEmailAddress($add)) {
-                $add = null;
-            }
-            if (!empty($add)) {
-                XDB::execute('INSERT INTO  virtual_redirect (vid, redirect)
-                                   SELECT  vid, {?}
-                                     FROM  virtual
-                                    WHERE  alias = {?}', strtolower($add), $lfull);
-                $page->trigSuccess($add . ' ajouté.');
+                add_to_list_alias($user, $local_part, $domain);
+                $page->trigSuccess($email . ' ajouté.');
             } else {
-                $page->trigError($add . " n'existe pas.");
+                $page->trigError($email . " n'existe pas.");
             }
         }
 
         if (Env::has('del_member')) {
             S::assert_xsrf_token();
-            XDB::query(
-                    "DELETE FROM  virtual_redirect
-                           USING  virtual_redirect
-                      INNER JOIN  virtual USING(vid)
-                           WHERE  redirect={?} AND alias={?}", Env::v('del_member'), $lfull);
-            pl_redirect('alias/admin/'.$lfull);
-        }
 
-        global $globals;
-        $emails = XDB::fetchColumn('SELECT  redirect
-                                      FROM  virtual_redirect AS vr
-                                INNER JOIN  virtual          AS v  USING(vid)
-                                     WHERE  v.alias = {?}
-                                  ORDER BY  redirect', $lfull);
-        $mem = array();
-        foreach ($emails as $email) {
-            $user = User::getSilent($email);
-            if ($user) {
-                $mem[] = array('user' => $user, 'email' => $email);
-            } else {
-                $mem[] = array('email' => $email);
-            }
+            $user = User::getSilent(Env::t('del_member'));
+            delete_from_list_alias($user, $local_part, $domain);
+            $page->trigSuccess($user->fullName() . ' supprimé.');
         }
-        $page->assign('mem', $mem);
+
+        $page->assign('members', list_alias_members($local_part, $domain));
     }
 
     function handler_acreate($page)
@@ -310,24 +258,21 @@ class XnetListsModule extends ListsModule
             $page->trigError('Le champs «&nbsp;adresse souhaitée&nbsp;» est vide.');
             return;
         }
-        $liste = Post::v('liste');
-        if (!preg_match("/^[a-zA-Z0-9\-\.]*$/", $liste)) {
-            $page->trigError('le nom de l\'alias ne doit contenir que des lettres,'
-                            .' chiffres, tirets et points');
+        $list = Post::v('liste');
+        if (!preg_match("/^[a-zA-Z0-9\-\.]*$/", $list)) {
+            $page->trigError('Le nom de l\'alias ne doit contenir que des lettres,'
+                            .' chiffres, tirets et points.');
             return;
         }
 
-        $new = $liste.'@'.$globals->asso('mail_domain');
-        $res = XDB::query('SELECT COUNT(*) FROM virtual WHERE alias = {?}', $new);
-        $n   = $res->fetchOneCell();
-        if ($n) {
-            $page->trigError('cet alias est déjà pris');
+        require_once 'emails.inc.php';
+        if (list_exist($list, $globals->asso('mail_domain'))) {
+            $page->trigError('Cet alias est déjà pris.');
             return;
         }
 
-        XDB::query('INSERT INTO virtual (alias,type) VALUES({?}, "user")', $new);
-
-        pl_redirect("alias/admin/$new");
+        add_to_list_alias(S::user(), $list, $globals->asso('mail_domain'));
+        pl_redirect('alias/admin/' . $list . '@' . $globals->asso('mail_domain'));
     }
 
     function handler_profile($page, $user = null)
index 654c45f..3a8680a 100644 (file)
 <p>[<a href='{$platal->ns}lists'>retour à la page des listes</a>]</p>
 
 <h1>Membres de {$platal->argv[1]}</h1>
-
 <table class='tinybicol'>
-  {if $mem|@count}
-  {foreach from=$mem item=m}
+  {if $members|@count}
+  {foreach from=$members item=member}
   <tr>
     <td>
-      {if $m.user}
-      {if $m.admin}<strong>{/if}
-      <a href="https://www.polytechnique.org/profile/{$m.user->login()}" class="popup2">{$m.user->fullName()}</a>
-      {if $m.admin}</strong>{/if}
-      {else}
-      {$m.email}
-      {/if}
-    </td>
-    <td class="right">
-      {if $m.user}
-      {if $m.admin}<strong>{/if}
-      {$m.user->promo()}
-      {if $m.admin}</strong>{/if}
-      {/if}
+      <a href="https://www.polytechnique.org/profile/{$member->profile()->hrpid}" class="popup2">{$member->fullName()}</a>
     </td>
+    <td class="right">{$member->promo()}</td>
     <td class="center">
-      <a href='{$platal->ns}alias/admin/{$platal->argv[1]}?del_member={$m.email|urlencode}&amp;token={xsrf_token}'>
+      <a href='{$platal->ns}alias/admin/{$platal->argv[1]}?del_member={$member->id()}&amp;token={xsrf_token}'>
       {icon name=delete title='retirer membre'}
       </a>
     </td>
@@ -54,7 +41,7 @@
   {else}
   <tr>
     <td colspan="3">
-      <em>aucun membre&hellip;<em>
+      <em>aucun membre&hellip;</em>
     </td>
   </tr>
   {/if}
index 75dcdbc..3bae8b3 100644 (file)
@@ -112,18 +112,18 @@ t'empêcherait de t'y réabonner par la suite sans l'aide d'un administrateur.
   <tr>
     <th{if $may_update} colspan='3'{/if}>Alias</th>
   </tr>
-  {if $alias->total()}
-  {iterate from=$alias item=a}
+  {if $aliases|@count}
+  {foreach from=$aliases item=alias}
   <tr>
     {if $may_update}
-    <td class="center"><a href='mailto:{$a.alias}'>{icon name=email title="email"}</a></td>
-    <td><a href="{$platal->ns}alias/admin/{$a.alias}">{$a.alias}</a></td>
-    <td class="center"><a href="{$platal->ns}lists?del_alias={$a.alias}">{icon name=delete title='supprimer'}</a></td>
+    <td class="center"><a href='mailto:{$alias}'>{icon name=email title="email"}</a></td>
+    <td><a href="{$platal->ns}alias/admin/{$alias}">{$alias}</a></td>
+    <td class="center"><a href="{$platal->ns}lists?del_alias={$alias}">{icon name=delete title='supprimer'}</a></td>
     {else}
-    <td><a href='mailto:{$a.alias}'>{icon name=email title="email"} {$a.alias}</a></td>
+    <td><a href='mailto:{$alias}'>{icon name=email title="email"} {$alias}</a></td>
     {/if}
   </tr>
-  {/iterate}
+  {/foreach}
   {else}
   <tr>
     <td{if $may_update} colspan='3'{/if}>Aucun alias pour ce groupe</td>