Commit ecc2b8da authored by Aaron Wells's avatar Aaron Wells Committed by Robert Lyon

Bug 1620879: Changes to service groups to facilitate automation

- Adding "shortname" field to service groups
- Marking plugin-created service groups, as the ones that have
a "component" field. Make the function list for plugin-created
service groups not editable by users.
- Since users may have already edited the old "sample" service
groups, removing the "component" value from those.
- And, to avoid trouble going forward, preventing the install
of the sample service groups on new installations

behatnotneeded: Tests to be written later

Change-Id: I23c781d6f2bbf689c12de30a67882bf3f1f4aff9
(cherry picked from commit d159aaf4)
parent 3763b1e9
......@@ -8,6 +8,7 @@
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" UNSIGNED="true" SEQUENCE="true"/>
<FIELD NAME="name" TYPE="char" LENGTH="200" NOTNULL="true" SEQUENCE="false"/>
<FIELD NAME="shortname" TYPE="char" LENGTH="200" NOTNULL="false" SEQUENCE="false"/>
<FIELD NAME="enabled" TYPE="int" LENGTH="1" NOTNULL="true" UNSIGNED="true" SEQUENCE="false"/>
<FIELD NAME="restrictedusers" TYPE="int" LENGTH="1" NOTNULL="true" UNSIGNED="true" SEQUENCE="false"/>
<FIELD NAME="tokenusers" TYPE="int" LENGTH="1" NOTNULL="true" UNSIGNED="true" SEQUENCE="false"/>
......@@ -20,6 +21,7 @@
</KEYS>
<INDEXES>
<INDEX NAME="nameuk" UNIQUE="true" FIELDS="name"/>
<INDEX NAME="shortnamecompix" UNIQUE="false" FIELDS="shortname,component"/>
</INDEXES>
</TABLE>
<TABLE NAME="external_functions" COMMENT="list of all external functions">
......
......@@ -483,6 +483,23 @@ function xmldb_auth_webservice_upgrade($oldversion=0) {
}
}
if ($oldversion < 2016090700) {
log_debug('Adding shortname column to external_services table');
$table = new XMLDBTable('external_services');
$field = new XMLDBField('shortname');
$field->setAttributes(XMLDB_TYPE_CHAR, 200, null, null, null, null, null, null, '', 'name');
if (!field_exists($table, $field)) {
add_field($table, $field);
$index = new XMLDBIndex('shortnamecompuix');
$index->setAttributes(XMLDB_INDEX_NOTUNIQUE, array('shortname', 'component'));
add_index($table, $index);
}
log_debug('Clearing out "component" field from old example service groups');
set_field('external_services', 'component', '', 'component', 'webservice');
}
// sweep for webservice updates everytime
$status = external_reload_webservices();
......
......@@ -105,10 +105,11 @@ $string['servicegroup'] = 'Service group: %s';
$string['sfgdescription'] = 'Build lists of functions into service groups that can be allocated to users authorised for execution.';
$string['name'] = 'Name';
$string['component'] = 'Component';
$string['customservicegroup'] = '(Custom)';
$string['functions'] = 'Functions';
$string['enableservice'] = 'Enable or disable the service';
$string['existingserviceusers'] = 'Cannot switch to token only users because service users are linked to this service.';
$string['existingtokens'] = 'Cannot switch to authorised service users because token users exist for this service.';
$string['restricteduserswarning'] = 'Warning: There are existing token users for this service, who may be unable to access it if you enable "%s".';
$string['tokenuserswarning'] = 'Warning: There are existing token users for this service, who may be unable to access it if you disable "%s".';
$string['usersonly'] = 'Users only';
$string['tokensonly'] = 'Tokens only';
$string['switchtousers'] = 'Switch to users';
......@@ -129,6 +130,8 @@ $string['missingretvaldesc'] = 'Missing returned values description';
$string['missingparamdesc'] = 'Missing parameter description';
$string['missingimplofmeth'] = 'Missing implementation method of "%s"';
$string['cannotfindimplfile'] = 'Cannot find file with external function implementation';
$string['servicenamemustbeunique'] = 'That name is already in use by another service group.';
$string['serviceshortnamemustbeunique'] = 'That short name is already in use by another service group.';
$string['apptokens'] = 'Application connections';
$string['connections'] = 'Connection manager';
......@@ -320,6 +323,11 @@ $string['selectedcapability'] = 'Selected';
$string['selectspecificuser'] = 'Select a specific user';
$string['service'] = 'Service';
$string['serviceusers'] = 'Authorised users';
$string['servicenamelabel'] = 'Name';
$string['servicenamedesc'] = 'A human-readable name for this service group.';
$string['serviceshortnamelabel'] = 'Short name';
$string['serviceshortnamedesc'] = 'A machine-readable name for this service group. (This is the name that will be used if an external service needs to refer to this service group.)';
$string['servicecomponentnote'] = 'This service provides functionality for the component: %s';
$string['simpleauthlog'] = 'Simple authentication login';
$string['step'] = 'Step';
$string['testclient'] = 'Web service test client';
......
......@@ -11,8 +11,8 @@
defined('INTERNAL') || die();
$config = new StdClass;
$config->version = 2016071400;
$config->release = '1.1.0';
$config = new stdClass();
$config->version = 2016090700;
$config->release = '2.0.0';
$config->requires_config = 0;
$config->requires_parent = 0;
......@@ -545,16 +545,24 @@ function webservice_function_groups_form() {
),
);
$dbservices = get_records_array('external_services', null, null, 'name');
$dbservices = get_records_array(
'external_services',
null,
null,
'component, name',
'id, name, shortname, component, enabled, restrictedusers, tokenusers'
);
if ($dbservices) {
foreach ($dbservices as $service) {
$iscustomservice = ($service->component === '');
$form['elements']['id'. $service->id . '_service'] = array(
'value' => $service->name,
'value' => $service->name .
($service->shortname ? ' (' . $service->shortname . ')' : ''),
'type' => 'html',
'key' => $service->name,
);
$form['elements']['id'. $service->id . '_component'] = array(
'value' => $service->component,
'value' => ($iscustomservice ? get_string('customservicegroup', 'auth.webservice') : $service->component),
'type' => 'html',
'key' => $service->name,
);
......@@ -611,25 +619,29 @@ function webservice_function_groups_form() {
),
),
))
. pieform(array(
'name' => 'webservices_function_groups_delete_' . $service->id,
'renderer' => 'div',
'class' => 'form-as-button pull-left',
'successcallback' => 'webservice_function_groups_submit',
'jsform' => false,
'action' => get_config('wwwroot') . 'webservice/admin/index.php',
'elements' => array(
'service' => array('type' => 'hidden', 'value' => $service->id),
'action' => array('type' => 'hidden', 'value' => 'delete'),
'submit' => array(
'type' => 'button',
'usebuttontag' => true,
'class' => 'btn-default btn-sm',
'value' => '<span class="'.$deleteicon.'"></span>' . get_string('delete', 'mahara'),
'elementtitle' => get_string('delete'),
),
),
)),
. (
$iscustomservice ?
pieform(array(
'name' => 'webservices_function_groups_delete_' . $service->id,
'renderer' => 'div',
'class' => 'form-as-button pull-left',
'successcallback' => 'webservice_function_groups_submit',
'jsform' => false,
'action' => get_config('wwwroot') . 'webservice/admin/index.php',
'elements' => array(
'service' => array('type' => 'hidden', 'value' => $service->id),
'action' => array('type' => 'hidden', 'value' => 'delete'),
'submit' => array(
'type' => 'button',
'usebuttontag' => true,
'class' => 'btn-default btn-sm',
'value' => '<span class="'.$deleteicon.'"></span>' . get_string('delete', 'mahara'),
'elementtitle' => get_string('delete'),
),
),
))
: ''
),
'type' => 'html',
'key' => $service->name,
'class' => 'webserviceconfigcontrols btn-group',
......@@ -676,7 +688,7 @@ function webservice_function_groups_submit(Pieform $form, $values) {
$SESSION->add_error_msg(get_string('invalidinput', 'auth.webservice'));
}
else {
$service = array('name' => $service, 'restrictedusers' => 0, 'enabled' => 0, 'tokenusers' => 0, 'component' => 'webservice', 'ctime' => db_format_timestamp(time()));
$service = array('name' => $service, 'restrictedusers' => 0, 'enabled' => 0, 'tokenusers' => 0, 'component' => '', 'ctime' => db_format_timestamp(time()));
insert_record('external_services', $service);
$SESSION->add_ok_msg(get_string('configsaved', 'auth.webservice'));
}
......@@ -688,13 +700,17 @@ function webservice_function_groups_submit(Pieform $form, $values) {
redirect('/webservice/admin/serviceconfig.php?service=' . $values['service']);
}
else if ($values['action'] == 'delete') {
// remove everything associated with a service
$params = array($values['service']);
delete_records_select('external_tokens', "externalserviceid = ?", $params);
delete_records_select('external_services_users', "externalserviceid = ?", $params);
delete_records_select('external_services_functions', "externalserviceid = ?", $params);
delete_records('external_services', 'id', $values['service']);
$SESSION->add_ok_msg(get_string('configsaved', 'auth.webservice'));
$component = get_field('external_services', 'component', 'id', $values['service']);
// Can't manually delete plugin-provided services; only disable them.
if ($component === '') {
// remove everything associated with a service
$params = array($values['service']);
delete_records_select('external_tokens', "externalserviceid = ?", $params);
delete_records_select('external_services_users', "externalserviceid = ?", $params);
delete_records_select('external_services_functions', "externalserviceid = ?", $params);
delete_records('external_services', 'id', $values['service']);
$SESSION->add_ok_msg(get_string('configsaved', 'auth.webservice'));
}
}
}
}
......
......@@ -16,7 +16,12 @@ require(dirname(dirname(dirname(__FILE__))) . '/init.php');
define('TITLE', get_string('pluginadmin', 'admin'));
$service = param_integer('service', 0);
$dbservice = get_record('external_services', 'id', $service);
$dbservice = get_record(
'external_services',
'id', $service,
null, null, null, null,
'id, name, shortname, component, restrictedusers, tokenusers, enabled'
);
if (empty($dbservice)) {
$SESSION->add_error_msg(get_string('invalidservice', 'auth.webservice'));
redirect('/webservice/admin/index.php?open=webservices_function_groups');
......@@ -24,6 +29,7 @@ if (empty($dbservice)) {
$enabled = $dbservice->enabled;
$restrictedusers = ($dbservice->restrictedusers <= 0 ? 0 : 1);
$tokenusers = ($dbservice->tokenusers <= 0 ? 0 : 1);
$ispluginservice = ('' !== $dbservice->component);
$functions = array(
'elements' => array(
......@@ -48,13 +54,30 @@ $functions = array(
),
);
$dbfunctions = get_records_array('external_functions', null, null, 'name');
if (!$ispluginservice) {
// Custom service - let the user add/remove functions
$dbfunctions = get_records_array('external_functions', null, null, 'name');
}
else {
// In a non-custom service, the list of functions can't be changed. So
// only display the ones that are actually included in this service.
$dbfunctions = get_records_sql_array(
'SELECT ef.*
FROM
{external_services_functions} esf
INNER JOIN {external_functions} ef
ON esf.functionname = ef.name
WHERE esf.externalserviceid = ?
ORDER BY ef.name',
array($dbservice->id)
);
}
foreach ($dbfunctions as $function) {
$sfexists = record_exists('external_services_functions', 'externalserviceid', $dbservice->id, 'functionname', $function->name);
$functions['elements']['id' . $function->id . '_enabled'] = array(
'defaultvalue' => ($sfexists ? 'checked' : ''),
'type' => 'switchbox',
'disabled' => false,
'disabled' => $ispluginservice,
'title' => $function->name,
);
......@@ -77,12 +100,43 @@ $functions['elements']['submit'] = array(
'value' => array(get_string('save'), get_string('back')),
'goto' => get_config('wwwroot') . 'webservice/admin/index.php?open=webservices_function_groups',
);
$heading = get_string('servicegroup', 'auth.webservice', $dbservice->name);
$elements = array(
'service' => array(
'type' => 'hidden',
'value' => $dbservice->id
),
// fieldset of name & shortname
'namefieldset' => array(
'legend' => $heading,
'type' => 'fieldset',
'elements' => array(
'name' => array(
'type' => 'text',
'title' => get_string('servicenamelabel', 'auth.webservice'),
'rules' => array(
'required' => !$ispluginservice,
'maxlength' => 200
),
'description' => get_string('servicenamedesc', 'auth.webservice'),
'defaultvalue' => $dbservice->name,
'disabled' => $ispluginservice,
),
'shortname' => array(
'type' => 'text',
'title' => get_string('serviceshortnamelabel', 'auth.webservice'),
'rules' => array(
'maxlength' => 200
),
'description' => get_string('serviceshortnamedesc', 'auth.webservice'),
'defaultvalue' => $dbservice->shortname,
'disabled' => $ispluginservice,
),
),
'collapsible' => true,
'collapsed' => false,
),
// fieldset of master switch
'webservicesmaster' => array(
'type' => 'fieldset',
......@@ -131,11 +185,17 @@ $elements = array(
),
);
if ($ispluginservice) {
$elements['namefieldset']['elements']['component'] = array(
'type' => 'html',
'value' => get_string('servicecomponentnote', 'auth.webservice', $dbservice->component)
);
}
$form = array(
'renderer' => 'div',
'type' => 'div',
'id' => 'maintable',
'elementclasses' => false,
'elements' => $elements,
'jsform' => false,
);
......@@ -170,66 +230,152 @@ $smarty->assign('form', $form);
$smarty->assign('PAGEHEADING', $heading);
$smarty->display('form.tpl');
function serviceconfig_validate(Pieform $form, $values) {
// Some fields can't be edited on a plugin's service. So don't bother validating
// any inputs relating to them.
$ispluginservice = ('' !== get_field('external_services', 'component', 'id', $values['service']));
if (!$ispluginservice) {
// name must be unique
if (
isset($values['name'])
&& record_exists_select(
'external_services',
'name = ? AND id <> ?',
array($values['name'], $values['service'])
)
) {
$form->set_error('name', get_string('servicenamemustbeunique', 'auth.webservice'));
}
// component and shortname together must be unique
if (
isset($values['shortname'])
&& $values['shortname'] !== ''
&& record_exists_select(
'external_services',
'shortname = ? AND (component = \'\' OR component IS NULL) AND id <> ?',
array($values['shortname'], $values['service'])
)
) {
$form->set_error('shortname', get_string('serviceshortnamemustbeunique', 'auth.webservice'));
}
}
}
/**
* HACK: This function was not written following normal Mahara coding standards.
* The following things should be cleaned up at some point:
*
* 1. It mixes submission and validation (throwing exceptions in the case of
* a validation failure). The validation should be moved to a separate
* Pieforms validation handler.
*
* 2. Remove the global $service. Instead, use $values['service'].
*
* 3. Use a clean "$todb" object to update only the changed fields in the
* database (instead of using the global $dbservice, which is a whole-row record.
*
* 4. Remove the global $dbservice once it's not needed.
*
* 5. Instead of the backwards check for form fields that look like function
* names and then checking the DB to see if each one exists; just do one
* query to get all the functions, then check for a form field that looks like
* each one.
*
* 6. Do one "update_record()" call at the end of the function, instead
* of a separate one for each form field.
*
* @param Pieform $form
* @param array $values
*/
function serviceconfig_submit(Pieform $form, $values) {
global $SESSION, $service, $dbservice;
// Indicates whether this service was generated by a plugin (in which case
// the admin can change access to it, but can't change its function list
// or its name & shortname
$ispluginservice = ('' !== $dbservice->component);
// Can't edit name of plugin-provided services
if (!$ispluginservice && isset($values['name'])) {
$dbservice->name = $values['name'];
update_record('external_services', $dbservice);
}
// Can't edit shortname of plugin-provided services
if (!$ispluginservice && isset($values['shortname'])) {
$dbservice->shortname = $values['shortname'];
update_record('external_services', $dbservice);
}
if (isset($values['enabled'])) {
$enabled = $values['enabled'] ? 1 : 0;
$dbservice->enabled = $enabled;
update_record('external_services', $dbservice);
}
$tokenwarning = false;
if (isset($values['tokenusers'])) {
$tokenusers = $values['tokenusers'] ? 1 : 0;
// We may need to issue a warning that this will cut off some users' access
if ($dbservice->tokenusers && !$tokenusers) {
$tokenwarning = true;
}
$dbservice->tokenusers = $tokenusers;
update_record('external_services', $dbservice);
}
$restrictwarning = false;
if (isset($values['restrictedusers'])) {
// flip flop
$restrict = ($values['restrictedusers'] <= 0 ? 0 : 1);
if ($restrict) {
// must not disable token users
$cnt = count_records('external_tokens', 'externalserviceid', $service);
if ($cnt > 0) {
$SESSION->add_error_msg(get_string('existingtokens', 'auth.webservice'));
redirect('/webservice/admin/serviceconfig.php?service=' . $service);;
}
}
else {
// must not disable auth users
$cnt = count_records('external_services_users', 'externalserviceid', $service);
if ($cnt > 0) {
$SESSION->add_error_msg(get_string('existingserviceusers', 'auth.webservice'));
redirect('/webservice/admin/serviceconfig.php?service=' . $service);;
}
// We may need to issue a warning that this will cut off some users' access
if (!$dbservice->restrictedusers && $restrict) {
$restrictwarning = true;
}
$dbservice->restrictedusers = $restrict;
update_record('external_services', $dbservice);
}
foreach (array_keys($values) as $key) {
if (preg_match('/^id(\d+)\_enabled$/', $key, $matches)) {
$function = $matches[1];
$dbfunction = get_record('external_functions', 'id', $function);
if (empty($dbfunction)) {
$SESSION->add_error_msg(get_string('invalidinput', 'auth.webservice'));
redirect('/webservice/admin/serviceconfig.php?service=' . $service);
if ($tokenwarning || $restrictwarning) {
// Warn if disabling token users
$cnt = count_records('external_tokens', 'externalserviceid', $service);
if ($cnt > 0) {
if ($tokenwarning) {
$SESSION->add_error_msg(get_string('tokenuserswarning', 'auth.webservice', get_string('fortokenusers', 'auth.webservice')));
}
$service_function = record_exists('external_services_functions', 'externalserviceid', $service, 'functionname',$dbfunction->name);
// record should exist - so create if necessary
if ($values[$key]) {
if (!$service_function) {
$service_function = array('externalserviceid' => $service, 'functionname' => $dbfunction->name);
insert_record('external_services_functions', $service_function);
$dbservice->mtime = db_format_timestamp(time());
update_record('external_services', $dbservice);
}
if ($restrictwarning) {
$SESSION->add_error_msg(get_string('restricteduserswarning', 'auth.webservice', get_string('restrictedusers', 'auth.webservice')));
}
else {
// disabled - record should not exist
if ($service_function) {
delete_records('external_services_functions', 'externalserviceid', $service, 'functionname',$dbfunction->name);
$dbservice->mtime = db_format_timestamp(time());
update_record('external_services', $dbservice);
}
}
// Can't add/remove functions from a plugin's service
if (!$ispluginservice) {
foreach (array_keys($values) as $key) {
if (preg_match('/^id(\d+)\_enabled$/', $key, $matches)) {
$function = $matches[1];
$dbfunction = get_record('external_functions', 'id', $function);
if (empty($dbfunction)) {
$SESSION->add_error_msg(get_string('invalidinput', 'auth.webservice'));
redirect('/webservice/admin/serviceconfig.php?service=' . $service);
}
$service_function = record_exists('external_services_functions', 'externalserviceid', $service, 'functionname',$dbfunction->name);
// record should exist - so create if necessary
if ($values[$key]) {
if (!$service_function) {
$service_function = array('externalserviceid' => $service, 'functionname' => $dbfunction->name);
insert_record('external_services_functions', $service_function);
$dbservice->mtime = db_format_timestamp(time());
update_record('external_services', $dbservice);
}
}
else {
// disabled - record should not exist
if ($service_function) {
delete_records('external_services_functions', 'externalserviceid', $service, 'functionname',$dbfunction->name);
$dbservice->mtime = db_format_timestamp(time());
update_record('external_services', $dbservice);
}
}
}
}
......
......@@ -1978,6 +1978,7 @@ function external_reload_component($component, $dir=true) {
$service['enabled'] = empty($service['enabled']) ? 0 : $service['enabled'];
$service['restrictedusers'] = ((isset($service['restrictedusers']) && $service['restrictedusers'] == 1) ? 1 : 0);
$service['tokenusers'] = ((isset($service['tokenusers']) && $service['tokenusers'] == 1) ? 1 : 0);
$service['shortname'] = (isset($service['shortname']) ? $service['shortname'] : '');
$update = false;
if ($dbservice->enabled != $service['enabled']) {
......@@ -1992,7 +1993,12 @@ function external_reload_component($component, $dir=true) {
$dbservice->tokenusers = $service['tokenusers'];
$update = true;
}
if ($dbservice->shortname !== $service['shortname']) {
$dbservice->shortname = $service['shortname'];
$update = true;
}
if ($update) {
$dbservice->mtime = db_format_timestamp(time());
update_record('external_services', $dbservice);
}
......@@ -2020,11 +2026,13 @@ function external_reload_component($component, $dir=true) {
foreach ($services as $name => $service) {
$dbservice = new stdClass();
$dbservice->name = $name;
$dbservice->shortname = (isset($service['shortname']) ? $service['shortname'] : '');
$dbservice->enabled = empty($service['enabled']) ? 0 : $service['enabled'];
$dbservice->restrictedusers = ((isset($service['restrictedusers']) && $service['restrictedusers'] == 1) ? 1 : 0);
$dbservice->tokenusers = ((isset($service['tokenusers']) && $service['tokenusers'] == 1) ? 1 : 0);
$dbservice->component = $component;
$dbservice->ctime = db_format_timestamp(time());
$dbservice->mtime = $dbservice->ctime;
$dbservice->id = insert_record('external_services', $dbservice, 'id', true);
foreach ($service['functions'] as $fname) {
$newf = new stdClass();
......
......@@ -249,72 +249,11 @@ $functions = array(
);
/**
* Prepopulated service groups that propose units of access
* Prepopulated service groups
* (None are actually needed yet for the "webservice" pseudo-module.
* See module.mobileapi for one that does have service groups.)
*/
$services = array(
'User Provisioning' => array(
'functions' => array ('mahara_user_get_online_users', 'mahara_user_get_all_favourites', 'mahara_user_get_favourites', 'mahara_user_update_favourites', 'mahara_user_get_users', 'mahara_user_get_users_by_id', 'mahara_user_create_users', 'mahara_user_delete_users', 'mahara_user_update_users', 'mahara_user_get_context', 'mahara_user_get_extended_context'),
'enabled'=>1,
),
'User Query' => array(
'functions' => array ('mahara_user_get_online_users', 'mahara_user_get_all_favourites', 'mahara_user_get_favourites', 'mahara_user_get_users', 'mahara_user_get_users_by_id', 'mahara_user_get_context', 'mahara_user_get_extended_context'),
'enabled'=>1,
),
'Simple User Provisioning' => array(
'functions' => array ('mahara_user_get_online_users', 'mahara_user_get_all_favourites', 'mahara_user_get_favourites', 'mahara_user_update_favourites', 'mahara_user_get_users', 'mahara_user_get_users_by_id', 'mahara_user_create_users', 'mahara_user_delete_users', 'mahara_user_update_users', 'mahara_user_get_context', 'mahara_user_get_extended_context'),
'enabled'=>1,
'restrictedusers'=>1,
),
'Simple User Query' => array(
'functions' => array ('mahara_user_get_online_users', 'mahara_user_get_all_favourites', 'mahara_user_get_favourites', 'mahara_user_get_users', 'mahara_user_get_users_by_id', 'mahara_user_get_context', 'mahara_user_get_extended_context'),
'enabled'=>1,
'restrictedusers'=>1,
),
'UserToken User Query' => array(
'functions' => array ('mahara_user_get_my_user', 'mahara_user_get_context', 'mahara_user_get_extended_context'),
'enabled'=>1,
'tokenusers'=>1,
),
'Group Provisioning' => array(
'functions' => array ('mahara_group_get_groups', 'mahara_group_get_groups_by_id', 'mahara_group_create_groups', 'mahara_group_delete_groups', 'mahara_group_update_groups', 'mahara_group_update_group_members'),
'enabled'=>1,
),
'Group Query' => array(
'functions' => array ('mahara_group_get_groups', 'mahara_group_get_groups_by_id'),
'enabled'=>1,
),
'Simple Group Provisioning' => array(
'functions' => array ('mahara_group_get_groups', 'mahara_group_get_groups_by_id', 'mahara_group_create_groups', 'mahara_group_delete_groups', 'mahara_group_update_groups', 'mahara_group_update_group_members'),
'enabled'=>1,
'restrictedusers'=>1,
),
'Simple Group Query' => array(
'functions' => array ('mahara_group_get_groups', 'mahara_group_get_groups_by_id'),
'enabled'=>1,
'restrictedusers'=>1,