]> git.mjollnir.org Git - moodle.git/commitdiff
roles explanation: MDL-13538 Make a table showing the result of has_capability for...
authortjhunt <tjhunt>
Wed, 12 Nov 2008 07:55:09 +0000 (07:55 +0000)
committertjhunt <tjhunt>
Wed, 12 Nov 2008 07:55:09 +0000 (07:55 +0000)
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'.

admin/roles/explain.php [new file with mode: 0755]
admin/roles/explainhascapabiltiy.php
lib/adminlib.php
theme/standard/styles_color.css
theme/standard/styles_layout.css
user/selector/lib.php
user/selector/script.js

diff --git a/admin/roles/explain.php b/admin/roles/explain.php
new file mode 100755 (executable)
index 0000000..e2c4a98
--- /dev/null
@@ -0,0 +1,215 @@
+<?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&amp;course=$courseid", 'type' => 'misc');
+            $navlinks[] = array('name' => $straction, 'link' => null, 'type' => 'misc');
+            $navigation = build_navigation($navlinks);
+
+            print_header($title, $fullname, $navigation, '', '', true, '&nbsp;', navmenu($course));
+
+        /// site header
+        } else {
+            $navlinks[] = array('name' => $fullname, 'link' => "$CFG->wwwroot/user/view.php?id=$contextuserid&amp;course=$courseid", 'type' => 'misc');
+            $navlinks[] = array('name' => $straction, 'link' => null, 'type' => 'misc');
+            $navigation = build_navigation($navlinks);
+            print_header($title, $course->fullname, $navigation, "", "", true, "&nbsp;", 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 .
+                        '&amp;contextid=' . $context->id . '&amp;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);
+?>
index 6af2c13e06f2b916e0540fb7394235dd4daba033..12280173f20358c85f0486f34d82fd4c38e3c21f 100644 (file)
@@ -76,6 +76,47 @@ foreach ($roles as $role) {
 }
 $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');
 
@@ -124,7 +165,9 @@ echo '</thead><tbody>';
 // 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);
     }
@@ -151,7 +194,16 @@ foreach ($contexts as $con) {
             } 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 = '';
@@ -162,9 +214,8 @@ echo '</tbody></table>';
 
 // 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();
index 9326535e9cb75269a0768f82ae78e55cacdaaf8c..df8c8ab59e2b75600dd3d3aa1f4bcd0f18fb5d31 100644 (file)
@@ -6115,3 +6115,134 @@ class admin_setting_managerepository extends admin_setting {
         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 &lt;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
index ff24cbd0e675f1132ab4177f991d595da0952b9c..87812b74634700d58090b274f8b53430228ce831 100644 (file)
@@ -1169,7 +1169,21 @@ table.quizreviewsummary td.cell {
   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;
index 44a02fc14b00ca28debad8716cec329c2d274e87..4f99e6a5c9356242573d86d6feac27d75e4d624e 100644 (file)
@@ -582,6 +582,11 @@ div.hide {
 #userselector_options .collapsibleregioncaption {
     font-weight: bold;
 }
+#userselector_options p {
+    margin:0.2em 0pt;
+    text-align:left;
+}
+
 /***
  *** Forms
  ***/
@@ -1106,13 +1111,13 @@ body#admin-modules table.generaltable td.c0
     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;
@@ -1120,8 +1125,15 @@ body#admin-modules table.generaltable td.c0
   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;
 }
@@ -1134,15 +1146,14 @@ table.rolecap .hiddenrow {
   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;
 }
 
index ecf4d36e0846254bb05b8947eaf8d3f02e234508..dde9405e816bf4a48982b874a08d4d2fc5fbe278 100644 (file)
@@ -144,6 +144,24 @@ abstract class user_selector_base {
         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
@@ -721,7 +739,7 @@ class potential_assignees_below_course extends role_assign_user_selector_base {
         $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()) {
index 0e8a292741062613059e29518bfc408c7c4b7313..0ced0bffbc7a731e16805c4b1bd75ac35b385752 100644 (file)
@@ -496,6 +496,8 @@ YAHOO.lang.augmentProto(user_selector, YAHOO.util.EventProvider);
  */
 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',