Capabilities in Shared Context

Capabilities in Shared Context

by Eric Bollens -
Number of replies: 6
The basic premise: I am trying to modify message/discussion.php (and message/user.php) so that a user only sees the fullname() of another user if they have either user:viewdetails or user:viewfullnames in a shared context. Basically, in this modification, any teacher who has the user enrolled should be able to see the full name but other teachers should not be able to see the full name. However, I've run into difficulty sculpting the right set of statements to run a has_capability check on the correct contexts.

Initially I thought I'd use this conditional:

if( ($USER->id == $user->id)
or has_capability('moodle/user:viewdetails', $sitecontext)
or has_capability('moodle/site:viewfullnames', $sitecontext) ){

However, this clearly won't work. It only returns true for the administrator or the user him/herself (assume I define $sitecontext since its not in discussion.php). Instead, what I think I need is a has_capability('moodle/user:viewdetails', $coursecontext) check for all $coursecontext values shared between $USER and $user. Any ideas on an efficient and streamlined way of doing this? Messaging does not normally involve a course so I can't create a course context for discussion.php like in user/view.php.
Average of ratings: -
In reply to Eric Bollens

Re: Capabilities in Shared Context

by Martín Langhoff -
It will be insanely expensive to calculate all the possible "shared contexts". So while it is possible, it's not possible to do it acceptably fast.

At least not at the moment anyway. There are some new data structures related to roles/caps that will appear in a late 1.9 or 2.0 that might allow us to do it - using the context.path field a bit. But it would have to be done as part of accesslib and with some pretty goopy SQL.

Edit: the other thing we've been doing wrt messaging within a course is resurrecting mod/dialogue for people that want course-scoped messaging, where logs are kept in the course and can be seen by other teachers, etc.
In reply to Martín Langhoff

Re: Capabilities in Shared Context

by Eric Bollens -
Darn, that's what I feared. Do you have any ideas on inexpensive alternatives that might get me "close" to the desired outcome?
In reply to Eric Bollens

Re: Capabilities in Shared Context

by Martín Langhoff -

Hmmmm. It's all a big mess.

Once the new code is in, you can just do

  • get_my_courses() on the "looked-at" user (which will fetch the courses with a handy context obj)
  • loop over those and ask if the looking user has_capability() over each of those course contexts

This will only take into account "shared course contexts", this will incur in 3 DBq to load get_my_courses() for the looked-at user, and the rest will be damn fast if the "looker" is $USER. Otherwise, it'll be an additional 3DBq.

Looking strictly at shared course contexts makes sense IMHO, and keeps you well within the optimised codepaths in the new accesslib.

With a vanilla v1.8... you cannot resolve it efficiently without the path field. Your starting point would be

SELECT rc.roleid,rc.capability,rc.permission,ctx.path
FROM   mdl_role_capabilities rc
JOIN   mdl_context ctx 
  ONrc.contextid=ctx.id
JOIN   mdl_role_assignments  ra
  ON   rc.roleid=ra.roleid
WHERE  ctx.contextlevel in (10,40,50)
       AND ra.userid=$mylookinguser
       AND (rc.capability='moodle/user:viewdetails'
       OR rc.capability='moodle/user:whatever');

And a similar one for the looked-at user to find where he is a participant.

But then to draw overlaps and aggregate the "permission" field properly based on those 2 datasets, you need the context path.

In reply to Martín Langhoff

Re: Capabilities in Shared Context

by Eric Bollens -
Makes sense for the most part. However, confused on the context.path call - no such column exists in my Moodle database. Has this been added in 1.9 or something?
In reply to Martín Langhoff

Re: Capabilities in Shared Context

by Eric Bollens -
Martín, I've been working at this quite a bit today. Initially tried pursuing your suggested route but ran into some trouble because I'm not sure I fully understand the relationships between rc.permission and ctx.contextlevel - worried about ensuring that a PROHIBIT in one context carries over appropriately.

Tried another route after, determining all shared contexts and then running has_capability() within a loop. However, I am unsure of the database strain caused by has_capability() - somewhere I remember hearing about a capability cache so I am hoping its fairly minimal. If so, what do you think about this approach:

SELECT ra.contextid,ctx.contextlevel,ctx.instanceid
FROM mdl_context ctx
JOIN mdl_role_assignments ra
ON ctx.id = ra.contextid
WHERE ctx.contextlevel IN (10,40,50)
AND ra.userid IN ( '" . $USER->id ."', '" . $user->id . "' )
GROUP BY ra.contextid
HAVING count(ra.contextid) > 1
AND count(ra.userid) > 1;

Then I add the site level context (1,10,0) to the array in the event that both users don't have roles at the system level context. Then in a foreach() loop I simply run a has_capability() check. Will this kill performance when users share a good number of contexts or no?
In reply to Eric Bollens

Re: Capabilities in Shared Context

by Martín Langhoff -

I am hoping its fairly minimal

Don't hope - measure, it's easy wink

Enable perf stats, specially DB perf stats and see what it tells you. I suspect it'll be horrid mainly because you are pulling a ton of contexts in. But I may just be a worrywart.

And yes, context.path is a 1.9 trick.

Your SQL doesn't look right to me...

  • It will return too many matches. You really want to narrow down the roleids you want on each side. For the looker, there is a specific cap you need. Figure out what roles have some definition of it, and then look for RAs of matching the roleids and userid.
  • Once you have narrowed it down really tight, start your looping. Your query will catch too much.
  • Special case: the "Default logged in role" has the cap with a >0 permission, then the answer is yes.

Will this kill performance when users share a good number of contexts or no?

This will kill perf on any decent sized site.

Also there is a situation that your plan doesn't seem to address, is that a $USER may have an RA at the category, and $user at a course.