MDL-59365 enrol_manual: Rewrite the yui enrolment popup in amd

The add/roles thing was only converted to bootstrap 2/4 markup and the yui left in place.

The modal for adding users to a course was unsavable so I rewrote it with an mform in a popup, still calling
the same (barely) modified ajax script.

The webservice for searching cohorts was taken from admin/tool/lp and moved into /cohort. I added a generic "cohort"
mform element at the same time.

The webservice for searching for users was taken from the original ajax script.
This commit is contained in:
Damyon Wiese 2016-10-20 17:13:49 +08:00 committed by Dan Poltawski
parent bdf31b0919
commit a60e8ba51e
33 changed files with 1136 additions and 1226 deletions

View File

@ -920,119 +920,6 @@ class external extends external_api {
));
}
/**
* Returns the description of external function parameters.
*
* @return external_function_parameters
*/
public static function search_cohorts_parameters() {
$query = new external_value(
PARAM_RAW,
'Query string'
);
$includes = new external_value(
PARAM_ALPHA,
'What other contexts to fetch the frameworks from. (all, parents, self)',
VALUE_DEFAULT,
'parents'
);
$limitfrom = new external_value(
PARAM_INT,
'limitfrom we are fetching the records from',
VALUE_DEFAULT,
0
);
$limitnum = new external_value(
PARAM_INT,
'Number of records to fetch',
VALUE_DEFAULT,
25
);
return new external_function_parameters(array(
'query' => $query,
'context' => self::get_context_parameters(),
'includes' => $includes,
'limitfrom' => $limitfrom,
'limitnum' => $limitnum
));
}
/**
* Search cohorts.
* TODO: MDL-52243 Move this function to cohorts/externallib.php
*
* @param string $query
* @param array $context
* @param string $includes
* @param int $limitfrom
* @param int $limitnum
* @return array
*/
public static function search_cohorts($query, $context, $includes = 'parents', $limitfrom = 0, $limitnum = 25) {
global $DB, $CFG, $PAGE;
require_once($CFG->dirroot . '/cohort/lib.php');
$params = self::validate_parameters(self::search_cohorts_parameters(), array(
'query' => $query,
'context' => $context,
'includes' => $includes,
'limitfrom' => $limitfrom,
'limitnum' => $limitnum,
));
$query = $params['query'];
$includes = $params['includes'];
$context = self::get_context_from_params($params['context']);
$limitfrom = $params['limitfrom'];
$limitnum = $params['limitnum'];
self::validate_context($context);
$output = $PAGE->get_renderer('tool_lp');
$manager = has_capability('moodle/cohort:manage', $context);
if (!$manager) {
require_capability('moodle/cohort:view', $context);
}
// TODO Make this more efficient.
if ($includes == 'self') {
$results = cohort_get_cohorts($context->id, $limitfrom, $limitnum, $query);
$results = $results['cohorts'];
} else if ($includes == 'parents') {
$results = cohort_get_cohorts($context->id, $limitfrom, $limitnum, $query);
$results = $results['cohorts'];
if (!$context instanceof context_system) {
$results = array_merge($results, cohort_get_available_cohorts($context, COHORT_ALL, $limitfrom, $limitnum, $query));
}
} else if ($includes == 'all') {
$results = cohort_get_all_cohorts($limitfrom, $limitnum, $query);
$results = $results['cohorts'];
} else {
throw new coding_exception('Invalid parameter value for \'includes\'.');
}
$cohorts = array();
foreach ($results as $key => $cohort) {
$cohortcontext = context::instance_by_id($cohort->contextid);
$exporter = new cohort_summary_exporter($cohort, array('context' => $cohortcontext));
$newcohort = $exporter->export($output);
$cohorts[$key] = $newcohort;
}
return array('cohorts' => $cohorts);
}
/**
* Returns description of external function result value.
*
* @return external_description
*/
public static function search_cohorts_returns() {
return new external_single_structure(array(
'cohorts' => new external_multiple_structure(cohort_summary_exporter::get_read_structure())
));
}
/**
* Returns description of external function.
*

View File

@ -131,10 +131,11 @@ $functions = array(
'capabilities' => '',
'ajax' => true,
),
// This function was originally in this plugin but has been moved to core.
'tool_lp_search_cohorts' => array(
'classname' => 'tool_lp\external',
'classname' => 'core_cohort_external',
'methodname' => 'search_cohorts',
'classpath' => '',
'classpath' => 'cohort/externallib.php',
'description' => 'Search for cohorts.',
'type' => 'read',
'capabilities' => 'moodle/cohort:view',

View File

@ -461,76 +461,4 @@ class tool_lp_external_testcase extends externallib_advanced_testcase {
$this->assertEquals('A', $summary->evidence[1]->gradename);
}
/**
* Search cohorts.
*/
public function test_search_cohorts() {
$this->resetAfterTest(true);
$syscontext = array('contextid' => context_system::instance()->id);
$catcontext = array('contextid' => context_coursecat::instance($this->category->id)->id);
$othercatcontext = array('contextid' => context_coursecat::instance($this->othercategory->id)->id);
$cohort1 = $this->getDataGenerator()->create_cohort(array_merge($syscontext, array('name' => 'Cohortsearch 1')));
$cohort2 = $this->getDataGenerator()->create_cohort(array_merge($catcontext, array('name' => 'Cohortsearch 2')));
$cohort3 = $this->getDataGenerator()->create_cohort(array_merge($othercatcontext, array('name' => 'Cohortsearch 3')));
// Check for parameter $includes = 'parents'.
// A user without permission in the system.
$this->setUser($this->user);
try {
$result = external::search_cohorts("Cohortsearch", $syscontext, 'parents');
$this->fail('Invalid permissions in system');
} catch (required_capability_exception $e) {
// All good.
}
// A user without permission in a category.
$this->setUser($this->catuser);
try {
$result = external::search_cohorts("Cohortsearch", $catcontext, 'parents');
$this->fail('Invalid permissions in category');
} catch (required_capability_exception $e) {
// All good.
}
// A user with permissions in the system.
$this->setUser($this->creator);
$result = external::search_cohorts("Cohortsearch", $syscontext, 'parents');
$this->assertEquals(1, count($result['cohorts']));
$this->assertEquals('Cohortsearch 1', $result['cohorts'][$cohort1->id]->name);
// A user with permissions in the category.
$this->setUser($this->catcreator);
$result = external::search_cohorts("Cohortsearch", $catcontext, 'parents');
$this->assertEquals(2, count($result['cohorts']));
$cohorts = array();
foreach ($result['cohorts'] as $cohort) {
$cohorts[] = $cohort->name;
}
$this->assertTrue(in_array('Cohortsearch 1', $cohorts));
$this->assertTrue(in_array('Cohortsearch 2', $cohorts));
// Check for parameter $includes = 'self'.
$this->setUser($this->creator);
$result = external::search_cohorts("Cohortsearch", $othercatcontext, 'self');
$this->assertEquals(1, count($result['cohorts']));
$this->assertEquals('Cohortsearch 3', $result['cohorts'][$cohort3->id]->name);
// Check for parameter $includes = 'all'.
$this->setUser($this->creator);
$result = external::search_cohorts("Cohortsearch", $syscontext, 'all');
$this->assertEquals(3, count($result['cohorts']));
// Detect invalid parameter $includes.
$this->setUser($this->creator);
try {
$result = external::search_cohorts("Cohortsearch", $syscontext, 'invalid');
$this->fail('Invalid parameter includes');
} catch (coding_exception $e) {
// All good.
}
}
}

View File

@ -25,6 +25,6 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2017051500; // The current plugin version (Date: YYYYMMDDXX).
$plugin->version = 2017062700; // The current plugin version (Date: YYYYMMDDXX).
$plugin->requires = 2017050500; // Requires this Moodle version.
$plugin->component = 'tool_lp'; // Full name of the plugin (used for diagnostics).

View File

@ -274,6 +274,137 @@ class core_cohort_external extends external_api {
);
}
/**
* Returns the description of external function parameters.
*
* @return external_function_parameters
*/
public static function search_cohorts_parameters() {
$query = new external_value(
PARAM_RAW,
'Query string'
);
$includes = new external_value(
PARAM_ALPHA,
'What other contexts to fetch the frameworks from. (all, parents, self)',
VALUE_DEFAULT,
'parents'
);
$limitfrom = new external_value(
PARAM_INT,
'limitfrom we are fetching the records from',
VALUE_DEFAULT,
0
);
$limitnum = new external_value(
PARAM_INT,
'Number of records to fetch',
VALUE_DEFAULT,
25
);
return new external_function_parameters(array(
'query' => $query,
'context' => self::get_context_parameters(),
'includes' => $includes,
'limitfrom' => $limitfrom,
'limitnum' => $limitnum
));
}
/**
* Search cohorts.
*
* @param string $query
* @param array $context
* @param string $includes
* @param int $limitfrom
* @param int $limitnum
* @return array
*/
public static function search_cohorts($query, $context, $includes = 'parents', $limitfrom = 0, $limitnum = 25) {
global $DB, $CFG, $PAGE;
require_once($CFG->dirroot . '/cohort/lib.php');
$params = self::validate_parameters(self::search_cohorts_parameters(), array(
'query' => $query,
'context' => $context,
'includes' => $includes,
'limitfrom' => $limitfrom,
'limitnum' => $limitnum,
));
$query = $params['query'];
$includes = $params['includes'];
$context = self::get_context_from_params($params['context']);
$limitfrom = $params['limitfrom'];
$limitnum = $params['limitnum'];
self::validate_context($context);
$output = $PAGE->get_renderer('tool_lp');
$manager = has_capability('moodle/cohort:manage', $context);
if (!$manager) {
require_capability('moodle/cohort:view', $context);
}
// TODO Make this more efficient.
if ($includes == 'self') {
$results = cohort_get_cohorts($context->id, $limitfrom, $limitnum, $query);
$results = $results['cohorts'];
} else if ($includes == 'parents') {
$results = cohort_get_cohorts($context->id, $limitfrom, $limitnum, $query);
$results = $results['cohorts'];
if (!$context instanceof context_system) {
$results = array_merge($results, cohort_get_available_cohorts($context, COHORT_ALL, $limitfrom, $limitnum, $query));
}
} else if ($includes == 'all') {
$results = cohort_get_all_cohorts($limitfrom, $limitnum, $query);
$results = $results['cohorts'];
} else {
throw new coding_exception('Invalid parameter value for \'includes\'.');
}
$cohorts = array();
foreach ($results as $key => $cohort) {
$cohortcontext = context::instance_by_id($cohort->contextid);
if (!isset($cohort->description)) {
$cohort->description = '';
}
if (!isset($cohort->descriptionformat)) {
$cohort->descriptionformat = FORMAT_PLAIN;
}
list($cohort->description, $cohort->descriptionformat) =
external_format_text($cohort->description, $cohort->descriptionformat,
$cohortcontext->id, 'cohort', 'description', $cohort->id);
$cohorts[$key] = $cohort;
}
return array('cohorts' => $cohorts);
}
/**
* Returns description of external function result value.
*
* @return external_description
*/
public static function search_cohorts_returns() {
return new external_single_structure(array(
'cohorts' => new external_multiple_structure(
new external_single_structure(array(
'id' => new external_value(PARAM_INT, 'ID of the cohort'),
'name' => new external_value(PARAM_RAW, 'cohort name'),
'idnumber' => new external_value(PARAM_RAW, 'cohort idnumber'),
'description' => new external_value(PARAM_RAW, 'cohort description'),
'descriptionformat' => new external_format_value('description'),
'visible' => new external_value(PARAM_BOOL, 'cohort visible'),
))
)
));
}
/**
* Returns description of method parameters
*

View File

@ -330,6 +330,33 @@ function cohort_can_view_cohort($cohortorid, $currentcontext) {
return false;
}
/**
* Get a cohort by id. Also does a visibility check and returns false if the user cannot see this cohort.
*
* @param stdClass|int $cohortorid cohort object or id
* @param context $currentcontext current context (course) where visibility is checked
* @return stdClass|boolean
*/
function cohort_get_cohort($cohortorid, $currentcontext) {
global $DB;
if (is_numeric($cohortorid)) {
$cohort = $DB->get_record('cohort', array('id' => $cohortorid), 'id, contextid, visible');
} else {
$cohort = $cohortorid;
}
if ($cohort && in_array($cohort->contextid, $currentcontext->get_parent_context_ids())) {
if ($cohort->visible) {
return $cohort;
}
$cohortcontext = context::instance_by_id($cohort->contextid);
if (has_capability('moodle/cohort:view', $cohortcontext)) {
return $cohort;
}
}
return false;
}
/**
* Produces a part of SQL query to filter cohorts by the search string
*

View File

@ -454,4 +454,102 @@ class core_cohort_externallib_testcase extends externallib_advanced_testcase {
$this->unassignUserCapability('moodle/cohort:assign', $context->id, $roleid);
core_cohort_external::delete_cohort_members(array($cohortdel1, $cohortdel2));
}
/**
* Search cohorts.
*/
public function test_search_cohorts() {
$this->resetAfterTest(true);
$creator = $this->getDataGenerator()->create_user();
$user = $this->getDataGenerator()->create_user();
$catuser = $this->getDataGenerator()->create_user();
$catcreator = $this->getDataGenerator()->create_user();
$category = $this->getDataGenerator()->create_category();
$othercategory = $this->getDataGenerator()->create_category();
$syscontext = context_system::instance();
$catcontext = context_coursecat::instance($category->id);
// Fetching default authenticated user role.
$userroles = get_archetype_roles('user');
$this->assertCount(1, $userroles);
$authrole = array_pop($userroles);
// Reset all default authenticated users permissions.
unassign_capability('moodle/cohort:manage', $authrole->id);
// Creating specific roles.
$creatorrole = create_role('Creator role', 'creatorrole', 'creator role description');
$userrole = create_role('User role', 'userrole', 'user role description');
assign_capability('moodle/cohort:manage', CAP_ALLOW, $creatorrole, $syscontext->id);
// Check for parameter $includes = 'parents'.
role_assign($creatorrole, $creator->id, $syscontext->id);
role_assign($creatorrole, $catcreator->id, $catcontext->id);
role_assign($userrole, $user->id, $syscontext->id);
role_assign($userrole, $catuser->id, $catcontext->id);
$syscontext = array('contextid' => context_system::instance()->id);
$catcontext = array('contextid' => context_coursecat::instance($category->id)->id);
$othercatcontext = array('contextid' => context_coursecat::instance($othercategory->id)->id);
$cohort1 = $this->getDataGenerator()->create_cohort(array_merge($syscontext, array('name' => 'Cohortsearch 1')));
$cohort2 = $this->getDataGenerator()->create_cohort(array_merge($catcontext, array('name' => 'Cohortsearch 2')));
$cohort3 = $this->getDataGenerator()->create_cohort(array_merge($othercatcontext, array('name' => 'Cohortsearch 3')));
// A user without permission in the system.
$this->setUser($user);
try {
$result = core_cohort_external::search_cohorts("Cohortsearch", $syscontext, 'parents');
$this->fail('Invalid permissions in system');
} catch (required_capability_exception $e) {
// All good.
}
// A user without permission in a category.
$this->setUser($catuser);
try {
$result = core_cohort_external::search_cohorts("Cohortsearch", $catcontext, 'parents');
$this->fail('Invalid permissions in category');
} catch (required_capability_exception $e) {
// All good.
}
// A user with permissions in the system.
$this->setUser($creator);
$result = core_cohort_external::search_cohorts("Cohortsearch", $syscontext, 'parents');
$this->assertEquals(1, count($result['cohorts']));
$this->assertEquals('Cohortsearch 1', $result['cohorts'][$cohort1->id]->name);
// A user with permissions in the category.
$this->setUser($catcreator);
$result = core_cohort_external::search_cohorts("Cohortsearch", $catcontext, 'parents');
$this->assertEquals(2, count($result['cohorts']));
$cohorts = array();
foreach ($result['cohorts'] as $cohort) {
$cohorts[] = $cohort->name;
}
$this->assertTrue(in_array('Cohortsearch 1', $cohorts));
// Check for parameter $includes = 'self'.
$this->setUser($creator);
$result = core_cohort_external::search_cohorts("Cohortsearch", $othercatcontext, 'self');
$this->assertEquals(1, count($result['cohorts']));
$this->assertEquals('Cohortsearch 3', $result['cohorts'][$cohort3->id]->name);
// Check for parameter $includes = 'all'.
$this->setUser($creator);
$result = core_cohort_external::search_cohorts("Cohortsearch", $syscontext, 'all');
$this->assertEquals(3, count($result['cohorts']));
// Detect invalid parameter $includes.
$this->setUser($creator);
try {
$result = core_cohort_external::search_cohorts("Cohortsearch", $syscontext, 'invalid');
$this->fail('Invalid parameter includes');
} catch (coding_exception $e) {
// All good.
}
}
}

View File

@ -385,6 +385,81 @@ class core_enrol_external extends external_api {
);
}
/**
* Returns description of method parameters value
*
* @return external_description
*/
public static function get_potential_users_parameters() {
return new external_function_parameters(
array(
'courseid' => new external_value(PARAM_INT, 'course id'),
'enrolid' => new external_value(PARAM_INT, 'enrolment id'),
'search' => new external_value(PARAM_RAW, 'query'),
'searchanywhere' => new external_value(PARAM_BOOL, 'find a match anywhere, or only at the beginning'),
'page' => new external_value(PARAM_INT, 'Page number'),
'perpage' => new external_value(PARAM_INT, 'Number per page'),
)
);
}
/**
* Get potential users.
*
* @param int $courseid Course id
* @param int $enrolid Enrolment id
* @param string $search The query
* @param boolean $searchanywhere Match anywhere in the string
* @param int $page Page number
* @param int $perpage Max per page
* @return array An array of users
*/
public static function get_potential_users($courseid, $enrolid, $search, $searchanywhere, $page, $perpage) {
global $PAGE;
$params = self::validate_parameters(
self::get_potential_users_parameters(),
array(
'courseid' => $courseid,
'enrolid' => $enrolid,
'search' => $search,
'searchanywhere' => $searchanywhere,
'page' => $page,
'perpage' => $perpage
)
);
$context = context_course::instance($params['courseid']);
require_capability('moodle/course:enrolreview', $context);
$course = $DB->get_record('course', array('id' => $params['courseid']));
$manager = new course_enrolment_manager($PAGE, $course);
$users = $manager->get_potential_users($params['enrolid'],
$params['search'],
$params['searchanywhere'],
$params['page'],
$params['perpage']);
$results = array();
foreach ($users as $id => $user) {
if ($userdetails = user_get_user_details($user)) {
$results[] = $userdetails;
}
}
return $results;
}
/**
* Returns description of method result value
*
* @return external_description
*/
public static function get_potential_users_returns() {
global $CFG;
require_once($CFG->dirroot . '/user/externallib.php');
return new external_multiple_structure(core_user_external::user_description());
}
/**
* Returns description of method parameters
*

View File

@ -61,62 +61,32 @@ $outcome->error = '';
$searchanywhere = get_user_preferences('userselector_searchanywhere', false);
switch ($action) {
case 'getassignable':
$otheruserroles = optional_param('otherusers', false, PARAM_BOOL);
$outcome->response = array_reverse($manager->get_assignable_roles($otheruserroles), true);
break;
case 'searchusers':
$enrolid = required_param('enrolid', PARAM_INT);
$search = optional_param('search', '', PARAM_RAW);
$page = optional_param('page', 0, PARAM_INT);
$addedenrollment = optional_param('enrolcount', 0, PARAM_INT);
$perpage = optional_param('perpage', 25, PARAM_INT); // This value is hard-coded to 25 in quickenrolment.js
$outcome->response = $manager->get_potential_users($enrolid, $search, $searchanywhere, $page, $perpage, $addedenrollment);
$extrafields = get_extra_user_fields($context);
$useroptions = array();
// User is not enrolled yet, either link to site profile or do not link at all.
if (has_capability('moodle/user:viewdetails', context_system::instance())) {
$useroptions['courseid'] = SITEID;
} else {
$useroptions['link'] = false;
}
$viewfullnames = has_capability('moodle/site:viewfullnames', $context);
foreach ($outcome->response['users'] as &$user) {
$user->picture = $OUTPUT->user_picture($user, $useroptions);
$user->fullname = fullname($user, $viewfullnames);
$fieldvalues = array();
foreach ($extrafields as $field) {
$fieldvalues[] = s($user->{$field});
unset($user->{$field});
}
$user->extrafields = implode(', ', $fieldvalues);
}
// Chrome will display users in the order of the array keys, so we need
// to ensure that the results ordered array keys. Fortunately, the JavaScript
// does not care what the array keys are. It uses user.id where necessary.
$outcome->response['users'] = array_values($outcome->response['users']);
$outcome->success = true;
break;
case 'searchcohorts':
$enrolid = required_param('enrolid', PARAM_INT);
$search = optional_param('search', '', PARAM_RAW);
$page = optional_param('page', 0, PARAM_INT);
$addedenrollment = optional_param('enrolcount', 0, PARAM_INT);
$perpage = optional_param('perpage', 25, PARAM_INT); // This value is hard-coded to 25 in quickenrolment.js
$outcome->response = enrol_manual_get_potential_cohorts($context, $enrolid, $search, $page, $perpage, $addedenrollment);
$outcome->success = true;
break;
case 'enrol':
$enrolid = required_param('enrolid', PARAM_INT);
$cohort = $user = null;
$cohorts = $users = [];
$userids = optional_param('userlist', [], PARAM_SEQUENCE);
$userid = optional_param('userid', 0, PARAM_INT);
if ($userid) {
$userids[] = $userid;
}
if ($userids) {
foreach ($userids as $userid) {
$users[] = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
}
}
$cohortids = optional_param('cohortlist', [], PARAM_SEQUENCE);
$cohortid = optional_param('cohortid', 0, PARAM_INT);
if (!$cohortid) {
$userid = required_param('userid', PARAM_INT);
$user = $DB->get_record('user', array('id' => $userid), '*', MUST_EXIST);
} else {
$cohort = $DB->get_record('cohort', array('id' => $cohortid), '*', MUST_EXIST);
if (!cohort_can_view_cohort($cohort, $context)) {
throw new enrol_ajax_exception('invalidenrolinstance'); // TODO error text!
if ($cohortid) {
$cohortids[] = $cohortid;
}
if ($cohortids) {
foreach ($cohortids as $cohortid) {
$cohort = $DB->get_record('cohort', array('id' => $cohortid), '*', MUST_EXIST);
if (!cohort_can_view_cohort($cohort, $context)) {
throw new enrol_ajax_exception('invalidenrolinstance'); // TODO error text!
}
$cohorts[] = $cohort;
}
}
@ -169,9 +139,10 @@ switch ($action) {
}
$plugin = $plugins[$instance->enrol];
if ($plugin->allow_enrol($instance) && has_capability('enrol/'.$plugin->get_name().':enrol', $context)) {
if ($user) {
foreach ($users as $user) {
$plugin->enrol_user($instance, $user->id, $roleid, $timestart, $timeend, null, $recovergrades);
} else {
}
foreach ($cohorts as $cohort) {
$plugin->enrol_cohort($instance, $cohort->id, $roleid, $timestart, $timeend, null, $recovergrades);
}
} else {

View File

@ -0,0 +1 @@
define(["jquery","core/ajax","core/templates"],function(a,b,c){return{processResults:function(b,c){var d=[];return a.each(c,function(a,b){d.push({value:b.id,label:b._label})}),d},transport:function(d,e,f,g){var h,i=a(d).data("courseid");"undefined"==typeof i&&(i="1");var j=a(d).data("enrolid");"undefined"==typeof j&&(j=""),h=b.call([{methodname:"core_enrol_get_potential_users",args:{courseid:i,enrolid:j,search:e,searchanywhere:!0,page:0,perpage:30}}]),h[0].then(function(b){var d=[],e=0;return a.each(b.users,function(b,e){var f=e,g=[];a.each(["idnumber","email","phone1","phone2","department","institution"],function(a,b){"undefined"!=typeof e[b]&&""!==e[b]&&(f.hasidentity=!0,g.push(e[b]))}),f.identity=g.join(", "),d.push(c.render("enrol_manual/form-user-selector-suggestion",f))}),a.when.apply(a.when,d).then(function(){var c=arguments;a.each(b.users,function(a,b){b._label=c[e],e++}),f(b.users)})},g)}}});

View File

@ -0,0 +1 @@
define(["core/templates","jquery","core/str","core/config","core/notification","core/modal_factory","core/modal_events","core/fragment"],function(a,b,c,d,e,f,g,h){var i=function(a){this.contextid=a.contextid,this.initModal()};return i.prototype.courseid=0,i.prototype.modal=null,i.prototype.initModal=function(){var a=b('.enrolusersbutton.enrol_manual_plugin [type="submit"]');c.get_string("enrolusers","enrol_manual").then(function(b){return f.create({title:b,body:this.getBody(),footer:this.getFooter()},a).then(function(a){this.modal=a,this.modal.setLarge(),this.modal.getRoot().on(g.hidden,function(){this.modal.setBody(this.getBody())}.bind(this)),this.modal.getFooter().on("click",'[data-action="submit"]',this.submitForm.bind(this)),this.modal.getRoot().on("submit","form",this.submitFormAjax.bind(this))}.bind(this))}.bind(this)).fail(e.exception)},i.prototype.submitForm=function(a){a.preventDefault(),this.modal.getRoot().find("form").submit()},i.prototype.submitFormAjax=function(a){a.preventDefault();var c=this.modal.getRoot().find("form").serialize();this.modal.hide();var f={type:"GET",processData:!1,contentType:"application/json"},g=d.wwwroot+"/enrol/manual/ajax.php?"+c;b.ajax(g,f).then(function(a){a.error?e.addNotification({message:a.error,type:"error"}):("undefined"!=typeof window.M.core_formchangechecker&&window.M.core_formchangechecker.reset_form_dirty_state(),window.location.reload())}).fail(e.exception)},i.prototype.getBody=function(){return h.loadFragment("enrol_manual","enrol_users_form",this.contextid,{}).fail(e.exception)},i.prototype.getFooter=function(){return a.render("enrol_manual/enrol_modal_footer",{})},{init:function(a){new i(a)}}});

View File

@ -0,0 +1,97 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Potential user selector module.
*
* @module enrol_manual/form-potential-user-selector
* @class form-potential-user-selector
* @package enrol_manual
* @copyright 2016 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['jquery', 'core/ajax', 'core/templates'], function($, Ajax, Templates) {
return /** @alias module:enrol_manual/form-potential-user-selector */ {
processResults: function(selector, results) {
var users = [];
$.each(results, function(index, user) {
users.push({
value: user.id,
label: user._label
});
});
return users;
},
transport: function(selector, query, success, failure) {
var promise;
var courseid = $(selector).data('courseid');
if (typeof courseid === "undefined") {
courseid = '1';
}
var enrolid = $(selector).data('enrolid');
if (typeof enrolid === "undefined") {
enrolid = '';
}
promise = Ajax.call([{
methodname: 'core_enrol_get_potential_users',
args: {
courseid: courseid,
enrolid: enrolid,
search: query,
searchanywhere: true,
page: 0,
perpage: 30
}
}]);
promise[0].then(function(results) {
var promises = [],
i = 0;
// Render the label.
$.each(results.users, function(index, user) {
var ctx = user,
identity = [];
$.each(['idnumber', 'email', 'phone1', 'phone2', 'department', 'institution'], function(i, k) {
if (typeof user[k] !== 'undefined' && user[k] !== '') {
ctx.hasidentity = true;
identity.push(user[k]);
}
});
ctx.identity = identity.join(', ');
promises.push(Templates.render('enrol_manual/form-user-selector-suggestion', ctx));
});
// Apply the label to the results.
return $.when.apply($.when, promises).then(function() {
var args = arguments;
$.each(results.users, function(index, user) {
user._label = args[i];
i++;
});
success(results.users);
});
}, failure);
}
};
});

View File

@ -0,0 +1,168 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Quick enrolment AMD module.
*
* @module enrol_manual/quickenrolment
* @copyright 2016 Damyon Wiese <damyon@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
define(['core/templates',
'jquery',
'core/str',
'core/config',
'core/notification',
'core/modal_factory',
'core/modal_events',
'core/fragment',
],
function(Template, $, Str, Config, Notification, ModalFactory, ModalEvents, Fragment) {
/**
* Constructor
*
* Each call to templates.render gets it's own instance of this class.
*/
var QuickEnrolment = function(options) {
this.contextid = options.contextid;
this.initModal();
};
// Class variables and functions.
/** @var {number} courseid - */
QuickEnrolment.prototype.courseid = 0;
/** @var {Modal} modal */
QuickEnrolment.prototype.modal = null;
/**
* Private method
*
* @method initModal
* @private
*/
QuickEnrolment.prototype.initModal = function() {
var triggerButtons = $('.enrolusersbutton.enrol_manual_plugin [type="submit"]');
Str.get_string('enrolusers', 'enrol_manual').then(function(modalTitle) {
return ModalFactory.create({
title: modalTitle,
body: this.getBody(),
footer: this.getFooter()
}, triggerButtons).then(function(modal) {
this.modal = modal;
this.modal.setLarge();
// We want the reset the form every time it is opened.
this.modal.getRoot().on(ModalEvents.hidden, function() {
this.modal.setBody(this.getBody());
}.bind(this));
this.modal.getFooter().on('click', '[data-action="submit"]', this.submitForm.bind(this));
this.modal.getRoot().on('submit', 'form', this.submitFormAjax.bind(this));
}.bind(this));
}.bind(this)).fail(Notification.exception);
};
/**
* This triggers a form submission, so that any mform elements can do final tricks before the form submission is processed.
*
* @method submitForm
* @private
*/
QuickEnrolment.prototype.submitForm = function(e) {
e.preventDefault();
this.modal.getRoot().find('form').submit();
};
/**
* Private method
*
* @method submitForm
* @private
* @return Promise
*/
QuickEnrolment.prototype.submitFormAjax = function(e) {
// We don't want to do a real form submission.
e.preventDefault();
var formData = this.modal.getRoot().find('form').serialize();
this.modal.hide();
var settings = {
type: 'GET',
processData: false,
contentType: "application/json"
};
var script = Config.wwwroot + '/enrol/manual/ajax.php?' + formData;
$.ajax(script, settings)
.then(function(response) {
if (response.error) {
Notification.addNotification({
message: response.error,
type: "error"
});
} else {
// Reload the page, don't show changed data warnings.
if (typeof window.M.core_formchangechecker !== "undefined") {
window.M.core_formchangechecker.reset_form_dirty_state();
}
window.location.reload();
}
})
.fail(Notification.exception);
};
/**
* Private method
*
* @method getBody
* @private
* @return Promise
*/
QuickEnrolment.prototype.getBody = function() {
return Fragment.loadFragment('enrol_manual', 'enrol_users_form', this.contextid, {}).fail(Notification.exception);
};
/**
* Private method
*
* @method getFooter
* @private
* @return Promise
*/
QuickEnrolment.prototype.getFooter = function() {
return Template.render('enrol_manual/enrol_modal_footer', {});
};
return /** @alias module:enrol_manual/quickenrolment */ {
// Public variables and functions.
/**
* Every call to init creates a new instance of the class with it's own event listeners etc.
*
* @method init
* @public
* @param {object} config - config variables for the module.
*/
init: function(config) {
(new QuickEnrolment(config));
}
};
});

View File

@ -0,0 +1,132 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Enrol users form.
*
* Simple form to search for users and add them using a manual enrolment to this course.
*
* @package enrol_manual
* @copyright 2016 Damyon Wiese
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir.'/formslib.php');
class enrol_manual_enrol_users_form extends moodleform {
/**
* Form definition.
* @return void
*/
public function definition() {
global $PAGE, $DB, $CFG;
require_once($CFG->dirroot . '/enrol/locallib.php');
$context = $this->_customdata->context;
// Get the course and enrolment instance.
$coursecontext = $context->get_course_context();
$course = $DB->get_record('course', ['id' => $coursecontext->instanceid]);
$manager = new course_enrolment_manager($PAGE, $course);
$instance = null;
foreach ($manager->get_enrolment_instances() as $tempinstance) {
if ($tempinstance->enrol == 'manual') {
if ($instance === null) {
$instance = $tempinstance;
break;
}
}
}
$mform = $this->_form;
$mform->setDisableShortforms();
$mform->disable_form_change_checker();
// Build the list of options for the enrolment period dropdown.
$unlimitedperiod = get_string('unlimited');
$periodmenu = array();
$periodmenu[''] = $unlimitedperiod;
for ($i=1; $i<=365; $i++) {
$seconds = $i * 86400;
$periodmenu[$seconds] = get_string('numdays', '', $i);
}
// Work out the apropriate default settings.
$defaultperiod = $instance->enrolperiod;
if ($instance->enrolperiod > 0 && !isset($periodmenu[$instance->enrolperiod])) {
$periodmenu[$instance->enrolperiod] = format_time($instance->enrolperiod);
}
if (empty($extendbase)) {
if (!$extendbase = get_config('enrol_manual', 'enrolstart')) {
// Default to now if there is no system setting.
$extendbase = 4;
}
}
// Build the list of options for the starting from dropdown.
$now = time();
$today = make_timestamp(date('Y', $now), date('m', $now), date('d', $now), 0, 0, 0);
$dateformat = get_string('strftimedatefullshort');
// Enrolment start.
$basemenu = array();
if ($course->startdate > 0) {
$basemenu[2] = get_string('coursestart') . ' (' . userdate($course->startdate, $dateformat) . ')';
}
$basemenu[3] = get_string('today') . ' (' . userdate($today, $dateformat) . ')';
$basemenu[4] = get_string('now', 'enrol_manual') . ' (' . userdate($now, get_string('strftimedatetimeshort')) . ')';
$mform->addElement('header', 'main', get_string('enrolmentoptions', 'enrol'));
$roles = get_assignable_roles($context);
$mform->addElement('select', 'role', get_string('assignrole', 'enrol_manual'), $roles);
$keys = array_keys($roles);
$defaultrole = end($keys);
$mform->setDefault('role', $defaultrole);
$options = array(
'ajax' => 'tool_lp/form-user-selector',
'multiple' => true,
'courseid' => $course->id,
'enrolid' => $instance->id
);
$mform->addElement('autocomplete', 'userlist', get_string('selectusers', 'enrol_manual'), array(), $options);
$options = ['contextid' => $context->id, 'multiple' => true];
$mform->addElement('cohort', 'cohortlist', get_string('selectcohorts', 'enrol_manual'), $options);
$mform->addAdvancedStatusElement('main');
$mform->addElement('checkbox', 'recovergrades', get_string('recovergrades', 'enrol'));
$mform->setAdvanced('recovergrades');
$mform->addElement('select', 'duration', get_string('defaultperiod', 'enrol_manual'), $periodmenu);
$mform->setDefault('duration', $defaultperiod);
$mform->setAdvanced('duration');
$mform->addElement('select', 'startdate', get_string('startingfrom'), $basemenu);
$mform->setDefault('startdate', $extendbase);
$mform->setAdvanced('startdate');
$mform->addElement('hidden', 'id', $course->id);
$mform->setType('id', PARAM_INT);
$mform->addElement('hidden', 'action', 'enrol');
$mform->setType('action', PARAM_ALPHA);
$mform->addElement('hidden', 'enrolid', $instance->id);
$mform->setType('enrolid', PARAM_INT);
}
}

View File

@ -22,10 +22,12 @@
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
$string['advanced'] = 'Advanced';
$string['alterstatus'] = 'Alter status';
$string['altertimeend'] = 'Alter end time';
$string['altertimestart'] = 'Alter start time';
$string['assignrole'] = 'Assign role';
$string['assignroles'] = 'Assign roles';
$string['browseusers'] = 'Browse users';
$string['browsecohorts'] = 'Browse cohorts';
$string['confirmbulkdeleteenrolment'] = 'Are you sure you want to delete these users enrolments?';
@ -37,6 +39,7 @@ $string['deleteselectedusers'] = 'Delete selected user enrolments';
$string['editselectedusers'] = 'Edit selected user enrolments';
$string['enrolledincourserole'] = 'Enrolled in "{$a->course}" as "{$a->role}"';
$string['enrolusers'] = 'Enrol users';
$string['enroluserscohorts'] = 'Enrol selected users and cohorts';
$string['expiredaction'] = 'Enrolment expiry action';
$string['expiredaction_help'] = 'Select action to carry out when user enrolment expires. Please note that some user data and settings are purged from course during course unenrolment.';
$string['expirymessageenrollersubject'] = 'Enrolment expiry notification';
@ -60,6 +63,9 @@ $string['messageprovider:expiry_notification'] = 'Manual enrolment expiry notifi
$string['now'] = 'Now';
$string['pluginname'] = 'Manual enrolments';
$string['pluginname_desc'] = 'The manual enrolments plugin allows users to be enrolled manually via a link in the course administration settings, by a user with appropriate permissions such as a teacher. The plugin should normally be enabled, since certain other enrolment plugins, such as self enrolment, require it.';
$string['selection'] = 'Selection';
$string['selectusers'] = 'Select users';
$string['selectcohorts'] = 'Select cohorts';
$string['status'] = 'Enable manual enrolments';
$string['status_desc'] = 'Allow course access of internally enrolled users. This should be kept enabled in most cases.';
$string['status_help'] = 'This setting determines whether users can be enrolled manually, via a link in the course administration settings, by a user with appropriate permissions such as a teacher.';

View File

@ -194,7 +194,7 @@ class enrol_manual_plugin extends enrol_plugin {
* @return enrol_user_button
*/
public function get_manual_enrol_button(course_enrolment_manager $manager) {
global $CFG;
global $CFG, $PAGE;
require_once($CFG->dirroot.'/cohort/lib.php');
$instance = null;
@ -211,72 +211,18 @@ class enrol_manual_plugin extends enrol_plugin {
return false;
}
if (!$manuallink = $this->get_manual_enrol_link($instance)) {
$link = $this->get_manual_enrol_link($instance);
if (!$link) {
return false;
}
$button = new enrol_user_button($manuallink, get_string('enrolusers', 'enrol_manual'), 'get');
$button = new enrol_user_button($link, get_string('enrolusers', 'enrol_manual'), 'get');
$button->class .= ' enrol_manual_plugin';
$startdate = $manager->get_course()->startdate;
if (!$defaultstart = get_config('enrol_manual', 'enrolstart')) {
// Default to now if there is no system setting.
$defaultstart = 4;
}
$startdateoptions = array();
$dateformat = get_string('strftimedatefullshort');
if ($startdate > 0) {
$startdateoptions[2] = get_string('coursestart') . ' (' . userdate($startdate, $dateformat) . ')';
}
$now = time();
$today = make_timestamp(date('Y', $now), date('m', $now), date('d', $now), 0, 0, 0);
$startdateoptions[3] = get_string('today') . ' (' . userdate($today, $dateformat) . ')';
$startdateoptions[4] = get_string('now', 'enrol_manual') . ' (' . userdate($now, get_string('strftimedatetimeshort')) . ')';
$defaultduration = $instance->enrolperiod > 0 ? $instance->enrolperiod / DAYSECS : '';
$context = context_course::instance($instance->courseid);
$arguments = array('contextid' => $context->id);
$modules = array('moodle-enrol_manual-quickenrolment', 'moodle-enrol_manual-quickenrolment-skin');
$arguments = array(
'instances' => $instances,
'courseid' => $instance->courseid,
'ajaxurl' => '/enrol/manual/ajax.php',
'url' => $manager->get_moodlepage()->url->out(false),
'optionsStartDate' => $startdateoptions,
'defaultRole' => $instance->roleid,
'defaultDuration' => $defaultduration,
'defaultStartDate' => (int)$defaultstart,
'disableGradeHistory' => $CFG->disablegradehistory,
'recoverGradesDefault'=> '',
'cohortsAvailable' => cohort_get_available_cohorts($manager->get_context(), COHORT_WITH_NOTENROLLED_MEMBERS_ONLY, 0, 1) ? true : false
);
if ($CFG->recovergradesdefault) {
$arguments['recoverGradesDefault'] = ' checked="checked"';
}
$function = 'M.enrol_manual.quickenrolment.init';
$button->require_yui_module($modules, $function, array($arguments));
$button->strings_for_js(array(
'ajaxoneuserfound',
'ajaxxusersfound',
'ajaxnext25',
'enrol',
'enrolmentoptions',
'enrolusers',
'enrolxusers',
'errajaxfailedenrol',
'errajaxsearch',
'foundxcohorts',
'none',
'usersearch',
'unlimitedduration',
'startdatetoday',
'durationdays',
'enrolperiod',
'finishenrollingusers',
'recovergrades'), 'enrol');
$button->strings_for_js(array('browseusers', 'browsecohorts'), 'enrol_manual');
$button->strings_for_js('assignroles', 'role');
$button->strings_for_js('startingfrom', 'moodle');
$PAGE->requires->js_call_amd('enrol_manual/quickenrolment', 'init', array($arguments));
return $button;
}
@ -730,3 +676,25 @@ class enrol_manual_plugin extends enrol_plugin {
}
}
/**
* Serve the manual enrol users form as a fragment.
*
* @param array $args List of named arguments for the fragment loader.
* @return string
*/
function enrol_manual_output_fragment_enrol_users_form($args) {
$args = (object) $args;
$context = $args->context;
$o = '';
require_capability('enrol/manual:enrol', $context);
$mform = new enrol_manual_enrol_users_form(null, $args);
ob_start();
$mform->display();
$o .= ob_get_contents();
ob_end_clean();
return $o;
}

View File

@ -0,0 +1,2 @@
<input type="button" class="btn btn-primary" data-action="submit" value="{{#str}}enroluserscohorts, enrol_manual{{/str}}">
<input type="button" class="btn btn-secondary" data-action="hide" value="{{#str}}cancel{{/str}}">

View File

@ -0,0 +1,57 @@
{{!
This file is part of Moodle - http://moodle.org/
Moodle is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Moodle is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Moodle. If not, see <http://www.gnu.org/licenses/>.
}}
{{!
@template enrol_manual/form-user-selector-suggestion
Moodle template for the list of valid options in an autocomplate form element.
Classes required for JS:
* none
Data attributes required for JS:
* none
Context variables required for this template:
* profileimageurlsmall Url to a small profile image.
* profileimageurl Url to a profile image.
* fullname Users full name
* hasidentity boolean to say if there are identity fields to show
* identity concatenated list of identity fields.
* id user id field
* email user email field
* idnumber user idnumber field
* phone1 user phone1 field
* phone2 user phone2 field
* department user department field
* institution user institution field
Example context (json):
{ "id": "1",
"fullname": "Admin",
"hasidentity": true,
"identity": "Admin User, 0144114141",
"profileimageurl": "invalid url",
"profileimageurlsmall": "invalid url"
}
}}
<span>
<img height="18" src="{{profileimageurlsmall}}" alt="" role="presentation">
<span>{{fullname}}</span>
{{#hasidentity}}
<span><small>{{identity}}</small></span>
{{/hasidentity}}
</span>

View File

@ -1,197 +0,0 @@
/* stylelint-disable unit-blacklist */
/**************************************
Structure of the user enroller panel
.user-enroller-panel(.visible)
.uep-wrap
.uep-header
.uep-content
.uep-ajax-content
.uep-search-results
.totalusers
.users
.user.clearfix(.odd|.even)(.enrolled)
.count
.picture
.details
.fullname
.extrafields
.options
.enrol
.uep-more-results
.uep-loading-lightbox(.hidden)
.loading-icon
.uep-footer
.uep-search
input
.uep-searchoptions
.collapsibleheading
.collapsiblearea(.hidden)
.uep-enrolment-option
.role
.startdate
.duration
**************************************/
.user-enroller-panel {
width: 400px;
position: absolute;
}
.user-enroller-panel.hidden {
display: none;
}
.user-enroller-panel .uep-wrap {
height: inherit;
position: relative;
}
.user-enroller-panel .uep-search-results .user .count {
display: none;
}
.user-enroller-panel .uep-search-results .cohort .count {
display: none;
}
.user-enroller-panel .uep-content {
position: relative;
width: 100%;
box-sizing: border-box;
max-height: 1000px;
}
.user-enroller-panel .uep-ajax-content {
height: 375px;
overflow: auto;
}
.user-enroller-panel .uep-search-results .user .picture {
width: 45px;
float: left;
margin: 3px;
}
/* Note this file isn't auto flipped, so we need dir-rtl rules*/
.dir-rtl .user-enroller-panel .uep-search-results .user .picture {
float: right;
}
.user-enroller-panel .uep-search-results .user .details {
width: 180px;
float: left;
margin: 3px;
}
/* Note this file isn't auto flipped, so we need dir-rtl rules*/
.dir-rtl .user-enroller-panel .uep-search-results .user .details {
float: right;
}
.user-enroller-panel .uep-search-results .user .options {
padding-right: 7px;
margin: 3px;
}
.user-enroller-panel .uep-search-results .user .options .enrol {
margin: 3px;
float: right;
cursor: pointer;
}
/* Note this file isn't auto flipped, so we need dir-rtl rules*/
.dir-rtl .user-enroller-panel .uep-search-results .user .options .enrol {
float: left;
}
.user-enroller-panel .uep-search-results .cohort {
width: 100%;
text-align: left;
}
.user-enroller-panel .uep-search-results .cohort .count {
display: none;
}
.user-enroller-panel .uep-search-results .cohort .details {
width: 180px;
float: left;
margin: 5px;
}
.user-enroller-panel .uep-search-results .cohort .options .enrol {
margin: 3px;
float: right;
cursor: pointer;
}
.user-enroller-panel .uep-search-results .cohort.enrolled .count {
width: 40px;
}
.user-enroller-panel .uep-loading-lightbox {
position: absolute;
width: 100%;
height: 100%;
background-color: #ddd;
top: 0;
left: 0;
min-width: 50px;
min-height: 50px;
}
.user-enroller-panel .uep-loading-lightbox.hidden {
display: none;
}
.user-enroller-panel .uep-loading-lightbox .loading-icon {
margin: auto;
vertical-align: middle;
margin-top: 125px;
display: block;
}
.user-enroller-panel .uep-footer {
text-align: right;
}
.user-enroller-panel .uep-search {
margin: 3px;
}
.user-enroller-panel .uep-search label {
padding-right: 8px;
}
.user-enroller-panel .uep-search input {
width: 50%;
}
.user-enroller-panel .uep-search input.uep-search-btn {
width: 20%;
}
.user-enroller-panel .uep-searchoptions {
margin: 3px;
cursor: pointer;
}
.user-enroller-panel .uep-controls select {
margin-left: 1em;
margin-bottom: 0;
}
.user-enroller-panel .collapsibleheading img {
margin-right: 8px;
}
.user-enroller-panel .collapsiblearea {
border: 1px solid #ddd;
padding: 0.5rem;
}
.user-enroller-panel .collapsiblearea.hidden {
display: none;
}
.user-enroller-panel .collapsiblearea .uep-enrolment-option {
margin: 5px 1em;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 930 B

View File

@ -1,668 +0,0 @@
YUI.add('moodle-enrol_manual-quickenrolment', function(Y) {
var UEP = {
NAME : 'Enrolment Manager',
/** Properties **/
BASE : 'base',
SEARCH : 'search',
SEARCHBTN : 'searchbtn',
PARAMS : 'params',
URL : 'url',
AJAXURL : 'ajaxurl',
MULTIPLE : 'multiple',
PAGE : 'page',
COURSEID : 'courseid',
USERS : 'users',
USERCOUNT : 'userCount',
REQUIREREFRESH : 'requiresRefresh',
LASTSEARCH : 'lastPreSearchValue',
INSTANCES : 'instances',
OPTIONSTARTDATE : 'optionsStartDate',
DEFAULTROLE : 'defaultRole',
DEFAULTSTARTDATE : 'defaultStartDate',
DEFAULTDURATION : 'defaultDuration',
ASSIGNABLEROLES : 'assignableRoles',
DISABLEGRADEHISTORY : 'disableGradeHistory',
RECOVERGRADESDEFAULT : 'recoverGradesDefault',
ENROLCOUNT : 'enrolCount',
PERPAGE : 'perPage',
COHORTSAVAILABLE : 'cohortsAvailable',
COHORTCOUNT : 'cohortCount'
};
/** CSS classes for nodes in structure **/
var CSS = {
PANEL : 'user-enroller-panel',
WRAP : 'uep-wrap modal-dialog',
HEADER : 'uep-header',
CONTENT : 'uep-content',
AJAXCONTENT : 'uep-ajax-content',
SEARCHRESULTS : 'uep-search-results',
TOTALUSERS : 'totalusers',
USERS : 'users',
USER : 'user',
MORERESULTS : 'uep-more-results',
LIGHTBOX : 'uep-loading-lightbox',
LOADINGICON : 'loading-icon',
FOOTER : 'uep-footer',
ENROL : 'enrol',
ENROLLED : 'enrolled',
COUNT : 'count',
PICTURE : 'picture',
DETAILS : 'details',
FULLNAME : 'fullname',
EXTRAFIELDS : 'extrafields',
OPTIONS : 'options',
ODD : 'odd',
EVEN : 'even',
HIDDEN : 'hidden',
RECOVERGRADES : 'recovergrades',
RECOVERGRADESTITLE : 'recovergradestitle',
SEARCHOPTIONS : 'uep-searchoptions',
COLLAPSIBLEHEADING : 'collapsibleheading',
COLLAPSIBLEAREA : 'collapsiblearea',
ENROLMENTOPTION : 'uep-enrolment-option',
SEARCHCONTROLS : 'uep-controls',
ROLE : 'role',
STARTDATE : 'startdate',
DURATION : 'duration',
ACTIVE : 'active',
SEARCH : 'uep-search',
SEARCHBTN : 'uep-search-btn',
CLOSE : 'close',
CLOSEBTN : 'close-button',
ENTITYSELECTOR : 'uep-entity-selector',
COHORTS : 'cohorts',
COHORT : 'cohort',
COHORTNAME : 'cohortname',
TOTALCOHORTS : 'totalcohorts'
};
var create = Y.Node.create;
var USERENROLLER = function(config) {
USERENROLLER.superclass.constructor.apply(this, arguments);
};
Y.extend(USERENROLLER, Y.Base, {
_searchTimeout : null,
_loadingNode : null,
_escCloseEvent : null,
initializer : function(config) {
var recovergrades = null;
if (this.get(UEP.DISABLEGRADEHISTORY) != true) {
recovergrades = create('<div class="'+CSS.ENROLMENTOPTION+' '+CSS.RECOVERGRADES+'"></div>')
.append(create('<label class="'+CSS.RECOVERGRADESTITLE+'" for="'+CSS.RECOVERGRADES+'">'+M.util.get_string('recovergrades', 'enrol')+'</label>'))
.append(create('<input type="checkbox" class="m-x-1" id="'+CSS.RECOVERGRADES+'" name="'+CSS.RECOVERGRADES+'"'+ this.get(UEP.RECOVERGRADESDEFAULT) +' />'))
}
this.set(UEP.BASE, create('<div class="'+CSS.PANEL+' '+CSS.HIDDEN+'"></div>')
.append(create('<div class="'+CSS.WRAP+' modal show modal-dialog modal-content"></div>')
.append(create('<div class="'+CSS.HEADER+' header modal-header"></div>')
.append(create('<div class="'+CSS.CLOSE+'">&times;</div>'))
.append(create('<h2 class="modal-title">'+M.util.get_string('enrolusers', 'enrol')+'</h2>')))
.append(create('<div class="'+CSS.CONTENT+' modal-body"></div>')
.append(create('<div class="'+CSS.SEARCHCONTROLS+' form-inline"></div>')
.append(create('<div class="'+CSS.ENROLMENTOPTION+' '+CSS.ROLE+'"><label for="id_enrol_manual_assignable_roles">'+M.util.get_string('assignroles', 'role')+'</label></div>')
.append(create('<select id="id_enrol_manual_assignable_roles" class="custom-select"><option value="">'+M.util.get_string('none', 'enrol')+'</option></select>'))
)
.append(create('<div class="'+CSS.ENTITYSELECTOR+' m-y-1"></div>'))
.append(create('<div class="'+CSS.SEARCHOPTIONS+'"></div>')
.append(create('<div class="'+CSS.COLLAPSIBLEHEADING+'"><img alt="" />'+M.util.get_string('enrolmentoptions', 'enrol')+'</div>'))
.append(create('<div class="'+CSS.COLLAPSIBLEAREA+' '+CSS.HIDDEN+'"></div>')
.append(recovergrades)
.append(create('<div class="'+CSS.ENROLMENTOPTION+' '+CSS.STARTDATE+'">'+M.util.get_string('startingfrom', 'moodle')+'</div>')
.append(create('<select class="custom-select"></select>')))
.append(create('<div class="'+CSS.ENROLMENTOPTION+' '+CSS.DURATION+'">'+M.util.get_string('enrolperiod', 'enrol')+'</div>')
.append(create('<select class="custom-select"><option value="0" selected="selected">'+M.util.get_string('unlimitedduration', 'enrol')+'</option></select>')))
)
)
.append(create('<div class="'+CSS.SEARCH+'"><label for="enrolusersearch" class="accesshide">'+M.util.get_string('usersearch', 'enrol')+'</label></div>')
.append(create('<input class="form-control" type="text" id="enrolusersearch" value="" />'))
.append(create('<input type="button" id="searchbtn" class="'+CSS.SEARCHBTN+' btn btn-secondary m-l-1" value="'+M.util.get_string('usersearch', 'enrol')+'" />'))
)
)
.append(create('<div class="'+CSS.AJAXCONTENT+'"></div>'))
.append(create('<div class="'+CSS.LIGHTBOX+' '+CSS.HIDDEN+'"></div>')
.append(create('<img alt="loading" class="'+CSS.LOADINGICON+'" />')
.setAttribute('src', M.util.image_url('i/loading', 'moodle')))
.setStyle('opacity', 0.5)))
.append(create('<div class="'+CSS.FOOTER+' modal-footer"></div>')
.append(create('<div class="'+CSS.CLOSEBTN+'"></div>')
.append(create('<input type="button" class="btn btn-primary" value="'+M.util.get_string('finishenrollingusers', 'enrol')+'" />'))
)
)
)
);
this.set(UEP.SEARCH, this.get(UEP.BASE).one('#enrolusersearch'));
this.set(UEP.SEARCHBTN, this.get(UEP.BASE).one('#searchbtn'));
Y.all('.enrol_manual_plugin input').each(function(node){
if (node.getAttribute('type', 'submit')) {
node.on('click', this.show, this);
}
}, this);
this.get(UEP.BASE).one('.'+CSS.HEADER+' .'+CSS.CLOSE).on('click', this.hide, this);
this.get(UEP.BASE).one('.'+CSS.FOOTER+' .'+CSS.CLOSEBTN+' input').on('click', this.hide, this);
this._loadingNode = this.get(UEP.BASE).one('.'+CSS.CONTENT+' .'+CSS.LIGHTBOX);
var params = this.get(UEP.PARAMS);
params['id'] = this.get(UEP.COURSEID);
this.set(UEP.PARAMS, params);
Y.on('key', this.preSearch, this.get(UEP.SEARCH), 'down:13', this);
this.get(UEP.SEARCHBTN).on('click', this.preSearch, this);
if (this.get(UEP.COHORTSAVAILABLE)) {
this.get(UEP.BASE).one('.'+CSS.ENTITYSELECTOR)
.append(create('<input type="radio" id="id_enrol_manual_entity_users" name="enrol_manual_entity" value="users" class="m-r-1" checked="checked"/>'))
.append(create('<label for="id_enrol_manual_entity_users">'+ M.util.get_string('browseusers', 'enrol_manual')+'</label>'))
.append(create('<input type="radio" id="id_enrol_manual_entity_cohorts" name="enrol_manual_entity" class="m-x-1" value="cohorts"/>'))
.append(create('<label for="id_enrol_manual_entity_cohorts">'+M.util.get_string('browsecohorts', 'enrol_manual')+'</label>'));
this.get(UEP.BASE).one('#id_enrol_manual_entity_cohorts').on('change', this.search, this);
this.get(UEP.BASE).one('#id_enrol_manual_entity_users').on('change', this.search, this);
} else {
this.get(UEP.BASE).one('.'+CSS.ENTITYSELECTOR)
.append(create('<input type="hidden" name="enrol_manual_entity" value="users"/>'));
}
Y.one(document.body).append(this.get(UEP.BASE));
var base = this.get(UEP.BASE);
base.plug(Y.Plugin.Drag);
base.dd.addHandle('.'+CSS.HEADER+' h2');
base.one('.'+CSS.HEADER+' h2').setStyle('cursor', 'move');
var collapsedimage = 't/collapsed'; // ltr mode
if ( Y.one(document.body).hasClass('dir-rtl') ) {
collapsedimage = 't/collapsed_rtl';
} else {
collapsedimage = 't/collapsed';
}
this.get(UEP.BASE).one('.'+CSS.SEARCHOPTIONS+' .'+CSS.COLLAPSIBLEHEADING).one('img').setAttribute('src', M.util.image_url(collapsedimage, 'moodle'));
this.populateStartDates();
this.populateDuration();
this.get(UEP.BASE).one('.'+CSS.SEARCHOPTIONS+' .'+CSS.COLLAPSIBLEHEADING).on('click', function(){
this.get(UEP.BASE).one('.'+CSS.SEARCHOPTIONS+' .'+CSS.COLLAPSIBLEHEADING).toggleClass(CSS.ACTIVE);
this.get(UEP.BASE).one('.'+CSS.SEARCHOPTIONS+' .'+CSS.COLLAPSIBLEAREA).toggleClass(CSS.HIDDEN);
if (this.get(UEP.BASE).one('.'+CSS.SEARCHOPTIONS+' .'+CSS.COLLAPSIBLEAREA).hasClass(CSS.HIDDEN)) {
this.get(UEP.BASE).one('.'+CSS.SEARCHOPTIONS+' .'+CSS.COLLAPSIBLEHEADING).one('img').setAttribute('src', M.util.image_url(collapsedimage, 'moodle'));
} else {
this.get(UEP.BASE).one('.'+CSS.SEARCHOPTIONS+' .'+CSS.COLLAPSIBLEHEADING).one('img').setAttribute('src', M.util.image_url('t/expanded', 'moodle'));
}
}, this);
this.populateAssignableRoles();
},
populateAssignableRoles : function() {
this.on('assignablerolesloaded', function(){
var roles = this.get(UEP.ASSIGNABLEROLES);
var s = this.get(UEP.BASE).one('.'+CSS.ENROLMENTOPTION+'.'+CSS.ROLE+' select');
var v = this.get(UEP.DEFAULTROLE);
var index = 0, count = 0;
for (var i in roles) {
count++;
var option = create('<option value="' + roles[i].id + '">' + roles[i].name + '</option>');
if (roles[i].id == v) {
index = count;
}
s.append(option);
}
s.set('selectedIndex', index);
Y.one('#id_enrol_manual_assignable_roles').focus();
}, this);
this.getAssignableRoles();
},
populateStartDates : function() {
var select = this.get(UEP.BASE).one('.'+CSS.ENROLMENTOPTION+'.'+CSS.STARTDATE+' select');
var defaultvalue = this.get(UEP.DEFAULTSTARTDATE);
var options = this.get(UEP.OPTIONSTARTDATE);
var index = 0, count = 0;
for (var i in options) {
var option = create('<option value="'+i+'">'+options[i]+'</option>');
if (i == defaultvalue) {
index = count;
}
select.append(option);
count++;
}
select.set('selectedIndex', index);
},
populateDuration : function() {
var select = this.get(UEP.BASE).one('.'+CSS.ENROLMENTOPTION+'.'+CSS.DURATION+' select');
var defaultvalue = this.get(UEP.DEFAULTDURATION);
var prefix = Math.round(defaultvalue) != defaultvalue ? '≈' : '';
var index = 0, count = 0;
var durationdays = M.util.get_string('durationdays', 'enrol', '{a}');
for (var i = 1; i <= 365; i++) {
count++;
var option = create('<option value="'+i+'">'+durationdays.replace('{a}', i)+'</option>');
if (i == defaultvalue) {
index = count;
}
select.append(option);
}
if (!index && defaultvalue > 0) {
select.append(create('<option value="'+defaultvalue+'">'+durationdays.replace('{a}',
prefix + (Math.round(defaultvalue * 100) / 100))+'</option>'));
index = ++count;
}
select.set('selectedIndex', index);
},
getAssignableRoles : function(){
Y.io(M.cfg.wwwroot+'/enrol/ajax.php', {
method:'POST',
data:'id='+this.get(UEP.COURSEID)+'&action=getassignable&sesskey='+M.cfg.sesskey,
on: {
complete: function(tid, outcome, args) {
try {
var roles = Y.JSON.parse(outcome.responseText);
this.set(UEP.ASSIGNABLEROLES, roles.response);
} catch (e) {
new M.core.exception(e);
}
this.getAssignableRoles = function() {
this.fire('assignablerolesloaded');
};
this.getAssignableRoles();
}
},
context:this
});
},
preSearch : function(e) {
this.search(e, false);
/*
var value = this.get(UEP.SEARCH).get('value');
if (value.length < 3 || value == this.get(UEP.LASTSEARCH)) {
return;
}
this.set(UEP.LASTSEARCH, value);
if (this._searchTimeout) {
clearTimeout(this._searchTimeout);
this._searchTimeout = null;
}
var self = this;
this._searchTimeout = setTimeout(function(){
self._searchTimeout = null;
self.search(null, false);
}, 300);
*/
},
show : function(e) {
e.preventDefault();
e.halt();
var base = this.get(UEP.BASE);
base.removeClass(CSS.HIDDEN);
var x = (base.get('winWidth') - 400)/2;
var y = (parseInt(base.get('winHeight'))-base.get('offsetHeight'))/2 + parseInt(base.get('docScrollY'));
if (y < parseInt(base.get('winHeight'))*0.1) {
y = parseInt(base.get('winHeight'))*0.1;
}
base.setXY([x,y]);
var zindex = 0;
Y.all('.moodle-has-zindex').each(function() {
if (parseInt(this.getComputedStyle('zIndex'), 10) > zindex) {
zindex = parseInt(this.getComputedStyle('zIndex'), 10);
}
});
base.setStyle('zIndex', zindex + 1);
if (this.get(UEP.USERS)===null) {
this.search(e, false);
}
this._escCloseEvent = Y.on('key', this.hide, document.body, 'down:27', this);
var rolesselect = Y.one('#id_enrol_manual_assignable_roles');
if (rolesselect) {
rolesselect.focus();
}
},
hide : function(e) {
if (this._escCloseEvent) {
this._escCloseEvent.detach();
this._escCloseEvent = null;
}
this.get(UEP.BASE).addClass(CSS.HIDDEN);
if (this.get(UEP.REQUIREREFRESH)) {
window.location = this.get(UEP.URL);
}
},
currentEntity : function() {
var entity = CSS.USER;
var cohortsinput = Y.one('#id_enrol_manual_entity_cohorts');
if (cohortsinput && cohortsinput.get('checked')) {
entity = CSS.COHORT;
}
return entity;
},
search : function(e, append) {
var entity = this.currentEntity();
if (e) {
e.halt();
e.preventDefault();
}
var on, params;
if (append) {
this.set(UEP.PAGE, this.get(UEP.PAGE)+1);
} else {
this.set(UEP.USERCOUNT, 0);
this.set(UEP.COHORTCOUNT, 0);
this.set(UEP.PAGE, 0);
}
params = this.get(UEP.PARAMS);
params['sesskey'] = M.cfg.sesskey;
params['action'] = (entity === CSS.USER) ? 'searchusers' : 'searchcohorts';
params['search'] = this.get(UEP.SEARCH).get('value');
params['page'] = this.get(UEP.PAGE);
params['enrolcount'] = this.get(UEP.ENROLCOUNT);
params['perpage'] = this.get(UEP.PERPAGE);
if (this.get(UEP.MULTIPLE)) {
alert('oh no there are multiple');
} else {
var instance = this.get(UEP.INSTANCES)[0];
params['enrolid'] = instance.id;
}
Y.io(M.cfg.wwwroot+this.get(UEP.AJAXURL), {
method:'POST',
data:build_querystring(params),
on : {
start : this.displayLoading,
complete: ((entity === CSS.USER) ? this.processSearchResults : this.processCohortsSearchResults),
end : this.removeLoading
},
context:this,
arguments:{
append:append,
enrolid:params['enrolid']
}
});
},
displayLoading : function() {
this._loadingNode.removeClass(CSS.HIDDEN);
},
removeLoading : function() {
this._loadingNode.addClass(CSS.HIDDEN);
},
processSearchResults : function(tid, outcome, args) {
try {
var result = Y.JSON.parse(outcome.responseText);
if (result.error) {
return new M.core.ajaxException(result);
}
} catch (e) {
new M.core.exception(e);
}
if (!result.success) {
this.setContent = M.util.get_string('errajaxsearch', 'enrol');
}
var users;
if (!args.append) {
users = create('<div class="'+CSS.USERS+' list-group"></div>');
} else {
users = this.get(UEP.BASE).one('.'+CSS.SEARCHRESULTS+' .'+CSS.USERS);
}
var count = this.get(UEP.USERCOUNT);
for (var i in result.response.users) {
count++;
var user = result.response.users[i];
users.append(create('<div class="'+CSS.USER+' clearfix list-group-item list-group-item-action" rel="'+user.id+'"></div>')
.addClass((count%2)?CSS.ODD:CSS.EVEN)
.append(create('<div class="'+CSS.COUNT+'">'+count+'</div>'))
.append(create('<div class="'+CSS.PICTURE+'"></div>')
.append(create(user.picture)))
.append(create('<div class="'+CSS.DETAILS+'"></div>')
.append(create('<div class="'+CSS.FULLNAME+'">'+user.fullname+'</div>'))
.append(create('<div class="'+CSS.EXTRAFIELDS+'">'+user.extrafields+'</div>')))
.append(create('<div class="'+CSS.OPTIONS+'"></div>')
.append(create('<input type="button" class="'+CSS.ENROL+' btn btn-secondary" value="'+M.util.get_string('enrol', 'enrol')+'" />')))
);
}
this.set(UEP.USERCOUNT, count);
if (!args.append) {
var usersstr = (result.response.totalusers == '1')?M.util.get_string('ajaxoneuserfound', 'enrol'):M.util.get_string('ajaxxusersfound','enrol', result.response.totalusers);
var content = create('<div class="'+CSS.SEARCHRESULTS+'"></div>')
.append(create('<div class="'+CSS.TOTALUSERS+'">'+usersstr+'</div>'))
.append(users);
if (result.response.totalusers > (this.get(UEP.PAGE)+1)*this.get(UEP.PERPAGE)) {
var fetchmore = create('<div class="'+CSS.MORERESULTS+'"><a href="#">'+M.util.get_string('ajaxnext25', 'enrol')+'</a></div>');
fetchmore.on('click', this.search, this, true);
content.append(fetchmore)
}
this.setContent(content);
Y.delegate("click", this.enrolUser, users, '.'+CSS.USER+' .'+CSS.ENROL, this, args);
} else {
if (result.response.totalusers <= (this.get(UEP.PAGE)+1)*this.get(UEP.PERPAGE)) {
this.get(UEP.BASE).one('.'+CSS.MORERESULTS).remove();
}
}
},
processCohortsSearchResults : function(tid, outcome, args) {
try {
var result = Y.JSON.parse(outcome.responseText);
if (result.error) {
return new M.core.ajaxException(result);
}
} catch (e) {
new M.core.exception(e);
}
if (!result.success) {
this.setContent = M.util.get_string('errajaxsearch', 'enrol');
}
var cohorts;
if (!args.append) {
cohorts = create('<div class="'+CSS.COHORTS+'"></div>');
} else {
cohorts = this.get(UEP.BASE).one('.'+CSS.SEARCHRESULTS+' .'+CSS.COHORTS);
}
var count = this.get(UEP.COHORTCOUNT);
for (var i in result.response.cohorts) {
count++;
var cohort = result.response.cohorts[i];
cohorts.append(create('<div class="'+CSS.COHORT+' clearfix" rel="'+cohort.id+'"></div>')
.addClass((count%2)?CSS.ODD:CSS.EVEN)
.append(create('<div class="'+CSS.COUNT+'">'+count+'</div>'))
.append(create('<div class="'+CSS.DETAILS+'"></div>')
.append(create('<div class="'+CSS.COHORTNAME+'">'+cohort.name+'</div>')))
.append(create('<div class="'+CSS.OPTIONS+'"></div>')
.append(create('<input type="button" class="' + CSS.ENROL + ' btn btn-secondary" value="' + M.util.get_string('enrolxusers', 'enrol', cohort.cnt) + '" />')))
);
}
this.set(UEP.COHORTCOUNT, count);
if (!args.append) {
//var usersstr = (result.response.totalusers == '1')?M.util.get_string('ajaxoneuserfound', 'enrol'):M.util.get_string('ajaxxusersfound','enrol', result.response.totalusers);
var cohortsstr = M.util.get_string('foundxcohorts', 'enrol', result.response.totalcohorts);
var content = create('<div class="'+CSS.SEARCHRESULTS+'"></div>')
.append(create('<div class="'+CSS.TOTALCOHORTS+'">'+cohortsstr+'</div>'))
.append(cohorts);
if (result.response.totalcohorts > (this.get(UEP.PAGE)+1)*this.get(UEP.PERPAGE)) {
var fetchmore = create('<div class="'+CSS.MORERESULTS+'"><a href="#">'+M.util.get_string('ajaxnext25', 'enrol')+'</a></div>');
fetchmore.on('click', this.search, this, true);
content.append(fetchmore)
}
this.setContent(content);
Y.delegate("click", this.enrolUser, cohorts, '.'+CSS.COHORT+' .'+CSS.ENROL, this, args);
} else {
if (result.response.totalcohorts <= (this.get(UEP.PAGE)+1)*this.get(UEP.PERPAGE)) {
this.get(UEP.BASE).one('.'+CSS.MORERESULTS).remove();
}
}
},
enrolUser : function(e, args) {
var entityname = this.currentEntity();
var entity = e.currentTarget.ancestor('.'+entityname);
var params = [];
params['id'] = this.get(UEP.COURSEID);
if (entityname === CSS.USER) {
params['userid'] = entity.getAttribute("rel");
} else {
params['cohortid'] = entity.getAttribute("rel");
}
params['enrolid'] = args.enrolid;
params['sesskey'] = M.cfg.sesskey;
params['action'] = 'enrol';
params['role'] = this.get(UEP.BASE).one('.'+CSS.ENROLMENTOPTION+'.'+CSS.ROLE+' select').get('value');
params['startdate'] = this.get(UEP.BASE).one('.'+CSS.ENROLMENTOPTION+'.'+CSS.STARTDATE+' select').get('value');
params['duration'] = this.get(UEP.BASE).one('.'+CSS.ENROLMENTOPTION+'.'+CSS.DURATION+' select').get('value');
if (this.get(UEP.DISABLEGRADEHISTORY) != true) {
params['recovergrades'] = this.get(UEP.BASE).one('#'+CSS.RECOVERGRADES).get('checked')?1:0;
} else {
params['recovergrades'] = 0;
}
Y.io(M.cfg.wwwroot+this.get(UEP.AJAXURL), {
method:'POST',
data:build_querystring(params),
on: {
start : this.displayLoading,
complete : function(tid, outcome, args) {
try {
var result = Y.JSON.parse(outcome.responseText);
if (result.error) {
return new M.core.ajaxException(result);
} else {
args.entityNode.addClass(CSS.ENROLLED);
args.entityNode.one('.'+CSS.ENROL).remove();
this.set(UEP.REQUIREREFRESH, true);
var countenrol = this.get(UEP.ENROLCOUNT)+1;
this.set(UEP.ENROLCOUNT, countenrol);
}
} catch (e) {
new M.core.exception(e);
}
},
end : this.removeLoading
},
context:this,
arguments:{
params : params,
entityNode : entity
}
});
},
setContent: function(content) {
this.get(UEP.BASE).one('.'+CSS.CONTENT+' .'+CSS.AJAXCONTENT).setContent(content);
}
}, {
NAME : UEP.NAME,
ATTRS : {
url : {
validator : Y.Lang.isString
},
ajaxurl : {
validator : Y.Lang.isString
},
base : {
setter : function(node) {
var n = Y.one(node);
if (!n) {
Y.fail(UEP.NAME+': invalid base node set');
}
return n;
}
},
users : {
validator : Y.Lang.isArray,
value : null
},
courseid : {
value : null
},
params : {
validator : Y.Lang.isArray,
value : []
},
instances : {
validator : Y.Lang.isArray,
setter : function(instances) {
var i,ia = [], count=0;
for (i in instances) {
ia.push(instances[i]);
count++;
}
this.set(UEP.MULTIPLE, (count>1));
}
},
multiple : {
validator : Y.Lang.isBool,
value : false
},
page : {
validator : Y.Lang.isNumber,
value : 0
},
userCount : {
value : 0,
validator : Y.Lang.isNumber
},
requiresRefresh : {
value : false,
validator : Y.Lang.isBool
},
search : {
setter : function(node) {
var n = Y.one(node);
if (!n) {
Y.fail(UEP.NAME+': invalid search node set');
}
return n;
}
},
lastPreSearchValue : {
value : '',
validator : Y.Lang.isString
},
strings : {
value : {},
validator : Y.Lang.isObject
},
defaultRole : {
value : 0
},
defaultStartDate : {
value : 4,
validator : Y.Lang.isNumber
},
defaultDuration : {
value : ''
},
assignableRoles : {
value : []
},
optionsStartDate : {
value : []
},
disableGradeHistory : {
value : 0
},
recoverGradesDefault : {
value : ''
},
enrolCount : {
value : 0,
validator : Y.Lang.isNumber
},
perPage : {
value: 25,
Validator: Y.Lang.isNumber
},
cohortCount : {
value : 0,
validator : Y.Lang.isNumber
},
cohortsAvailable : {
value : null
}
}
});
Y.augment(USERENROLLER, Y.EventTarget);
M.enrol_manual = M.enrol_manual || {};
M.enrol_manual.quickenrolment = {
init : function(cfg) {
new USERENROLLER(cfg);
}
}
}, '@VERSION@', {requires:['base','node', 'overlay', 'io-base', 'test', 'json-parse', 'event-delegate', 'dd-plugin', 'event-key', 'moodle-core-notification']});

View File

@ -95,16 +95,12 @@ class behat_enrol extends behat_base {
$this->execute("behat_forms::press_button", get_string('enrolusers', 'enrol'));
if ($this->running_javascript()) {
$this->execute('behat_forms::i_set_the_field_to', array(get_string('assignroles', 'role'), $rolename));
$this->execute('behat_forms::i_set_the_field_to', array(get_string('assignroles', 'enrol_manual'), $rolename));
// We have a div here, not a tr.
$userliteral = behat_context_helper::escape($userfullname);
$userrowxpath = "//div[contains(concat(' ',normalize-space(@class),' '),' user ')][contains(., $userliteral)]";
$this->execute('behat_forms::i_set_the_field_to', array(get_string('selectusers', 'enrol_manual'), $userfullname));
$this->execute('behat_general::i_click_on_in_the',
array(get_string('enrol', 'enrol'), "button", $userrowxpath, "xpath_element")
);
$this->execute("behat_forms::press_button", get_string('finishenrollingusers', 'enrol'));
$this->execute("behat_forms::press_button", get_string('enroluserscohorts', 'enrol_manual'));
} else {
$this->execute('behat_forms::i_set_the_field_to', array(get_string('assignrole', 'role'), $rolename));

View File

@ -1,47 +0,0 @@
.enrolpanel {
display: none;
position: absolute;
background-color: #666;
padding: 0 5px;
min-width: 200px;
}
.enrolpanel.visible {
display: block;
}
.enrolpanel .container {
position: relative;
background-color: #fff;
border: 1px solid #999;
top: -5px;
}
.enrolpanel .container .header {
border-bottom: 1px solid #999;
}
.enrolpanel .container .header h2 {
font-size: 90%;
text-align: center;
margin: 5px;
}
.enrolpanel .container .header .close {
width: 25px;
height: 15px;
position: absolute;
top: 5px;
right: 1em;
cursor: pointer;
background: url("sprite.png") no-repeat scroll 0 0 transparent;
}
.enrolpanel .container .content input {
margin: 5px;
font-size: 10px;
}
.enrolpanel.roleassign.visible .container {
width: auto;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 930 B

View File

@ -0,0 +1 @@
define(["core/ajax","jquery"],function(a,b){return{processResults:function(a,c){var d=[],e=0,f=String(b(a).data("exclude")).split(",");for(e=0;e<c.cohorts.length;e++)f.indexOf(String(c.cohorts[e].id))===-1&&d.push({value:c.cohorts[e].id,label:c.cohorts[e].name});return d},transport:function(c,d,e,f){var g=b(c),h=null;"undefined"==typeof d&&(d="");var i=g.data("contextid"),j={query:d,includes:"parents",limitfrom:0,limitnum:100,context:{contextid:i}},k=[{methodname:"core_cohort_search_cohorts",args:j}];h=a.call(k),b.when.apply(b.when,h).done(function(a){e(a)}).fail(f)}}});

View File

@ -0,0 +1,76 @@
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Course selector adaptor for auto-complete form element.
*
* @module core/form-cohort-selector
* @class form-cohort-selector
* @package core
* @copyright 2016 Damyon Wiese <damyon@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
* @since 3.1
*/
define(['core/ajax', 'jquery'], function(ajax, $) {
return /** @alias module:core/form-cohort-selector */ {
// Public variables and functions.
processResults: function(selector, data) {
// Mangle the results into an array of objects.
var results = [];
var i = 0;
var excludelist = String($(selector).data('exclude')).split(',');
for (i = 0; i < data.cohorts.length; i++) {
if (excludelist.indexOf(String(data.cohorts[i].id)) === -1) {
results.push({value: data.cohorts[i].id, label: data.cohorts[i].name});
}
}
return results;
},
transport: function(selector, query, success, failure) {
var el = $(selector);
// Parse some data-attributes from the form element.
// Build the query.
var promises = null;
if (typeof query === "undefined") {
query = '';
}
var contextid = el.data('contextid');
var searchargs = {
query: query,
includes: 'parents',
limitfrom: 0,
limitnum: 100,
context: { contextid: contextid }
};
var calls = [{
methodname: 'core_cohort_search_cohorts', args: searchargs
}];
// Go go go!
promises = ajax.call(calls);
$.when.apply($.when, promises).done(function(data) {
success(data);
}).fail(failure);
}
};
});

View File

@ -165,6 +165,15 @@ $functions = array(
'type' => 'read',
'capabilities' => 'moodle/cohort:view'
),
'core_cohort_search_cohorts' => array(
'classname' => 'core_cohort_external',
'methodname' => 'search_cohorts',
'classpath' => 'cohort/externallib.php',
'description' => 'Search for cohorts.',
'type' => 'read',
'ajax' => true,
'capabilities' => 'moodle/cohort:view'
),
'core_cohort_get_cohorts' => array(
'classname' => 'core_cohort_external',
'methodname' => 'get_cohorts',
@ -454,6 +463,14 @@ $functions = array(
and have that capability',
'type' => 'read',
),
'core_enrol_get_potential_users' => array(
'classname' => 'core_enrol_external',
'methodname' => 'get_potential_users',
'classpath' => 'enrol/externallib.php',
'description' => 'Get the list of potential users to enrol',
'type' => 'read',
'capabilities' => 'moodle/course:enrolreview'
),
'core_enrol_get_users_courses' => array(
'classname' => 'core_enrol_external',
'methodname' => 'get_users_courses',

View File

@ -508,6 +508,37 @@ class external_api {
throw new invalid_parameter_exception('Missing parameters, please provide either context level with instance id or contextid');
}
}
/**
* Returns a prepared structure to use a context parameters.
* @return external_single_structure
*/
protected static function get_context_parameters() {
$id = new external_value(
PARAM_INT,
'Context ID. Either use this value, or level and instanceid.',
VALUE_DEFAULT,
0
);
$level = new external_value(
PARAM_ALPHA,
'Context level. To be used with instanceid.',
VALUE_DEFAULT,
''
);
$instanceid = new external_value(
PARAM_INT,
'Context instance ID. To be used with level',
VALUE_DEFAULT,
0
);
return new external_single_structure(array(
'contextid' => $id,
'contextlevel' => $level,
'instanceid' => $instanceid,
));
}
}
/**

150
lib/form/cohort.php Normal file
View File

@ -0,0 +1,150 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* Course selector field.
*
* Allows auto-complete ajax searching for cohort.
*
* @package core_form
* @copyright 2015 Damyon Wiese <damyon@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
global $CFG;
require_once($CFG->libdir . '/form/autocomplete.php');
/**
* Form field type for choosing a cohort.
*
* Allows auto-complete ajax searching for cohort.
*
* @package core_form
* @copyright 2016 Damyon Wiese <damyon@moodle.com>
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class MoodleQuickForm_cohort extends MoodleQuickForm_autocomplete {
/**
* @var array $exclude Exclude a list of cohorts from the list (e.g. the current cohort).
*/
protected $exclude = array();
/**
* @var int $contextid The context id to fetch cohorts in.
*/
protected $contextid = 0;
/**
* @var boolean $allowmultiple Allow selecting more than one cohort.
*/
protected $multiple = false;
/**
* @var array $requiredcapabilities Array of extra capabilities to check at the cohort context.
*/
protected $requiredcapabilities = array();
/**
* Constructor
*
* @param string $elementname Element name
* @param mixed $elementlabel Label(s) for an element
* @param array $options Options to control the element's display
* Valid options are:
* 'multiple' - boolean multi select
* 'exclude' - array or int, list of course ids to never show
* 'requiredcapabilities' - array of capabilities. Uses ANY to combine them.
*/
public function __construct($elementname = null, $elementlabel = null, $options = array()) {
if (isset($options['multiple'])) {
$this->multiple = $options['multiple'];
}
if (isset($options['contextid'])) {
$this->contextid = $options['contextid'];
} else {
$this->contextid = context_system::instance()->id;
}
if (isset($options['exclude'])) {
$this->exclude = $options['exclude'];
if (!is_array($this->exclude)) {
$this->exclude = array($this->exclude);
}
}
if (isset($options['requiredcapabilities'])) {
$this->requiredcapabilities = $options['requiredcapabilities'];
}
$validattributes = array(
'ajax' => 'core/form-cohort-selector',
'data-exclude' => implode(',', $this->exclude),
'data-contextid' => (int)$this->contextid
);
if ($this->multiple) {
$validattributes['multiple'] = 'multiple';
}
if (isset($options['noselectionstring'])) {
$validattributes['noselectionstring'] = $options['noselectionstring'];
}
if (isset($options['placeholder'])) {
$validattributes['placeholder'] = $options['placeholder'];
}
parent::__construct($elementname, $elementlabel, array(), $validattributes);
}
/**
* Set the value of this element. If values can be added or are unknown, we will
* make sure they exist in the options array.
* @param string|array $value The value to set.
* @return boolean
*/
public function setValue($value) {
global $DB;
$values = (array) $value;
$cohortstofetch = array();
foreach ($values as $onevalue) {
if ((!$this->optionExists($onevalue)) &&
($onevalue !== '_qf__force_multiselect_submission')) {
array_push($cohortstofetch, $onevalue);
}
}
if (empty($cohortstofetch)) {
$this->setSelected($values);
return true;
}
list($whereclause, $params) = $DB->get_in_or_equal($cohortstofetch, SQL_PARAMS_NAMED, 'id');
$list = $DB->get_records_select('cohort', 'id ' . $whereclause, $params, 'name');
$currentcontext = context_helper::instance_by_id($this->contextid);
foreach ($list as $cohort) {
// Make sure we can see the cohort.
if (!cohort_can_view_cohort($cohort, $currentcontext)) {
continue;
}
$label = format_string($cohort->name, true, $currentcontext);
$this->addOption($label, $cohort->id);
}
$this->setSelected($values);
return true;
}
}

View File

@ -3111,6 +3111,7 @@ MoodleQuickForm::registerElementType('autocomplete', "$CFG->libdir/form/autocomp
MoodleQuickForm::registerElementType('button', "$CFG->libdir/form/button.php", 'MoodleQuickForm_button');
MoodleQuickForm::registerElementType('cancel', "$CFG->libdir/form/cancel.php", 'MoodleQuickForm_cancel');
MoodleQuickForm::registerElementType('course', "$CFG->libdir/form/course.php", 'MoodleQuickForm_course');
MoodleQuickForm::registerElementType('cohort', "$CFG->libdir/form/cohort.php", 'MoodleQuickForm_cohort');
MoodleQuickForm::registerElementType('searchableselector', "$CFG->libdir/form/searchableselector.php", 'MoodleQuickForm_searchableselector');
MoodleQuickForm::registerElementType('checkbox', "$CFG->libdir/form/checkbox.php", 'MoodleQuickForm_checkbox');
MoodleQuickForm::registerElementType('date_selector', "$CFG->libdir/form/dateselector.php", 'MoodleQuickForm_date_selector');

View File

@ -40,7 +40,7 @@
<div class="form-autocomplete-selection {{#multiple}}form-autocomplete-multiple{{/multiple}}" id="{{selectionId}}" role="list" aria-atomic="true" {{#multiple}}tabindex="0" aria-multiselectable="true"{{/multiple}}>
<span class="accesshide">{{#str}}selecteditems, form{{/str}}</span>
{{#items}}
<span role="listitem" data-value="{{value}}" aria-selected="true" class="label label-info">
<span role="listitem" data-value="{{value}}" aria-selected="true" class="label label-info" style="font-size: larger; margin-bottom: 0.5rem;">
{{#multiple}}<span aria-hidden="true">× </span>{{/multiple}}{{{label}}}
</span>
{{/items}}

View File

@ -40,7 +40,7 @@
<div class="form-autocomplete-selection {{#multiple}}form-autocomplete-multiple{{/multiple}}" id="{{selectionId}}" role="list" aria-atomic="true" {{#multiple}}tabindex="0" aria-multiselectable="true"{{/multiple}}>
<span class="accesshide">{{#str}}selecteditems, form{{/str}}</span>
{{#items}}
<span role="listitem" data-value="{{value}}" aria-selected="true" class="tag tag-info">
<span role="listitem" data-value="{{value}}" aria-selected="true" class="tag tag-info m-b-1 m-r-1" style="font-size: larger">
{{#multiple}}<span aria-hidden="true">× </span>{{/multiple}}{{{label}}}
</span>
{{/items}}

View File

@ -29,7 +29,7 @@
defined('MOODLE_INTERNAL') || die();
$version = 2017071100.00; // YYYYMMDD = weekly release date of this DEV branch.
$version = 2017071101.00; // YYYYMMDD = weekly release date of this DEV branch.
// RR = release increments - 00 in DEV branches.
// .XX = incremental changes.