From 6db3eee02b0f6472d32dcffb7938ca34201cd034 Mon Sep 17 00:00:00 2001 From: Sam Hemelryk Date: Fri, 16 Jul 2010 08:30:06 +0000 Subject: [PATCH] enrol MDL-23117 Implements the other users UI for users with roles in a course but not enrolled. At the same time this commit introduces a standardised dialogue YUI module to enrol and implements it across the board. This commit also addresses MDL-23292 and MDL-23319. --- calendar/yui/eventmanager/eventmanager.js | 6 +- enrol/ajax.php | 92 ++-- enrol/locallib.php | 228 +++++++++- enrol/otherusers.php | 95 +++-- enrol/renderer.php | 170 +++++++- enrol/users.php | 3 +- .../yui/enrolmentmanager/enrolmentmanager.js | 25 +- .../assets/skins/sam/notification.css | 22 + .../notification/assets/skins/sam/sprite.png | Bin 0 -> 1002 bytes enrol/yui/notification/notification.js | 374 +++++++++++++++++ .../assets/skins/sam/otherusersmanager.css | 64 +++ .../assets/skins/sam/sprite.png | Bin 0 -> 1002 bytes .../otherusersmanager/otherusersmanager.js | 392 ++++++++++++++++++ .../assets/skins/sam/quickcohortenrolment.css | 4 +- .../quickcohortenrolment.js | 49 ++- enrol/yui/rolemanager/rolemanager.js | 42 +- lang/en/enrol.php | 15 + lang/en/role.php | 5 + lib/outputrenderers.php | 4 + lib/setuplib.php | 1 + theme/standard/style/core.css | 4 +- 21 files changed, 1439 insertions(+), 156 deletions(-) create mode 100644 enrol/yui/notification/assets/skins/sam/notification.css create mode 100644 enrol/yui/notification/assets/skins/sam/sprite.png create mode 100644 enrol/yui/notification/notification.js create mode 100644 enrol/yui/otherusersmanager/assets/skins/sam/otherusersmanager.css create mode 100644 enrol/yui/otherusersmanager/assets/skins/sam/sprite.png create mode 100644 enrol/yui/otherusersmanager/otherusersmanager.js diff --git a/calendar/yui/eventmanager/eventmanager.js b/calendar/yui/eventmanager/eventmanager.js index 96fbebe3d0d65..58315d7f289b8 100644 --- a/calendar/yui/eventmanager/eventmanager.js +++ b/calendar/yui/eventmanager/eventmanager.js @@ -14,7 +14,11 @@ YUI.add('moodle-calendar-eventmanager', function(Y) { } Y.extend(EVENT, Y.Base, { initializer : function(config){ - var id = this.get(EVENTID), node = this.get(EVENTNODE), td = node.ancestor('td'), constraint = td.ancestor('div'), panel; + var id = this.get(EVENTID), node = this.get(EVENTNODE); + if (!node) { + return false; + } + var td = node.ancestor('td'), constraint = td.ancestor('div'), panel; this.publish('showevent'); this.publish('hideevent'); panel = new Y.Overlay({ diff --git a/enrol/ajax.php b/enrol/ajax.php index e65b0dca1eff6..2841944a76a06 100644 --- a/enrol/ajax.php +++ b/enrol/ajax.php @@ -17,6 +17,9 @@ /** * This file processes AJAX enrolment actions and returns JSON * + * The general idea behind this file is that any errors should throw exceptions + * which will be returned and acted upon by the calling AJAX script. + * * @package moodlecore * @copyright 2010 Sam Hemelryk * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later @@ -40,91 +43,76 @@ $context = get_context_instance(CONTEXT_COURSE, $course->id, MUST_EXIST); if ($course->id == SITEID) { - redirect(new moodle_url('/')); + throw new moodle_exception('invalidcourse'); } require_login($course); require_capability('moodle/course:enrolreview', $context); +require_sesskey(); $manager = new course_enrolment_manager($course); $outcome = new stdClass; -$outcome->success = false; +$outcome->success = true; $outcome->response = new stdClass; $outcome->error = ''; -if (!confirm_sesskey()) { - $outcome->error = 'invalidsesskey'; - echo json_encode($outcome); - die(); -} - switch ($action) { case 'unenrol': $ue = $DB->get_record('user_enrolments', array('id'=>required_param('ue', PARAM_INT)), '*', MUST_EXIST); list ($instance, $plugin) = $manager->get_user_enrolment_components($ue); - if ($instance && $plugin && $plugin->allow_unenrol($instance) && has_capability("enrol/$instance->enrol:unenrol", $manager->get_context()) && $manager->unenrol_user($ue)) { - $outcome->success = true; - } else { - $outcome->error = 'unabletounenrol'; + if (!$instance || !$plugin || !$plugin->allow_unenrol($instance) || !has_capability("enrol/$instance->enrol:unenrol", $manager->get_context()) || !$manager->unenrol_user($ue)) { + throw new enrol_ajax_exception('unenrolnotpermitted'); } break; case 'unassign': $role = required_param('role', PARAM_INT); $user = required_param('user', PARAM_INT); - if (has_capability('moodle/role:assign', $manager->get_context()) && $manager->unassign_role_from_user($user, $role)) { - $outcome->success = true; - } else { - $outcome->error = 'unabletounassign'; + if (!has_capability('moodle/role:assign', $manager->get_context()) || !$manager->unassign_role_from_user($user, $role)) { + throw new enrol_ajax_exception('unassignnotpermitted'); } break; case 'assign': $user = $DB->get_record('user', array('id'=>required_param('user', PARAM_INT)), '*', MUST_EXIST); $roleid = required_param('roleid', PARAM_INT); - - if (!is_enrolled($context, $user)) { - $outcome->error = 'mustbeenrolled'; - break; // no roles without enrolments here in this script - } if (!array_key_exists($roleid, $manager->get_assignable_roles())) { - $outcome->error = 'invalidrole'; - break; + throw new enrol_ajax_exception('invalidrole'); } - - if (has_capability('moodle/role:assign', $manager->get_context()) && $manager->assign_role_to_user($roleid, $user->id)) { - $outcome->success = true; - $outcome->response->roleid = $roleid; - } else { - $outcome->error = 'unabletoassign'; + if (!has_capability('moodle/role:assign', $manager->get_context()) || !$manager->assign_role_to_user($roleid, $user->id)) { + throw new enrol_ajax_exception('assignnotpermitted'); } + $outcome->response->roleid = $roleid; break; case 'getassignable': - $outcome->success = true; $outcome->response = $manager->get_assignable_roles(); break; case 'getcohorts': require_capability('moodle/course:enrolconfig', $context); - $outcome->success = true; $outcome->response = $manager->get_cohorts(); break; case 'enrolcohort': require_capability('moodle/course:enrolconfig', $context); $roleid = required_param('roleid', PARAM_INT); $cohortid = required_param('cohortid', PARAM_INT); - $outcome->success = $manager->enrol_cohort($cohortid, $roleid); + if (!$manager->enrol_cohort($cohortid, $roleid)) { + throw new enrol_ajax_exception('errorenrolcohort'); + } break; case 'enrolcohortusers': require_capability('moodle/course:enrolconfig', $context); $roleid = required_param('roleid', PARAM_INT); $cohortid = required_param('cohortid', PARAM_INT); $result = $manager->enrol_cohort_users($cohortid, $roleid); - if ($result !== false) { - $outcome->success = true; - $outcome->response->users = $result; - $outcome->response->message = get_string('enrollednewusers', 'enrol', $result); + if ($result === false) { + throw new enrol_ajax_exception('errorenrolcohortusers'); } + $outcome->success = true; + $outcome->response->users = $result; + $outcome->response->title = get_string('success'); + $outcome->response->message = get_string('enrollednewusers', 'enrol', $result); + $outcome->response->yesLabel = get_string('ok'); break; case 'searchusers': $enrolid = required_param('enrolid', PARAM_INT); @@ -138,6 +126,19 @@ $outcome->success = true; break; + case 'searchotherusers': + $search = optional_param('search', '', PARAM_CLEAN); + $page = optional_param('page', 0, PARAM_INT); + $outcome->response = $manager->search_other_users($search, false, $page); + foreach ($outcome->response['users'] as &$user) { + $user->userId = $user->id; + $user->picture = $OUTPUT->user_picture($user); + $user->fullname = fullname($user); + unset($user->id); + } + $outcome->success = true; + break; + case 'enrol': $enrolid = required_param('enrolid', PARAM_INT); $userid = required_param('userid', PARAM_INT); @@ -170,29 +171,22 @@ $instances = $manager->get_enrolment_instances(); $plugins = $manager->get_enrolment_plugins(); if (!array_key_exists($enrolid, $instances)) { - $outcome->error = 'invalidinstance'; - break; + throw new enrol_ajax_exception('invalidenrolinstance'); } $instance = $instances[$enrolid]; $plugin = $plugins[$instance->enrol]; if ($plugin->allow_enrol($instance) && has_capability('enrol/'.$plugin->get_name().':enrol', $context)) { - try { - $plugin->enrol_user($instance, $user->id, $roleid, $timestart, $timeend); - } catch (Exception $e) { - $outcome->error = 'unabletoenrol'; - break; - } + $plugin->enrol_user($instance, $user->id, $roleid, $timestart, $timeend); } else { - $outcome->error = 'unablenotallowed'; - break; + throw new enrol_ajax_exception('enrolnotpermitted'); } $outcome->success = true; break; default: - $outcome->error = 'unknownaction'; - break; + throw new enrol_ajax_exception('unknowajaxaction'); } +header('Content-type: application/json'); echo json_encode($outcome); -die(); \ No newline at end of file +die(); diff --git a/enrol/locallib.php b/enrol/locallib.php index a4427dd9ad91b..f0ba77822b447 100644 --- a/enrol/locallib.php +++ b/enrol/locallib.php @@ -64,6 +64,20 @@ class course_enrolment_manager { */ protected $users = array(); + /** + * An array of users who have roles within this course but who have not + * been enrolled in the course + * @var array + */ + protected $otherusers = array(); + + /** + * The total number of users who hold a role within the course but who + * arn't enrolled. + * @var int + */ + protected $totalotherusers = null; + /**#@+ * These variables are used to cache the information this class uses * please never use these directly instead use thier get_ counterparts. @@ -113,6 +127,38 @@ public function get_total_users() { return $this->totalusers; } + /** + * Returns the total number of enrolled users in the course. + * + * If a filter was specificed this will be the total number of users enrolled + * in this course by means of that instance. + * + * @global moodle_database $DB + * @return int + */ + public function get_total_other_users() { + global $DB; + if ($this->totalotherusers === null) { + list($ctxcondition, $params) = $DB->get_in_or_equal(get_parent_contexts($this->context, true), SQL_PARAMS_NAMED, 'ctx00'); + $params['courseid'] = $this->course->id; + $sql = "SELECT COUNT(DISTINCT u.id) + FROM {role_assignments} ra + JOIN {user} u ON u.id = ra.userid + JOIN {context} ctx ON ra.contextid = ctx.id + LEFT JOIN ( + SELECT ue.id, ue.userid + FROM {user_enrolments} ue + LEFT JOIN {enrol} e ON e.id=ue.enrolid + WHERE e.courseid = :courseid + ) ue ON ue.userid=u.id + WHERE + ctx.id $ctxcondition AND + ue.id IS NULL"; + $this->totalotherusers = (int)$DB->count_records_sql($sql, $params); + } + return $this->totalotherusers; + } + /** * Gets all of the users enrolled in this course. * @@ -153,6 +199,49 @@ public function get_users($sort, $direction='ASC', $page=0, $perpage=25) { return $this->users[$key]; } + /** + * Gets and array of other users. + * + * Other users are users who have been assigned roles or inherited roles + * within this course but who have not been enrolled in the course + * + * @global moodle_database $DB + * @param string $sort + * @param string $direction + * @param int $page + * @param int $perpage + * @return array + */ + public function get_other_users($sort, $direction='ASC', $page=0, $perpage=25) { + global $DB; + if ($direction !== 'ASC') { + $direction = 'DESC'; + } + $key = md5("$sort-$direction-$page-$perpage"); + if (!array_key_exists($key, $this->otherusers)) { + list($ctxcondition, $params) = $DB->get_in_or_equal(get_parent_contexts($this->context, true), SQL_PARAMS_NAMED, 'ctx00'); + $params['courseid'] = $this->course->id; + $params['cid'] = $this->course->id; + $sql = "SELECT ra.id as raid, ra.contextid, ra.component, ctx.contextlevel, ra.roleid, u.*, ue.lastseen + FROM {role_assignments} ra + JOIN {user} u ON u.id = ra.userid + JOIN {context} ctx ON ra.contextid = ctx.id + LEFT JOIN ( + SELECT ue.id, ue.userid, ul.timeaccess AS lastseen + FROM {user_enrolments} ue + LEFT JOIN {enrol} e ON e.id=ue.enrolid + LEFT JOIN {user_lastaccess} ul ON (ul.courseid = e.courseid AND ul.userid = ue.userid) + WHERE e.courseid = :courseid + ) ue ON ue.userid=u.id + WHERE + ctx.id $ctxcondition AND + ue.id IS NULL + ORDER BY u.$sort $direction, ctx.depth DESC"; + $this->otherusers[$key] = $DB->get_records_sql($sql, $params, $page*$perpage, $perpage); + } + return $this->otherusers[$key]; + } + /** * Gets an array of the users that can be enrolled in this course. * @@ -204,6 +293,58 @@ public function get_potential_users($enrolid, $search='', $searchanywhere=false, return array('totalusers'=>$totalusers, 'users'=>$availableusers); } + /** + * Searches other users and returns paginated results + * + * @global moodle_database $DB + * @param string $search + * @param bool $searchanywhere + * @param int $page Starting at 0 + * @param int $perpage + * @return array + */ + public function search_other_users($search='', $searchanywhere=false, $page=0, $perpage=25) { + global $DB; + + // Add some additional sensible conditions + $tests = array("u.username <> 'guest'", 'u.deleted = 0', 'u.confirmed = 1'); + $params = array(); + if (!empty($search)) { + $conditions = array('u.firstname','u.lastname'); + $ilike = ' ' . $DB->sql_ilike(); + if ($searchanywhere) { + $searchparam = '%' . $search . '%'; + } else { + $searchparam = $search . '%'; + } + $i = 0; + foreach ($conditions as &$condition) { + $condition .= "$ilike :con{$i}00"; + $params["con{$i}00"] = $searchparam; + $i++; + } + $tests[] = '(' . implode(' OR ', $conditions) . ')'; + } + $wherecondition = implode(' AND ', $tests); + + + $fields = 'SELECT u.id, u.firstname, u.lastname, u.username, u.email, u.lastaccess, u.picture, u.imagealt, '.user_picture::fields('u');; + $countfields = 'SELECT COUNT(u.id)'; + $sql = " FROM {user} u + WHERE $wherecondition + AND u.id NOT IN ( + SELECT u.id + FROM {role_assignments} r, {user} u + WHERE r.contextid = :contextid + AND u.id = r.userid)"; + $order = ' ORDER BY lastname ASC, firstname ASC'; + + $params['contextid'] = $this->context->id; + $totalusers = $DB->count_records_sql($countfields . $sql, $params); + $availableusers = $DB->get_records_sql($fields . $sql . $order, $params, $page*$perpage, $perpage); + return array('totalusers'=>$totalusers, 'users'=>$availableusers); + } + /** * Gets an array containing some SQL to user for when selecting, params for * that SQL, and the filter that was used in constructing the sql. @@ -373,6 +514,9 @@ public function unassign_role_from_user($userid, $roleid) { try { role_unassign($roleid, $user->id, $this->context->id, '', NULL); } catch (Exception $e) { + if (is_defined('AJAX_SCRIPT')) { + throw $e; + } return false; } return true; @@ -388,6 +532,9 @@ public function unassign_role_from_user($userid, $roleid) { public function assign_role_to_user($roleid, $userid) { require_capability('moodle/role:assign', $this->context); if (!array_key_exists($roleid, $this->get_assignable_roles())) { + if (is_defined('AJAX_SCRIPT')) { + throw new moodle_; + } return false; } return role_assign($roleid, $userid, $this->context->id, '', NULL); @@ -560,6 +707,69 @@ public function get_context() { return $this->context; } + /** + * Gets an array of other users in this course ready for display. + * + * Other users are users who have been assigned or inherited roles within this + * course but have not been enrolled. + * + * @param core_enrol_renderer $renderer + * @param moodle_url $pageurl + * @param string $sort + * @param string $direction ASC | DESC + * @param int $page Starting from 0 + * @param int $perpage + * @return array + */ + public function get_other_users_for_display(core_enrol_renderer $renderer, moodle_url $pageurl, $sort, $direction, $page, $perpage) { + + $userroles = $this->get_other_users($sort, $direction, $page, $perpage); + $roles = $this->get_all_roles(); + + $courseid = $this->get_course()->id; + $context = $this->get_context(); + + $users = array(); + foreach ($userroles as $userrole) { + if (!array_key_exists($userrole->id, $users)) { + $users[$userrole->id] = array( + 'userid' => $userrole->id, + 'courseid' => $courseid, + 'picture' => new user_picture($userrole), + 'firstname' => fullname($userrole, true), + 'email' => $userrole->email, + 'roles' => array() + ); + } + $a = new stdClass; + $a->role = $roles[$userrole->roleid]->localname; + $changeable = ($userrole->component == ''); + if ($userrole->contextid == $this->context->id) { + $roletext = get_string('rolefromthiscourse', 'enrol', $a); + } else { + $changeable = false; + switch ($userrole->contextlevel) { + case CONTEXT_COURSE : + // Meta course + $roletext = get_string('rolefrommetacourse', 'enrol', $a); + break; + case CONTEXT_COURSECAT : + $roletext = get_string('rolefromcategory', 'enrol', $a); + break; + case CONTEXT_SYSTEM: + default: + $roletext = get_string('rolefromsystem', 'enrol', $a); + break; + } + } + $users[$userrole->id]['roles'][$userrole->roleid] = array( + 'text' => $roletext, + 'unchangeable' => !$changeable + ); + } + return $users; + } + /** * Gets all the cohorts the user is able to view. * @@ -715,8 +925,8 @@ public function get_users_for_display(core_enrol_renderer $renderer, moodle_url 'enrolments' => array() ); - if ($user->lastseen) { - $details['lastseen'] = format_time($user->lastaccess); + if ($user->lastaccess) { + $details['lastseen'] = format_time($now - $user->lastaccess); } // Roles @@ -757,4 +967,18 @@ public function get_users_for_display(core_enrol_renderer $renderer, moodle_url } return $userdetails; } +} + +class enrol_ajax_exception extends moodle_exception { + /** + * Constructor + * @param string $errorcode The name of the string from error.php to print + * @param string $module name of module + * @param string $link The url where the user will be prompted to continue. If no url is provided the user will be directed to the site index page. + * @param object $a Extra words and phrases that might be required in the error string + * @param string $debuginfo optional debugging information + */ + public function __construct($errorcode, $link = '', $a = NULL, $debuginfo = null) { + parent::__construct($errorcode, 'enrol', $link, $a, $debuginfo); + } } \ No newline at end of file diff --git a/enrol/otherusers.php b/enrol/otherusers.php index 401e12333e969..7f689011fab2b 100644 --- a/enrol/otherusers.php +++ b/enrol/otherusers.php @@ -24,16 +24,13 @@ */ require('../config.php'); +require_once("$CFG->dirroot/enrol/locallib.php"); +require_once("$CFG->dirroot/enrol/renderer.php"); +require_once("$CFG->dirroot/group/lib.php"); $id = required_param('id', PARAM_INT); // course id $action = optional_param('action', '', PARAM_ACTION); -$confirm = optional_param('confirm', 0, PARAM_BOOL); - -$ifilter = optional_param('ifilter', 0, PARAM_INT); // only one instance -$page = optional_param('page', 0, PARAM_INT); -$perpage = optional_param('perpage', 20, PARAM_INT); -$sort = optional_param('sort', 'lastname', PARAM_ALPHA); -$dir = optional_param('dir', 'ASC', PARAM_ALPHA); +$filter = optional_param('ifilter', 0, PARAM_INT); $course = $DB->get_record('course', array('id'=>$id), '*', MUST_EXIST); $context = get_context_instance(CONTEXT_COURSE, $course->id, MUST_EXIST); @@ -45,47 +42,59 @@ redirect("$CFG->wwwroot/"); } -$instances = enrol_get_instances($course->id, true); -$plugins = enrol_get_plugins(true); -$inames = array(); -foreach ($instances as $k=>$i) { - if (!isset($plugins[$i->enrol])) { - // weird, some broken stuff in plugin - unset($instances[$k]); - continue; - } - $inames[$k] = $plugins[$i->enrol]->get_instance_name($i); -} - -// validate paging params -if ($ifilter != 0 and !isset($instances[$ifilter])) { - $ifilter = 0; -} -if ($perpage < 3) { - $perpage = 3; -} -if ($page < 0) { - $page = 0; -} -if (!in_array($dir, array('ASC', 'DESC'))) { - $dir = 'ASC'; -} -if (!in_array($sort, array('firstname', 'lastname', 'email', 'lastseen'))) { - $dir = 'lastname'; -} - -$PAGE->set_url('/enrol/notenrolled.php', array('id'=>$course->id, 'page'=>$page, 'sort'=>$sort, 'dir'=>$dir, 'perpage'=>$perpage, 'ifilter'=>$ifilter)); +$PAGE->set_url('/enrol/otherusers.php', array('id'=>$course->id)); $PAGE->set_pagelayout('admin'); -//lalala- nav hack -navigation_node::override_active_url(new moodle_url('/enrol/otherusers.php', array('id'=>$course->id))); +$manager = new course_enrolment_manager($course, $filter); +$table = new course_enrolment_other_users_table($manager, $PAGE->url); +$pageurl = new moodle_url($PAGE->url, $manager->get_url_params()+$table->get_url_params()); -echo $OUTPUT->header(); +/*** + * Actions will go here + */ -//TODO: MDL-22854 add some new role related UI for users that are not enrolled but still got a role somehow in this course context +/*$fields = array( + 'userdetails' => array ( + 'picture' => false, + 'firstname' => get_string('firstname'), + 'lastname' => get_string('lastname'), + 'email' => get_string('email') + ), + 'lastseen' => get_string('lastaccess'), + 'role' => array( + 'roles' => get_string('roles', 'role'), + 'context' => get_string('context') + ) +);*/ +$fields = array( + 'userdetails' => array ( + 'picture' => false, + 'firstname' => get_string('firstname'), + 'lastname' => get_string('lastname'), + 'email' => get_string('email') + ), + 'lastseen' => get_string('lastaccess'), + 'role' => get_string('roles', 'role') +); +$table->set_fields($fields); + +//$users = $manager->get_other_users($table->sort, $table->sortdirection, $table->page, $table->perpage); + +$renderer = $PAGE->get_renderer('core_enrol'); +$canassign = has_capability('moodle/role:assign', $manager->get_context()); +$users = $manager->get_other_users_for_display($renderer, $pageurl, $table->sort, $table->sortdirection, $table->page, $table->perpage); +$assignableroles = $manager->get_assignable_roles(); +foreach ($users as $userid=>&$user) { + $user['picture'] = $OUTPUT->render($user['picture']); + $user['role'] = $renderer->user_roles_and_actions($userid, $user['roles'], $assignableroles, $canassign, $pageurl); +} -notify('This page is not implemented yet, sorry. See MDL-21782 in our tracker for more information.'); +$table->set_total_users($manager->get_total_other_users()); +$table->set_users($users); -echo $OUTPUT->single_button(new moodle_url('/admin/roles/assign.php', array('contextid'=>$context->id)), 'Continue to old Assign roles UI'); +$PAGE->set_title($course->fullname.': '.get_string('totalotherusers', 'enrol', $manager->get_total_other_users())); +$PAGE->set_heading($PAGE->title); +echo $OUTPUT->header(); +echo $renderer->render($table); echo $OUTPUT->footer(); diff --git a/enrol/renderer.php b/enrol/renderer.php index c484a2e417159..4ce600428fcfa 100644 --- a/enrol/renderer.php +++ b/enrol/renderer.php @@ -37,7 +37,7 @@ class core_enrol_renderer extends plugin_renderer_base { * @param course_enrolment_table $table * @return string */ - protected function render_course_enrolment_table(course_enrolment_table $table) { + protected function render_course_enrolment_users_table(course_enrolment_users_table $table) { $content = ''; $enrolmentselector = $table->get_enrolment_selector($this->page); if ($enrolmentselector) { @@ -62,6 +62,29 @@ protected function render_course_enrolment_table(course_enrolment_table $table) return $content; } + /** + * Renders a course enrolment table + * + * @param course_enrolment_table $table + * @return string + */ + protected function render_course_enrolment_other_users_table(course_enrolment_other_users_table $table) { + $content = ''; + $searchbutton = $table->get_user_search_button($this->page); + if ($searchbutton) { + $content .= $this->output->render($searchbutton); + } + $content .= html_writer::tag('div', get_string('otheruserdesc', 'enrol'), array('class'=>'otherusersdesc')); + $content .= $this->output->render($table->get_paging_bar()); + $content .= html_writer::table($table); + $content .= $this->output->render($table->get_paging_bar()); + $searchbutton = $table->get_user_search_button($this->page); + if ($searchbutton) { + $content .= $this->output->render($searchbutton); + } + return $content; + } + /** * Generates HTML to display the users roles and any available actions * @@ -303,7 +326,12 @@ class course_enrolment_table extends html_table implements renderable { */ protected $fields = array(); - protected static $sortablefields = array('firstname', 'lastname', 'email', 'lastaccess'); + /** + * An array of sortable fields + * @static + * @var array + */ + protected static $sortablefields = array('firstname', 'lastname', 'email'); /** * Constructs the table @@ -332,7 +360,6 @@ public function __construct(course_enrolment_manager $manager, moodle_url $pageu } $this->id = html_writer::random_id(); - $this->set_total_users($manager->get_total_users()); } /** @@ -455,8 +482,20 @@ public function set_users(array $users, moodle_page $page=null) { $this->data[] = $row; } if (has_capability('moodle/role:assign', $this->manager->get_context())) { - $arguments = array(array('containerId'=>$this->id, 'userIds'=>array_keys($users), 'courseId'=>$this->manager->get_course()->id)); - $page->requires->yui_module(array('moodle-enrol-rolemanager', 'moodle-enrol-rolemanager-skin'), 'M.enrol.rolemanager.init', $arguments); + $page->requires->strings_for_js(array( + 'assignroles', + 'confirmunassign', + 'confirmunassigntitle', + 'confirmunassignyes', + 'confirmunassignno' + ), 'role'); + $modules = array('moodle-enrol-rolemanager', 'moodle-enrol-rolemanager-skin'); + $function = 'M.enrol.rolemanager.init'; + $arguments = array( + 'containerId'=>$this->id, + 'userIds'=>array_keys($users), + 'courseId'=>$this->manager->get_course()->id); + $page->requires->yui_module($modules, $function, array($arguments)); } } @@ -504,18 +543,22 @@ public function get_url_params() { self::SORTDIRECTIONVAR => $this->sortdirection ); } +} + +/** + * Table control used for enrolled users + * + * @copyright 2010 Sam Hemelryk + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class course_enrolment_users_table extends course_enrolment_table { /** - * Gets the enrolment type filter control for this table - * - * @return single_select + * An array of sortable fields + * @static + * @var array */ - public function get_enrolment_type_filter() { - $url = new moodle_url($this->pageurl, $this->manager->get_url_params()+$this->get_url_params()); - $selector = new single_select($url, 'ifilter', array(0=>get_string('all')) + (array)$this->manager->get_enrolment_instance_names(), $this->manager->get_enrolment_filter(), array()); - $selector->set_label( get_string('enrolmentinstances', 'enrol')); - return $selector; - } + protected static $sortablefields = array('firstname', 'lastname', 'email', 'lastaccess'); /** * Returns a button to enrol cohorts or thier users @@ -557,13 +600,15 @@ public function get_cohort_enrolment_control(moodle_page $page) { } } } - - $arguments = array(array( + + $modules = array('moodle-enrol-quickcohortenrolment', 'moodle-enrol-quickcohortenrolment-skin'); + $function = 'M.enrol.quickcohortenrolment.init'; + $arguments = array( 'courseid'=>$course->id, 'ajaxurl'=>'/enrol/ajax.php', 'url'=>$url->out(false), - 'manualEnrolment'=>$hasmanualinstance)); - $page->requires->yui_module(array('moodle-enrol-quickcohortenrolment', 'moodle-enrol-quickcohortenrolment-skin'), 'M.enrol.quickcohortenrolment.init', $arguments); + 'manualEnrolment'=>$hasmanualinstance); + $page->requires->yui_module($modules, $function, array($arguments)); } return $control; } @@ -636,18 +681,99 @@ public function get_enrolment_selector(moodle_page $page) { $page->requires->string_for_js('assignroles', 'role'); $page->requires->string_for_js('startingfrom', 'moodle'); - - $arguments = array(array( + $modules = array('moodle-enrol-enrolmentmanager', 'moodle-enrol-enrolmentmanager-skin'); + $function = 'M.enrol.enrolmentmanager.init'; + $arguments = array( 'instances'=>$arguments, 'courseid'=>$course->id, 'ajaxurl'=>'/enrol/ajax.php', 'url'=>$url->out(false), 'optionsStartDate'=>$startdateoptions, - 'defaultRole'=>get_config('enrol_manual', 'roleid'))); - $page->requires->yui_module(array('moodle-enrol-enrolmentmanager', 'moodle-enrol-enrolmentmanager-skin'), 'M.enrol.enrolmentmanager.init', $arguments); + 'defaultRole'=>get_config('enrol_manual', 'roleid')); + $page->requires->yui_module($modules, $function, array($arguments)); } return $control; } return null; } + /** + * Gets the enrolment type filter control for this table + * + * @return single_select + */ + public function get_enrolment_type_filter() { + $url = new moodle_url($this->pageurl, $this->manager->get_url_params()+$this->get_url_params()); + $selector = new single_select($url, 'ifilter', array(0=>get_string('all')) + (array)$this->manager->get_enrolment_instance_names(), $this->manager->get_enrolment_filter(), array()); + $selector->set_label( get_string('enrolmentinstances', 'enrol')); + return $selector; + } +} + +/** + * Table used for other users + * + * Other users are users who have roles but are not enrolled. + * + * @copyright 2010 Sam Hemelryk + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class course_enrolment_other_users_table extends course_enrolment_table { + + /** + * Constructs the table + * + * @param course_enrolment_manager $manager + * @param moodle_url $pageurl + */ + public function __construct(course_enrolment_manager $manager, moodle_url $pageurl) { + parent::__construct($manager, $pageurl); + $this->attributes = array('class'=>'userenrolment otheruserenrolment'); + } + + /** + * Gets a button to search users and assign them roles in the course. + * + * @staticvar int $count + * @param int $page + * @return single_button + */ + public function get_user_search_button($page) { + global $CFG; + static $count = 0; + if (!has_capability('moodle/role:assign', $this->manager->get_context())) { + return false; + } + $count++; + $url = new moodle_url('/'.$CFG->admin.'/roles/assign.php', array('contextid'=>$this->manager->get_context()->id, 'sesskey'=>sesskey())); + $control = new single_button($url, get_string('assignroles', 'role'), 'get'); + $control->class = 'singlebutton assignuserrole instance'.$count; + if ($count == 1) { + $page->requires->strings_for_js(array( + 'ajaxoneuserfound', + 'ajaxxusersfound', + 'ajaxnext25', + 'enrol', + 'enrolmentoptions', + 'enrolusers', + 'errajaxfailedenrol', + 'errajaxsearch', + 'none', + 'usersearch', + 'unlimitedduration', + 'startdatetoday', + 'durationdays', + 'enrolperiod'), 'enrol'); + $page->requires->string_for_js('assignrole', 'role'); + + $modules = array('moodle-enrol-otherusersmanager', 'moodle-enrol-otherusersmanager-skin'); + $function = 'M.enrol.otherusersmanager.init'; + $url = new moodle_url($this->pageurl, $this->manager->get_url_params()+$this->get_url_params()); + $arguments = array( + 'courseId'=> $this->manager->get_course()->id, + 'ajaxUrl' => '/enrol/ajax.php', + 'url' => $url->out(false)); + $page->requires->yui_module($modules, $function, array($arguments)); + } + return $control; + } } diff --git a/enrol/users.php b/enrol/users.php index aed52bf07a03c..5bfe6c4e6a37a 100644 --- a/enrol/users.php +++ b/enrol/users.php @@ -46,7 +46,7 @@ $PAGE->set_pagelayout('admin'); $manager = new course_enrolment_manager($course, $filter); -$table = new course_enrolment_table($manager, $PAGE->url); +$table = new course_enrolment_users_table($manager, $PAGE->url); $pageurl = new moodle_url($PAGE->url, $manager->get_url_params()+$table->get_url_params()); // Check if there is an action to take @@ -226,6 +226,7 @@ $user['group'] = $renderer->user_groups_and_actions($userid, $user['groups'], $manager->get_all_groups(), has_capability('moodle/course:managegroups', $manager->get_context()), $pageurl); $user['enrol'] = $renderer->user_enrolments_and_actions($userid, $user['enrolments'], $pageurl); } +$table->set_total_users($manager->get_total_users()); $table->set_users($users); $PAGE->set_title($PAGE->course->fullname.': '.get_string('totalenrolledusers', 'enrol', $manager->get_total_users())); diff --git a/enrol/yui/enrolmentmanager/enrolmentmanager.js b/enrol/yui/enrolmentmanager/enrolmentmanager.js index b67fcdbdfadbc..14ef9578c9e26 100644 --- a/enrol/yui/enrolmentmanager/enrolmentmanager.js +++ b/enrol/yui/enrolmentmanager/enrolmentmanager.js @@ -193,7 +193,7 @@ YUI.add('moodle-enrol-enrolmentmanager', function(Y) { var roles = Y.JSON.parse(outcome.responseText); this.set(UEP.ASSIGNABLEROLES, roles.response); } catch (e) { - Y.fail(UEP.NAME+': Failed to load assignable roles'); + new M.core.exception(e); } this.getAssignableRoles = function() { this.fire('assignablerolesloaded'); @@ -298,8 +298,11 @@ YUI.add('moodle-enrol-enrolmentmanager', function(Y) { processSearchResults : function(tid, outcome, args) { try { var result = Y.JSON.parse(outcome.responseText); + if (result.error) { + return new M.core.ajaxException(result); + } } catch (e) { - Y.fail(UEP.NAME+': Failed to parse user search response ['+e.linenum+':'+e.message+']'); + new M.core.exception(e); } if (!result.success) { this.setContent = M.str.enrol.errajaxsearch; @@ -364,15 +367,15 @@ YUI.add('moodle-enrol-enrolmentmanager', function(Y) { complete : function(tid, outcome, args) { try { var result = Y.JSON.parse(outcome.responseText); + if (result.error) { + return new M.core.ajaxException(result); + } else { + args.userNode.addClass(CSS.ENROLLED); + args.userNode.one('.'+CSS.ENROL).remove(); + this.set(UEP.REQUIREREFRESH, true); + } } catch (e) { - Y.fail(UEP.NAME+': Failed to parse user search response ['+e.linenum+':'+e.message+']'); - } - if (result.success) { - args.userNode.addClass(CSS.ENROLLED); - args.userNode.one('.'+CSS.ENROL).remove(); - this.set(UEP.REQUIREREFRESH, true); - } else { - alert(M.str.enrol.errajaxfailedenrol); + new M.core.exception(e); } }, end : this.removeLoading @@ -489,4 +492,4 @@ YUI.add('moodle-enrol-enrolmentmanager', function(Y) { } } -}, '@VERSION@', {requires:['base','node', 'overlay', 'io', 'test', 'json-parse', 'event-delegate', 'dd-plugin', 'event-key']}); \ No newline at end of file +}, '@VERSION@', {requires:['base','node', 'overlay', 'io', 'test', 'json-parse', 'event-delegate', 'dd-plugin', 'event-key', 'moodle-enrol-notification']}); \ No newline at end of file diff --git a/enrol/yui/notification/assets/skins/sam/notification.css b/enrol/yui/notification/assets/skins/sam/notification.css new file mode 100644 index 0000000000000..2a0a5bc64c6a5 --- /dev/null +++ b/enrol/yui/notification/assets/skins/sam/notification.css @@ -0,0 +1,22 @@ +.moodle-dialogue-base .hidden, +.moodle-dialogue-base .moodle-dialogue-hidden {display:none;} +.moodle-dialogue-base .moodle-dialogue-lightbox {background-color:#AAA;position:absolute;top:0;left:0;width:100%;height:100%;} +.moodle-dialogue-base .moodle-dialogue {background-color:#666;border:0 solid #666;border-right-width:3px;border-bottom-width:3px;} +.moodle-dialogue-base .moodle-dialogue-wrap {background-color:#FFF;margin-top:-3px;margin-left:-3px;border:1px solid #555;} +.moodle-dialogue-base .moodle-dialogue-hd {font-size:110%;color:inherit;font-weight:bold;text-align:left;padding:5px 6px;margin:0;border-bottom:1px solid #ccc;background-color:#f6f6f6;} +.moodle-dialogue-base .closebutton {background-image:url(sprite.png);width:25px;height:15px;background-repeat:no-repeat;float:right;vertical-align:middle;display:inline-block;cursor:pointer;} +.moodle-dialogue-base .moodle-dialogue-bd {padding:5px;} +.moodle-dialogue-base .moodle-dialogue-fd {} + +.moodle-dialogue-confirm .confirmation-dialogue {text-align:center;} +.moodle-dialogue-confirm .confirmation-message {margin:0.5em 1em;} +.moodle-dialogue-confirm .confirmation-dialogue input {min-width:80px;text-align:center;} + +.moodle-dialogue-exception .moodle-exception-message {text-align:center;margin:1em;} +.moodle-dialogue-exception .moodle-exception-param {margin-bottom:0.5em;} +.moodle-dialogue-exception .moodle-exception-param label {width:150px;font-weight:bold;} +.moodle-dialogue-exception .param-stacktrace label {display:block;background-color:#EEE;margin:0;padding:4px 1em;border:1px solid #ccc;border-bottom-width:0;} +.moodle-dialogue-exception .param-stacktrace pre {display:block;border:1px solid #ccc;background-color:#fff;height:200px;overflow:auto;} +.moodle-dialogue-exception .param-stacktrace .stacktrace-file {color:navy;display:inline-block;font-size:80%;margin:4px 0;} +.moodle-dialogue-exception .param-stacktrace .stacktrace-line {color:#AA0000;display:inline-block;font-size:80%;width:50px;margin:4px 1em;} +.moodle-dialogue-exception .param-stacktrace .stacktrace-call {color:#333;font-size:90%;padding-left:25px;margin-bottom:4px;padding-bottom:4px;border-bottom:1px solid #eee;} \ No newline at end of file diff --git a/enrol/yui/notification/assets/skins/sam/sprite.png b/enrol/yui/notification/assets/skins/sam/sprite.png new file mode 100644 index 0000000000000000000000000000000000000000..3716fc0b95fc1f9f585dd316d99358ff95d4d164 GIT binary patch literal 1002 zcmVP000jN0{{R3Cb*Q>00001b5ch_0Itp) z=>Px&08mU+Mc3Hc!NI}){{C%kZT$TF`}_O#_4WGt`uF$u+Ip;Ol~;p6cC z?eFjU`uped|Jv#Q-rwNm=H~hN`G0?Z;Naong(Ft+uYpU(9zQ8 z=<3(k*t*#N?CkBn!NT$K^7Qod#Kp$l-QTmdx9I5V`TF`WR<$ltu=MryKy202)z(05 z);VFsxVyc{%FFfk_BUU`tgo;=XU;oi%Q<7n?CtIE@9#is)bQ}}@bK{P@$vET@$&NY zC`_g+POI(h@8#v@_V)M8&d~NWG~ss}ot>TSi5v0KSST@*rOW@}dJ?g*vH85b=&L)H zs>908>F1<9>cA=Ly*KH)CD*ek_zMejb93v$HTmea=(0L)imdfVN0yeB>9;ue6&3T& zJm{wz_#`CrQb!XGOXi$E)Cd7dNlE2s1fg>dE;pR@%S`679^vQj_+eh?l0xR173Y2z z=H}+rF#+b3J0BbPgpe zhv?|&_f=K)YHGBTNc6~};8_CBu}p-A!S}>~;$aQ-vQ>V6tgW%cQe(RAk14H&A|E7o z_27&=QMUYXcb=os<;qQ@tK0kP-{y8M;qL$6$#wnj;_Fsd?TaDvqdV}HEY>_A*~W|Z zdV1k>5qETx&@#10r382@1hjKs(kvjGXVVoaH0W*xSF zY&y)v7&1UX9ey25P=>;M1&07*qoM6N<$f^5H2h5!Hn literal 0 HcmV?d00001 diff --git a/enrol/yui/notification/notification.js b/enrol/yui/notification/notification.js new file mode 100644 index 0000000000000..78d621a5cdc75 --- /dev/null +++ b/enrol/yui/notification/notification.js @@ -0,0 +1,374 @@ +YUI.add('moodle-enrol-notification', function(Y) { + +var DIALOGUE_NAME = 'Moodle dialogue', + DIALOGUE_PREFIX = 'moodle-dialogue', + CONFIRM_NAME = 'Moodle confirmation dialogue', + EXCEPTION_NAME = 'Moodle exception', + AJAXEXCEPTION_NAME = 'Moodle AJAX exception', + ALERT_NAME = 'Moodle alert', + C = Y.Node.create, + BASE = 'notificationBase', + LIGHTBOX = 'lightbox', + NODELIGHTBOX = 'nodeLightbox', + COUNT = 0, + CONFIRMYES = 'yesLabel', + CONFIRMNO = 'noLabel', + TITLE = 'title', + QUESTION = 'question', + CSS = { + BASE : 'moodle-dialogue-base', + WRAP : 'moodle-dialogue-wrap', + HEADER : 'moodle-dialogue-hd', + BODY : 'moodle-dialogue-bd', + CONTENT : 'moodle-dialogue-content', + FOOTER : 'moodle-dialogue-fd', + HIDDEN : 'hidden', + LIGHTBOX : 'moodle-dialogue-lightbox' + }; + +var DIALOGUE = function(config) { + COUNT++; + var id = 'moodle-dialogue-'+COUNT; + config.notificationBase = + C('
') + .append(C('
')) + .append(C('
') + .append(C('
')) + .append(C('
')) + .append(C('
'))); + Y.one(document.body).append(config.notificationBase); + config.srcNode = '#'+id; + config.width = config.width || '400px'; + config.visible = config.visible || false; + config.center = config.centered || true; + config.centered = false; + DIALOGUE.superclass.constructor.apply(this, [config]); +} +Y.extend(DIALOGUE, Y.Overlay, { + initializer : function(config) { + this.set(NODELIGHTBOX, this.get(BASE).one('.'+CSS.LIGHTBOX).setStyle('opacity', 0.5)); + this.after('visibleChange', this.visibilityChanged, this); + this.after('headerContentChange', function(e){ + var h = (this.get('closeButton'))?this.get(BASE).one('.'+CSS.HEADER):false; + if (h && !h.one('.closebutton')) { + var c = C('
'); + c.on('click', this.hide, this); + h.append(c); + } + }, this); + this.render(); + this.show(); + }, + visibilityChanged : function(e) { + switch (e.attrName) { + case 'visible': + if (this.get(LIGHTBOX)) { + var l = this.get(NODELIGHTBOX); + if (!e.prevVal && e.newVal) { + l.setStyle('height',l.get('docHeight')+'px').removeClass(CSS.HIDDEN); + } else if (e.prevVal && !e.newVal) { + l.addClass(CSS.HIDDEN); + } + } + if (this.get('center') && !e.prevVal && e.newVal) { + this.centerDialogue(); + } + break; + } + }, + centerDialogue : function() { + var bb = this.get('boundingBox'), hidden = bb.hasClass(DIALOGUE_PREFIX+'-hidden'); + if (hidden) { + bb.setStyle('top', '-1000px').removeClass(DIALOGUE_PREFIX+'-hidden'); + } + var x = Math.round((bb.get('winWidth') - bb.get('offsetWidth'))/2); + var y = Math.round((bb.get('winHeight') - bb.get('offsetHeight'))/2)+Y.one(window).get('scrollTop'); + if (hidden) { + bb.addClass(DIALOGUE_PREFIX+'-hidden'); + } + bb.setStyle('left', x).setStyle('top', y); + } +}, { + NAME : DIALOGUE_NAME, + CSS_PREFIX : DIALOGUE_PREFIX, + ATTRS : { + notificationBase : { + + }, + nodeLightbox : { + value : null + }, + lightbox : { + validator : Y.Lang.isBoolean, + value : true + }, + closeButton : { + validator : Y.Lang.isBoolean, + value : true + }, + center : { + validator : Y.Lang.isBoolean, + value : true + } + } +}); + +var ALERT = function(config) { + config.closeButton = false; + ALERT.superclass.constructor.apply(this, [config]); +} +Y.extend(ALERT, DIALOGUE, { + _enterKeypress : null, + initializer : function(config) { + this.publish('complete'); + var yes = C(''), + content = C('
') + .append(C('
'+this.get('message')+'
')) + .append(C('
') + .append(yes)); + this.get(BASE).addClass('moodle-dialogue-confirm'); + this.setStdModContent(Y.WidgetStdMod.BODY, content, Y.WidgetStdMod.REPLACE); + this.setStdModContent(Y.WidgetStdMod.HEADER, this.get(TITLE), Y.WidgetStdMod.REPLACE); + this.after('destroyedChange', function(){this.get(BASE).remove();}, this); + this._enterKeypress = Y.on('key', this.submit, window, 'down:13', this); + yes.on('click', this.submit, this); + }, + submit : function(e, outcome) { + this._enterKeypress.detach(); + this.fire('complete'); + this.hide(); + this.destroy(); + } +}, { + NAME : ALERT_NAME, + CSS_PREFIX : DIALOGUE_PREFIX, + ATTRS : { + title : { + validator : Y.Lang.isString, + value : 'Alert' + }, + message : { + validator : Y.Lang.isString, + value : 'Confirm' + }, + yesLabel : { + validator : Y.Lang.isString, + setter : function(txt) { + if (!txt) { + txt = 'Ok'; + } + return txt; + }, + value : 'Ok' + } + } +}); + +var CONFIRM = function(config) { + CONFIRM.superclass.constructor.apply(this, [config]); +} +Y.extend(CONFIRM, DIALOGUE, { + _enterKeypress : null, + _escKeypress : null, + initializer : function(config) { + this.publish('complete'); + this.publish('complete-yes'); + this.publish('complete-no'); + var yes = C(''), + no = C(''), + content = C('
') + .append(C('
'+this.get(QUESTION)+'
')) + .append(C('
') + .append(yes) + .append(no)); + this.get(BASE).addClass('moodle-dialogue-confirm'); + this.setStdModContent(Y.WidgetStdMod.BODY, content, Y.WidgetStdMod.REPLACE); + this.setStdModContent(Y.WidgetStdMod.HEADER, this.get(TITLE), Y.WidgetStdMod.REPLACE); + this.after('destroyedChange', function(){this.get(BASE).remove();}, this); + this._enterKeypress = Y.on('key', this.submit, window, 'down:13', this, true); + this._escKeypress = Y.on('key', this.submit, window, 'down:27', this, false); + yes.on('click', this.submit, this, true); + no.on('click', this.submit, this, false); + }, + submit : function(e, outcome) { + this._enterKeypress.detach(); + this._escKeypress.detach(); + this.fire('complete', outcome); + if (outcome) { + this.fire('complete-yes'); + } else { + this.fire('complete-no'); + } + this.hide(); + this.destroy(); + } +}, { + NAME : CONFIRM_NAME, + CSS_PREFIX : DIALOGUE_PREFIX, + ATTRS : { + yesLabel : { + validator : Y.Lang.isString, + value : 'Yes' + }, + noLabel : { + validator : Y.Lang.isString, + value : 'No' + }, + title : { + validator : Y.Lang.isString, + value : 'Confirm' + }, + question : { + validator : Y.Lang.isString, + value : 'Are you sure?' + } + } +}); +Y.augment(CONFIRM, Y.EventTarget); + +var EXCEPTION = function(config) { + config.width = config.width || (M.cfg.developerdebug)?Math.floor(Y.one(document.body).get('winWidth')/3)+'px':null; + config.closeButton = true; + EXCEPTION.superclass.constructor.apply(this, [config]); +} +Y.extend(EXCEPTION, DIALOGUE, { + _hideTimeout : null, + _keypress : null, + initializer : function(config) { + this.get(BASE).addClass('moodle-dialogue-exception'); + this.setStdModContent(Y.WidgetStdMod.HEADER, config.name, Y.WidgetStdMod.REPLACE); + var content = C('
') + .append(C('
'+this.get('message')+'
')) + .append(C('')) + .append(C('')) + .append(C('')); + if (M.cfg.developerdebug) { + content.all('.moodle-exception-param').removeClass('hidden'); + } + this.setStdModContent(Y.WidgetStdMod.BODY, content, Y.WidgetStdMod.REPLACE); + + var self = this; + var delay = this.get('hideTimeoutDelay'); + if (delay) { + this._hideTimeout = setTimeout(function(){self.hide();}, delay); + } + this.after('visibleChange', this.visibilityChanged, this); + this.after('destroyedChange', function(){this.get(BASE).remove();}, this); + this._keypress = Y.on('key', this.hide, window, 'down:13,27', this); + this.centerDialogue(); + }, + visibilityChanged : function(e) { + if (e.attrName == 'visible' && e.prevVal && !e.newVal) { + if (this._keypress) this._keypress.detach(); + var self = this; + setTimeout(function(){self.destroy();}, 1000); + } + } +}, { + NAME : EXCEPTION_NAME, + CSS_PREFIX : DIALOGUE_PREFIX, + ATTRS : { + message : { + value : '' + }, + name : { + value : '' + }, + fileName : { + value : '' + }, + lineNumber : { + value : '' + }, + stack : { + setter : function(str) { + var lines = str.split("\n"); + var pattern = new RegExp('^(.+)@('+M.cfg.wwwroot+')?(.{0,75}).*:(\\d+)$'); + for (var i in lines) { + lines[i] = lines[i].replace(pattern, "
ln: $4
$3
$1
"); + } + return lines.join(''); + }, + value : '' + }, + hideTimeoutDelay : { + validator : Y.Lang.isNumber, + value : null + } + } +}); + +var AJAXEXCEPTION = function(config) { + config.name = config.name || 'Error'; + config.closeButton = true; + AJAXEXCEPTION.superclass.constructor.apply(this, [config]); +} +Y.extend(AJAXEXCEPTION, DIALOGUE, { + _keypress : null, + initializer : function(config) { + this.get(BASE).addClass('moodle-dialogue-exception'); + this.setStdModContent(Y.WidgetStdMod.HEADER, config.name, Y.WidgetStdMod.REPLACE); + var content = C('
') + .append(C('
'+this.get('error')+'
')) + .append(C('')) + .append(C('')) + .append(C('')); + if (M.cfg.developerdebug) { + content.all('.moodle-exception-param').removeClass('hidden'); + } + this.setStdModContent(Y.WidgetStdMod.BODY, content, Y.WidgetStdMod.REPLACE); + + var self = this; + var delay = this.get('hideTimeoutDelay'); + if (delay) { + this._hideTimeout = setTimeout(function(){self.hide();}, delay); + } + this.after('visibleChange', this.visibilityChanged, this); + this._keypress = Y.on('key', this.hide, window, 'down:13, 27', this); + this.centerDialogue(); + }, + visibilityChanged : function(e) { + if (e.attrName == 'visible' && e.prevVal && !e.newVal) { + var self = this; + this._keypress.detach(); + setTimeout(function(){self.destroy();}, 1000); + } + } +}, { + NAME : AJAXEXCEPTION_NAME, + CSS_PREFIX : DIALOGUE_PREFIX, + ATTRS : { + error : { + validator : Y.Lang.isString, + value : 'Unknown error' + }, + debuginfo : { + value : null + }, + stacktrace : { + value : null + }, + reproductionlink : { + setter : function(link) { + if (link !== null) { + link = ''+link.replace(M.cfg.wwwroot, '')+''; + } + return link; + }, + value : null + }, + hideTimeoutDelay : { + validator : Y.Lang.isNumber, + value : null + } + } +}); + +M.core = M.core || {}; +M.core.dialogue = DIALOGUE; +M.core.alert = ALERT; +M.core.confirm = CONFIRM; +M.core.exception = EXCEPTION; +M.core.ajaxException = AJAXEXCEPTION; + +}, '@VERSION@', {requires:['base','node','overlay','event-key', 'moodle-enrol-notification-skin']}); \ No newline at end of file diff --git a/enrol/yui/otherusersmanager/assets/skins/sam/otherusersmanager.css b/enrol/yui/otherusersmanager/assets/skins/sam/otherusersmanager.css new file mode 100644 index 0000000000000..b19cb3efed0cb --- /dev/null +++ b/enrol/yui/otherusersmanager/assets/skins/sam/otherusersmanager.css @@ -0,0 +1,64 @@ +/************************************** + +Structure of the other user role assignment panel + +.other-user-manager-panel(.visible) + .oump-wrap + .oump-header + .oump-content + .oump-ajax-content + .oump-search-results + .oump-total-users + .oump-users + .oump-user.clearfix(.odd|.even)(.enrolled) + .count + .oump-user-details + .oump-user-picture + .oump-user-specifics + .oump-user-fullname + .oump-user-email + .oump-role-options + .label + .oump-assignable-role + .oump-more-results + .oump-loading-lightbox(.hidden) + .loading-icon + .oump-footer + .oump-search + input + +**************************************/ + +.other-user-manager-panel {width:400px;background-color:#666;position:absolute;top:10%;left:10%;border:1px solid #666;border-width:0 5px 5px 0;} +.other-user-manager-panel.hidden {display:none;} +.other-user-manager-panel .oump-wrap {margin-top:-5px;margin-left:-5px;background-color:#FFF;border:1px solid #999;height:inherit;} + +.other-user-manager-panel .oump-header {background-color:#eee;padding:1px;} +.other-user-manager-panel .oump-header h2 {margin:3px 1em 0.5em 1em;font-size:1em;} +.other-user-manager-panel .oump-header .oump-panel-close {width:25px;height:15px;position:absolute;top:2px;right:1em;cursor:pointer;background:url("sprite.png") no-repeat scroll 0 0 transparent;} + +.other-user-manager-panel .oump-content {text-align:center;position:relative;width:100%;border-top:1px solid #999;border-bottom:1px solid #999;} +.other-user-manager-panel .oump-ajax-content {height:375px;overflow:auto;} +.other-user-manager-panel .oump-search-results .oump-total-users {background-color:#eee;padding:5px;border-bottom:1px solid #BBB;font-size:7pt;font-weight: bold;} + +.other-user-manager-panel .oump-search-results .oump-user {width:100%;text-align:left;font-size:9pt;background-color:#ddd;border-bottom:1px solid #aaa;} +.other-user-manager-panel .oump-search-results .oump-user .oump-user-details {background-color:#fff;margin-left:25px;border-left:1px solid #bbb;} +.other-user-manager-panel .oump-search-results .oump-user.odd .oump-user-details {background-color:#f9f9f9;} +.other-user-manager-panel .oump-search-results .oump-user .count {width:20px;font-size:7pt;line-height:100%;text-align:right;float:left;padding:5px 5px 2px 2px} +.other-user-manager-panel .oump-search-results .oump-user .oump-user-details .oump-user-picture {display:inline-block;margin:3px;} +.other-user-manager-panel .oump-search-results .oump-user .oump-user-details .oump-user-specifics {width:250px;display:inline-block;margin:3px;vertical-align:top;} +.other-user-manager-panel .oump-search-results .oump-user .oump-user-details .oump-role-options {font-size:8pt;margin-top:2px;text-align:right;margin-right:2px;} +.other-user-manager-panel .oump-search-results .oump-user .oump-user-details .oump-role-options .oump-assignable-role {display:inline-block;margin:0;padding:3px 4px;cursor:pointer;} +.other-user-manager-panel .oump-search-results .oump-user.assignment-in-progress .oump-assignable-role {color:#666;cursor:default;} +.other-user-manager-panel .oump-search-results .oump-more-results {background-color:#eee;padding:5px;cursor:pointer;} +.other-user-manager-panel .oump-search-results .oump-user.oump-has-all-roles {background-color:#CCC;} +.other-user-manager-panel .oump-search-results .oump-user.oump-has-all-roles .count {width:40px;} + +.other-user-manager-panel .oump-loading-lightbox {position:absolute;width:100%;height:100%;top:0;left:0;background-color:#FFF;min-width:50px;min-height:50px;} +.other-user-manager-panel .oump-loading-lightbox.hidden {display:none;} +.other-user-manager-panel .oump-loading-lightbox .loading-icon {margin:auto;vertical-align:middle;margin-top:125px;} + +.other-user-manager-panel .oump-footer {padding:3px;background-color:#ddd;} +.other-user-manager-panel .oump-search {margin:3px;} +.other-user-manager-panel .oump-search label {padding-right:8px;} +.other-user-manager-panel .oump-search input {width:70%;} diff --git a/enrol/yui/otherusersmanager/assets/skins/sam/sprite.png b/enrol/yui/otherusersmanager/assets/skins/sam/sprite.png new file mode 100644 index 0000000000000000000000000000000000000000..3716fc0b95fc1f9f585dd316d99358ff95d4d164 GIT binary patch literal 1002 zcmVP000jN0{{R3Cb*Q>00001b5ch_0Itp) z=>Px&08mU+Mc3Hc!NI}){{C%kZT$TF`}_O#_4WGt`uF$u+Ip;Ol~;p6cC z?eFjU`uped|Jv#Q-rwNm=H~hN`G0?Z;Naong(Ft+uYpU(9zQ8 z=<3(k*t*#N?CkBn!NT$K^7Qod#Kp$l-QTmdx9I5V`TF`WR<$ltu=MryKy202)z(05 z);VFsxVyc{%FFfk_BUU`tgo;=XU;oi%Q<7n?CtIE@9#is)bQ}}@bK{P@$vET@$&NY zC`_g+POI(h@8#v@_V)M8&d~NWG~ss}ot>TSi5v0KSST@*rOW@}dJ?g*vH85b=&L)H zs>908>F1<9>cA=Ly*KH)CD*ek_zMejb93v$HTmea=(0L)imdfVN0yeB>9;ue6&3T& zJm{wz_#`CrQb!XGOXi$E)Cd7dNlE2s1fg>dE;pR@%S`679^vQj_+eh?l0xR173Y2z z=H}+rF#+b3J0BbPgpe zhv?|&_f=K)YHGBTNc6~};8_CBu}p-A!S}>~;$aQ-vQ>V6tgW%cQe(RAk14H&A|E7o z_27&=QMUYXcb=os<;qQ@tK0kP-{y8M;qL$6$#wnj;_Fsd?TaDvqdV}HEY>_A*~W|Z zdV1k>5qETx&@#10r382@1hjKs(kvjGXVVoaH0W*xSF zY&y)v7&1UX9ey25P=>;M1&07*qoM6N<$f^5H2h5!Hn literal 0 HcmV?d00001 diff --git a/enrol/yui/otherusersmanager/otherusersmanager.js b/enrol/yui/otherusersmanager/otherusersmanager.js new file mode 100644 index 0000000000000..4ff889ec25533 --- /dev/null +++ b/enrol/yui/otherusersmanager/otherusersmanager.js @@ -0,0 +1,392 @@ +YUI.add('moodle-enrol-otherusersmanager', function(Y) { + + var OUMANAGERNAME = 'Other users manager', + OTHERUSERNAME = 'Other user (not enroled in course)', + COURSEID = 'courseId', + USERID = 'userId', + BASE = 'base', + SEARCH = 'search', + REQUIREREFRESH = 'requiresRefresh', + PAGE = 'page', + USERCOUNT = 'userCount', + PICTURE = 'picture', + FULLNAME = 'fullname', + EMAIL = 'email', + ASSIGNABLEROLES = 'assignableRoles', + USERS = 'users', + URL = 'url', + AJAXURL = 'ajaxUrl'; + + CSS = { + PANEL : 'other-user-manager-panel', + WRAP : 'oump-wrap', + HEADER : 'oump-header', + CONTENT : 'oump-content', + AJAXCONTENT : 'oump-ajax-content', + SEARCHRESULTS : 'oump-search-results', + TOTALUSERS : 'oump-total-users', + USERS : 'oump-users', + USER : 'oump-user', + USERDETAILS : 'oump-user-details', + MORERESULTS : 'oump-more-results', + LIGHTBOX : 'oump-loading-lightbox', + LOADINGICON : 'loading-icon', + FOOTER : 'oump-footer', + COUNT : 'count', + PICTURE : 'oump-user-picture', + DETAILS : 'oump-user-specifics', + FULLNAME : 'oump-user-fullname', + EMAIL : 'oump-user-email', + OPTIONS : 'oump-role-options', + ROLEOPTION : 'oump-assignable-role', + ODD : 'odd', + EVEN : 'even', + HIDDEN : 'hidden', + SEARCH : 'oump-search', + CLOSE : 'oump-panel-close', + ALLROLESASSIGNED : 'oump-has-all-roles' + }; + + var OUMANAGER = function(config) { + OUMANAGER.superclass.constructor.apply(this, arguments); + } + Y.extend(OUMANAGER, Y.Base, { + _loadingNode : null, + _escCloseEvent : null, + initializer : function(config) { + this.set(BASE, Y.Node.create('
') + .append(Y.Node.create('
') + .append(Y.Node.create('
') + .append(Y.Node.create('
')) + .append(Y.Node.create('

'+M.str.enrol.usersearch+'

'))) + .append(Y.Node.create('
') + .append(Y.Node.create('
')) + .append(Y.Node.create('
') + .append(Y.Node.create('loading') + .setAttribute('src', M.util.image_url('i/loading', 'moodle'))) + .setStyle('opacity', 0.5))) + .append(Y.Node.create('
') + .append(Y.Node.create('
') + .append(Y.Node.create('')) + ) + ) + ) + ); + this.set(SEARCH, this.get(BASE).one('#oump-usersearch')); + Y.all('.assignuserrole input').each(function(node){ + if (node.getAttribute('type', 'submit')) { + node.on('click', this.show, this); + } + }, this); + this.get(BASE).one('.'+CSS.HEADER+' .'+CSS.CLOSE).on('click', this.hide, this); + this._loadingNode = this.get(BASE).one('.'+CSS.CONTENT+' .'+CSS.LIGHTBOX); + Y.on('key', this.getUsers, this.get(SEARCH), 'down:13', this); + Y.one(document.body).append(this.get(BASE)); + + var base = this.get(BASE); + base.plug(Y.Plugin.Drag); + base.dd.addHandle('.'+CSS.HEADER+' h2'); + base.one('.'+CSS.HEADER+' h2').setStyle('cursor', 'move'); + + this.getAssignableRoles(); + }, + show : function(e) { + e.preventDefault(); + e.halt(); + + var base = this.get(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]); + + if (this.get(USERS)===null) { + this.getUsers(e, false); + } + + this._escCloseEvent = Y.on('key', this.hide, document.body, 'down:27', this); + }, + hide : function() { + if (this._escCloseEvent) { + this._escCloseEvent.detach(); + this._escCloseEvent = null; + } + this.get(BASE).addClass(CSS.HIDDEN); + if (this.get(REQUIREREFRESH)) { + window.location = this.get(URL); + } + }, + getUsers : function(e, append) { + if (e) { + e.halt(); + e.preventDefault(); + } + var on, params; + if (append) { + this.set(PAGE, this.get(PAGE)+1); + } else { + this.set(USERCOUNT, 0); + } + + params = []; + params['id'] = this.get(COURSEID); + params['sesskey'] = M.cfg.sesskey; + params['action'] = 'searchotherusers'; + params['search'] = this.get(SEARCH).get('value'); + params['page'] = this.get(PAGE); + + Y.io(M.cfg.wwwroot+this.get(AJAXURL), { + method:'POST', + data:build_querystring(params), + on : { + start : this.displayLoading, + complete: this.processSearchResults, + end : this.removeLoading + }, + context:this, + arguments:{ + append:append, + params:params + } + }); + }, + 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.str.enrol.errajaxsearch; + } + var usersnode, users = [], i=0, count=0, user; + if (!args.append) { + usersnode = Y.Node.create('
'); + } else { + usersnode = this.get(BASE).one('.'+CSS.SEARCHRESULTS+' .'+CSS.USERS); + } + count = this.get(USERCOUNT); + for (i in result.response.users) { + count++; + user = new OTHERUSER(result.response.users[i], count, this); + usersnode.append(user.toHTML()); + users[user.get(USERID)] = user; + } + this.set(USERCOUNT, count); + if (!args.append) { + var usersstr = (result.response.totalusers == '1')?M.str.enrol.ajaxoneuserfound:M.str.enrol.ajaxxusersfound.replace(/\[users\]/, result.response.totalusers); + var content = Y.Node.create('
') + .append(Y.Node.create('
'+usersstr+'
')) + .append(usersnode); + if (result.response.totalusers > (this.get(PAGE)+1)*25) { + var fetchmore = Y.Node.create(''); + fetchmore.on('click', this.getUsers, this, true); + content.append(fetchmore) + } + this.setContent(content); + } else { + if (result.response.totalusers <= (this.get(PAGE)+1)*25) { + this.get(BASE).one('.'+CSS.MORERESULTS).remove(); + } + } + }, + setContent : function(content) { + this.get(BASE).one('.'+CSS.CONTENT+' .'+CSS.AJAXCONTENT).setContent(content); + }, + getAssignableRoles : function() { + Y.io(M.cfg.wwwroot+'/enrol/ajax.php', { + method:'POST', + data:'id='+this.get(COURSEID)+'&action=getassignable&sesskey='+M.cfg.sesskey, + on: { + complete: function(tid, outcome, args) { + try { + var roles = Y.JSON.parse(outcome.responseText); + if (roles.error) { + new M.core.ajaxException(roles); + } else { + this.set(ASSIGNABLEROLES, roles.response); + } + } catch (e) { + new M.core.exception(e); + } + this.getAssignableRoles = function() { + this.fire('assignablerolesloaded'); + } + this.getAssignableRoles(); + } + }, + context:this + }); + } + }, { + NAME : OUMANAGERNAME, + ATTRS : { + courseId : { + + }, + ajaxUrl : { + validator : Y.Lang.isString + }, + url : { + validator : Y.Lang.isString + }, + roles : { + validator :Y.Lang.isArray, + value : [] + }, + base : { + setter : function(node) { + var n = Y.one(node); + if (!n) { + Y.fail(OUMANAGERNAME+': invalid base node set'); + } + return n; + } + }, + search : { + setter : function(node) { + var n = Y.one(node); + if (!n) { + Y.fail(OUMANAGERNAME+': invalid base node set'); + } + return n; + } + }, + requiresRefresh : { + validator : Y.Lang.isBoolean, + value : false + }, + users : { + validator : Y.Lang.isArray, + value : null + }, + page : { + validator : Y.Lang.isNumber, + value : 0 + }, + userCount : { + validator : Y.Lang.isNumber, + value : 0 + }, + assignableRoles : { + value : [] + } + } + }); + + var OTHERUSER = function(config, count, manager) { + this._count = count; + this._manager = manager; + OTHERUSER.superclass.constructor.apply(this, arguments); + } + Y.extend(OTHERUSER, Y.Base, { + _count : 0, + _manager : null, + _node : null, + _assignmentInProgress : false, + initializer : function(config) { + this.publish('assignrole:success'); + this.publish('assignrole:failure'); + }, + toHTML : function() { + this._node = Y.Node.create('
') + .addClass((this._count%2)?CSS.ODD:CSS.EVEN) + .append(Y.Node.create('
'+this._count+'
')) + .append(Y.Node.create('
') + .append(Y.Node.create('
') + .append(Y.Node.create(this.get(PICTURE))) + ) + .append(Y.Node.create('
') + .append(Y.Node.create('
'+this.get(FULLNAME)+'
')) + .append(Y.Node.create('
'+this.get(EMAIL)+'
')) + ) + .append(Y.Node.create('
'+M.str.role.assignrole+':
')) + ); + var id = 0, roles = this._manager.get(ASSIGNABLEROLES); + for (id in roles) { + var role = Y.Node.create(''+roles[id]+''); + role.on('click', this.assignRoleToUser, this, id, role); + this._node.one('.'+CSS.OPTIONS).append(role); + } + return this._node; + }, + assignRoleToUser : function(e, roleid, node) { + e.halt(); + if (this._assignmentInProgress) { + return true; + } + this._node.addClass('assignment-in-progress'); + this._assignmentInProgress = true; + Y.io(M.cfg.wwwroot+'/enrol/ajax.php', { + method:'POST', + data:'id='+this._manager.get(COURSEID)+'&action=assign&sesskey='+M.cfg.sesskey+'&roleid='+roleid+'&user='+this.get(USERID), + on: { + complete: function(tid, outcome, args) { + try { + var o = Y.JSON.parse(outcome.responseText); + if (o.success) { + var options = args.node.ancestor('.'+CSS.OPTIONS); + if (options.all('.'+CSS.ROLEOPTION).size() == 1) { + // This is the last node so remove the options div + options.remove(); + options.ancestor(CSS.USER).addClass(CSS.ALLROLESASSIGNED); + } else { + // There are still more assignable roles + args.node.remove(); + } + this._manager.set(REQUIREREFRESH, true); + } + } catch (e) { + new M.core.exception(e); + } + this._assignmentInProgress = false; + this._node.removeClass('assignment-in-progress'); + } + }, + context:this, + arguments:{ + roleid : roleid, + node : node + } + }); + return true; + } + }, { + NAME : OTHERUSERNAME, + ATTRS : { + userId : { + + }, + fullname : { + validator : Y.Lang.isString + }, + email : { + validator : Y.Lang.isString + }, + picture : { + validator : Y.Lang.isString + } + } + }); + Y.augment(OTHERUSER, Y.EventTarget); + + M.enrol = M.enrol || {}; + M.enrol.otherusersmanager = { + init : function(cfg) { + new OUMANAGER(cfg); + } + } + +}, '@VERSION@', {requires:['base','node', 'overlay', 'io', 'test', 'json-parse', 'event-delegate', 'dd-plugin', 'event-key', 'moodle-enrol-notification']}); \ No newline at end of file diff --git a/enrol/yui/quickcohortenrolment/assets/skins/sam/quickcohortenrolment.css b/enrol/yui/quickcohortenrolment/assets/skins/sam/quickcohortenrolment.css index 2d6dabf1de2f8..76e2fd0062e97 100644 --- a/enrol/yui/quickcohortenrolment/assets/skins/sam/quickcohortenrolment.css +++ b/enrol/yui/quickcohortenrolment/assets/skins/sam/quickcohortenrolment.css @@ -19,4 +19,6 @@ .qce-panel .qce-cohort .qce-cohort-users {position:absolute;right:5px;top:0;width:30px;text-align:right;height:24px;line-height:24px;} .qce-panel .qce-assignable-roles {margin:3px 5px 2px;} .qce-panel .qce-cohort.headings {font-weight:bold;border-width:0;} -.qce-panel .qce-cohort.headings .qce-cohort-button {display:none;} \ No newline at end of file +.qce-panel .qce-cohort.headings .qce-cohort-button {display:none;} +.qce-panel .performing-action {position:absolute;top:0;left:0;width:100%;height:100%;background-color:#fff;text-align:center;} +.qce-panel .performing-action img {margin-top:150px;} \ No newline at end of file diff --git a/enrol/yui/quickcohortenrolment/quickcohortenrolment.js b/enrol/yui/quickcohortenrolment/quickcohortenrolment.js index 0a686d2e31b1d..865f33e5c1a47 100644 --- a/enrol/yui/quickcohortenrolment/quickcohortenrolment.js +++ b/enrol/yui/quickcohortenrolment/quickcohortenrolment.js @@ -34,10 +34,13 @@ YUI.add('moodle-enrol-quickcohortenrolment', function(Y) { CONTROLLER.superclass.constructor.apply(this, arguments); } Y.extend(CONTROLLER, Y.Base, { + _preformingAction : false, initializer : function(config) { COUNT ++; this.publish('assignablerolesloaded'); this.publish('cohortsloaded'); + this.publish('performingaction'); + this.publish('actioncomplete'); var close = Y.Node.create('
'); var panel = new Y.Overlay({ @@ -57,6 +60,12 @@ YUI.add('moodle-enrol-quickcohortenrolment', function(Y) { this.on('hide', function() { this.hide(); }, panel); + this.on('performingaction', function(){ + this.get('boundingBox').append(Y.Node.create('
').append(Y.Node.create('loading').setAttribute('src', M.cfg.loadingicon)).setStyle('opacity', 0.5)); + }, panel); + this.on('actioncomplete', function(){ + this.get('boundingBox').one('.performing-action').remove(); + }, panel); this.on('assignablerolesloaded', this.updateContent, this, panel); this.on('cohortsloaded', this.updateContent, this, panel); close.on('click', this.hide, this); @@ -121,9 +130,13 @@ YUI.add('moodle-enrol-quickcohortenrolment', function(Y) { complete: function(tid, outcome, args) { try { var cohorts = Y.JSON.parse(outcome.responseText); - this.setCohorts(cohorts.response); + if (cohorts.error) { + new M.core.ajaxException(cohorts); + } else { + this.setCohorts(cohorts.response); + } } catch (e) { - Y.fail(CONTROLLERNAME+': Failed to load cohorts'); + return new M.core.exception(e); } this.getCohorts = function() { this.fire('cohortsloaded'); @@ -151,7 +164,7 @@ YUI.add('moodle-enrol-quickcohortenrolment', function(Y) { var roles = Y.JSON.parse(outcome.responseText); this.set(ASSIGNABLEROLES, roles.response); } catch (e) { - Y.fail(CONTROLLERNAME+': Failed to load assignable roles'); + return new M.core.exception(e); } this.getAssignableRoles = function() { this.fire('assignablerolesloaded'); @@ -163,6 +176,11 @@ YUI.add('moodle-enrol-quickcohortenrolment', function(Y) { }); }, enrolCohort : function(e, cohort, node, usersonly) { + if (this._preformingAction) { + return true; + } + this._preformingAction = true; + this.fire('performingaction'); var params = { id : this.get(COURSEID), roleid : node.one('.'+CSS.PANELROLES+' select').get('value'), @@ -177,23 +195,30 @@ YUI.add('moodle-enrol-quickcohortenrolment', function(Y) { complete: function(tid, outcome, args) { try { var result = Y.JSON.parse(outcome.responseText); - if (result.success) { - if (result.response && result.response.message) { - alert(result.response.message); + if (result.error) { + new M.core.ajaxException(result); + } else { + var redirect = function() { + if (result.response.users) { + window.location.href = this.get(URL); + } } - if (result.response.users) { - window.location.href = this.get(URL); + if (result.response && result.response.message) { + new M.core.alert(result.response).on('complete', redirect, this); + } else { + redirect(); } - } else { - alert('Failed to enrol cohort'); } + this._preformingAction = false; + this.fire('actioncomplete'); } catch (e) { - Y.fail(CONTROLLERNAME+': Failed to enrol cohort'); + new M.core.exception(e); } } }, context:this }); + return true; } }, { NAME : CONTROLLERNAME, @@ -276,4 +301,4 @@ YUI.add('moodle-enrol-quickcohortenrolment', function(Y) { } } -}, '@VERSION@', {requires:['base','node', 'overlay', 'io', 'test', 'json-parse', 'event-delegate', 'dd-plugin', 'event-key']}); \ No newline at end of file +}, '@VERSION@', {requires:['base','node', 'overlay', 'io', 'test', 'json-parse', 'event-delegate', 'dd-plugin', 'event-key', 'moodle-enrol-notification']}); \ No newline at end of file diff --git a/enrol/yui/rolemanager/rolemanager.js b/enrol/yui/rolemanager/rolemanager.js index 36ab64bd4f7e7..f8cf04935dd0b 100644 --- a/enrol/yui/rolemanager/rolemanager.js +++ b/enrol/yui/rolemanager/rolemanager.js @@ -86,11 +86,13 @@ YUI.add('moodle-enrol-rolemanager', function(Y) { complete: function(tid, outcome, args) { try { var o = Y.JSON.parse(outcome.responseText); - if (o.success) { + if (o.error) { + new M.core.ajaxException(o); + } else { panel.user.addRoleToDisplay(args.roleid, this.get(ASSIGNABLEROLES)[args.roleid]); } } catch (e) { - Y.fail(MOD_PANEL+': Failed to parse role assignment response ['+e.linenum+':'+e.message+']'); + new M.core.exception(e); } panel.hide(); } @@ -103,9 +105,18 @@ YUI.add('moodle-enrol-rolemanager', function(Y) { }, removeRole : function(e, user, roleid) { e.halt(); - if (confirm('Are you sure you wish to remove this role from this user?')) { - this.removeRoleCallback(e, user.get(USERID), roleid); - } + var event = this.on('assignablerolesloaded', function(){ + event.detach(); + var s = M.str.role, confirmation = { + lightbox : true, + title : s.confirmunassigntitle, + question : s.confirmunassign, + yesLabel : s.confirmunassignyes, + noLabel : s.confirmunassignno + } + new M.core.confirm(confirmation).on('complete-yes', this.removeRoleCallback, this, user.get(USERID), roleid); + }, this); + this._loadAssignableRoles(); }, removeRoleCallback : function(e, userid, roleid) { Y.io(M.cfg.wwwroot+'/enrol/ajax.php', { @@ -113,13 +124,16 @@ YUI.add('moodle-enrol-rolemanager', function(Y) { data:'id='+this.get(COURSEID)+'&action=unassign&sesskey='+M.cfg.sesskey+'&role='+roleid+'&user='+userid, on: { complete: function(tid, outcome, args) { + var o; try { - var o = Y.JSON.parse(outcome.responseText); - if (o.success) { + o = Y.JSON.parse(outcome.responseText); + if (o.error) { + new M.core.ajaxException(o); + } else { this.users[userid].removeRoleFromDisplay(args.roleid); } } catch (e) { - Y.fail(MOD_PANEL+': Failed to parse role assignment response ['+e.linenum+':'+e.message+']'); + new M.core.exception(e); } } }, @@ -140,7 +154,7 @@ YUI.add('moodle-enrol-rolemanager', function(Y) { var roles = Y.JSON.parse(outcome.responseText); this.set(ASSIGNABLEROLES, roles.response); } catch (e) { - Y.fail(MOD_NAME+': Failed to load assignable roles'); + new M.core.exception(e); } this._loadAssignableRoles = function() { this.fire('assignablerolesloaded'); @@ -260,7 +274,9 @@ YUI.add('moodle-enrol-rolemanager', function(Y) { } else { if (!link) { var m = this.get(MANIPULATOR); - link = Y.Node.create('
 
'); + link = Y.Node.create('
').append( + Y.Node.create('').setAttribute('src', M.util.image_url('t/enroladd', 'moodle')) + ); link.on('click', m.addRole, m, this); this.get(CONTAINER).one('.col_role').insert(link, 0); this.set(ASSIGNROLELINK, link); @@ -351,8 +367,8 @@ YUI.add('moodle-enrol-rolemanager', function(Y) { this.user = user; var roles = this.user.get(CONTAINER).one('.col_role .roles'); var x = roles.getX() + 10; - var y = roles.getY() + this.user.get(CONTAINER).get('offsetHeight') - 10 + Y.one(window).get('scrollTop'); - this.get('elementNode').setXY([x, y]); + var y = roles.getY() + this.user.get(CONTAINER).get('offsetHeight') - 10; + this.get('elementNode').setStyle('left', x).setStyle('top', y); this.get('elementNode').addClass('visible'); this.escCloseEvent = Y.on('key', this.hide, document.body, 'down:27', this); this.displayed = true; @@ -386,4 +402,4 @@ YUI.add('moodle-enrol-rolemanager', function(Y) { } } -}, '@VERSION@', {requires:['base','node','io','json-parse','test']}); \ No newline at end of file +}, '@VERSION@', {requires:['base','node','io','json-parse','test','moodle-enrol-notification']}); \ No newline at end of file diff --git a/lang/en/enrol.php b/lang/en/enrol.php index 2bb91d3f2e6ce..c1e0f2249d8dc 100644 --- a/lang/en/enrol.php +++ b/lang/en/enrol.php @@ -29,6 +29,7 @@ $string['ajaxoneuserfound'] = '1 user found'; $string['ajaxxusersfound'] = '[users] users found'; $string['ajaxnext25'] = 'Next 25...'; +$string['assignnotpermitted'] = 'You do not have permission or can not assign roles in this course.'; $string['configenrolplugins'] = 'Please select all required plugins and arrange then in appropriate order.'; $string['defaultenrol'] = 'Add instance to new courses'; $string['defaultenrol_desc'] = 'It is possible to add this plugin to all new courses by default.'; @@ -48,6 +49,7 @@ $string['enrolmentnewuser'] = '{$a->user} has enrolled in course "{$a->course}"'; $string['enrolments'] = 'Enrolments'; $string['enrolmentoptions'] = 'Enrolment options'; +$string['enrolnotpermitted'] = 'You do not have permission or are not allowed to enrol someone in this course'; $string['enrolperiod'] = 'Enrolment duration'; $string['enrolusage'] = 'Instances / enrolments'; $string['enrolusers'] = 'Enrol users'; @@ -55,6 +57,10 @@ $string['enroltimestart'] = 'Enrolment starts'; $string['errajaxfailedenrol'] = 'Failed to enrol user'; $string['errajaxsearch'] = 'Error when searching users'; +$string['errorenrolcohort'] = 'Error creating cohort sync enrolment instance in this course.'; +$string['errorenrolcohortusers'] = 'Error enrolling cohort members in this course.'; +$string['invalidenrolinstance'] = 'Invalid enrolment instance'; +$string['invalidrole'] = 'Invalid role'; $string['manageenrols'] = 'Manage enrol plugins'; $string['manageinstance'] = 'Manage'; $string['noexistingparticipants'] = 'No existing participants'; @@ -62,21 +68,30 @@ $string['none'] = 'None'; $string['notenrollable'] = 'This course is not enrollable at the moment.'; $string['notenrolledusers'] = 'Other users'; +$string['otheruserdesc'] = 'The following users are not enrolled in this course but do have roles, inherited or assigned within it.'; $string['participationactive'] = 'Active'; $string['participationstatus'] = 'Status'; $string['participationsuspended'] = 'Suspended'; $string['periodend'] = 'until {$a}'; $string['periodstart'] = 'from {$a}'; $string['periodstartend'] = 'from {$a->start} until {$a->end}'; +$string['rolefromthiscourse'] = '{$a->role} (Assigned in this course)'; +$string['rolefrommetacourse'] = '{$a->role} (Inherited from parent course)'; +$string['rolefromcategory'] = '{$a->role} (Inherited from course category)'; +$string['rolefromsystem'] = '{$a->role} (Assigned at site level)'; $string['startdatetoday'] = 'Today'; $string['synced'] = 'Synced'; $string['totalenrolledusers'] = '{$a} enrolled users'; +$string['totalotherusers'] = '{$a} other users'; +$string['unassignnotpermitted'] = 'You do not have permission to unassign roles in this course'; $string['unenrol'] = 'Unenrol'; $string['unenrolconfirm'] = 'Do you really want to unenrol user "{$a->user}" from course "{$a->course}"?'; $string['unenrolme'] = 'Unenrol me from {$a}'; +$string['unenrolnotpermitted'] = 'You do not have permission or can not unenrol this user from this course.'; $string['unenrolroleusers'] = 'Unenrol users'; $string['uninstallconfirm'] = 'You are about to completely delete the enrol plugin \'{$a}\'. This will completely delete everything in the database associated with this enrolment type. Are you SURE you want to continue?'; $string['uninstalldeletefiles'] = 'All data associated with the enrol plugin \'{$a->plugin}\' has been deleted from the database. To complete the deletion (and prevent the plugin re-installing itself), you should now delete this directory from your server: {$a->directory}'; +$string['unknowajaxaction'] = 'Unknown action requested'; $string['unlimitedduration'] = 'Unlimited'; $string['usersearch'] = 'Search '; $string['extremovedaction'] = 'External unenrol action'; diff --git a/lang/en/role.php b/lang/en/role.php index b2b1b499e966d..a29523dcaa5a2 100644 --- a/lang/en/role.php +++ b/lang/en/role.php @@ -52,6 +52,7 @@ $string['assignmentcontext'] = 'Assignment context'; $string['assignmentoptions'] = 'Assignment options'; $string['assignrolenameincontext'] = 'Assign role \'{$a->role}\' in {$a->context}'; +$string['assignrole'] = 'Assign role'; $string['assignroles'] = 'Assign roles'; $string['assignroles_help'] = 'By assigning a role to a user in a context, you are granting them the permissions contained in that role, for the current context and all lower contexts. For example, if a user is assigned the role of student in a course, they will also have the role of student for all activities and blocks within the course.'; $string['assignroles_link'] = 'admin/roles/assign'; @@ -102,6 +103,10 @@ $string['confirmdeladmin'] = 'Do you really want to remove user {$a} from the list of site administrators?'; $string['confirmroleprevent'] = 'Do you really want to remove {$a->role} from the list of allowed roles for capability {$a->cap} in context {$a->context}?'; $string['confirmroleunprohibit'] = 'Do you really want to remove {$a->role} from the list of prohibited roles for capability {$a->cap} in context {$a->context}?'; +$string['confirmunassign'] = 'Are you sure you wish to remove this role from this user?'; +$string['confirmunassigntitle'] = 'Confirm role change'; +$string['confirmunassignyes'] = 'Remove'; +$string['confirmunassignno'] = 'Cancel'; $string['context'] = 'Context'; $string['course:activityvisibility'] = 'Hide/show activities'; $string['course:bulkmessaging'] = 'Send a message to many people'; diff --git a/lib/outputrenderers.php b/lib/outputrenderers.php index 1dcd9f6f2daf8..096983dbe96fd 100644 --- a/lib/outputrenderers.php +++ b/lib/outputrenderers.php @@ -2573,11 +2573,14 @@ class core_renderer_ajax extends core_renderer { * @return string A template fragment for a fatal error */ public function fatal_error($message, $moreinfourl, $link, $backtrace, $debuginfo = null) { + global $FULLME, $USER; $e = new stdClass(); $e->error = $message; $e->stacktrace = NULL; $e->debuginfo = NULL; + $e->reproductionlink = NULL; if (!empty($CFG->debug) and $CFG->debug >= DEBUG_DEVELOPER) { + $e->reproductionlink = $link; if (!empty($debuginfo)) { $e->debuginfo = $debuginfo; } @@ -2585,6 +2588,7 @@ public function fatal_error($message, $moreinfourl, $link, $backtrace, $debuginf $e->stacktrace = format_backtrace($backtrace, true); } } + @header('Content-type: application/json'); return json_encode($e); } diff --git a/lib/setuplib.php b/lib/setuplib.php index 1b0cbb39de8cc..18678eb6e181e 100644 --- a/lib/setuplib.php +++ b/lib/setuplib.php @@ -1035,6 +1035,7 @@ public static function early_error($message, $moreinfourl, $link, $backtrace, $d $e->stacktrace = format_backtrace($backtrace, true); } } + @header('Content-Type: application/json'); echo json_encode($e); return; } diff --git a/theme/standard/style/core.css b/theme/standard/style/core.css index 88c139c155cfa..0b51228b75bd3 100644 --- a/theme/standard/style/core.css +++ b/theme/standard/style/core.css @@ -439,7 +439,9 @@ table#tag-management-list {margin: 10px auto;width: 80%;} .userenrolment .col_group .addgroup {background-color:#DDD;border:1px outset #EEE;-moz-border-radius:5px;} .userenrolment .col_enrol {max-width:300px;} .userenrolment .col_enrol .enrolment {border:1px outset #E6E6E6;background-color:#EEE;line-height:10px;font-size:10px;-moz-border-radius:5px;} +.userenrolment.otheruserenrolment .col_role .role {float:none;} .path-enrol .enrolusersbutton, .path-enrol .enrolcohortbutton {float:left;} .path-enrol .enrolusersbutton.instance1, -.path-enrol .enrolcohortbutton.instance1 {float:right;} +.path-enrol .enrolcohortbutton.instance1, +.path-enrol .assignuserrole.instance1 {float:right;}