Moodle Capabilities, Roles & Permissions – Moodle 2.3.3

Moodle capabilities can be quite complicated to properly get your head around until you go quite deep into code and follow it to see where it all goes and where it all comes from.

Here is an attempt at explaining it, as far as I have worked out.

Before we look at the roles and capabilities themselves, first we should understand Moodle contexts and how they are structured.

Here is an example context from my Moodle database (mdl_context):

contextlevel

This defines what kind of context it is, based on the value.

These values are set in the accesslib.php file within Moodle.

10 = System

30 = User

40 = Course Category

50 = Course

70 = Module

80 = Block

So if the level is 50 we know the record is the context of a course.

If it’s 30 we know the record is the context of a user.

And so on.

instanceid

This points to a record in another table, dependant on what level the context has.

For instance, if it’s a course context, the instanceid is a course id from mdl_course.

If it’s a user context, instanceid is a user id from mdl_user.

And so on.

So in this example, this context record is a course context pointing to course id (2).

path

The context paths are very important in working out whether a user has a certain capability or not. They are structured as a string with forward slashes separating each context id.

From left to right, it goes from parent to children contexts.

So using the example record from earlier, our path is 1/3/19

The last element in that path is 19, which is the id of the record itself (that is always the case).

From what we did earlier we know that context id 19 is a course context pointing to course id (2).

Next we move one step up to its first parent, which is context id 3:

As we can see from the database that context is level 40 which is a course category of id (1).

So that makes sense, we have a course (id 2) within a course category (id 1).

And then the last step up the parenting ladder is to context id 1. Context id 1 will always be the first element in the path (the highest parent) as context id 1 refers to the SYSTEM:

So if we break that down into a tree we can see that it makes total sense really:

So the context we are looking at is a course, within a course category, within the whole system.

Makes sense so far.

The context record also has a depth field which just specifies how many layers of contexts there are in the path. So ours has 3.

So now we know what the contexts actually are and how they are structured it’s time to look at how they are used to specify what capabilities a user has.

When you are logged into Moodle, there will be a property on your $USER object called “access” which stores various pieces of information in an array. The two bits of information we are interested in are the elements “ra” and “rdef”.

If you print out the access property doing something like:

print_object($USER->access);

You should be able to see all this information for the currently logged in user.

$USER->access[‘ra’]:

The “ra” element holds an array of all the context paths the user is assigned to (ra = “role assignments”).

This might look something like this:

Each context path holds the role ids that the user has linked to that particular context. Role ids can be looked up in the mdl_role table. For example, these are mine:

So let’s look at that example we have in the [‘ra’] element.

Our logged in user has a role on 4 contexts within Moodle:

/1 – System. Here we have role id 7, which means that our user is an AUTHENICATED USER in the context of the SYSTEM

/1/2 – This context refers to the front page course and our user has roles 8 & 9. So that means that she is an AUTHENTICATED USER ON FRONTPAGE and a FRONT PAGE TEACHER in the context of the front page course (same as any course really).

/1/3/19 – This context refers to a course with id 2 (as used in an example earlier). With role id 4 which is non-editing teacher. This means our user is a NON-EDITING TEACHER in the context of COURSE ID 2

/1/45 – This is a user context (lvl 30) which refers to a user with the id of 14 (instanceid). It has the role id of 10, which is one of my custom roles “Personal Tutor”. This means that our user is a PERSONAL TUTOR in the context of USER id 14, who happens to be called George Reynolds.

/1/52 – This is the same as above but for a different student.

So that all makes logical sense, the user is a <ROLE> in the context of <context>.

So that information is all loaded up when the user logs in (if they are assigned to a new role they might need to logout/login for it to appear).

Next we’ll have a brief look at the [‘rdef’] element, which will be much larger and look something like:

Etc….

Each element within the [‘rdef’] array is defined with the system context (/1) and a role id (e.g. in that picture “9”) in the format of: [CONTEXT:ROLE]. Each of those then contains an array of capabilities that role has.

I’ll explain that a bit more later once we get there.

So let’s say I have something in Moodle that generates a report about a student and I only want certain people to be able to see that report (certainly not the student!).

Let’s call the capability “view_report” and we’ll say it’s in a block called “test” for example’s sake.

In our example system there are only two types of people we want to be able to see this report:

-Teachers who are teaching on the student’s course

-The student’s personal tutor

And again as an example let’s say the course the student is on is ID # 2, since we used that earlier and it’s easier to reuse the same example. Let’s also say our user is user id # 21.

So, if the user is a teacher (non-editing for this example) on course # 2 – YES

If the user is a personal tutor of user id #21 – YES

Otherwise – NO

At the moment I have not given any roles the ALLOW value for this capability, so everyone has it set to either “Not Set” or “Prevent”, so nobody will pass the test for has_capability on it.

So firstly I’ll go into Moodle Site Administration -> Users -> Permissions -> Define Roles and I’ll set that non-editing teachers have the value of ALLOW for capability “view_report”. This in itself is meaningless, what is really relevant is the context which is passed into the has_capability function.

Since we are testing is the user is a non-editing teacher on course id #2, the context we want to use is:

get_context_instance(CONTEXT_COURSE, 2)

Which when printed out looks like this:

So this is just a record from mdl_context really with the same values as we’d find in there.

So let’s pass that into our has_capability function and then follow the process it goes through:

If (has_capability('block/test:view_report', get_context_instance(CONTEXT_COURSE, 2))){

// Do something…

}

The first thing that happens is that the path of the context we passed in is converted to an array of all its parent/child elements.

So we passed in a context with the path “/1/3/19”, which is converted to the array:

$paths = array(

“/1/3/19”, “/1/3”, “/1”

);

Next an array of all your roles within any of these contexts is built up.

So we loop through that array of context paths and see if there is a record within our [‘ra’] element of the $USER->access array.

So first one, is there an element [‘ra’][‘/1/3/19’]? Yes there is an in there we have the role id of 4, so that role id is added to our new $roles array with the value of null (I’ll explain why null in a bit).

Next one, is there a [‘ra’][‘1/3’]? No, so move on.

Is there a [‘ra’][‘/1’]? Yes, with the role id of 7, so that’s added to our $roles array.

Now we have an array called $roles which looks like this:

$roles = array(

4 => null,

7 => null

);

What does that mean? That means that in the given context (the course we passed in) and all its parent contexts (category and then system) we have the following valid roles assigned to us:

4 – Non-Editing teacher

7 – Authenticated User

If at this point we have passed in the context of a course we were NOT assigned to our $roles array would only have the value 7. We have the value 4 here because we ARE a non-editing teacher on that specific course.

So now we have an array of all the roles we have within the given context, we can see if any of those roles has the capability we are looking for.

To do that it loops through the $paths array again and for each element in there, it then loops round the $roles array as well. What it’s doing is looking in [‘rdef’] for an element with the name [PATH:ROLE].

So for example let’s loop through everything like the code does:

Looping $paths

-/1/3/19

-Looping $roles

  • 4
  • Is there an element [‘rdef’][‘/1/3/19:4’]?
  • No, move on
  • 7
  • Is there an element [‘rdef’][‘/1/3/19:7’]?
  • No, move on

-/1/3

-Looping $roles

  • 4
  • Is there an element [‘rdef’][‘/1/3:4’]?
  • No, move on
  • 7
  • Is there an element [‘rdef’][‘/1/3:7’]?
  • No, move on

-/1

-Looping $roles

  • 4
  • Is there an element [‘rdef’][‘/1:4’]?
  • Yes there is.
  • Does it have its own array element with the name of the capability in it? [‘rdef’][‘/1:4’][‘block/test:view_report’]?
  • Yes it does
  • What value is it?
  • 1 = ALLOW
  • -1 = PREVENT
  • -1000 = PROHIBIT
  • 7
  • Is there an element [‘rdef’][‘/1:7’]?
  • No, move on

So now we’ve finished looping everything we found one value in our [‘rdef’] and that was with the value 1. It was in the path and role: /1:4, which is system with role id 4 (non-editing teacher).

Where it can be confusing is that when you make the has_capability call it seems like you are saying:

“Does this user has the capability <x> on the course <y>”

This isn’t the case. Essentially it’s doing 2 different things, firstly its saying:

“For this given context (course) is the user assigned to it in any roles?”

-Our answer here was yes: role 4 and role 7

“Do any of these roles have capability <x>?”

-Our answer was yes – non-editing teacher has it

When you set a capability to a role, you are setting it at system level, not to a specific course or a specific user (usually), so the has_capability check is checking if you have a role in that context (any role will do) and then if any of those roles have that capability.

So for example, let’s say the following is true:

-Logged in user is a non-editing teacher on course id 2

-Logged in user is not assigned to course id 3 at all

-Logged in user is a student on course id 6

We then do test runs of has_capability using those course ids along with CONTEXT_COURSE to see if the user has that capability in the context of those 3 courses.

Course ID 2

The first check will return that the user has the roles 4 & 7 in this context (note: not “on this course”, but “in this context”).

Then it checks if any of those roles have the capability, which returns true as role id 4 (non-editing teacher) does.

Course ID 3

Returns only role id 7 (authenticated user in system context) and so the check for the capability on that role returns false.

Course ID 6

Returns roles 5 (student) and 7. It checks those roles for the capability and returns false because they don’t have it.

It works exactly the same for other contexts, for example if we only wanted Personal Tutors to have that capability, we would pass in the context of the student. The first part would check to see if the user has any valid roles assigned to that student, and if so, it then checks to see if they of those roles have that capability in the system.