This implements the page showing the table. It is not yet integrated into the tab bar. To try this, go to an Assign roles page (one with URL .../admin/roles/assign.php?contextid=...) and change the 'assign' to 'explain'.
--- /dev/null
+<?php // $Id$
+ // Script to assign users to contexts
+
+ require_once(dirname(__FILE__) . '/../../config.php');
+ require_once($CFG->libdir.'/adminlib.php');
+ require_once($CFG->dirroot.'/user/selector/lib.php');
+
+ $contextid = required_param('contextid',PARAM_INT);
+ $contextuserid = optional_param('userid', 0, PARAM_INT); // needed for user tabs
+ $courseid = optional_param('courseid', 0, PARAM_INT); // needed for user tabs
+
+ if (! $context = get_context_instance_by_id($contextid)) {
+ print_error('wrongcontextid', 'error');
+ }
+ $isfrontpage = $context->contextlevel == CONTEXT_COURSE && $context->instanceid == SITEID;
+ $contextname = print_context_name($context);
+
+ if ($context->contextlevel == CONTEXT_COURSE) {
+ $courseid = $context->instanceid;
+ if (!$course = $DB->get_record('course', array('id'=>$courseid))) {
+ print_error('invalidcourse', 'error');
+ }
+
+ } else if (!empty($courseid)){ // we need this for user tabs in user context
+ if (!$course = $DB->get_record('course', array('id'=>$courseid))) {
+ print_error('invalidcourse', 'error');
+ }
+
+ } else {
+ $courseid = SITEID;
+ $course = clone($SITE);
+ }
+
+/// Check login and permissions.
+ require_login($course);
+ $canview = has_any_capability(array('moodle/role:assign', 'moodle/role:safeoverride',
+ 'moodle/role:override', 'moodle/role:manage'), $context);
+ if (!$canview) {
+ print_error('nopermissions', 'error', '', get_string('explainpermissions', 'role'));
+ }
+
+/// These are needed early because of tabs.php
+ $assignableroles = get_assignable_roles($context, ROLENAME_BOTH);
+ $overridableroles = get_overridable_roles($context, ROLENAME_BOTH);
+
+/// Get the user_selector we will need.
+/// Teachers within a course just get to see the same list of people they can
+/// assign roles to. Admins (people with moodle/role:manage) can run this report for any user.
+ $options = array('context' => $context, 'roleid' => 0);
+ if ($context->contextlevel > CONTEXT_COURSE && !is_inside_frontpage($context) && !has_capability('moodle/role:manage', $context)) {
+ $userselector = new potential_assignees_below_course('reportuser', $options);
+ } else {
+ $userselector = new potential_assignees_course_and_above('reportuser', $options);
+ }
+ $userselector->set_multiselect(false);
+ $userselector->set_rows(10);
+
+/// Work out an appropriate page title.
+ $title = get_string('explainpermissionsin', 'role', $contextname);
+ $straction = get_string('explainpermissions', 'role'); // Used by tabs.php
+
+/// Print the header and tabs
+ if ($context->contextlevel == CONTEXT_USER) {
+ $contextuser = $DB->get_record('user', array('id' => $contextuserid));
+ $fullname = fullname($contextuser, has_capability('moodle/site:viewfullnames', $context));
+
+ /// course header
+ $navlinks = array();
+ if ($courseid != SITEID) {
+ if (has_capability('moodle/course:viewparticipants', get_context_instance(CONTEXT_COURSE, $courseid))) {
+ $navlinks[] = array('name' => get_string('participants'), 'link' => "$CFG->wwwroot/user/index.php?id=$courseid", 'type' => 'misc');
+ }
+ $navlinks[] = array('name' => $fullname, 'link' => "$CFG->wwwroot/user/view.php?id=$contextuserid&course=$courseid", 'type' => 'misc');
+ $navlinks[] = array('name' => $straction, 'link' => null, 'type' => 'misc');
+ $navigation = build_navigation($navlinks);
+
+ print_header($title, $fullname, $navigation, '', '', true, ' ', navmenu($course));
+
+ /// site header
+ } else {
+ $navlinks[] = array('name' => $fullname, 'link' => "$CFG->wwwroot/user/view.php?id=$contextuserid&course=$courseid", 'type' => 'misc');
+ $navlinks[] = array('name' => $straction, 'link' => null, 'type' => 'misc');
+ $navigation = build_navigation($navlinks);
+ print_header($title, $course->fullname, $navigation, "", "", true, " ", navmenu($course));
+ }
+
+ $showroles = 1;
+ $currenttab = 'explain';
+ include_once($CFG->dirroot.'/user/tabs.php');
+
+ } else if ($context->contextlevel == CONTEXT_SYSTEM) {
+ admin_externalpage_setup('assignroles');
+// admin_externalpage_setup('explainpermissions');
+ admin_externalpage_print_header();
+
+ } else if ($context->contextlevel == CONTEXT_COURSE and $context->instanceid == SITEID) {
+ admin_externalpage_setup('explainfrontpagepermissions');
+ admin_externalpage_print_header();
+ $currenttab = 'explain';
+ include_once('tabs.php');
+
+ } else {
+ $currenttab = 'explain';
+ include_once('tabs.php');
+ }
+
+/// Print heading.
+ print_heading_with_help($title, 'explainpermissions');
+
+/// If a user has been chosen, show all the permissions for this user.
+ $user = $userselector->get_selected_user();
+ if (!is_null($user)) {
+
+ /// Class for rendering the table.
+ class explain_cabability_table extends cabability_table_base {
+ protected $user;
+ protected $fullname;
+ protected $baseurl;
+ protected $stryes;
+ protected $strno;
+ protected $strexplanation;
+ private $hascap;
+ public function __construct($context, $user) {
+ global $CFG;
+ parent::__construct($context, 'explaincaps');
+ $this->user = $user;
+ $this->fullname = fullname($user);
+ $this->baseurl = $CFG->wwwroot . '/' . $CFG->admin .
+ '/roles/explainhascapabiltiy.php?user=' . $user->id .
+ '&contextid=' . $context->id . '&capability=';
+ $this->stryes = get_string('yes');
+ $this->strno = get_string('no');
+ $this->strexplanation = get_string('explanation');
+ }
+ protected function add_header_cells() {
+ echo '<th>' . get_string('allowed', 'role') . '</th>';
+ echo '<th>' . $this->strexplanation . '</th>';
+ }
+ protected function num_extra_columns() {
+ return 2;
+ }
+ protected function skip_row($capability) {
+ return $capability->name != 'moodle/site:doanything' && is_legacy($capability->name);
+ }
+ protected function get_row_classes($capability) {
+ $this->hascap = has_capability($capability->name, $this->context, $this->user->id);
+ if ($this->hascap) {
+ return array('yes');
+ } else {
+ return array('no');
+ }
+ }
+ protected function add_row_cells($capability) {
+ if ($this->hascap) {
+ $result = $this->stryes;
+ $tooltip = 'explainwhyhascap';
+ } else {
+ $result = $this->strno;
+ $tooltip = 'explainwhyhasnotcap';
+ }
+ $a = new stdClass;
+ $a->username = $this->fullname;
+ $a->capability = $capability->name;
+ echo '<td>' . $result . '</td>';
+ echo '<td>';
+ link_to_popup_window($this->baseurl . $capability->name, 'hascapabilityexplanation',
+ $this->strexplanation, 600, 600, get_string($tooltip, 'role', $a));
+ echo '</td>';
+ }
+ }
+
+ require_js(array('yui_yahoo', 'yui_dom', 'yui_event'));
+ require_js($CFG->admin . '/roles/roles.js');
+ print_box_start('generalbox boxaligncenter');
+ print_heading(get_string('permissionsforuser', 'role', fullname($user)), '', 3);
+
+ $table = new explain_cabability_table($context, $user);
+ $table->display();
+ print_box_end();
+
+ $selectheading = get_string('selectanotheruser', 'role');
+ } else {
+ $selectheading = get_string('selectauser', 'role');
+ }
+
+/// Show UI for choosing a user to report on.
+ print_box_start('generalbox boxwidthnormal boxaligncenter', 'chooseuser');
+ echo '<form method="get" action="' . $CFG->wwwroot . '/' . $CFG->admin . '/roles/explain.php" >';
+
+/// Hidden fields.
+ echo '<input type="hidden" name="contextid" value="' . $context->id . '" />';
+ if (!empty($contextuserid)) {
+ echo '<input type="hidden" name="userid" value="' . $contextuserid . '" />';
+ }
+ if ($courseid && $courseid != SITEID) {
+ echo '<input type="hidden" name="courseid" value="' . $courseid . '" />';
+ }
+
+/// User selector.
+ print_heading('<label for="reportuser">' . $selectheading . '</label>', '', 3);
+ $userselector->display();
+
+/// Submit button and the end of the form.
+ echo '<p id="chooseusersubmit"><input type="submit" value="' . get_string('showthisuserspermissions', 'role') . '" /></p>';
+ echo '</form>';
+ print_box_end();
+
+/// Appropriate back link.
+ if (!$isfrontpage && ($url = get_context_url($context))) {
+ echo '<div class="backlink"><a href="' . $url . '">' .
+ get_string('backto', '', $contextname) . '</a></div>';
+ }
+
+ print_footer($course);
+?>
}
$rolenames = role_fix_names($rolenames, $context);
+// Pass over the data once, to find the cell that determines the result.
+$userhascapability = has_capability($capability, $context, $userid, false);
+$areprohibits = false;
+$decisiveassigncon = 0;
+$decisiveoverridecon = 0;
+foreach ($contexts as $con) {
+ if (!empty($accessdata['ra'][$con->path])) {
+ // The array_unique here is to work around bug MDL-14817. Once that bug is
+ // fixed, it can be removed
+ $ras = array_unique($accessdata['ra'][$con->path]);
+ } else {
+ $ras = array();
+ }
+ foreach ($contexts as $ocon) {
+ $summedpermission = 0;
+ foreach ($ras as $roleid) {
+ if (isset($accessdata['rdef'][$ocon->path . ':' . $roleid][$capability])) {
+ $perm = $accessdata['rdef'][$ocon->path . ':' . $roleid][$capability];
+ } else {
+ $perm = CAP_INHERIT;
+ }
+ if ($perm == CAP_PROHIBIT) {
+ $areprohibits = true;
+ $decisiveassigncon = 0;
+ $decisiveoverridecon = 0;
+ break 3;
+ }
+ $summedpermission += $perm;
+ }
+ if ($summedpermission) {
+ $decisiveassigncon = $con->id;
+ $decisiveoverridecon = $ocon->id;
+ break 2;
+ }
+ }
+}
+if (!$areprohibits && !$decisiveassigncon) {
+ $decisiveassigncon = SYSCONTEXTID;
+ $decisiveoverridecon = SYSCONTEXTID;
+}
+
// Make a fake role to simplify rendering the table below.
$rolenames[0] = get_string('none');
// Now print the bulk of the table.
foreach ($contexts as $con) {
if (!empty($accessdata['ra'][$con->path])) {
- $ras = $accessdata['ra'][$con->path];
+ // The array_unique here is to work around bug MDL-14817. Once that bug is
+ // fixed, it can be removed
+ $ras = array_unique($accessdata['ra'][$con->path]);
} else {
$ras = array(0);
}
} else {
$permission = $strperm[$perm];
}
- echo '<td class="cell ' . $cssclasses[$perm] . '">' . $permission . '</td>';
+ $classes = $cssclasses[$perm];
+ if (!$areprohibits && $decisiveassigncon == $con->id && $decisiveoverridecon == $ocon->id) {
+ $classes .= ' decisive';
+ if ($userhascapability) {
+ $classes .= ' has';
+ } else {
+ $classes .= ' hasnot';
+ }
+ }
+ echo '<td class="cell ' . $classes . '">' . $permission . '</td>';
}
echo '</tr>';
$firstcell = '';
// Finish the page.
echo get_string('explainpermissionsinfo', 'role');
-if ($userid && $capability != 'moodle/site:doanything' &&
- has_capability('moodle/site:doanything', $context, $userid) &&
- !has_capability($capability, $context, $userid, false)) {
+if ($userid && $capability != 'moodle/site:doanything' && !$userhascapability &&
+ has_capability('moodle/site:doanything', $context, $userid)) {
echo '<p>' . get_string('explainpermissionsdoanything', 'role', $capability) . '</p>';
}
close_window_button();
return highlight($query, $output);
}
}
+
+/**
+ * This class represents a table with one row for each of a list of capabilities
+ * where the first cell in the row contains the capability name, and there is
+ * arbitrary stuff in the rest of the row. This class is used by
+ * admin/roles/manage.php, override.php and explain.php. There is also some
+ * ajaxy search UI shown at the top, if JavaScript is on.
+ */
+abstract class cabability_table_base {
+ protected $context;
+ protected $capabilities = array();
+ protected $id;
+ protected $classes = array('rolecap');
+ const NUM_CAPS_FOR_SEARCH = 12;
+
+ /**
+ * Constructor
+ * @param object $context the context this table relates to.
+ * @param string $id what to put in the id="" attribute.
+ */
+ public function __construct($context, $id) {
+ $this->context = $context;
+ $this->capabilities = fetch_context_capabilities($context);
+ $this->id = $id;
+ }
+
+ /**
+ * Use this to add class="" attributes to the table. You get the rolecap by
+ * default.
+ * @param array $classnames of class names.
+ */
+ public function add_classes($classnames) {
+ $this->classes = array_unique(array_merge($this->classes, $classnames));
+ }
+
+ /**
+ * Display the table.
+ */
+ public function display() {
+ echo '<table class="' . implode(' ', $this->classes) . '" id="' . $this->id . '">' . "\n<thead>\n";
+ echo '<tr><th class="name" align="left" scope="col">' . get_string('capability','role') . '</th>';
+ $this->add_header_cells();
+ echo "</tr>\n</thead>\n<tbody>\n";
+
+ /// Loop over capabilities.
+ $contextlevel = 0;
+ $component = '';
+ foreach ($this->capabilities as $capability) {
+ if ($this->skip_row($capability)) {
+ continue;
+ }
+
+ /// Prints a breaker if component or name or context level has changed
+ if (component_level_changed($capability, $component, $contextlevel)) {
+ $this->print_heading_row($capability);
+ }
+ $contextlevel = $capability->contextlevel;
+ $component = $capability->component;
+
+ /// Start the row.
+ echo '<tr class="' . implode(' ', array_unique(array_merge(array('rolecap'),
+ $this->get_row_classes($capability)))) . '">';
+
+ /// Table cell for the capability name.
+ echo '<td class="name"><span class="cap-desc">' . get_capability_docs_link($capability) .
+ '<span class="cap-name">' . $capability->name . '</span></span></td>';
+
+ /// Add the cells specific to this table.
+ $this->add_row_cells($capability);
+
+ /// End the row.
+ echo "</tr>\n";
+ }
+
+ /// End of the table.
+ echo "</tbody>\n</table>\n";
+ if (count($this->capabilities) > cabability_table_base::NUM_CAPS_FOR_SEARCH) {
+ print_js_call('cap_table_filter.init',
+ array($this->id, get_string('search'), get_string('clear')));
+ }
+ }
+
+ /**
+ * Used to output a heading rows when the context level or component changes.
+ * @param object $capability gives the new component and contextlevel.
+ */
+ protected function print_heading_row($capability) {
+ echo '<tr class="rolecapheading header"><td colspan="' . (1 + $this->num_extra_columns()) . '" class="header"><strong>' .
+ get_component_string($capability->component, $capability->contextlevel) .
+ '</strong></td></tr>';
+
+ }
+
+ /** For subclasses to override, output header cells, after the initial capability one. */
+ protected abstract function add_header_cells();
+
+ /** For subclasses to override, return the number of cells that add_header_cells/add_row_cells output. */
+ protected abstract function num_extra_columns();
+
+ /**
+ * For subclasses to override. Allows certain capabilties (e.g. legacy capabilities)
+ * to be left out of the table.
+ *
+ * @param object $capability the capability this row relates to.
+ * @return boolean. If true, this row is omitted from the table.
+ */
+ protected function skip_row($capability) {
+ return false;
+ }
+
+ /**
+ * For subclasses to override. A change to reaturn class names that are added
+ * to the class="" attribute on the <tr> for this capability.
+ *
+ * @param object $capability the capability this row relates to.
+ * @return array of class name strings.
+ */
+ protected function get_row_classes($capability) {
+ return array();
+ }
+
+ /**
+ * For subclasses to override. Output the data cells for this capability. The
+ * capability name cell will already have been output.
+ *
+ * You can rely on get_row_classes always being called before add_row_cells.
+ *
+ * @param object $capability the capability this row relates to.
+ */
+ protected abstract function add_row_cells($capability);
+}
\ No newline at end of file
background-color:#FFFFFF;
border: 1px solid #cecece;
}
-
+table.explainpermissions .decisive.has,
+#explaincaps .rolecap.yes {
+ background-color: #ddffdd;
+}
+table.explainpermissions .decisive.hasnot,
+#explaincaps .rolecap.no {
+ background-color: #ffdddd;
+}
+table.explainpermissions .prohibit {
+ background-color: #ffbbff;
+ font-weight: bold;
+}
+table.explainpermissions .decisive {
+ font-weight: bold;
+}
#admin-roles-manage .capdefault {
background-color:#dddddd;
border: 1px solid #cecece;
#userselector_options .collapsibleregioncaption {
font-weight: bold;
}
+#userselector_options p {
+ margin:0.2em 0pt;
+ text-align:left;
+}
+
/***
*** Forms
***/
font-weight: normal;
}
-#admin-roles-manage table.roledesc,
-#admin-roles-override table.roledesc {
+table.roledesc {
margin-left:auto;
margin-right:auto;
}
#admin-roles-manage .backlink,
+#admin-roles-explain .backlink,
#admin-roles-assign .backlink,
#admin-roles-override .backlink {
text-align: right;
margin: 2em auto 1em;
}
-#admin-roles-manage table.rolecap,
-#admin-roles-override table.rolecap {
+#admin-roles-explain #chooseuser h3 {
+ margin-top: 0;
+}
+#admin-roles-explain #chooseusersubmit {
+ margin: 1em 0 0;
+ text-align: center;
+}
+
+table.rolecap {
margin-left:auto;
margin-right:auto;
}
display: none;
}
-.rolecap .inherit,
-.rolecap .allow,
-.rolecap .prevent,
-.rolecap .prohibit {
+table.rolecap .inherit,
+table.rolecap .allow,
+table.rolecap .prevent,
+table.rolecap .prohibit {
text-align:center;
}
-#admin-roles-manage .rolecap .cap-desc .cap-name,
-#admin-roles-override .rolecap .cap-desc .cap-name {
+table.rolecap .cap-desc .cap-name {
display: block;
}
return $this->selected;
}
+ /**
+ * Convenience method for when multiselect is false (throws an exception if not).
+ * @return object the selected user object, or null if none.
+ */
+ public function get_selected_user() {
+ if ($this->multiselect) {
+ throw new moodle_exception('cannotcallusgetselecteduser');
+ }
+ $users = $this->get_selected_users();
+ if (count($users) == 1) {
+ return reset($users);
+ } else if (count($users) == 0) {
+ return null;
+ } else {
+ throw new moodle_exception('userselectortoomany');
+ }
+ }
+
/**
* If you update the database in such a way that it is likely to change the
* list of users that this component is allowed to select from, then you
$order = ' ORDER BY lastname ASC, firstname ASC';
$params[] = $this->context->id;
- $params[] = $this->roleid;
+ $params[] = $this->roleid;
// Check to see if there are too many to show sensibly.
if (!$this->is_validating()) {
*/
function user_selector_options_tracker() {
var oself = this;
+
+ // Add event listeners.
YAHOO.util.Event.addListener('userselector_preserveselectedid', 'click',
function(e) { oself.handle_option_change('userselector_preserveselected') });
YAHOO.util.Event.addListener('userselector_autoselectuniqueid', 'click',