MDL-31830 course: several management interface improvements

* Tidied up course detail permissions so that user is not shown information they couldn't access elsewhere.
* category link dimming now accounts for course creation as an action as well.
* category single select when in courses view mode is now limited to courses user can action in.
* There is now a check at the start of the management page to redirect to course/index.php if the user isn't able to manage in any category.
* Tweaked navigation again, to give the limited users a navbar structure similar to the system cap'd user.
* Cancelling a category delete now takes you back to the category you were viewing.
* Fixed undefined notice
* Improved placement of course request and approval links.
* Several styling tweaks/improvements to the base theme.
* Several styling tweaks/improvements to the bootstrapbase theme.
This commit is contained in:
Sam Hemelryk 2013-10-04 12:59:32 +13:00
parent c46996ea5a
commit 484c4c6cf3
14 changed files with 451 additions and 219 deletions

View File

@ -24,7 +24,7 @@
require_once("../config.php");
$categoryid = required_param('id', PARAM_INT); // Category id
$categoryid = required_param('id', PARAM_INT); // Category id.
debugging('Please use URL /course/index.php?categoryid=XXX instead of /course/category.php?id=XXX', DEBUG_DEVELOPER);

View File

@ -50,33 +50,8 @@ class helper {
*/
public static function get_course_detail_array(\course_in_list $course) {
global $DB;
$names = \role_get_names($course->get_context());
$sql = 'SELECT ra.roleid, COUNT(ra.id) AS rolecount
FROM {role_assignments} ra
WHERE ra.contextid = :contextid
GROUP BY ra.roleid';
$rolecounts = $DB->get_records_sql($sql, array('contextid' => $course->get_context()->id));
$roledetails = array();
foreach ($rolecounts as $result) {
$a = new \stdClass;
$a->role = $names[$result->roleid]->localname;
$a->count = $result->rolecount;
$roledetails[] = \get_string('assignedrolecount', 'moodle', $a);
}
$groups = \groups_get_course_data($course->id);
$enrolmentlines = array();
$instances = \enrol_get_instances($course->id, true);
$plugins = \enrol_get_plugins(true);
foreach ($instances as $instance) {
if (!isset($plugins[$instance->enrol])) {
// Weird.
continue;
}
$plugin = $plugins[$instance->enrol];
$enrolmentlines[] = $plugin->get_instance_name($instance);
}
$canaccess = $course->can_access();
$format = \course_get_format($course->id);
$modinfo = \get_fast_modinfo($course->id);
@ -84,7 +59,9 @@ class helper {
$sections = array();
if ($format->uses_sections()) {
foreach ($modinfo->get_section_info_all() as $section) {
$sections[] = $format->get_section_name($section);
if ($section->uservisible) {
$sections[] = $format->get_section_name($section);
}
}
}
@ -93,10 +70,6 @@ class helper {
$categoryname = $category->get_formatted_name();
$details = array(
'format' => array(
'key' => \get_string('format'),
'value' => \course_get_format($course)->get_format_name()
),
'fullname' => array(
'key' => \get_string('fullname'),
'value' => $course->get_formatted_fullname()
@ -112,33 +85,72 @@ class helper {
'category' => array(
'key' => \get_string('category'),
'value' => \html_writer::link($categoryurl, $categoryname)
),
'groupings' => array(
'key' => \get_string('groupings', 'group'),
'value' => count($groups->groupings)
),
'groups' => array(
'key' => \get_string('groups'),
'value' => count($groups->groups)
),
'roleassignments' => array(
'key' => \get_string('roleassignments'),
'value' => join('<br />', $roledetails)
),
'enrolmentmethods' => array(
'key' => \get_string('enrolmentmethods'),
'value' => join('<br />', $enrolmentlines)
),
'sections' => array(
'key' => \get_string('sections'),
'value' => join('<br />', $sections)
),
'modulesused' => array(
'key' => \get_string('modulesused'),
'value' => join('<br />', $modules)
)
);
if (has_capability('moodle/site:accessallgroups', $course->get_context())) {
$groups = \groups_get_course_data($course->id);
$details += array(
'groupings' => array(
'key' => \get_string('groupings', 'group'),
'value' => count($groups->groupings)
),
'groups' => array(
'key' => \get_string('groups'),
'value' => count($groups->groups)
)
);
}
if ($canaccess) {
$names = \role_get_names($course->get_context());
$sql = 'SELECT ra.roleid, COUNT(ra.id) AS rolecount
FROM {role_assignments} ra
WHERE ra.contextid = :contextid
GROUP BY ra.roleid';
$rolecounts = $DB->get_records_sql($sql, array('contextid' => $course->get_context()->id));
$roledetails = array();
foreach ($rolecounts as $result) {
$a = new \stdClass;
$a->role = $names[$result->roleid]->localname;
$a->count = $result->rolecount;
$roledetails[] = \get_string('assignedrolecount', 'moodle', $a);
}
$details['roleassignments'] = array(
'key' => \get_string('roleassignments'),
'value' => join('<br />', $roledetails)
);
}
if ($course->can_review_enrolments()) {
$enrolmentlines = array();
$instances = \enrol_get_instances($course->id, true);
$plugins = \enrol_get_plugins(true);
foreach ($instances as $instance) {
if (!isset($plugins[$instance->enrol])) {
// Weird.
continue;
}
$plugin = $plugins[$instance->enrol];
$enrolmentlines[] = $plugin->get_instance_name($instance);
}
$details['enrolmentmethods'] = array(
'key' => \get_string('enrolmentmethods'),
'value' => join('<br />', $enrolmentlines)
);
}
if ($canaccess) {
$details['format'] = array(
'key' => \get_string('format'),
'value' => \course_get_format($course)->get_format_name()
);
$details['sections'] = array(
'key' => \get_string('sections'),
'value' => join('<br />', $sections)
);
$details['modulesused'] = array(
'key' => \get_string('modulesused'),
'value' => join('<br />', $modules)
);
}
return $details;
}
@ -240,6 +252,56 @@ class helper {
return $actions;
}
/**
* Returns an array of actions for a course listitem.
*
* @param \coursecat $category
* @param \course_in_list $course
* @return string
*/
public static function get_course_listitem_actions(\coursecat $category, \course_in_list $course) {
$baseurl = new \moodle_url(
'/course/management.php',
array('courseid' => $course->id, 'categoryid' => $course->category, 'sesskey' => \sesskey())
);
$actions = array();
// Edit.
if ($course->can_edit()) {
$actions[] = array(
'url' => new \moodle_url('/course/edit.php', array('id' => $course->id)),
'icon' => new \pix_icon('t/edit', \get_string('edit')),
'attributes' => array('class' => 'action-edit')
);
}
// Show/Hide.
if ($course->can_change_visibility()) {
$actions[] = array(
'url' => new \moodle_url($baseurl, array('action' => 'hidecourse')),
'icon' => new \pix_icon('t/hide', \get_string('hide')),
'attributes' => array('data-action' => 'hide', 'class' => 'action-hide')
);
$actions[] = array(
'url' => new \moodle_url($baseurl, array('action' => 'showcourse')),
'icon' => new \pix_icon('t/show', \get_string('show')),
'attributes' => array('data-action' => 'show', 'class' => 'action-show')
);
}
// Move up/down.
if ($category->can_resort_courses()) {
$actions[] = array(
'url' => new \moodle_url($baseurl, array('action' => 'movecourseup')),
'icon' => new \pix_icon('t/up', \get_string('up')),
'attributes' => array('data-action' => 'moveup', 'class' => 'action-moveup')
);
$actions[] = array(
'url' => new \moodle_url($baseurl, array('action' => 'movecoursedown')),
'icon' => new \pix_icon('t/down', \get_string('down')),
'attributes' => array('data-action' => 'movedown', 'class' => 'action-movedown')
);
}
return $actions;
}
/**
* Returns an array of actions that can be performed on the course being displayed.
*
@ -251,7 +313,7 @@ class helper {
$baseurl = new \moodle_url('/course/management.php', $params);
$actions = array();
// View.
if ($course->can_access()) {
if ($course->is_uservisible()) {
$actions['view'] = array(
'url' => new \moodle_url('/course/view.php', array('id' => $course->id)),
'string' => \get_string('view')

View File

@ -42,7 +42,7 @@ class core_course_management_renderer extends plugin_renderer_base {
* course management pages that much cooler.
*/
public function enhance_management_interface() {
$this->page->requires->yui_module('moodle-course-management', 'M.course.init_management');
$this->page->requires->yui_module('moodle-course-management', 'M.course.management.init');
$this->page->requires->strings_for_js(
array('show', 'hide', 'expand', 'collapse', 'confirmcoursemove', 'yes', 'no', 'confirm'),
'moodle'
@ -64,7 +64,7 @@ class core_course_management_renderer extends plugin_renderer_base {
}
if ($viewmode !== null) {
if ($viewmode === 'courses') {
$categories = coursecat::make_categories_list();
$categories = coursecat::make_categories_list(array('moodle/category:manage', 'moodle/course:create'));
$nothing = false;
if ($categoryid === null) {
$nothing = array('' => get_string('selectacategory'));
@ -192,7 +192,7 @@ class core_course_management_renderer extends plugin_renderer_base {
$icon = html_writer::link($viewcaturl, $icon, array('class' => 'float-left'));
}
$actions = \core_course\management\helper::get_category_listitem_actions($category);
$hasactions = !empty($actions);
$hasactions = !empty($actions) || $category->can_create_course();
$html = html_writer::start_tag('li', $attributes);
$html .= html_writer::start_div('clearfix');
@ -276,6 +276,9 @@ class core_course_management_renderer extends plugin_renderer_base {
$menu->actionicon = new pix_icon('t/add', ' ', 'moodle', array('class' => 'iconsmall'));
$actions[] = $this->render($menu);
}
if (coursecat::can_approve_course_requests()) {
$actions[] = html_writer::link(new moodle_url('/course/pending.php'), get_string('coursespending'));
}
if ($category->can_resort_subcategories()) {
$hasitems = true;
$params = $this->page->url->params();
@ -404,8 +407,15 @@ class core_course_management_renderer extends plugin_renderer_base {
'limit' => $perpage
);
$courseid = isset($course) ? $course->id : null;
$class = '';
if ($page === 0) {
$class .= ' firstpage';
}
if ($page + 1 === (int)$totalpages) {
$class .= ' lastpage';
}
$html = html_writer::start_div('course-listing', array(
$html = html_writer::start_div('course-listing'.$class, array(
'data-category' => $category->id,
'data-page' => $page,
'data-totalpages' => $totalpages,
@ -513,6 +523,11 @@ class core_course_management_renderer extends plugin_renderer_base {
);
$bulkcourseinput = array('type' => 'checkbox', 'name' => 'bc[]', 'value' => $course->id, 'class' => 'bulk-action-checkbox');
if (!$category->has_manage_capability()) {
// Very very hardcoded here.
$bulkcourseinput['style'] = 'visibility:hidden';
}
$viewcourseurl = new moodle_url($this->page->url, array('courseid' => $course->id));
$html = html_writer::start_tag('li', $attributes);
@ -552,6 +567,11 @@ class core_course_management_renderer extends plugin_renderer_base {
$url = new moodle_url('/course/edit.php', array('category' => $category->id, 'returnto' => 'catmanage'));
$actions[] = html_writer::link($url, get_string('newcourse'));
}
if ($category->can_request_course()) {
// Request a new course.
$url = new moodle_url('/course/request.php', array('return' => 'management'));
$actions[] = html_writer::link($url, get_string('requestcourse'));
}
if ($category->can_resort_courses()) {
$params = $this->page->url->params();
$params['action'] = 'resortcourses';
@ -595,54 +615,15 @@ class core_course_management_renderer extends plugin_renderer_base {
* @return string
*/
public function course_listitem_actions(coursecat $category, course_in_list $course) {
$baseurl = new moodle_url(
'/course/management.php',
array('courseid' => $course->id, 'categoryid' => $course->category, 'sesskey' => sesskey())
);
$actions = array();
// Edit.
if ($course->can_edit()) {
$actions[] = $this->output->action_icon(
new moodle_url('/course/edit.php', array('id' => $course->id)),
new pix_icon('t/edit', get_string('edit')),
null,
array('class' => 'action-edit')
);
}
// Show/Hide.
if ($course->can_change_visibility()) {
$actions[] = $this->output->action_icon(
new moodle_url($baseurl, array('action' => 'hidecourse')),
new pix_icon('t/hide', get_string('hide')),
null,
array('data-action' => 'hide', 'class' => 'action-hide')
);
$actions[] = $this->output->action_icon(
new moodle_url($baseurl, array('action' => 'showcourse')),
new pix_icon('t/show', get_string('show')),
null,
array('data-action' => 'show', 'class' => 'action-show')
);
}
// Move up/down.
if ($category->can_resort_courses()) {
$actions[] = $this->action_icon(
new moodle_url($baseurl, array('action' => 'movecourseup')),
new pix_icon('t/up', get_string('up')),
null,
array('data-action' => 'moveup', 'class' => 'action-moveup')
);
$actions[] = $this->action_icon(
new moodle_url($baseurl, array('action' => 'movecoursedown')),
new pix_icon('t/down', get_string('down')),
null,
array('data-action' => 'movedown', 'class' => 'action-movedown')
);
}
$actions = \core_course\management\helper::get_course_listitem_actions($category, $course);
if (empty($actions)) {
return '';
}
return html_writer::span(join('', $actions), 'course-item-actions item-actions');
$actionshtml = array();
foreach ($actions as $action) {
$actionshtml[] = $this->output->action_icon($action['url'], $action['icon'], null, $action['attributes']);
}
return html_writer::span(join('', $actionshtml), 'course-item-actions item-actions');
}
/**
@ -696,9 +677,9 @@ class core_course_management_renderer extends plugin_renderer_base {
* @return string
*/
protected function detail_pair($key, $value, $class ='') {
$html = html_writer::start_div('detail-pair yui3-g '.preg_replace('#[^a-zA-Z0-9_\-]#', '-', $class));
$html .= html_writer::div(html_writer::span($key), 'pair-key yui3-u-1-4');
$html .= html_writer::div(html_writer::span($value), 'pair-value yui3-u-3-4');
$html = html_writer::start_div('detail-pair row yui3-g '.preg_replace('#[^a-zA-Z0-9_\-]#', '-', $class));
$html .= html_writer::div(html_writer::span($key), 'pair-key span3 yui3-u-1-4');
$html .= html_writer::div(html_writer::span($value), 'pair-value span9 yui3-u-3-4');
$html .= html_writer::end_div();
return $html;
}
@ -716,9 +697,9 @@ class core_course_management_renderer extends plugin_renderer_base {
}
$options = array();
foreach ($actions as $action) {
$options[] = $this->action_button($action['url'], $action['string']);
$options[] = $this->action_link($action['url'], $action['string']);
}
return html_writer::div(join('', $options), 'listing-actions course-detail-listing-actions');
return html_writer::div(join(' | ', $options), 'listing-actions course-detail-listing-actions');
}
/**
@ -1073,31 +1054,33 @@ class core_course_management_renderer extends plugin_renderer_base {
);
$actions = array();
// Edit.
if ($course->can_edit()) {
$actions[] = $this->output->action_icon(
new moodle_url('/course/edit.php', array('id' => $course->id)),
new pix_icon('t/edit', get_string('edit')),
null,
array('class' => 'action-edit')
);
}
// Show/Hide.
if ($course->can_change_visibility()) {
if ($course->visible) {
if ($course->can_access()) {
if ($course->can_edit()) {
$actions[] = $this->output->action_icon(
new moodle_url($baseurl, array('action' => 'hidecourse')),
new pix_icon('t/show', get_string('hide')),
new moodle_url('/course/edit.php', array('id' => $course->id)),
new pix_icon('t/edit', get_string('edit')),
null,
array('data-action' => 'hide', 'class' => 'action-hide')
);
} else {
$actions[] = $this->output->action_icon(
new moodle_url($baseurl, array('action' => 'showcourse')),
new pix_icon('t/hide', get_string('show')),
null,
array('data-action' => 'show', 'class' => 'action-show')
array('class' => 'action-edit')
);
}
// Show/Hide.
if ($course->can_change_visibility()) {
if ($course->visible) {
$actions[] = $this->output->action_icon(
new moodle_url($baseurl, array('action' => 'hidecourse')),
new pix_icon('t/show', get_string('hide')),
null,
array('data-action' => 'hide', 'class' => 'action-hide')
);
} else {
$actions[] = $this->output->action_icon(
new moodle_url($baseurl, array('action' => 'showcourse')),
new pix_icon('t/hide', get_string('show')),
null,
array('data-action' => 'show', 'class' => 'action-show')
);
}
}
}
if (empty($actions)) {
return '';

View File

@ -38,6 +38,10 @@ $search = optional_param('search', '', PARAM_RAW); // Search words. Shortname, f
$blocklist = optional_param('blocklist', 0, PARAM_INT); // Find courses containing this block.
$modulelist = optional_param('modulelist', '', PARAM_PLUGIN); // Find courses containing the given modules.
if (!in_array($viewmode, array('default', 'combined', 'courses', 'categories'))) {
$viewmode = 'default';
}
$issearching = ($search !== '' || $blocklist !== 0 || $modulelist !== '');
if ($issearching) {
$viewmode = 'courses';
@ -69,6 +73,7 @@ if ($courseid) {
}
$context = $systemcontext;
}
if ($page !== 0) {
$url->param('page', $page);
}
@ -93,6 +98,15 @@ $PAGE->set_pagelayout('admin');
$PAGE->set_title($title);
$PAGE->set_heading($title);
if (!coursecat::has_capability_on_any(array('moodle/category:manage', 'moodle/course:create'))) {
// The user isn't able to manage any categories. Lets redirect them to the relevant course/index.php page.
$url = new moodle_url('/course/index.php');
if ($categoryid) {
$url->param('categoryid', $categoryid);
}
redirect($url);
}
// If the user poses any of these capabilities then they will be able to see the admin
// tree and the management link within it.
// This is the most accurate form of navigation.
@ -104,17 +118,23 @@ $capabilities = array(
'moodle/site:approvecourse'
);
if ($category && !has_any_capability($capabilities, $systemcontext)) {
// If the user doesn't poses any of these system capabilities then we're going to trigger
// the page to set the specific category being viewed.
// If the user doesn't poses any of these system capabilities then we're going to mark the manage link in the settings block
// as active, tell the page to ignore the active path and just build what the user would expect.
// This will at least give the page some relevant navigation.
$PAGE->set_category_by_id($category->id);
navigation_node::override_active_url(new moodle_url('/course/management.php', array('categoryid' => $category->id)));
} else if (!$issearching && $category !== null) {
$PAGE->set_category_by_id($category->id);
$PAGE->navbar->ignore_active(true);
$PAGE->navbar->add(get_string('coursemgmt', 'admin'), $PAGE->url->out_omit_querystring());
}
if (!$issearching && $category !== null) {
$parents = coursecat::get_many($category->get_parents());
$parents[] = $category;
foreach ($parents as $parent) {
$PAGE->navbar->add($parent->get_formatted_name());
$PAGE->navbar->add(
$parent->get_formatted_name(),
new moodle_url('/course/management.php', array('categoryid' => $parent->id))
);
}
$PAGE->navbar->add($category->get_formatted_name());
if ($course instanceof course_in_list) {
// Use the list name so that it matches whats being displayed below.
$PAGE->navbar->add($course->get_formatted_name());
@ -204,7 +224,7 @@ if ($action !== false && confirm_sesskey()) {
require_once($CFG->dirroot.'/course/delete_category_form.php');
$mform = new core_course_deletecategory_form(null, $category);
if ($mform->is_cancelled()) {
redirect(new moodle_url('/course/management.php'));
redirect($PAGE->url);
}
// Start output.
/* @var core_course_management_renderer|core_renderer $renderer */
@ -355,6 +375,14 @@ if ($viewmode === 'default' || $viewmode === 'combined') {
$class = 'columns-1';
}
}
if ($viewmode === 'default' || $viewmode === 'combined') {
$class .= ' viewmode-cobmined';
} else {
$class .= ' viewmode-'.$viewmode;
}
if (($viewmode === 'default' || $viewmode === 'combined' || $viewmode === 'courses') && !empty($courseid)) {
$class .= ' course-selected';
}
/* @var core_course_management_renderer|core_renderer $renderer */
$renderer = $PAGE->get_renderer('core_course', 'management');
@ -402,10 +430,6 @@ if ($viewmode === 'default' || $viewmode === 'combined' || $viewmode === 'course
}
echo $renderer->grid_end();
if (!empty($CFG->enablecourserequests) && $id == $CFG->defaultrequestcategory) {
print_course_request_buttons(context_system::instance());
}
// End of the management form.
echo $renderer->management_form_end();
echo $renderer->footer();

View File

@ -27,13 +27,19 @@ require_once(dirname(__FILE__) . '/../config.php');
require_once($CFG->dirroot . '/course/lib.php');
require_once($CFG->dirroot . '/course/request_form.php');
$PAGE->set_url('/course/request.php');
// Where we came from. Used in a number of redirects.
$url = new moodle_url('/course/request.php');
$return = optional_param('return', null, PARAM_ALPHANUMEXT);
if ($return === 'management') {
$url->param('return', $return);
$returnurl = new moodle_url('/course/management.php', array('categoryid' => $CFG->defaultrequestcategory));
} else {
$returnurl = new moodle_url('/course/index.php');
}
/// Where we came from. Used in a number of redirects.
$returnurl = $CFG->wwwroot . '/course/index.php';
$PAGE->set_url($url);
/// Check permissions.
// Check permissions.
require_login();
if (isguestuser()) {
print_error('guestsarenotallowed', '', $returnurl);
@ -45,23 +51,23 @@ $context = context_system::instance();
$PAGE->set_context($context);
require_capability('moodle/course:request', $context);
/// Set up the form.
// Set up the form.
$data = course_request::prepare();
$requestform = new course_request_form($CFG->wwwroot . '/course/request.php', compact('editoroptions'));
$requestform = new course_request_form($url, compact('editoroptions'));
$requestform->set_data($data);
$strtitle = get_string('courserequest');
$PAGE->set_title($strtitle);
$PAGE->set_heading($strtitle);
/// Standard form processing if statement.
// Standard form processing if statement.
if ($requestform->is_cancelled()){
redirect($returnurl);
} else if ($data = $requestform->get_data()) {
$request = course_request::create($data);
// and redirect back to the course listing.
// And redirect back to the course listing.
notice(get_string('courserequestsuccess'), $returnurl);
}

View File

@ -358,9 +358,11 @@ Console.prototype = {
categories = this.get('categories'),
length = categories.length;
for (i = 0; i < length; i++) {
category = categories[i];
if (category.get('categoryid') === id) {
return category;
if (categories.hasOwnProperty(i)) {
category = categories[i];
if (category.get('categoryid') === id) {
return category;
}
}
}
return false;
@ -378,14 +380,14 @@ Console.prototype = {
courses = this.get('courses'),
length = courses.length;
for (i = 0; i < length; i++) {
if (!courses.hadOwnPropery(i)) {
if (courses.hasOwnProperty(i)) {
course = courses[i];
if (course.get('courseid') === id) {
return course;
}
}
}
return id;
return false;
},
/**
@ -841,7 +843,10 @@ Item.prototype = {
this.updated(true);
Y.log('Success: '+this.get('itemname')+' moved up by AJAX.', 'info', 'moodle-course-management');
} else {
Y.log(this.get('itemname')+' cannot be moved up as its the top item', 'warn', 'moodle-course-management');
// Aha it succeeded but this is the top item in the list. Pagination is in play!
// Refresh to update the state of things.
Y.log(this.get('itemname')+' cannot be moved up as its the top item on this page.', 'info', 'moodle-course-management');
window.location.reload();
}
},
@ -895,7 +900,10 @@ Item.prototype = {
this.updated(true);
Y.log('Success: '+this.get('itemname')+' moved down by AJAX.', 'info', 'moodle-course-management');
} else {
Y.log(this.get('itemname')+' cannot be moved down as its the last item', 'warn', 'moodle-course-management');
// Aha it succeeded but this is the bottom item in the list. Pagination is in play!
// Refresh to update the state of things.
Y.log(this.get('itemname')+' cannot be moved down as its the top item on this page.', 'info', 'moodle-course-management');
window.location.reload();
}
},

File diff suppressed because one or more lines are too long

View File

@ -351,9 +351,11 @@ Console.prototype = {
categories = this.get('categories'),
length = categories.length;
for (i = 0; i < length; i++) {
category = categories[i];
if (category.get('categoryid') === id) {
return category;
if (categories.hasOwnProperty(i)) {
category = categories[i];
if (category.get('categoryid') === id) {
return category;
}
}
}
return false;
@ -371,14 +373,14 @@ Console.prototype = {
courses = this.get('courses'),
length = courses.length;
for (i = 0; i < length; i++) {
if (!courses.hadOwnPropery(i)) {
if (courses.hasOwnProperty(i)) {
course = courses[i];
if (course.get('courseid') === id) {
return course;
}
}
}
return id;
return false;
},
/**
@ -827,6 +829,9 @@ Item.prototype = {
}
this.updated(true);
} else {
// Aha it succeeded but this is the top item in the list. Pagination is in play!
// Refresh to update the state of things.
window.location.reload();
}
},
@ -878,6 +883,9 @@ Item.prototype = {
}
this.updated(true);
} else {
// Aha it succeeded but this is the bottom item in the list. Pagination is in play!
// Refresh to update the state of things.
window.location.reload();
}
},

View File

@ -356,9 +356,11 @@ Console.prototype = {
categories = this.get('categories'),
length = categories.length;
for (i = 0; i < length; i++) {
category = categories[i];
if (category.get('categoryid') === id) {
return category;
if (categories.hasOwnProperty(i)) {
category = categories[i];
if (category.get('categoryid') === id) {
return category;
}
}
}
return false;
@ -376,14 +378,14 @@ Console.prototype = {
courses = this.get('courses'),
length = courses.length;
for (i = 0; i < length; i++) {
if (!courses.hadOwnPropery(i)) {
if (courses.hasOwnProperty(i)) {
course = courses[i];
if (course.get('courseid') === id) {
return course;
}
}
}
return id;
return false;
},
/**

View File

@ -126,7 +126,10 @@ Item.prototype = {
this.updated(true);
Y.log('Success: '+this.get('itemname')+' moved up by AJAX.', 'info', 'moodle-course-management');
} else {
Y.log(this.get('itemname')+' cannot be moved up as its the top item', 'warn', 'moodle-course-management');
// Aha it succeeded but this is the top item in the list. Pagination is in play!
// Refresh to update the state of things.
Y.log(this.get('itemname')+' cannot be moved up as its the top item on this page.', 'info', 'moodle-course-management');
window.location.reload();
}
},
@ -180,7 +183,10 @@ Item.prototype = {
this.updated(true);
Y.log('Success: '+this.get('itemname')+' moved down by AJAX.', 'info', 'moodle-course-management');
} else {
Y.log(this.get('itemname')+' cannot be moved down as its the last item', 'warn', 'moodle-course-management');
// Aha it succeeded but this is the bottom item in the list. Pagination is in play!
// Refresh to update the state of things.
Y.log(this.get('itemname')+' cannot be moved down as its the top item on this page.', 'info', 'moodle-course-management');
window.location.reload();
}
},

View File

@ -269,7 +269,7 @@ class coursecat implements renderable, cacheable_object, IteratorAggregate {
*
* @global moodle_database $DB
* @param array $ids An array of category ID's to load.
* @return array
* @return coursecat[]
*/
public static function get_many(array $ids) {
global $DB;
@ -1102,17 +1102,46 @@ class coursecat implements renderable, cacheable_object, IteratorAggregate {
* @return bool
*/
public static function has_manage_capability_on_any() {
return self::has_capability_on_any('moodle/category:manage');
}
/**
* Checks if the user has at least one of the given capabilities on any category.
*
* @param array|string $capabilities One or more capabilities to check. Check made is an OR.
* @return bool
*/
public static function has_capability_on_any($capabilities) {
global $DB;
if (!isloggedin() || isguestuser()) {
return false;
}
$cache = cache::make('core', 'coursecat');
$has = $cache->get('has_manage_capability');
if ($has !== false) {
return ((int)$has === 1);
if (!is_array($capabilities)) {
$capabilities = array($capabilities);
}
$keys = array();
foreach ($capabilities as $capability) {
$keys[$capability] = sha1($capability);
}
$has = false;
/* @var cache_session $cache */
$cache = cache::make('core', 'coursecat');
$hascapability = $cache->get_many($keys);
$needtoload = false;
foreach ($hascapability as $capability) {
if ($capability === '1') {
return true;
} else if ($capability === false) {
$needtoload = true;
}
}
if ($needtoload === false) {
// All capabilities were retrieved and the user didn't have any.
return false;
}
$haskey = null;
$fields = context_helper::get_preload_record_columns_sql('ctx');
$sql = "SELECT ctx.instanceid AS categoryid, $fields
FROM {context} ctx
@ -1123,14 +1152,25 @@ class coursecat implements renderable, cacheable_object, IteratorAggregate {
foreach ($recordset as $context) {
context_helper::preload_from_record($context);
$context = context_coursecat::instance($context->categoryid);
if (has_capability('moodle/category:manage', $context)) {
$has = true;
break;
foreach ($capabilities as $capability) {
if (has_capability($capability, $context)) {
$haskey = $capability;
break 2;
}
}
}
$recordset->close();
$cache->set('has_manage_capability', ($has) ? '1' : '0');
return $has;
if ($haskey === null) {
$data = array();
foreach ($keys as $key) {
$data[$key] = '0';
}
$cache->set_many($data);
return false;
} else {
$cache->set($haskey, '1');
return true;
}
}
/**
@ -1766,8 +1806,9 @@ class coursecat implements renderable, cacheable_object, IteratorAggregate {
* @see coursecat::update()
*
* @param coursecat $newparentcat
* @throws moodle_exception
*/
protected function change_parent_raw(coursecat $newparentcat) {
protected function change_parent_raw(coursecat $newparentcat) {
global $DB;
$context = $this->get_context();
@ -2458,7 +2499,39 @@ class coursecat implements renderable, cacheable_object, IteratorAggregate {
* @return coursecat
*/
public function get_parent_coursecat() {
return coursecat::get($this->parent);
return self::get($this->parent);
}
/**
* Returns true if the user is able to request a new course be created.
* @return bool
*/
public function can_request_course() {
global $CFG;
if (empty($CFG->enablecourserequests) || $this->id != $CFG->defaultrequestcategory) {
return false;
}
return !$this->can_create_course() && has_capability('moodle/course:request', $this->get_context());
}
/**
* Returns true if the user can approve course requests.
* @return bool
*/
public static function can_approve_course_requests() {
global $CFG, $DB;
if (empty($CFG->enablecourserequests)) {
return false;
}
$context = context_system::instance();
if (!has_capability('moodle/site:approvecourse', $context)) {
return false;
}
if (!$DB->record_exists('course_request', array())) {
return false;
}
return true;
}
}
@ -2519,6 +2592,9 @@ class course_in_list implements IteratorAggregate {
/** @var array array of course contacts - stores result of call to get_course_contacts() */
protected $coursecontacts;
/** @var bool true if the current user can access the course, false otherwise. */
protected $canaccess = null;
/**
* Creates an instance of the class from record
*
@ -2775,11 +2851,18 @@ class course_in_list implements IteratorAggregate {
* @return bool
*/
public function can_access() {
return can_access_course($this->record);
if ($this->canaccess === null) {
$this->canaccess = can_access_course($this->record);
}
return $this->canaccess;
}
/**
* Returns true if the user can edit this courses settings.
*
* Note: this function does not check that the current user can access the course.
* To do that please call require_login with the course, or if not possible call {@see course_in_list::can_access()}
*
* @return bool
*/
public function can_edit() {
@ -2788,6 +2871,10 @@ class course_in_list implements IteratorAggregate {
/**
* Returns true if the user can change the visibility of this course.
*
* Note: this function does not check that the current user can access the course.
* To do that please call require_login with the course, or if not possible call {@see course_in_list::can_access()}
*
* @return bool
*/
public function can_change_visibility() {
@ -2803,8 +2890,20 @@ class course_in_list implements IteratorAggregate {
return context_course::instance($this->__get('id'));
}
/**
* Returns true if this course is visible to the current user.
* @return bool
*/
public function is_uservisible() {
return $this->visible || has_capability('moodle/course:viewhiddencourses', $this->get_context());
}
/**
* Returns true if the current user can review enrolments for this course.
*
* Note: this function does not check that the current user can access the course.
* To do that please call require_login with the course, or if not possible call {@see course_in_list::can_access()}
*
* @return bool
*/
public function can_review_enrolments() {
@ -2813,6 +2912,10 @@ class course_in_list implements IteratorAggregate {
/**
* Returns true if the current user can delete this course.
*
* Note: this function does not check that the current user can access the course.
* To do that please call require_login with the course, or if not possible call {@see course_in_list::can_access()}
*
* @return bool
*/
public function can_delete() {
@ -2821,6 +2924,10 @@ class course_in_list implements IteratorAggregate {
/**
* Returns true if the current user can backup this course.
*
* Note: this function does not check that the current user can access the course.
* To do that please call require_login with the course, or if not possible call {@see course_in_list::can_access()}
*
* @return bool
*/
public function can_backup() {
@ -2829,6 +2936,10 @@ class course_in_list implements IteratorAggregate {
/**
* Returns true if the current user can restore this course.
*
* Note: this function does not check that the current user can access the course.
* To do that please call require_login with the course, or if not possible call {@see course_in_list::can_access()}
*
* @return bool
*/
public function can_restore() {

View File

@ -223,17 +223,21 @@ input.titleeditor { width: 330px; vertical-align: text-bottom; }
/** Two column layout */
#course-category-listings.columns-2 > #category-listing > div {border-right:1px solid #e1e1e8;}
#course-category-listings.columns-2 > #course-listing > div {border-left:1px solid #e1e1e8;position:relative;left:-1px;}
#course-category-listings.columns-2 > #course-listing > div {border-left:1px solid #e1e1e8;margin-left:-1px;}
#course-category-listings.columns-2.viewmode-courses.course-selected > #course-listing > div {border-right:1px solid #e1e1e8;margin-right:-1px;}
#course-category-listings.columns-2 > #course-detail > div {border-left:1px solid #e1e1e8;}
/** Three column layout */
#course-category-listings.columns-3 > #course-listing > div {border-right:1px solid #e1e1e8;border-left:1px solid #e1e1e8;height:100%;}
#course-category-listings.columns-3 #category-listing > div {border-right:1px solid #DDD;}
#course-category-listings.columns-3 #course-listing > div {border-right:1px solid #e1e1e8;border-left:1px solid #e1e1e8;margin-right:-1px;margin-left:-1px;}
#course-category-listings.columns-3 #course-detail > div {border-left:1px solid #DDD;}
#course-category-listings > div {}
#course-category-listings > div > div {min-height:300px;}
#course-category-listings h3 {margin:0;padding:0.6em 1em 0.5em;text-align:left;background-color:#f7f7f9;border-bottom:1px solid #e1e1e8;}
#course-category-listings h4 {margin:1em 0 0;padding:0.6em 1em 0.5em;text-align:left;}
#course-category-listings .listing-actions {text-align:center;}
#course-category-listings .listing-actions > * {margin:0.4em 0.3em 0.3em;}
#course-category-listings .listing-actions > * {margin:0.4em 0.3em 0.3em;display:inline-block;line-height:2.2em;}
#course-category-listings .listing-actions > .moodle-actionmenu {display:inline-block;}
#course-category-listings .listing-actions > .moodle-actionmenu .menu a {padding-left:1em;}
#course-category-listings .listing-actions .iconsmall {margin-left:0.5em;}
@ -242,8 +246,9 @@ input.titleeditor { width: 330px; vertical-align: text-bottom; }
#course-category-listings li {line-height:2.2em;}
#course-category-listings li > div {border-bottom:1px solid #fff;border-top:1px solid #fff;}
#course-category-listings li > div:hover {background-color:#fafafa;}
#course-category-listings li.highlight > div {background-color:#ddffaa;}
#course-category-listings li.highlight > div:hover {background-color:#ddffaa;}
#course-category-listings li.highlight > div,
#course-category-listings li.highlight > div:hover,
#course-category-listings li[data-selected='1'].highlight > div {background-color:#ddffaa;}
#course-category-listings li+li > div,
#course-category-listings li:first-child > div {border-top-color:#f7f7f9;}
#course-category-listings li .tree-icon {vertical-align:text-top;margin-right:0.5em;width:12px;height:12px;}
@ -264,6 +269,7 @@ input.titleeditor { width: 330px; vertical-align: text-bottom; }
#course-category-listings .item-actions {margin-right:1em;display:inline-block;display:initial;}
#course-category-listings .item-actions img {margin: 0 0.2em;}
#course-category-listings .item-actions .menu .menu-action {margin-right:1em;}
#course-category-listings li .tree-icon {margin-left:1em;}
#course-category-listings li li .tree-icon {margin-left:2em;}
@ -271,6 +277,7 @@ input.titleeditor { width: 330px; vertical-align: text-bottom; }
#course-category-listings li li li li .tree-icon {margin-left:4em;}
#course-category-listings li li li li li .tree-icon {margin-left:5em;}
#course-listing .listitem .drag-handle {margin-right:0.5em;}
#course-listing .listitem .idnumber {color:#a1a1a8;margin-right:2em;}
#course-listing .listitem .categoryname {display:inline-block;margin-left:1em;color:#a1a1a8;}
#course-listing .listitem .coursename {display:inline-block;min-width:50%;}
@ -290,8 +297,11 @@ input.titleeditor { width: 330px; vertical-align: text-bottom; }
#course-category-listings .listitem[data-visible="0"] > div .item-actions .action-hide,
#course-category-listings .listitem[data-visible="0"] > ul .item-actions.category-item-actions .action-hide,
#course-category-listings .listitem[data-visible="0"] > ul .item-actions.category-item-actions .action-show,
#course-category-listings .listitem:first-child > div .item-actions .action-moveup,
#course-category-listings .listitem:last-child > div .item-actions .action-movedown {display: none;}
#category-listing .listitem:first-child > div .item-actions .action-moveup,
#category-listing .listitem:last-child > div .item-actions .action-movedown,
#course-listing > .firstpage .listitem:first-child > div .item-actions .action-moveup,
#course-listing > .lastpage .listitem:last-child > div .item-actions .action-movedown {display: none;}
#course-category-listings .listitem > div a.without-actions {color: #333;}
@ -313,12 +323,8 @@ input.titleeditor { width: 330px; vertical-align: text-bottom; }
.coursecat-management-header {vertical-align:middle;}
.coursecat-management-header h2 {display:inline-block;text-align:left;}
.coursecat-management-header > div {display:inline-block;float:right;margin-left:1em;}
.coursecat-management-header select {max-width: 300px;
cursor: pointer;
padding: 0.4em 0.5em 0.45em 1em;
vertical-align: baseline;
white-space: nowrap;
}
.coursecat-management-header .view-mode-selector img {margin-left:0.5em;vertical-align: baseline;}
.coursecat-management-header select {max-width: 300px;white-space: nowrap;}
.listing-pagination,
.listing-pagination-totals {text-align:center;}
@ -345,14 +351,22 @@ input.titleeditor { width: 330px; vertical-align: text-bottom; }
#course-category-listings.columns-3 #category-listing > div,
#course-category-listings.columns-3 #course-listing > div,
#course-category-listings.columns-3 #course-detail {border:1px solid #e1e1e8;background-color:#FFF;}
#course-category-listings.columns-3 #course-detail > div {border:0;}
#course-category-listings.columns-3 #course-detail {width:100%;margin-top:1em;}
}
@media (max-width: 1199px) {
#course-category-listings.columns-2,
#course-category-listings.columns-3 {background-color:transparent;border:0;}
#course-category-listings.columns-2 #category-listing,
#course-category-listings.columns-2 #course-listing,
#course-category-listings.columns-2 #course-detail,
#course-category-listings.columns-3 #category-listing,
#course-category-listings.columns-3 #course-listing,
#course-category-listings.columns-3 #course-detail {width:100%;margin-bottom:1em;}
#course-category-listings.columns-2 #category-listing > div,
#course-category-listings.columns-2 #course-listing > div,
#course-category-listings.columns-2 #course-detail > div,
#course-category-listings.columns-3 #category-listing > div,
#course-category-listings.columns-3 #course-listing > div,
#course-category-listings.columns-3 #course-detail > div {border:1px solid #e1e1e8;background-color:#FFF;}

View File

@ -710,17 +710,22 @@ span.editinstructions {
vertical-align:text-top;
margin: 2px 6px 0 0;
}
&[data-selected='1'] > div {
background-color:#f7f7f9;
border-top-color: #e1e1e8;
border-bottom-color:#f7f7f9;
}
&[data-selected='1'] li:first-of-type > div,
&[data-selected='1'][data-expandable='0']+li > div {
border-top-color:#e1e1e8;
}
&[data-selected='1']:last-of-type > div {
-bottom-color:#e1e1e8;
&[data-selected='1'] {
> div {
background-color:#f7f7f9;
border-top-color: #e1e1e8;
border-bottom-color:#f7f7f9;
}
&.highlight > div {
background-color:#ddffaa;
}
li:first-of-type > div,
&[data-expandable='0']+li > div {
border-top-color:#e1e1e8;
}
&:last-of-type > div {
border-bottom-color:#e1e1e8;
}
}
// Tree item indenting to represent depth.
@ -811,10 +816,6 @@ span.editinstructions {
}
}
}
&:first-child > div .item-actions .action-moveup,
&:last-child > div .item-actions .action-movedown {
display: none;
}
}
#course-listing {
@ -837,6 +838,8 @@ span.editinstructions {
margin: 0 6px 0 0;
}
}
> .firstpage .listitem:first-child > div .item-actions .action-moveup,
> .lastpage .listitem:last-child > div .item-actions .action-movedown {display: none;}
.bulk-action-checkbox {
vertical-align:middle;
margin:-2px 6px 0 0;
@ -846,6 +849,10 @@ span.editinstructions {
.listitem.collapsed > ul.ml {
display: none;
}
.listitem {
&:first-child > div .item-actions .action-moveup,
&:last-child > div .item-actions .action-movedown {display: none;}
}
.course-count {
color:#a1a1a8;
margin-right:2rem;
@ -974,7 +981,7 @@ span.editinstructions {
}
#category-listing > div,
#course-listing > div,
#course-detail {
#course-detail > div {
border:1px solid #e1e1e8;
background-color:#FFF;
}
@ -987,6 +994,7 @@ span.editinstructions {
}
@media (max-width: 1199px) {
#course-category-listings.columns-2,
#course-category-listings.columns-3 {
background-color:transparent;
border:0;
@ -994,7 +1002,7 @@ span.editinstructions {
#course-listing,
#course-detail {
width:100%;
margin-bottom:1em;
margin:0 0 1em;
}
#category-listing > div,
#course-listing > div,

File diff suppressed because one or more lines are too long