- Export contributions as CSV
- Drop group name uniqueness at same level
- Add information to display for dynamic fields
+- Add preferences for groups manager to edit, create members, edit groups, send mailing and perform exports
- Fix various XSS issues
- Fix possible SQL injection
- Add CSRF protection
'charts' => 'staff',
'/(.+)?plugin(.+)?/i' => 'admin',
'/(.+)?mailing(.+)?/i' => 'staff',
+ 'mailing' => 'groupmanager',
+ 'doMailing' => 'groupmanager',
+ 'mailingPreview' => 'groupmanager',
+ 'mailingRecipients' => 'groupmanager',
'/(.+)?history(.+)?/i' => 'staff',
'/(.+)?import(.+)?/i' => 'staff',
'/(.+)?export(.+)?/i' => 'staff',
'/(.+)?member(.+)?/i' => 'groupmanager',
'ajaxGroupMembers' => 'staff',
'duplicateMember' => 'staff',
- 'csv-memberslist' => 'staff',
'payments_filter' => 'member',
'adhesionForm' => 'member',
'getDynamicFile' => 'member',
// /Members rules
// Groups rules
'/(.+)?group(.+)?/i' => 'groupmanager',
- 'ajax_groups_reorder' => 'staff', //groups ordering is limited to staff
'add_group' => 'staff', //adding group is for staff only
// /Groups rules
'/(.+)?reminder(.+)?/i' => 'staff',
'/(.+)?paymentType(.+)?/i' => 'staff',
'/(.+)?dynamicTranslation(.+)?/i' => 'staff',
- 'previewAttachment' => 'staff',
+ 'previewAttachment' => 'groupmanager',
'getCsv' => 'staff',
'pdfModels' => 'staff',
'attendance_sheet_details' => 'groupmanager',
*
* PHP version 5
*
- * Copyright © 2020 The Galette Team
+ * Copyright © 2020-2021 The Galette Team
*
* This file is part of Galette (http://galette.tuxfamily.org).
*
* @package Galette
*
* @author Johan Cwiklinski <johan@x-tnd.be>
- * @copyright 2020 The Galette Team
+ * @copyright 2020-2021 The Galette Team
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
* @link http://galette.tuxfamily.org
* @since Available since 0.9.4dev - 2020-05-06
* Galette groups controller
*
* @category Controllers
- * @name PaymentTypeController
+ * @name GroupsController
* @package Galette
* @author Johan Cwiklinski <johan@x-tnd.be>
- * @copyright 2020 The Galette Team
+ * @copyright 2020-2021 The Galette Team
* @license http://www.gnu.org/licenses/gpl-3.0.html GPL License 3.0 or (at your option) any later version
* @link http://galette.tuxfamily.org
* @since Available since 0.9.4dev - 2020-05-06
public function add(Request $request, Response $response): Response
{
//no new page (included on list), just to satisfy inheritance
+ return $response;
}
/**
*/
public function reorder(Request $request, Response $response): Response
{
+ if (
+ !$this->login->isAdmin()
+ && !$this->login->isStaff()
+ && !($this->login->isGroupManager() && $this->preferences->pref_bool_groupsmanagers_edit_groups)
+ ) {
+ throw new \RuntimeException('Trying to reorder groups without appropriate permissions');
+ }
+
$post = $request->getParsedBody();
if (!isset($post['to']) || !isset($post['id_group']) || $post['id_group'] == '') {
Analog::log(
}
if ($this->login->isGroupManager()) {
- //store requested groups
- $groups_adh = $post['groups_adh'] ?? null;
- $managed_groups_adh = $post['groups_managed_adh'] ?? null;
-
//add/remove user from groups
+ $groups_adh = $post['groups_adh'] ?? null;
$add_groups = Groups::addMemberToGroups(
$member,
$groups_adh
if ($add_groups === false) {
$error_detected[] = _T("An error occurred adding member to its groups.");
}
-
+ }
+ if ($this->login->isSuperAdmin() || $this->login->isAdmin() || $this->login->isStaff()) {
//add/remove manager from groups
+ $managed_groups_adh = $post['groups_managed_adh'] ?? null;
$add_groups = Groups::addMemberToGroups(
$member,
$managed_groups_adh,
* @property integer $pref_password_strength
* @property integer $pref_default_paymenttype
* @property boolean $pref_bool_create_member
+ * @property boolean $pref_bool_groupsmanagers_create_member
+ * @property boolean $pref_bool_groupsmanagers_edit_member
+ * @property boolean $pref_bool_groupsmanagers_edit_groups
+ * @property boolean $pref_bool_groupsmanagers_mailings
+ * @property boolean $pref_bool_groupsmanagers_exports
* @property-read string $vpref_email_newadh Comma separated list of mail senders
*/
class Preferences
'pref_password_blacklist' => false,
'pref_password_strength' => self::PWD_NONE,
'pref_default_paymenttype' => PaymentType::CHECK,
- 'pref_bool_create_member' => false
+ 'pref_bool_create_member' => false,
+ 'pref_bool_groupsmanagers_create_member' => false,
+ 'pref_bool_groupsmanagers_edit_member' => false,
+ 'pref_bool_groupsmanagers_edit_groups' => false,
+ 'pref_bool_groupsmanagers_mailings' => false,
+ 'pref_bool_groupsmanagers_exports' => true
);
/** @var Social[] */
*/
public function check(array $values, array $required, array $disabled)
{
+ global $login;
+
$this->errors = array();
//Sanitize
$this->_parent = null;
}
+ if ($login->isGroupManager() && !$login->isAdmin() && !$login->isStaff()) {
+ if (!isset($values['groups_adh'])) {
+ $this->errors[] = _T('You have to select a group you own!');
+ } else {
+ foreach ($values['groups_adh'] as $group) {
+ list($gid) = explode('|', $group);
+ if (!$login->isGroupManager($gid)) {
+ $this->errors[] = _T('You have to select a group you own!');
+ }
+ }
+ }
+ }
+
$this->dynamicsCheck($values, $required, $disabled);
$this->checkSocials($values);
}
} catch (Throwable $e) {
Analog::log(
- 'An error occurred checking member email unicity.',
+ 'An error occurred checking member email uniqueness.',
Analog::ERROR
);
$this->errors[] = _T("An error has occurred while looking if login already exists.");
return true;
}
+ if ($preferences->pref_bool_groupsmanagers_create_member && $login->isGroupManager()) {
+ return true;
+ }
+
if ($preferences->pref_bool_create_member && $login->isLogged()) {
return true;
}
*/
public function canEdit(Login $login): bool
{
+ global $preferences;
+
//admin and staff users can edit, as well as member itself
if ($this->id && $login->id == $this->id || $login->isAdmin() || $login->isStaff()) {
return true;
return true;
}
- //group managers can edit members of groups they manage
- if ($login->isGroupManager()) {
+ //group managers can edit members of groups they manage when pref is on
+ if ($preferences->pref_bool_groupsmanagers_edit_member && $login->isGroupManager()) {
foreach ($this->getGroups() as $g) {
if ($login->isGroupManager($g->getId())) {
return true;
*/
public function canShow(Login $login): bool
{
+ //group managers can show members of groups they manage
+ if ($login->isGroupManager()) {
+ foreach ($this->getGroups() as $g) {
+ if ($login->isGroupManager($g->getId())) {
+ return true;
+ }
+ }
+ }
+
return $this->canEdit($login);
}
{/foreach}
</tbody>
</table>
-{if $nb_members != 0}
+{if $nb_members != 0 && ($login->isGroupManager() && $preferences->pref_bool_groupsmanagers_exports || $login->isAdmin() || $login->isStaff())}
<div class="center cright">
{_T string="Pages:"}<br/>
<ul class="pages">{$pagination}</ul>
<i class="fas fa-cookie-bite fa-fw"></i> {_T string="Mass add contributions"}
</button>
</li>
+ {/if}
+ {if $login->isAdmin() or $login->isStaff() or $login->isGroupManager() and $preferences->pref_bool_groupsmanagers_mailings}
{if $pref_mail_method neq constant('Galette\Core\GaletteMail::METHOD_DISABLED')}
<li>
<button type="submit" id="sendmail" name="mailing">
</li>
{/if}
{/if}
+
+ {if $login->isGroupManager() && $preferences->pref_bool_groupsmanagers_exports || $login->isAdmin() || $login->isStaff()}
<li>
<button type="submit" id="attendance_sheet" name="attendance_sheet">
<i class="fas fa-file-alt fa-fw"></i> {_T string="Attendance sheet"}
<i class="fas fa-id-badge fa-fw"></i> {_T string="Generate Member Cards"}
</button>
</li>
- {if $login->isAdmin() or $login->isStaff()}
<li>
<button type="submit" id="csv" name="csv">
<i class="fas fa-file-csv fa-fw"></i> {_T string="Export as CSV"}
</section>
</div>
<div class="button-container">
+{if $login->isGroupManager() && $preferences->pref_bool_groupsmanagers_exports || $login->isAdmin() || $login->isStaff()}
<a href="{path_for name="pdf_groups"}" class="button tooltip" title="{_T string="Export all groups and their members as PDF"}">
<i class="fas fa-file-pdf"></i> {_T string="All groups PDF"}
</a>
+{/if}
</div>
{/block}
+{assign var="canEdit" value=$login->isGroupManager() && $preferences->pref_bool_groupsmanagers_edit_groups || $login->isAdmin() || $login->isStaff()}
+{assign var="canExport" value=$login->isGroupManager() && $preferences->pref_bool_groupsmanagers_exports || $login->isAdmin() || $login->isStaff()}
<form class="tabbed" action="{path_for name="doEditGroup" data=["id" => $group->getId()]}" method="post" enctype="multipart/form-data" id="group_form">
<div id="group">
<ul>
</p>
{/if}
<p>
+{if $canEdit}
<label for="group_name" class="bline">{_T string="Name:"}</label>
<input type="text" name="group_name" id="group_name" value="{$group->getName()}" maxlength="20" required/>
+{else}
+ <span class="bline">{_T string="Name:"}</span>
+ {$group->getName()}
+{/if}
</p>
+
+
{if $group->getParentGroup()}
{assign var='pgroup' value=$group->getParentGroup()}
{/if}
<p>
-{if !$login->isAdmin() && !$login->isStaff()}
+{if $canEdit}
+ <label for="parent_group" class="bline">{_T string="Parent group:"}</label>
+ <select name="parent_group" id="parent_group">
+ <option value="">{_T string="None"}</option>
+ {foreach item=g from=$groups}
+ {if $group->canSetParentGroup($g)}
+ <option value="{$g->getId()}"{if isset($pgroup) and $pgroup->getId() eq $g->getId()} selected="selected"{/if}>{$g->getIndentName()}</option>
+ {/if}
+ {/foreach}
+ </select>
+{else}
<span class="bline">{_T string="Parent group:"}</span>
<span>
{if isset($pgroup)}
{$pgroup->getName()}
<input type="hidden" name="parent_group" value="{$pgroup->getId()}"/>
+ {else}
+ -
{/if}
</span>
-{else}
- <label for="parent_group" class="bline">{_T string="Parent group:"}</label>
- <select name="parent_group" id="parent_group">
- <option value="">{_T string="None"}</option>
-{foreach item=g from=$groups}
- {if $group->canSetParentGroup($g)}
- <option value="{$g->getId()}"{if isset($pgroup) and $pgroup->getId() eq $g->getId()} selected="selected"{/if}>{$g->getIndentName()}</option>
- {/if}
-{/foreach}
- </select>
{/if}
</p>
</div>
{/if}
</div>
<div class="button-container">
+{if $canEdit}
<button type="submit" name="valid" class="button action">
<i class="fas fa-save fa-fw"></i> {_T string="Save"}
</button>
+ <input type="hidden" name="id_group" id="id_group" value="{$group->getId()}"/>
+ {include file="forms_types/csrf.tpl"}
+{/if}
{if $login->isAdmin() or $login->isStaff()}
<a class="button delete" id="delete" href="{path_for name="removeGroup" data=["id" => $group->getId()]}">
<i class="fas fa-trash-alt fa-fw"></i>
{_T string="Delete"}
</a>
{/if}
+{if $canExport}
<a href="{path_for name="pdf_groups" data=["id" => $group->getId()]}" class="button tooltip" title="{_T string="Current group (and attached people) as PDF"}">
<i class="fas fa-file-pdf" aria-hidden="true"></i>
{_T string="Group PDF"}
</a>
- <input type="hidden" name="id_group" id="id_group" value="{$group->getId()}"/>
- {include file="forms_types/csrf.tpl"}
+{/if}
</div>
+{if $canEdit}
<p>{_T string="NB : The mandatory fields are in"} <span class="required">{_T string="red"}</span></p>
+{/if}
</form>
<script type="text/javascript">
$(function() {
{if $login->isAdmin() or $login->isStaff()}
<li{if $cur_route eq "contributions" and $cur_subroute eq "contributions"} class="selected"{/if}><a href="{path_for name="contributions" data=["type" => "contributions"]}" title="{_T string="View and filter contributions"}">{_T string="List of contributions"}</a></li>
<li{if $cur_route eq "contributions" and $cur_subroute eq "transactions"} class="selected"{/if}><a href="{path_for name="contributions" data=["type" => "transactions"]}" title="{_T string="View and filter transactions"}">{_T string="List of transactions"}</a></li>
+ {/if}
+ {if $login->isAdmin() or $login->isStaff() or ($login->isGroupManager() and $preferences->pref_bool_groupsmanagers_create_member)}
<li{if $cur_route eq "editMember" or $cur_route eq "addMember"} class="selected"{/if}><a href="{path_for name="addMember"}" title="{_T string="Add new member in database"}">{_T string="Add a member"}</a></li>
+ {/if}
+ {if $login->isAdmin() or $login->isStaff()}
<li{if $cur_route eq "addContribution" and $cur_subroute eq constant('Galette\Entity\Contribution::TYPE_FEE')} class="selected"{/if}><a href="{path_for name="addContribution" data=["type" => constant('Galette\Entity\Contribution::TYPE_FEE')]}" title="{_T string="Add new membership fee in database"}">{_T string="Add a membership fee"}</a></li>
<li{if $cur_route eq "addContribution" and $cur_subroute eq constant('Galette\Entity\Contribution::TYPE_DONATION')} class="selected"{/if}><a href="{path_for name="addContribution" data=["type" => constant('Galette\Entity\Contribution::TYPE_DONATION')]}" title="{_T string="Add new donation in database"}">{_T string="Add a donation"}</a></li>
<li{if $cur_route eq "addTransaction" or $cur_route eq "editTransaction"} class="selected"{/if}><a href="{path_for name="addTransaction"}" title="{_T string="Add new transaction in database"}">{_T string="Add a transaction"}</a></li>
<li{if $cur_route eq "export"} class="selected"{/if}><a href="{path_for name="export"}" title="{_T string="Export some data in various formats"}">{_T string="Exports"}</a></li>
<li{if $cur_route eq "import" or $cur_route eq "importModel"} class="selected"{/if}><a href="{path_for name="import"}" title="{_T string="Import members from CSV files"}">{_T string="Imports"}</a></li>
<li class="mnu_last{if $cur_route eq "charts"} selected{/if}"><a href="{path_for name="charts"}" title="{_T string="Various charts"}">{_T string="Charts"}</a></li>
- {else}
- <li{if $cur_route eq "contributions" and $cur_subroute eq "contributions"} class="selected"{/if}><a href="{path_for name="contributions" data=["type" => "contributions"]}" title="{_T string="View and filter all my contributions"}">{_T string="My contributions"}</a></li>
- <li{if $cur_route eq "contributions" and $cur_subroute eq "transactions"} class="selected"{/if}><a href="{path_for name="contributions" data=["type" => "transactions"]}" title="{_T string="View and filter all my transactions"}">{_T string="My transactions"}</a></li>
{/if}
{if !$login->isSuperAdmin()}
+ <li{if $cur_route eq "contributions" and $cur_subroute eq "contributions"} class="selected"{/if}><a href="{path_for name="contributions" data=["type" => "contributions"]}" title="{_T string="View and filter all my contributions"}">{_T string="My contributions"}</a></li>
+ <li{if $cur_route eq "contributions" and $cur_subroute eq "transactions"} class="selected"{/if}><a href="{path_for name="contributions" data=["type" => "transactions"]}" title="{_T string="View and filter all my transactions"}">{_T string="My transactions"}</a></li>
<li{if $cur_route eq "me" or $cur_route eq "member"} class="selected"{/if}><a href="{path_for name="me"}" title="{_T string="View my member card"}">{_T string="My information"}</a></li>
{/if}
</ul>
<li><a href="#general">{_T string="General"}</a></li>
<li><a href="#social">{_T string="Social networks"}</a></li>
<li><a href="#parameters">{_T string="Parameters"}</a></li>
+ <li><a href="#rights">{_T string="Rights"}</a></li>
<li><a href="#mail">{_T string="E-Mail"}</a></li>
<li><a href="#labels">{_T string="Labels"}</a></li>
<li><a href="#cards">{_T string="Cards"}</a></li>
</select>
</p>
<p>
- <label for="pref_bool_create_member" class="bline tooltip">{_T string="Can members create child?"}</label>
- <span class="tip">{_T string="Any logged in member will be able to create his own child cards"}</span>
- <input type="checkbox" name="pref_bool_create_member" id="pref_bool_create_member" value="1" {if $pref.pref_bool_create_member eq 1}checked="checked"{/if}{if isset($required.pref_bool_create_member) and $required.pref_bool_create_member eq 1} required="required"{/if}/>
- </p>
- <p>
-
<label for="pref_redirect_on_create" class="bline">{_T string="After member creation:"}</label>
<select name="pref_redirect_on_create" id="pref_redirect_on_create">
<option value="{constant('Galette\Entity\Adherent::AFTER_ADD_DEFAULT')}"{if $pref.pref_redirect_on_create == constant('Galette\Entity\Adherent::AFTER_ADD_DEFAULT')} selected="selected"{/if}>{_T string="create a new contribution (default action)"}</option>
</p>
</fieldset>
+ <fieldset class="cssform" id="rights">
+ <legend>{_T string="Rights"}</legend>
+ <p>
+ <label for="pref_bool_create_member" class="bline">{_T string="Can members create child?"}</label>
+ <input type="checkbox" name="pref_bool_create_member" id="pref_bool_create_member" value="1" {if $pref.pref_bool_create_member eq 1}checked="checked"{/if}{if isset($required.pref_bool_create_member) and $required.pref_bool_create_member eq 1} required="required"{/if}/>
+ </p>
+ <p>
+ <label for="pref_bool_groupsmanagers_edit_groups" class="bline">{_T string="Can group managers edit their groups?"}</label>
+ <input type="checkbox" name="pref_bool_groupsmanagers_edit_groups" id="pref_bool_groupsmanagers_edit_groups" value="1" {if $pref.pref_bool_groupsmanagers_edit_groups eq 1}checked="checked"{/if}/>
+ </p>
+ <p>
+ <label for="pref_bool_groupsmanagers_create_member" class="bline">{_T string="Can group managers create members?"}</label>
+ <input type="checkbox" name="pref_bool_groupsmanagers_create_member" id="pref_bool_groupsmanagers_create_member" value="1" {if $pref.pref_bool_groupsmanagers_create_member eq 1}checked="checked"{/if}/>
+ </p>
+ <p>
+ <label for="pref_bool_groupsmanagers_edit_member" class="bline">{_T string="Can group managers edit members?"}</label>
+ <input type="checkbox" name="pref_bool_groupsmanagers_edit_member" id="pref_bool_groupsmanagers_edit_member" value="1" {if $pref.pref_bool_groupsmanagers_edit_member eq 1}checked="checked"{/if}/>
+ </p>
+ <p>
+ <label for="pref_bool_groupsmanagers_mailings" class="bline">{_T string="Can group managers send mailings?"}</label>
+ <input type="checkbox" name="pref_bool_groupsmanagers_mailings" id="pref_bool_groupsmanagers_mailings" value="1" {if $pref.pref_bool_groupsmanagers_mailings eq 1}checked="checked"{/if}/>
+ </p>
+ <p>
+ <label for="pref_bool_groupsmanagers_exports" class="bline tooltip">{_T string="Can group managers do exports?"}</label>
+ <span class="tip">{_T string="Groups managers will be allowed to export members as csv, pdf cards, attendence sheetss and groups pdf"}</span>
+ <input type="checkbox" name="pref_bool_groupsmanagers_exports" id="pref_bool_groupsmanagers_exports" value="1" {if $pref.pref_bool_groupsmanagers_exports eq 1}checked="checked"{/if}/>
+ </p>
+ </fieldset>
+
<fieldset class="cssform" id="mail">
<legend>{_T string="Mail settings"}</legend>
{if $GALETTE_MODE eq 'DEMO'}
width: 19.9em;
}
+#rights .bline, #rights.galette_form label, #rights.galette_form .bline {
+ margin: 0 0 0 -22em;
+ width: 30em;
+}
+
.galette_form .radios label,
.galette_form label.labelalign {
width: auto;
padding-left: 0;
}
- .cssform .bline, .galette_form label, .galette_form .bline {
+ .cssform .bline, .galette_form label, .galette_form .bline, #rights .bline {
margin-left: 0;
margin-bottom: .2em;
padding-right: 0;
$expected = ['Status #256 does not exists in database.'];
$check = $adh->check($data, [], []);
$this->array($check)->isIdenticalTo($expected);
+
+ //tests for group managers
+ $g1 = new \mock\Galette\Entity\Group();
+ $this->calling($g1)->getId = 1;
+ $g2 = new \mock\Galette\Entity\Group();
+ $this->calling($g2)->getId = 2;
+
+ //groups managers must specify a group they manage
+ global $login;
+ $login = new \mock\Galette\Core\Login($this->zdb, $this->i18n);
+
+ $this->calling($login)->isGroupManager = function ($gid) use ($g1) {
+ return $gid === null || $gid == $g1->getId();
+ };
+
+ $data = ['id_statut' => ''];
+ $check = $adh->check($data, [], []);
+ $expected = ['You have to select a group you own!'];
+ $this->array($check)->isIdenticalTo($expected);
+
+ $data = ['groups_adh' => [$g2->getId()]];
+ $check = $adh->check($data, [], []);
+ $expected = ['You have to select a group you own!'];
+ $this->array($check)->isIdenticalTo($expected);
+
+ $data = ['groups_adh' => [$g1->getId()]];
+ $check = $adh->check($data, [], []);
+ $this->boolean($check)->isTrue();
}
/**
$g1 = new \mock\Galette\Entity\Group();
$this->calling($g1)->getId = 1;
$g2 = new \mock\Galette\Entity\Group();
- $this->calling($g1)->getId = 2;
+ $this->calling($g2)->getId = 2;
$this->calling($adh)->getGroups = [$g1, $g2];
$login = new \mock\Galette\Core\Login($this->zdb, $this->i18n);
$this->boolean($adh->canEdit($login))->isFalse();
- $this->calling($login)->isGroupManager = true;
- $this->boolean($adh->canEdit($login))->isTrue();
+ $this->calling($login)->isGroupManager = function ($gid) use ($g1) {
+ return $gid === null || $gid == $g1->getId();
+ };
+ $this->boolean($adh->canEdit($login))->isFalse();
+
+ $this->preferences->pref_bool_groupsmanagers_edit_member = true;
+ $canEdit = $adh->canEdit($login);
+ $this->preferences->pref_bool_groupsmanagers_edit_member = false; //reset
+ $this->boolean($canEdit)->isTrue();
+
+ //groups managers cannot edit members of the groups they do not own
+ $this->calling($adh)->getGroups = [$g2];
+ $this->boolean($adh->canEdit($login))->isFalse();
}
/**
//logout
$this->login->logOut();
$this->boolean($this->login->isLogged())->isFalse();
+
+ //tests for group managers
+ $adh = new \mock\Galette\Entity\Adherent($this->zdb);
+
+ $g1 = new \mock\Galette\Entity\Group();
+ $this->calling($g1)->getId = 1;
+ $g2 = new \mock\Galette\Entity\Group();
+ $this->calling($g2)->getId = 2;
+
+ //groups managers can show members of the groups they own
+ $this->calling($adh)->getGroups = [$g1, $g2];
+ $login = new \mock\Galette\Core\Login($this->zdb, $this->i18n);
+ $this->boolean($adh->canShow($login))->isFalse();
+
+ $this->calling($login)->isGroupManager = function ($gid) use ($g1) {
+ return $gid === null || $gid == $g1->getId();
+ };
+ $this->boolean($adh->canShow($login))->isTrue();
+
+ //groups managers cannot show members of the groups they do not own
+ $this->calling($adh)->getGroups = [$g2];
+ $this->boolean($adh->canShow($login))->isFalse();
}
/**
['filter-memberslist', 'groupmanager'],
['advanced-search', 'groupmanager'],
['batch-memberslist', 'groupmanager'],
- ['mailing', 'staff'],
- ['doMailing', 'staff'],
- ['mailingPreview', 'staff'],
- ['previewAttachment', 'staff'],
- ['mailingRecipients', 'staff'],
- ['csv-memberslist', 'staff'],
+ ['mailing', 'groupmanager'],
+ ['doMailing', 'groupmanager'],
+ ['mailingPreview', 'groupmanager'],
+ ['previewAttachment', 'groupmanager'],
+ ['mailingRecipients', 'groupmanager'],
+ ['csv-memberslist', 'groupmanager'],
['groups', 'groupmanager'],
['me', 'member'],
['member', 'member'],
['ajax_group', 'groupmanager'],
['ajax_groups', 'groupmanager'],
['ajax_groupname_unique', 'groupmanager'],
- ['ajax_groups_reorder', 'staff'],
+ ['ajax_groups_reorder', 'groupmanager'],
['add_group', 'staff'],
['removeGroup', 'staff'],
['doRemoveGroup', 'staff'],