adding capabilities
[moodle.git] / lib / accesslib.php
1 <?php
2  /**
3   * Capability session information format
4   * 2 x 2 array
5   * [context][capability]
6   * where context is the context id of the table 'context'
7   * and capability is a string defining the capability
8   * e.g.
9   *
10   * [Capabilities] => [26][mod/forum:viewpost] = 1
11   *                   [26][mod/forum:startdiscussion] = -8990
12   *                   [26][mod/forum:editallpost] = -1
13   *                   [273][moodle:blahblah] = 1
14   *                   [273][moodle:blahblahblah] = 2
15   */
17 // permission definitions
18 define('CAP_ALLOW', 1);
19 define('CAP_PREVENT', -1);
20 define('CAP_PROHIBIT', -1000);
22 // context definitions
23 define('CONTEXT_SYSTEM', 10);
24 define('CONTEXT_PERSONAL', 20);
25 define('CONTEXT_USERID', 30);
26 define('CONTEXT_COURSECAT', 40);
27 define('CONTEXT_COURSE', 50);
28 define('CONTEXT_GROUP', 60);
29 define('CONTEXT_MODULE', 70);
30 define('CONTEXT_BLOCK', 80);
32 $context_cache    = array();    // Cache of all used context objects for performance (by level and instance)
33 $context_cache_id = array();    // Index to above cache by id
36 /**
37  * Load default not logged in role capabilities when user is not logged in
38  * @return bool 
39  */
40 function load_notloggedin_role() {
41     global $CFG, $USER;
43     if (!$sitecontext = get_context_instance(CONTEXT_SYSTEM, SITEID)) {
44         return false;
45     }
47     if (empty($CFG->notloggedinroleid)) {    // Let's set the default to the guest role
48         if ($roles = get_roles_with_capability('moodle/legacy:guest', CAP_ALLOW)) {
49             $role = array_shift($roles);   // Pick the first one
50             set_config('notloggedinroleid', $role->id);
51         } else {
52             return false;
53         }
54     }
56     if ($capabilities = get_records_select('role_capabilities', 
57                                      "roleid = $CFG->notloggedinroleid AND contextid = $sitecontext->id")) {
58         foreach ($capabilities as $capability) {
59             $USER->capabilities[$sitecontext->id][$capability->capability] = $capability->permission;     
60         }
61     }
63     return true;
64 }
66 /**
67  * This functions get all the course categories in proper order
68  * @param int $context
69  * @param int $type
70  * @return array of contextids
71  */
72 function get_parent_cats($context, $type) {
73     
74     $parents = array();
75     
76     switch ($type) {
78         case CONTEXT_COURSECAT:
79             
80             if (!$cat = get_record('course_categories','id',$context->instanceid)) {
81                 break;
82             }
84             while (!empty($cat->parent)) {
85                 if (!$context = get_context_instance(CONTEXT_COURSECAT, $cat->parent)) {
86                     break;
87                 }
88                 $parents[] = $context->id;
89                 $cat = get_record('course_categories','id',$cat->parent);
90             }
92         break;
93         
94         case CONTEXT_COURSE:
95         
96             if (!$course = get_record('course', 'id', $context->instanceid)) {
97                 break;
98             }
99             if (!$catinstance = get_context_instance(CONTEXT_COURSECAT, $course->category)) {
100                 break;
101             }
103             $parents[] = $catinstance->id;
105             if (!$cat = get_record('course_categories','id',$course->category)) {
106                 break;
107             }
109             while (!empty($cat->parent)) {
110                 if (!$context = get_context_instance(CONTEXT_COURSECAT, $cat->parent)) {
111                     break;
112                 }
113                 $parents[] = $context->id;
114                 $cat = get_record('course_categories','id',$cat->parent);
115             }
116         break;
117         
118         default:
119         break;
121     }
122     
123     return array_reverse($parents);
128 /*************************************
129  * Functions for Roles & Capabilites *
130  *************************************/
133 /**
134  * This function checks for a capability assertion being true.  If it isn't
135  * then the page is terminated neatly with a standard error message
136  * @param string $capability - name of the capability
137  * @param object $context - a context object (record from context table)
138  * @param integer $userid - a userid number
139  * @param string $errorstring - an errorstring
140  */
141 function require_capability($capability, $context=NULL, $userid=NULL, $errormessage="nopermissions", $stringfile='') {
143     global $USER;
145 /// If the current user is not logged in, then make sure they are
147     if (empty($userid) and empty($USER->id)) {
148         if ($context && ($context->aggregatelevel == CONTEXT_COURSE)) {
149             require_login($context->instanceid);
150         } else {
151             require_login();
152         }
153     }
154    
155 /// OK, if they still don't have the capability then print a nice error message
157     if (!has_capability($capability, $context, $userid)) {
158         $capabilityname = get_capability_string($capability);
159         print_error($errormessage, $stringfile, '', $capabilityname);
160     }
164 /**
165  * This function returns whether the current user has the capability of performing a function
166  * For example, we can do has_capability('mod/forum:replypost',$cm) in forum
167  * only one of the 4 (moduleinstance, courseid, site, userid) would be set at 1 time
168  * This is a recursive funciton.
169  * @uses $USER
170  * @param string $capability - name of the capability
171  * @param object $context - a context object (record from context table)
172  * @param integer $userid - a userid number
173  * @param bool $doanything - if false, ignore do anything
174  * @return bool
175  */
176 function has_capability($capability, $context=NULL, $userid=NULL, $doanything='true') {
178     global $USER, $CONTEXT, $CFG;
180     if (empty($userid) && !isloggedin() && !isset($USER->capabilities)) {
181         load_notloggedin_role();
182     }
184     if ($userid && $userid != $USER->id) {
185         if (empty($USER->id) or ($userid != $USER->id)) {
186             $capabilities = load_user_capability($capability, $context, $userid);
187         } else { //$USER->id == $userid
188             $capabilities = empty($USER->capabilities) ? NULL : $USER->capabilities;
189         }
190     } else { // no userid
191         $capabilities = empty($USER->capabilities) ? NULL : $USER->capabilities;
192     }
194     if (empty($context)) {                 // Use default CONTEXT if none specified
195         if (empty($CONTEXT)) {
196             return false;
197         } else {
198             $context = $CONTEXT;
199         }
200     } else {                               // A context was given to us
201         if (empty($CONTEXT)) {
202             $CONTEXT = $context;           // Store FIRST used context in this global as future default
203         }
204     }
206     if ($doanything) {
207         // Check site
208         $sitecontext = get_context_instance(CONTEXT_SYSTEM, SITEID);
209         if (isset($capabilities[$sitecontext->id]['moodle/site:doanything'])) {
210             return (0 < $capabilities[$sitecontext->id]['moodle/site:doanything']);
211         }
212     
213         switch ($context->aggregatelevel) {
214         
215             case CONTEXT_COURSECAT:
216                 // Check parent cats.
217                 $parentcats = get_parent_cats($context, CONTEXT_COURSECAT);
218                 foreach ($parentcats as $parentcat) {
219                     if (isset($capabilities[$parentcat]['moodle/site:doanything'])) {
220                         return (0 < $capabilities[$parentcat]['moodle/site:doanything']);
221                     }
222                 }
223             break;
225             case CONTEXT_COURSE:
226                 // Check parent cat.
227                 $parentcats = get_parent_cats($context, CONTEXT_COURSE);
229                 foreach ($parentcats as $parentcat) {
230                     if (isset($capabilities[$parentcat]['do_anything'])) {
231                         return (0 < $capabilities[$parentcat]['do_anything']);
232                     }
233                 }
234             break;
236             case CONTEXT_GROUP:
237                 // Find course.
238                 $group = get_record('groups','id',$context->instanceid);
239                 $courseinstance = get_context_instance(CONTEXT_COURSE, $group->courseid);
241                 $parentcats = get_parent_cats($courseinstance, CONTEXT_COURSE);
242                 foreach ($parentcats as $parentcat) {
243                     if (isset($capabilities[$parentcat->id]['do_anything'])) {
244                         return (0 < $capabilities[$parentcat->id]['do_anything']);
245                     }
246                 }
248                 $coursecontext = '';
249                 if (isset($capabilities[$courseinstance->id]['do_anything'])) {
250                     return (0 < $capabilities[$courseinstance->id]['do_anything']);
251                 }
253             break;
255             case CONTEXT_MODULE:
256                 // Find course.
257                 $cm = get_record('course_modules', 'id', $context->instanceid);
258                 $courseinstance = get_context_instance(CONTEXT_COURSE, $cm->course);
260                 if ($parentcats = get_parent_cats($courseinstance, CONTEXT_COURSE)) {
261                     foreach ($parentcats as $parentcat) {
262                         if (isset($capabilities[$parentcat]['do_anything'])) {
263                             return (0 < $capabilities[$parentcat]['do_anything']);
264                         }
265                     }
266                 }
268                 if (isset($capabilities[$courseinstance->id]['do_anything'])) {
269                     return (0 < $capabilities[$courseinstance->id]['do_anything']);
270                 }
272             break;
274             case CONTEXT_BLOCK:
275                 // 1 to 1 to course.
276                 // Find course.
277                 $block = get_record('block_instance','id',$context->instanceid);
278                 $courseinstance = get_context_instance(CONTEXT_COURSE, $block->pageid); // needs check
280                 $parentcats = get_parent_cats($courseinstance, CONTEXT_COURSE);
281                 foreach ($parentcats as $parentcat) {
282                     if (isset($capabilities[$parentcat]['do_anything'])) {
283                         return (0 < $capabilities[$parentcat]['do_anything']);
284                     }
285                 }
287                 if (isset($capabilities[$courseinstance->id]['do_anything'])) {
288                     return (0 < $capabilities[$courseinstance->id]['do_anything']);
289                 }
290             break;
292             default:
293                 // CONTEXT_SYSTEM: CONTEXT_PERSONAL: CONTEXT_USERID:
294                 // Do nothing.
295             break;
296         }
298         // Last: check self.
299         if (isset($capabilities[$context->id]['do_anything'])) {
300             return (0 < $capabilities[$context->id]['do_anything']);
301         }
302     }
303     // do_anything has not been set, we now look for it the normal way.
304     return (0 < capability_search($capability, $context, $capabilities));
309 /**
310  * In a separate function so that we won't have to deal with do_anything.
311  * again. Used by function has_capability.
312  * @param $capability - capability string
313  * @param $context - the context object
314  * @param $capabilities - either $USER->capability or loaded array
315  * @return permission (int)
316  */
317 function capability_search($capability, $context, $capabilities) {
318    
319     global $USER, $CFG;
321     if (isset($capabilities[$context->id][$capability])) {
322         if ($CFG->debug > 15) {
323             notify("Found $capability in context $context->id at level $context->aggregatelevel: ".$capabilities[$context->id][$capability], 'notifytiny');
324         }
325         return ($capabilities[$context->id][$capability]);
326     }
328     /* Then, we check the cache recursively */
329     $permission = 0;
331     switch ($context->aggregatelevel) {
333         case CONTEXT_SYSTEM: // by now it's a definite an inherit
334             $permission = 0;
335         break;
337         case CONTEXT_PERSONAL:
338             $parentcontext = get_context_instance(CONTEXT_SYSTEM, SITEID);
339             $permission = capability_search($capability, $parentcontext, $capabilities);
340         break;
342         case CONTEXT_USERID:
343             $parentcontext = get_context_instance(CONTEXT_SYSTEM, SITEID);
344             $permission = capability_search($capability, $parentcontext, $capabilities);
345         break;
347         case CONTEXT_COURSECAT: // Coursecat -> coursecat or site
348             $coursecat = get_record('course_categories','id',$context->instanceid);
349             if (!empty($coursecat->parent)) { // return parent value if it exists
350                 $parentcontext = get_context_instance(CONTEXT_COURSECAT, $coursecat->parent);
351             } else { // else return site value
352                 $parentcontext = get_context_instance(CONTEXT_SYSTEM, SITEID);
353             }
354             $permission = capability_search($capability, $parentcontext, $capabilities);
355         break;
357         case CONTEXT_COURSE: // 1 to 1 to course cat
358             // find the course cat, and return its value
359             $course = get_record('course','id',$context->instanceid);
360             $parentcontext = get_context_instance(CONTEXT_COURSECAT, $course->category);
361             $permission = capability_search($capability, $parentcontext, $capabilities);
362         break;
364         case CONTEXT_GROUP: // 1 to 1 to course
365             $group = get_record('groups','id',$context->instanceid);
366             $parentcontext = get_context_instance(CONTEXT_COURSE, $group->courseid);
367             $permission = capability_search($capability, $parentcontext, $capabilities);
368         break;
370         case CONTEXT_MODULE: // 1 to 1 to course
371             $cm = get_record('course_modules','id',$context->instanceid);
372             $parentcontext = get_context_instance(CONTEXT_COURSE, $cm->course);
373             $permission = capability_search($capability, $parentcontext, $capabilities);
374         break;
376         case CONTEXT_BLOCK: // 1 to 1 to course
377             $block = get_record('block_instance','id',$context->instanceid);
378             $parentcontext = get_context_instance(CONTEXT_COURSE, $block->pageid); // needs check
379             $permission = capability_search($capability, $parentcontext, $capabilities);
380         break;
382         default:
383             error ('This is an unknown context!');
384         return false;
385     }
386     if ($CFG->debug > 15) {
387         notify("Found $capability recursively from context $context->id at level $context->aggregatelevel: $permission", 'notifytiny');
388     }
390     return $permission;
394 /**
395  * This function should be called immediately after a login, when $USER is set.
396  * It will build an array of all the capabilities at each level
397  * i.e. site/metacourse/course_category/course/moduleinstance
398  * Note we should only load capabilities if they are explicitly assigned already,
399  * we should not load all module's capability!
400  * @param $userid - the id of the user whose capabilities we want to load
401  * @return array
402  * possible just s simple 2D array with [contextid][capabilityname]
403  * [Capabilities] => [26][forum_post] = 1
404  *                   [26][forum_start] = -8990
405  *                   [26][forum_edit] = -1
406  *                   [273][blah blah] = 1
407  *                   [273][blah blah blah] = 2
408  */
409 function load_user_capability($capability='', $context ='', $userid='') {
411     global $USER, $CFG;
413     
414     if (empty($userid)) {
415         if (empty($USER->id)) {               // We have no user to get capabilities for
416             return false;
417         }
418         if (!empty($USER->capabilities)) {    // make sure it's cleaned when loaded (again)
419             unset($USER->capabilities);  
420         }
421         $userid = $USER->id;
422         $otheruserid = false;
423     } else {
424         $otheruserid = $userid;
425     }
427     if ($capability) {
428         $capsearch = " AND rc.capability = '$capability' ";
429     } else {
430         $capsearch ="";  
431     }
433 /// First we generate a list of all relevant contexts of the user
435     $usercontexts = array();
437     if ($context) { // if context is specified
438         $usercontexts = get_parent_contexts($context);          
439     } else { // else, we load everything
440         if ($userroles = get_records('role_assignments','userid',$userid)) {
441             foreach ($userroles as $userrole) {
442                 $usercontexts[] = $userrole->contextid;
443             }
444         }
445     }
447 /// Set up SQL fragments for searching contexts
449     if ($usercontexts) {
450         $listofcontexts = '('.implode(',', $usercontexts).')';
451         $searchcontexts1 = "c1.id IN $listofcontexts AND";
452         $searchcontexts2 = "c2.id IN $listofcontexts AND";
453     } else {
454         $listofcontexts = $searchcontexts1 = $searchcontexts2 = '';
455     }
457 /// Then we use 1 giant SQL to bring out all relevant capabilities.
458 /// The first part gets the capabilities of orginal role.
459 /// The second part gets the capabilities of overriden roles.
461     $siteinstance = get_context_instance(CONTEXT_SYSTEM, SITEID);
463     $SQL = " SELECT  rc.capability, c1.id, (c1.aggregatelevel * 100) AS aggrlevel,
464                      SUM(rc.permission) AS sum
465                      FROM
466                      {$CFG->prefix}role_assignments AS ra, 
467                      {$CFG->prefix}role_capabilities AS rc,
468                      {$CFG->prefix}context AS c1
469                      WHERE
470                      ra.contextid=c1.id AND
471                      ra.roleid=rc.roleid AND
472                      ra.userid=$userid AND
473                      $searchcontexts1
474                      rc.contextid=$siteinstance->id 
475                      $capsearch
476               GROUP BY
477                      rc.capability, (c1.aggregatelevel * 100), c1.id
478                      HAVING
479                      SUM(rc.permission) != 0
480               UNION
482               SELECT rc.capability, c1.id, (c1.aggregatelevel * 100 + c2.aggregatelevel) AS aggrlevel,
483                      SUM(rc.permission) AS sum
484                      FROM
485                      {$CFG->prefix}role_assignments AS ra,
486                      {$CFG->prefix}role_capabilities AS rc,
487                      {$CFG->prefix}context AS c1,
488                      {$CFG->prefix}context AS c2
489                      WHERE
490                      ra.contextid=c1.id AND
491                      ra.roleid=rc.roleid AND 
492                      ra.userid=$userid AND         
493                      rc.contextid=c2.id AND             
494                      $searchcontexts1
495                      $searchcontexts2
496                      rc.contextid != $siteinstance->id
497                      $capsearch
498                   
499               GROUP BY
500                      rc.capability, (c1.aggregatelevel * 100 + c2.aggregatelevel), c1.id
501                      HAVING
502                      SUM(rc.permission) != 0
503               ORDER BY
504                      aggrlevel ASC
505             ";
507     $capabilities = array();  // Reinitialize.
508     if (!$rs = get_recordset_sql($SQL)) {
509         error("Query failed in load_user_capability.");
510     }
512     if ($rs && $rs->RecordCount() > 0) {
513         while (!$rs->EOF) {
514             $array = $rs->fields;
515             $temprecord = new object;
516               
517             foreach ($array as $key=>$val) {
518                 if ($key == 'aggrlevel') {
519                     $temprecord->aggregatelevel = $val;
520                 } else {
521                     $temprecord->{$key} = $val;
522                 }
523             }
524             $capabilities[] = $temprecord;
525             $rs->MoveNext();
526         }
527     }
528     
529     /* so up to this point we should have somethign like this
530      * $capabilities[1]    ->aggregatelevel = 1000
531                            ->module = SITEID
532                            ->capability = do_anything
533                            ->id = 1 (id is the context id)
534                            ->sum = 0
535                            
536      * $capabilities[2]     ->aggregatelevel = 1000
537                             ->module = SITEID
538                             ->capability = post_messages
539                             ->id = 1
540                             ->sum = -9000
542      * $capabilittes[3]     ->aggregatelevel = 3000
543                             ->module = course
544                             ->capability = view_course_activities
545                             ->id = 25
546                             ->sum = 1
548      * $capabilittes[4]     ->aggregatelevel = 3000
549                             ->module = course
550                             ->capability = view_course_activities
551                             ->id = 26
552                             ->sum = 0 (this is another course)
553                             
554      * $capabilities[5]     ->aggregatelevel = 3050
555                             ->module = course
556                             ->capability = view_course_activities
557                             ->id = 25 (override in course 25)
558                             ->sum = -1
559      * ....
560      * now we proceed to write the session array, going from top to bottom
561      * at anypoint, we need to go up and check parent to look for prohibit
562      */
563     // print_object($capabilities);
565     /* This is where we write to the actualy capabilities array
566      * what we need to do from here on is
567      * going down the array from lowest level to highest level
568      * 1) recursively check for prohibit,
569      *  if any, we write prohibit
570      *  else, we write the value
571      * 2) at an override level, we overwrite current level
572      *  if it's not set to prohibit already, and if different
573      *  ........ that should be it ........
574      */
575     $usercap = array(); // for other user's capabilities
576     foreach ($capabilities as $capability) {
578         $context = get_context_instance_by_id($capability->id);
580         if (!empty($otheruserid)) { // we are pulling out other user's capabilities, do not write to session
581             
582             if (capability_prohibits($capability->capability, $context, $capability->sum, $usercap)) {
583                 $usercap[$capability->id][$capability->capability] = -9000;
584                 continue;
585             }
587             $usercap[$capability->id][$capability->capability] = $capability->sum;          
588           
589         } else {
591             if (capability_prohibits($capability->capability, $context, $capability->sum)) { // if any parent or parent's parent is set to prohibit
592                 $USER->capabilities[$capability->id][$capability->capability] = -9000;
593                 continue;
594             }
595     
596             // if no parental prohibit set
597             // just write to session, i am not sure this is correct yet
598             // since 3050 shows up after 3000, and 3070 shows up after 3050,
599             // it should be ok just to overwrite like this, provided that there's no
600             // parental prohibits
601             // no point writing 0, since 0 = inherit
602             // we need to write even if it's 0, because it could be an inherit override
603             $USER->capabilities[$capability->id][$capability->capability] = $capability->sum;
604         }
605     }
606     
607     // now we don't care about the huge array anymore, we can dispose it.
608     unset($capabilities);
609     
610     if (!empty($otheruserid)) {
611         return $usercap; // return the array  
612     }
613     // see array in session to see what it looks like
618 /**
619  * This is a recursive function that checks whether the capability in this
620  * context, or the parent capabilities are set to prohibit.
621  *
622  * At this point, we can probably just use the values already set in the
623  * session variable, since we are going down the level. Any prohit set in
624  * parents would already reflect in the session.
625  *
626  * @param $capability - capability name
627  * @param $sum - sum of all capabilities values
628  * @param $context - the context object
629  * @param $array - when loading another user caps, their caps are not stored in session but an array
630  */
631 function capability_prohibits($capability, $context, $sum='', $array='') {
632     global $USER;
634     if ($sum < -8000) {
635         // If this capability is set to prohibit.
636         return true;
637     }
638     
639     if (isset($array)) {
640         if (isset($array[$context->id][$capability]) 
641                 && $array[$context->id][$capability] < -8000) {
642             return true;
643         }    
644     } else {
645         // Else if set in session.
646         if (isset($USER->capabilities[$context->id][$capability]) 
647                 && $USER->capabilities[$context->id][$capability] < -8000) {
648             return true;
649         }
650     }
651     switch ($context->aggregatelevel) {
652         
653         case CONTEXT_SYSTEM:
654             // By now it's a definite an inherit.
655             return 0;
656         break;
658         case CONTEXT_PERSONAL:
659             $parent = get_context_instance(CONTEXT_SYSTEM, SITEID);
660             return capability_prohibits($capability, $parent);
661         break;
663         case CONTEXT_USERID:
664             $parent = get_context_instance(CONTEXT_SYSTEM, SITEID);
665             return capability_prohibits($capability, $parent);
666         break;
668         case CONTEXT_COURSECAT:
669             // Coursecat -> coursecat or site.
670             $coursecat = get_record('course_categories','id',$context->instanceid);
671             if (!empty($coursecat->parent)) {
672                 // return parent value if exist.
673                 $parent = get_context_instance(CONTEXT_COURSECAT, $coursecat->parent);
674             } else {
675                 // Return site value.
676                 $parent = get_context_instance(CONTEXT_SYSTEM, SITEID);
677             }
678             return capability_prohibits($capability, $parent);
679         break;
681         case CONTEXT_COURSE:
682             // 1 to 1 to course cat.
683             // Find the course cat, and return its value.
684             $course = get_record('course','id',$context->instanceid);
685             $parent = get_context_instance(CONTEXT_COURSECAT, $course->category);
686             return capability_prohibits($capability, $parent);
687         break;
689         case CONTEXT_GROUP:
690             // 1 to 1 to course.
691             $group = get_record('groups','id',$context->instanceid);
692             $parent = get_context_instance(CONTEXT_COURSE, $group->courseid);
693             return capability_prohibits($capability, $parent);
694         break;
696         case CONTEXT_MODULE:
697             // 1 to 1 to course.
698             $cm = get_record('course_modules','id',$context->instanceid);
699             $parent = get_context_instance(CONTEXT_COURSE, $cm->course);
700             return capability_prohibits($capability, $parent);
701         break;
703         case CONTEXT_BLOCK:
704             // 1 to 1 to course.
705             $block = get_record('block_instance','id',$context->instanceid);
706             $parent = get_context_instance(CONTEXT_COURSE, $block->pageid); // needs check
707             return capability_prohibits($capability, $parent);
708         break;
710         default:
711             error ('This is an unknown context!');
712         return false;
713     }
717 /**
718  * A print form function. This should either grab all the capabilities from
719  * files or a central table for that particular module instance, then present
720  * them in check boxes. Only relevant capabilities should print for known
721  * context.
722  * @param $mod - module id of the mod
723  */
724 function print_capabilities($modid=0) {
725     global $CFG;
726     
727     $capabilities = array();
729     if ($modid) {
730         // We are in a module specific context.
732         // Get the mod's name.
733         // Call the function that grabs the file and parse.
734         $cm = get_record('course_modules', 'id', $modid);
735         $module = get_record('modules', 'id', $cm->module);
736         
737     } else {
738         // Print all capabilities.
739         foreach ($capabilities as $capability) {
740             // Prints the check box component.
741         }
742     }
746 /**
747  * Installs the roles system.
748  * This function runs on a fresh install as well as on an upgrade from the old
749  * hard-coded student/teacher/admin etc. roles to the new roles system.
750  */
751 function moodle_install_roles() {
753     global $CFG, $db;
754     
755     // Create a system wide context for assignemnt.
756     $systemcontext = $context = get_context_instance(CONTEXT_SYSTEM, SITEID);
759     // Create default/legacy roles and capabilities.
760     // (1 legacy capability per legacy role at system level).
761     $adminrole = create_role(get_string('administrator'), get_string('administratordescription'), 'moodle/legacy:admin');   
762     if (!assign_capability('moodle/site:doanything', CAP_ALLOW, $adminrole, $systemcontext->id)) {
763         error('Could not assign moodle/site:doanything to the admin role');
764     }
765     $coursecreatorrole = create_role(get_string('coursecreators'), get_string('coursecreatorsdescription'), 'moodle/legacy:coursecreator');   
766     $noneditteacherrole = create_role(get_string('noneditingteacher'), get_string('noneditingteacherdescription'), 'moodle/legacy:teacher');    
767     $editteacherrole = create_role(get_string('defaultcourseteacher'), get_string('defaultcourseteacherdescription'), 'moodle/legacy:editingteacher');    
768     $studentrole = create_role(get_string('defaultcoursestudent'), get_string('defaultcoursestudentdescription'), 'moodle/legacy:student');
769     $guestrole = create_role(get_string('guest'), get_string('guestdescription'), 'moodle/legacy:guest');
772     // Look inside user_admin, user_creator, user_teachers, user_students and
773     // assign above new roles. If a user has both teacher and student role,
774     // only teacher role is assigned. The assignment should be system level.
775     $dbtables = $db->MetaTables('TABLES');
776     
778     /**
779      * Upgrade the admins.
780      * Sort using id ASC, first one is primary admin.
781      */
782     if (in_array($CFG->prefix.'user_admins', $dbtables)) {
783         if ($useradmins = get_records_sql('SELECT * from '.$CFG->prefix.'user_admins ORDER BY ID ASC')) { 
784             foreach ($useradmins as $admin) {
785                 role_assign($adminrole, $admin->userid, 0, $systemcontext->id);
786             }
787         }
788     } else {
789         // This is a fresh install.
790     }
793     /**
794      * Upgrade course creators.
795      */
796     if (in_array($CFG->prefix.'user_coursecreators', $dbtables)) {
797         if ($usercoursecreators = get_records('user_coursecreators')) {
798             foreach ($usercoursecreators as $coursecreator) {
799                 role_assign($coursecreatorrole, $coursecreator->userid, 0, $systemcontext->id);
800             }
801         }
802     }
805     /**
806      * Upgrade editting teachers and non-editting teachers.
807      */
808     if (in_array($CFG->prefix.'user_teachers', $dbtables)) {
809         if ($userteachers = get_records('user_teachers')) {
810             foreach ($userteachers as $teacher) {
811                 $coursecontext = get_context_instance(CONTEXT_COURSE, $teacher->course); // needs cache
812                 if ($teacher->editall) { // editting teacher
813                     role_assign($editteacherrole, $teacher->userid, 0, $coursecontext->id);
814                 } else {
815                     role_assign($noneditteacherrole, $teacher->userid, 0, $coursecontext->id);
816                 }
817             }
818         }
819     }
822     /**
823      * Upgrade students.
824      */
825     if (in_array($CFG->prefix.'user_students', $dbtables)) {
826         if ($userstudents = get_records('user_students')) {
827             foreach ($userstudents as $student) {
828                 $coursecontext = get_context_instance(CONTEXT_COURSE, $student->course);
829                 role_assign($studentrole, $student->userid, 0, $coursecontext->id);
830             }
831         }
832     }
835     /**
836      * Upgrade guest (only 1 entry).
837      */
838     if ($guestuser = get_record('user', 'username', 'guest')) {
839         role_assign($guestrole, $guestuser->id, 0, $systemcontext->id);
840     }
842     /**
843      * Insert the correct records for legacy roles 
844      */
845     allow_assign($adminrole, $adminrole);
846     allow_assign($adminrole, $coursecreatorrole);
847     allow_assign($adminrole, $noneditteacherrole);
848     allow_assign($adminrole, $editteacherrole);   
849     allow_assign($adminrole, $studentrole);
850     allow_assign($adminrole, $guestrole);
851     
852     allow_assign($coursecreatorrole, $noneditteacherrole);
853     allow_assign($coursecreatorrole, $editteacherrole);
854     allow_assign($coursecreatorrole, $studentrole);     
855     allow_assign($coursecreatorrole, $guestrole);
856     
857     allow_assign($editteacherrole, $noneditteacherrole);     
858     allow_assign($editteacherrole, $studentrole);      
859     allow_assign($editteacherrole, $guestrole);
860     
861     /// overrides
862     allow_override($adminrole, $adminrole);
863     allow_override($adminrole, $coursecreatorrole);
864     allow_override($adminrole, $noneditteacherrole);
865     allow_override($adminrole, $editteacherrole);   
866     allow_override($adminrole, $studentrole);
867     allow_override($adminrole, $guestrole);    
869     // Should we delete the tables after we are done? Not yet.
872 /**
873  * Assign the defaults found in this capabality definition to roles that have
874  * the corresponding legacy capabilities assigned to them.
875  * @param $legacyperms - an array in the format (example):
876  *                      'guest' => CAP_PREVENT,
877  *                      'student' => CAP_ALLOW,
878  *                      'teacher' => CAP_ALLOW,
879  *                      'editingteacher' => CAP_ALLOW,
880  *                      'coursecreator' => CAP_ALLOW,
881  *                      'admin' => CAP_ALLOW
882  * @return boolean - success or failure.
883  */
884 function assign_legacy_capabilities($capability, $legacyperms) {
885     
886     foreach ($legacyperms as $type => $perm) {
887         
888         $systemcontext = get_context_instance(CONTEXT_SYSTEM, SITEID);
889         
890         // The legacy capabilities are:
891         //   'moodle/legacy:guest'
892         //   'moodle/legacy:student'
893         //   'moodle/legacy:teacher'
894         //   'moodle/legacy:editingteacher'
895         //   'moodle/legacy:coursecreator'
896         //   'moodle/legacy:admin'
897         
898         if (!$roles = get_roles_with_capability('moodle/legacy:'.$type, CAP_ALLOW)) {
899             return false;
900         }
901         
902         foreach ($roles as $role) {
903             // Assign a site level capability.
904             if(!assign_capability($capability, $perm, $role->id, $systemcontext->id)) {
905                 return false;
906             }
907         }
908     }
909     return true;
913 /**
914  * Checks to see if a capability is a legacy capability.
915  * @param $capabilityname
916  * @return boolean
917  */
918 function islegacy($capabilityname) {
919     if (strstr($capabilityname, 'legacy') === false) {
920         return false;  
921     } else {
922         return true;  
923     }
928 /**********************************
929  * Context Manipulation functions *
930  **********************************/
932 /**
933  * This should be called prolly everytime a user, group, module, course,
934  * coursecat or site is set up maybe?
935  * @param $level
936  * @param $instanceid
937  */
938 function create_context($aggregatelevel, $instanceid) {
939     if (!get_record('context','aggregatelevel',$aggregatelevel,'instanceid',$instanceid)) {
940         $context = new object;
941         $context->aggregatelevel = $aggregatelevel;
942         $context->instanceid = $instanceid;
943         return insert_record('context',$context);
944     }
948 /**
949  * Get the context instance as an object. This function will create the
950  * context instance if it does not exist yet.
951  * @param $level
952  * @param $instance
953  */
954 function get_context_instance($aggregatelevel=NULL, $instance=SITEID) {
956     global $context_cache, $context_cache_id, $CONTEXT;
958 /// If no level is supplied then return the current global context if there is one
959     if (empty($aggregatelevel)) {
960         if (empty($CONTEXT)) {
961             if ($CFG->debug > 7) {
962                 notify("Error: get_context_instance() called without a context");
963             }
964         } else {
965             return $CONTEXT;
966         }
967     }
969 /// Check the cache
970     if (isset($context_cache[$aggregatelevel][$instance])) {  // Already cached
971         return $context_cache[$aggregatelevel][$instance];
972     }
974 /// Get it from the database, or create it
975     if (!$context = get_record('context', 'aggregatelevel', $aggregatelevel, 'instanceid', $instance)) {
976         create_context($aggregatelevel, $instance);
977         $context = get_record('context', 'aggregatelevel', $aggregatelevel, 'instanceid', $instance);
978     }
980 /// Update the cache
981     $context_cache[$aggregatelevel][$instance] = $context;    // Cache it for later
982     $context_cache_id[$context->id] = $context;      // Cache it for later
985     return $context;
989 /**
990  * Get a context instance as an object, from a given id.
991  * @param $id
992  */
993 function get_context_instance_by_id($id) {
995     global $context_cache, $context_cache_id;
997     if (isset($context_cache_id[$id])) {  // Already cached
998         return $context_cache_id[$id];
999     }
1001     if ($context = get_record('context', 'id', $id)) {   // Update the cache and return
1002         $context_cache[$context->aggregatelevel][$context->instanceid] = $context;
1003         $context_cache_id[$context->id] = $context;
1004         return $context;
1005     }
1007     return false;
1011 /**
1012  * Get the local override (if any) for a given capability in a role in a context
1013  * @param $roleid
1014  * @param $contextid
1015  * @param $capability
1016  */
1017 function get_local_override($roleid, $contextid, $capability) {
1018     return get_record('role_capabilities', 'roleid', $roleid, 'capability', $capability, 'contextid', $contextid);
1023 /************************************
1024  *    DB TABLE RELATED FUNCTIONS    *
1025  ************************************/
1027 /**
1028  * function that creates a role
1029  * @param name - role name
1030  * @param description - role description
1031  * @param legacy - optional legacy capability
1032  * @return id or false
1033  */
1034 function create_role($name, $description, $legacy='') {
1035           
1036     // check for duplicate role name
1037                 
1038     if ($role = get_record('role','name', $name)) {
1039         error('there is already a role with this name!');  
1040     }
1041     
1042     $role->name = $name;
1043     $role->description = $description;
1044     
1045     $context = get_context_instance(CONTEXT_SYSTEM, SITEID);                           
1046     
1047     if ($id = insert_record('role', $role)) {
1048         if ($legacy) {        
1049             assign_capability($legacy, CAP_ALLOW, $id, $context->id);            
1050         }
1051         
1052         /// By default, users with role:manage at site level
1053         /// should be able to assign users to this new role, and override this new role's capabilities
1054         
1055         // find all admin roles
1056         $adminroles = get_roles_with_capability('moodle/role:manage', CAP_ALLOW, $context);
1057         // foreach admin role
1058         foreach ($adminroles as $arole) {
1059             // write allow_assign and allow_overrid
1060             allow_assign($arole->id, $id);
1061             allow_override($arole->id, $id);  
1062         }
1063         
1064         return $id;
1065     } else {
1066         return false;  
1067     }
1068   
1071 /**
1072  * Function to write context specific overrides, or default capabilities.
1073  * @param module - string name
1074  * @param capability - string name
1075  * @param contextid - context id
1076  * @param roleid - role id
1077  * @param permission - int 1,-1 or -1000
1078  */
1079 function assign_capability($capability, $permission, $roleid, $contextid) {
1080     
1081     global $USER;
1082     
1083     if (empty($permission) || $permission == 0) { // if permission is not set
1084         unassign_capability($capability, $roleid, $contextid);      
1085     }
1086     
1087     $cap = new object;
1088     $cap->contextid = $contextid;
1089     $cap->roleid = $roleid;
1090     $cap->capability = $capability;
1091     $cap->permission = $permission;
1092     $cap->timemodified = time();
1093     $cap->modifierid = empty($USER->id) ? 0 : $USER->id;
1094     
1095     return insert_record('role_capabilities', $cap);
1099 /**
1100  * Unassign a capability from a role.
1101  * @param $roleid - the role id
1102  * @param $capability - the name of the capability
1103  * @return boolean - success or failure
1104  */
1105 function unassign_capability($capability, $roleid, $contextid=NULL) {
1106     
1107     if (isset($contextid)) {
1108         $status = delete_records('role_capabilities', 'capability', $capability,
1109                 'roleid', $roleid, 'contextid', $contextid);
1110     } else {
1111         $status = delete_records('role_capabilities', 'capability', $capability,
1112                 'roleid', $roleid);
1113     }
1114     return $status;
1118 /**
1119  * Get the roles that have a given capability.
1120  * @param $capability - capability name (string)
1121  * @param $permission - optional, the permission defined for this capability
1122  *                      either CAP_ALLOW, CAP_PREVENT or CAP_PROHIBIT
1123  * @return array or role objects
1124  */
1125 function get_roles_with_capability($capability, $permission=NULL, $context='') {
1127     global $CFG;
1128     
1129     if ($context) {
1130         if ($contexts = get_parent_contexts($context)) {
1131             $listofcontexts = '('.implode(',', $contexts).')';
1132         } else {
1133             $sitecontext = get_context_instance(CONTEXT_SYSTEM, SITEID);
1134             $listofcontexts = '('.$sitecontext->id.')'; // must be site  
1135         }  
1136         $contextstr = "AND (rc.contextid = '.$context->id.' OR  rc.contextid IN $listofcontexts)";
1137     } else {
1138         $contextstr = '';
1139     }
1140     
1141     $selectroles = "SELECT r.* 
1142                       FROM {$CFG->prefix}role AS r,
1143                            {$CFG->prefix}role_capabilities AS rc
1144                      WHERE rc.capability = '$capability'
1145                        AND rc.roleid = r.id $contextstr";
1147     if (isset($permission)) {
1148         $selectroles .= " AND rc.permission = '$permission'";
1149     }
1150     return get_records_sql($selectroles);
1154 /**
1155  * This function makes a role-assignment (a role for a user or group in a particular context)
1156  * @param $roleid - the role of the id
1157  * @param $userid - userid
1158  * @param $groupid - group id
1159  * @param $contextid - id of the context
1160  * @param $timestart - time this assignment becomes effective
1161  * @param $timeend - time this assignemnt ceases to be effective
1162  * @uses $USER
1163  * @return id - new id of the assigment
1164  */
1165 function role_assign($roleid, $userid, $groupid, $contextid, $timestart=0, $timeend=0, $hidden=0, $enrol='manual') {
1166     global $USER, $CFG;
1168     if ($CFG->debug > 7) {
1169         notify("Assign roleid $roleid userid $userid contextid $contextid", 'notifytiny');
1170     }
1172 /// Do some data validation
1174     if (empty($roleid)) {
1175         notify('Role ID not provided');
1176         return false;
1177     }
1179     if (empty($userid) && empty($groupid)) {
1180         notify('Either userid or groupid must be provided');
1181         return false;
1182     }
1183     
1184     if ($userid && !record_exists('user', 'id', $userid)) {
1185         notify('User does not exist!');
1186         return false;
1187     }
1189     if ($groupid && !record_exists('groups', 'id', $groupid)) {
1190         notify('Group does not exist!');
1191         return false;
1192     }
1194     if (!$context = get_context_instance_by_id($contextid)) {
1195         notify('A valid context must be provided');
1196         return false;
1197     }
1199     if (($timestart and $timeend) and ($timestart > $timeend)) {
1200         notify('The end time can not be earlier than the start time');
1201         return false;
1202     }
1205 /// Check for existing entry
1206     if ($userid) {
1207         $ra = get_record('role_assignments', 'roleid', $roleid, 'contextid', $context->id, 'userid', $userid);
1208     } else {
1209         $ra = get_record('role_assignments', 'roleid', $roleid, 'contextid', $context->id, 'groupid', $groupid);
1210     }
1213     $newra = new object;
1215     if (empty($ra)) {             // Create a new entry
1216         $newra->roleid = $roleid;
1217         $newra->contextid = $context->id;
1218         $newra->userid = $userid;
1219         $newra->groupid = $groupid;
1221         $newra->hidden = $hidden;
1222         $newra->enrol = $enrol;
1223         $newra->timestart = $timestart;
1224         $newra->timeend = $timeend;
1225         $newra->timemodified = time();
1226         $newra->modifier = empty($USER->id) ? 0 : $USER->id;
1228         $success = insert_record('role_assignments', $newra);
1230     } else {                      // We already have one, just update it
1232         $newra->id = $ra->id;
1233         $newra->hidden = $hidden;
1234         $newra->enrol = $enrol;
1235         $newra->timestart = $timestart;
1236         $newra->timeend = $timeend;
1237         $newra->timemodified = time();
1238         $newra->modifier = empty($USER->id) ? 0 : $USER->id;
1240         $success = update_record('role_assignments', $newra);
1241     }
1243     if ($success) {   /// Role was assigned, so do some other things
1245     /// If the user is the current user, then reload the capabilities too.
1246         if (!empty($USER->id) && $USER->id == $userid) {
1247             load_user_capability();
1248         }
1250     /// Make sure the user is subscribed to any appropriate forums in this context
1251         require_once($CFG->dirroot.'/mod/forum/lib.php');
1252         forum_add_user_default_subscriptions($userid, $context);
1253     }
1255     return $success;
1259 /**
1260  * Deletes one or more role assignments.   You must specify at least one parameter.
1261  * @param $roleid
1262  * @param $userid
1263  * @param $groupid
1264  * @param $contextid
1265  * @return boolean - success or failure
1266  */
1267 function role_unassign($roleid=0, $userid=0, $groupid=0, $contextid=0) {
1268     $args = array('roleid', 'userid', 'groupid', 'contextid');
1269     $select = array();
1270     foreach ($args as $arg) {
1271         if ($$arg) {
1272             $select[] = $arg.' = '.$$arg;
1273         }
1274     }
1275     if ($select) {
1276         return delete_records_select('role_assignments', implode(' AND ', $select)); 
1277     }
1278     return false;
1282 /**
1283  * Loads the capability definitions for the component (from file). If no
1284  * capabilities are defined for the component, we simply return an empty array.
1285  * @param $component - examples: 'moodle', 'mod/forum', 'block/quiz_results'
1286  * @return array of capabilities
1287  */
1288 function load_capability_def($component) {
1289     global $CFG;
1291     if ($component == 'moodle') {
1292         $defpath = $CFG->libdir.'/db/access.php';
1293         $varprefix = 'moodle';
1294     } else {
1295         $defpath = $CFG->dirroot.'/'.$component.'/db/access.php';
1296         $varprefix = str_replace('/', '_', $component);
1297     }
1298     $capabilities = array();
1299     
1300     if (file_exists($defpath)) {
1301         require_once($defpath);
1302         $capabilities = ${$varprefix.'_capabilities'};
1303     }
1304     return $capabilities;
1308 /**
1309  * Gets the capabilities that have been cached in the database for this
1310  * component.
1311  * @param $component - examples: 'moodle', 'mod/forum', 'block/quiz_results'
1312  * @return array of capabilities
1313  */
1314 function get_cached_capabilities($component='moodle') {
1315     if ($component == 'moodle') {
1316         $storedcaps = get_records_select('capabilities',
1317                         "name LIKE 'moodle/%:%'");
1318     } else {
1319         $storedcaps = get_records_select('capabilities',
1320                         "name LIKE '$component:%'");
1321     }
1322     return $storedcaps;
1326 /**
1327  * Updates the capabilities table with the component capability definitions.
1328  * If no parameters are given, the function updates the core moodle
1329  * capabilities.
1330  *
1331  * Note that the absence of the db/access.php capabilities definition file
1332  * will cause any stored capabilities for the component to be removed from
1333  * the database. 
1334  *
1335  * @param $component - examples: 'moodle', 'mod/forum', 'block/quiz_results'
1336  * @return boolean
1337  */
1338 function update_capabilities($component='moodle') {
1339     
1340     $storedcaps = array();
1342     $filecaps = load_capability_def($component);
1343     $cachedcaps = get_cached_capabilities($component);
1344     if ($cachedcaps) {
1345         foreach ($cachedcaps as $cachedcap) {
1346             array_push($storedcaps, $cachedcap->name);
1347             // update risk bitmasks in existing capabilitites if needed
1348             if (array_key_exists($cachedcap->name, $filecaps)) {
1349                 if (!array_key_exists('riskbitmask', $filecaps[$cachedcap->name])) {
1350                     $filecaps[$cachedcap->name]['riskbitmask'] = 0; // no risk if not specified
1351                 }
1352                 if ($cachedcap->riskbitmask != $filecaps[$cachedcap->name]['riskbitmask']) {
1353                     $updatecap = new object;
1354                     $updatecap->id = $cachedcap->id;
1355                     $updatecap->riskbitmask = $filecaps[$cachedcap->name]['riskbitmask'];
1356                     if (!update_record('capabilities', $updatecap)) {
1357                         return false;
1358                     }
1359                 }
1360             }
1361         }
1362     }
1364     // Are there new capabilities in the file definition?
1365     $newcaps = array();
1366     
1367     foreach ($filecaps as $filecap => $def) {
1368         if (!$storedcaps || 
1369                 ($storedcaps && in_array($filecap, $storedcaps) === false)) {
1370             if (!array_key_exists('riskbitmask', $def)) {
1371                 $def['riskbitmask'] = 0; // no risk if not specified
1372             }
1373             $newcaps[$filecap] = $def;
1374         }
1375     }
1376     // Add new capabilities to the stored definition.
1377     foreach ($newcaps as $capname => $capdef) {
1378         $capability = new object;
1379         $capability->name = $capname;
1380         $capability->captype = $capdef['captype'];
1381         $capability->contextlevel = $capdef['contextlevel'];
1382         $capability->component = $component;
1383         $capability->riskbitmask = $capdef['riskbitmask'];
1384         
1385         if (!insert_record('capabilities', $capability, false, 'id')) {
1386             return false;
1387         }
1388         // Do we need to assign the new capabilities to roles that have the
1389         // legacy capabilities moodle/legacy:* as well?
1390         if (isset($capdef['legacy']) && is_array($capdef['legacy']) &&
1391                     !assign_legacy_capabilities($capname, $capdef['legacy'])) {
1392             error('Could not assign legacy capabilities');
1393             return false;
1394         }
1395     }
1396     // Are there any capabilities that have been removed from the file
1397     // definition that we need to delete from the stored capabilities and
1398     // role assignments?
1399     capabilities_cleanup($component, $filecaps);
1400     
1401     return true;
1405 /**
1406  * Deletes cached capabilities that are no longer needed by the component.
1407  * Also unassigns these capabilities from any roles that have them.
1408  * @param $component - examples: 'moodle', 'mod/forum', 'block/quiz_results'
1409  * @param $newcapdef - array of the new capability definitions that will be
1410  *                     compared with the cached capabilities
1411  * @return int - number of deprecated capabilities that have been removed
1412  */
1413 function capabilities_cleanup($component, $newcapdef=NULL) {
1414     
1415     $removedcount = 0;
1416     
1417     if ($cachedcaps = get_cached_capabilities($component)) {
1418         foreach ($cachedcaps as $cachedcap) {
1419             if (empty($newcapdef) ||
1420                         array_key_exists($cachedcap->name, $newcapdef) === false) {
1421             
1422                 // Remove from capabilities cache.
1423                 if (!delete_records('capabilities', 'name', $cachedcap->name)) {
1424                     error('Could not delete deprecated capability '.$cachedcap->name);
1425                 } else {
1426                     $removedcount++;
1427                 }
1428                 // Delete from roles.
1429                 if($roles = get_roles_with_capability($cachedcap->name)) {
1430                     foreach($roles as $role) {
1431                         if (!unassign_capability($role->id, $cachedcap->name)) {
1432                             error('Could not unassign deprecated capability '.
1433                                     $cachedcap->name.' from role '.$role->name);
1434                         }
1435                     }
1436                 }
1437             } // End if.
1438         }
1439     }
1440     return $removedcount;
1445 /****************
1446  * UI FUNCTIONS *
1447  ****************/
1450 /**
1451  * prints human readable context identifier.
1452  */
1453 function print_context_name($context) {
1455     $name = '';
1456     switch ($context->aggregatelevel) {
1458         case CONTEXT_SYSTEM: // by now it's a definite an inherit
1459             $name = get_string('site');
1460             break;
1462         case CONTEXT_PERSONAL:
1463             $name = get_string('personal');
1464             break;
1466         case CONTEXT_USERID:
1467             if ($user = get_record('user', 'id', $context->instanceid)) {
1468                 $name = get_string('user').': '.fullname($user);
1469             }
1470             break;
1472         case CONTEXT_COURSECAT: // Coursecat -> coursecat or site
1473             if ($category = get_record('course_categories', 'id', $context->instanceid)) {
1474                 $name = get_string('category').': '.$category->name;
1475             }
1476             break;
1478         case CONTEXT_COURSE: // 1 to 1 to course cat
1479             if ($course = get_record('course', 'id', $context->instanceid)) {
1480                 $name = get_string('course').': '.$course->fullname;
1481             }
1482             break;
1484         case CONTEXT_GROUP: // 1 to 1 to course
1485             if ($group = get_record('groups', 'id', $context->instanceid)) {
1486                 $name = get_string('group').': '.$group->name;
1487             }
1488             break;
1490         case CONTEXT_MODULE: // 1 to 1 to course
1491             if ($cm = get_record('course_modules','id',$context->instanceid)) {
1492                 if ($module = get_record('modules','id',$cm->module)) {
1493                     if ($mod = get_record($module->name, 'id', $cm->instance)) {
1494                         $name = get_string('activitymodule').': '.$mod->name;
1495                     }
1496                 }
1497             }
1498             break;
1500         case CONTEXT_BLOCK: // 1 to 1 to course
1501             if ($blockinstance = get_record('block_instance','id',$context->instanceid)) {
1502                 if ($block = get_record('block','id',$blockinstance->blockid)) {
1503                     global $CFG;
1504                     require_once("$CFG->dirroot/blocks/moodleblock.class.php");
1505                     require_once("$CFG->dirroot/blocks/$block->name/block_$block->name.php");
1506                     $blockname = "block_$block->name";
1507                     if ($blockobject = new $blockname()) {
1508                         $name = $blockobject->title.' ('.get_string('block').')';
1509                     }
1510                 }
1511             }
1512             break;
1514         default:
1515             error ('This is an unknown context!');
1516             return false;
1518     }
1519     return $name;
1523 /**
1524  * Extracts the relevant capabilities given a contextid. 
1525  * All case based, example an instance of forum context.
1526  * Will fetch all forum related capabilities, while course contexts
1527  * Will fetch all capabilities
1528  * @param object context
1529  * @return array();
1530  *
1531  *  capabilities
1532  * `name` varchar(150) NOT NULL,
1533  * `captype` varchar(50) NOT NULL,
1534  * `contextlevel` int(10) NOT NULL,
1535  * `component` varchar(100) NOT NULL,
1536  */
1537 function fetch_context_capabilities($context) {
1538       
1539     global $CFG;
1541     $sort = 'ORDER BY contextlevel,component,id';   // To group them sensibly for display
1542       
1543     switch ($context->aggregatelevel) {
1545         case CONTEXT_SYSTEM: // all
1546             $SQL = "select * from {$CFG->prefix}capabilities";
1547         break;
1549         case CONTEXT_PERSONAL:
1550             $SQL = "select * from {$CFG->prefix}capabilities where contextlevel = ".CONTEXT_PERSONAL;
1551         break;
1552         
1553         case CONTEXT_USERID:
1554             $SQL = "select * from {$CFG->prefix}capabilities where contextlevel = ".CONTEXT_USERID;
1555         break;
1556         
1557         case CONTEXT_COURSECAT: // all
1558             $SQL = "select * from {$CFG->prefix}capabilities";
1559         break;
1561         case CONTEXT_COURSE: // all
1562             $SQL = "select * from {$CFG->prefix}capabilities";
1563         break;
1565         case CONTEXT_GROUP: // group caps
1566         break;
1568         case CONTEXT_MODULE: // mod caps
1569             $cm = get_record('course_modules', 'id', $context->instanceid);
1570             $module = get_record('modules', 'id', $cm->module);
1571         
1572             $SQL = "select * from {$CFG->prefix}capabilities where contextlevel = ".CONTEXT_MODULE."
1573                     and component = 'mod/$module->name'";
1574         break;
1576         case CONTEXT_BLOCK: // block caps
1577             $cb = get_record('block_instance', 'id', $context->instanceid);
1578             $block = get_record('block', 'id', $cb->blockid);
1579         
1580             $SQL = "select * from {$CFG->prefix}capabilities where contextlevel = ".CONTEXT_BLOCK."
1581                     and component = 'block/$block->name'";
1582         break;
1584         default:
1585         return false;
1586     }
1588     $records = get_records_sql($SQL.' '.$sort);
1589     return $records;
1590     
1594 /**
1595  * This function pulls out all the resolved capabilities (overrides and
1596  * defaults) of a role used in capability overrieds in contexts at a given
1597  * context.
1598  * @param obj $context
1599  * @param int $roleid
1600  * @return array
1601  */
1602 function role_context_capabilities($roleid, $context, $cap='') {
1603     global $CFG; 
1604     
1605     $sitecontext = get_context_instance(CONTEXT_SYSTEM, SITEID);
1606     if ($sitecontext->id == $context->id) {
1607         $contexts = array($sitecontext->id);  
1608     } else {
1609         // first of all, figure out all parental contexts
1610         $contexts = array_reverse(get_parent_contexts($context));
1611     }
1612     $contexts = '('.implode(',', $contexts).')';
1613     
1614     if ($cap) {
1615         $search = " AND rc.capability = '$cap' ";
1616     } else {
1617         $search = '';  
1618     }
1619     
1620     $SQL = "SELECT rc.* FROM {$CFG->prefix}role_capabilities rc, {$CFG->prefix}context c
1621             where rc.contextid in $contexts
1622             and rc.roleid = $roleid
1623             and rc.contextid = c.id $search
1624             ORDER BY c.aggregatelevel DESC, rc.capability DESC";
1625   
1626     $capabilities = array();
1627     
1628     if ($records = get_records_sql($SQL)) {
1629         // We are traversing via reverse order.
1630         foreach ($records as $record) {
1631             // If not set yet (i.e. inherit or not set at all), or currently we have a prohibit
1632             if (!isset($capabilities[$record->capability]) || $record->permission<-500) {
1633                 $capabilities[$record->capability] = $record->permission;
1634             }  
1635         }
1636     }
1637     return $capabilities;
1641 /**
1642  * Recursive function which, given a context, find all parent context ids, 
1643  * and return the array in reverse order, i.e. parent first, then grand
1644  * parent, etc.
1645  * @param object $context
1646  * @return array()
1647  */
1648 function get_parent_contexts($context) {
1649   
1650     switch ($context->aggregatelevel) {
1652         case CONTEXT_SYSTEM: // no parent
1653             return array();
1654         break;
1656         case CONTEXT_PERSONAL:
1657             if (!$parent = get_context_instance(CONTEXT_SYSTEM, SITEID)) {
1658                 return array();
1659             } else {
1660                 return array($parent->id);
1661             }
1662         break;
1663         
1664         case CONTEXT_USERID:
1665             if (!$parent = get_context_instance(CONTEXT_SYSTEM, SITEID)) {
1666                 return array();
1667             } else {
1668                 return array($parent->id);
1669             }
1670         break;
1671         
1672         case CONTEXT_COURSECAT: // Coursecat -> coursecat or site
1673             if (!$coursecat = get_record('course_categories','id',$context->instanceid)) {
1674                 return array();
1675             }
1676             if (!empty($coursecat->parent)) { // return parent value if exist
1677                 $parent = get_context_instance(CONTEXT_COURSECAT, $coursecat->parent);
1678                 return array_merge(array($parent->id), get_parent_contexts($parent));
1679             } else { // else return site value
1680                 $parent = get_context_instance(CONTEXT_SYSTEM, SITEID);
1681                 return array($parent->id);
1682             }
1683         break;
1685         case CONTEXT_COURSE: // 1 to 1 to course cat
1686             if (!$course = get_record('course','id',$context->instanceid)) {
1687                 return array();
1688             }
1689             if (!empty($course->category)) {
1690                 $parent = get_context_instance(CONTEXT_COURSECAT, $course->category);
1691                 return array_merge(array($parent->id), get_parent_contexts($parent));
1692             } else {
1693                 return array();
1694             }
1695         break;
1697         case CONTEXT_GROUP: // 1 to 1 to course
1698             if (!$group = get_record('groups','id',$context->instanceid)) {
1699                 return array();
1700             }
1701             if ($parent = get_context_instance(CONTEXT_COURSE, $group->courseid)) {
1702                 return array_merge(array($parent->id), get_parent_contexts($parent));
1703             } else {
1704                 return array();
1705             }
1706         break;
1708         case CONTEXT_MODULE: // 1 to 1 to course
1709             if (!$cm = get_record('course_modules','id',$context->instanceid)) {
1710                 return array();
1711             }
1712             if ($parent = get_context_instance(CONTEXT_COURSE, $cm->course)) {
1713                 return array_merge(array($parent->id), get_parent_contexts($parent));
1714             } else {
1715                 return array();
1716             }
1717         break;
1719         case CONTEXT_BLOCK: // 1 to 1 to course
1720             if (!$block = get_record('block_instance','id',$context->instanceid)) {
1721                 return array();
1722             }
1723             if ($parent = get_context_instance(CONTEXT_COURSE, $block->pageid)) {
1724                 return array_merge(array($parent->id), get_parent_contexts($parent));
1725             } else {
1726                 return array();
1727             }
1728         break;
1730         default:
1731             error('This is an unknown context!');
1732         return false;
1733     }
1737 /**
1738  * This function gets the capability of a role in a given context.
1739  * It is needed when printing override forms.
1740  * @param int $contextid
1741  * @param string $capability
1742  * @param array $capabilities - array loaded using role_context_capabilities
1743  * @return int (allow, prevent, prohibit, inherit)
1744  */
1745 function get_role_context_capability($contextid, $capability, $capabilities) {
1746     return $capabilities[$contextid][$capability];
1750 /**
1751  * Returns the human-readable, translated version of the capability.
1752  * Basically a big switch statement.
1753  * @param $capabilityname - e.g. mod/choice:readresponses
1754  */
1755 function get_capability_string($capabilityname) {
1757     // Typical capabilityname is mod/choice:readresponses
1759     $names = split('/', $capabilityname);
1760     $stringname = $names[1];                 // choice:readresponses
1761     $components = split(':', $stringname);   
1762     $componentname = $components[0];               // choice
1764     switch ($names[0]) {
1765         case 'mod':
1766             $string = get_string($stringname, $componentname);
1767         break;
1768         
1769         case 'block':
1770             $string = get_string($stringname, 'block_'.$componentname);
1771         break;
1773         case 'moodle':
1774             $string = get_string($stringname, 'role');
1775         break;
1776         
1777         case 'enrol':
1778             $string = get_string($stringname, 'enrol_'.$componentname);
1779         break;  
1780         
1781         default:
1782             $string = get_string($stringname);
1783         break;  
1784       
1785     }
1786     return $string;
1790 /**
1791  * This gets the mod/block/course/core etc strings.
1792  * @param $component
1793  * @param $contextlevel
1794  */
1795 function get_component_string($component, $contextlevel) {
1797     switch ($contextlevel) {
1799         case CONTEXT_SYSTEM:
1800             $string = get_string('coresystem');
1801         break;
1803         case CONTEXT_PERSONAL:
1804             $string = get_string('personal');
1805         break;
1807         case CONTEXT_USERID:
1808             $string = get_string('users');
1809         break;
1811         case CONTEXT_COURSECAT:
1812             $string = get_string('categories');
1813         break;
1815         case CONTEXT_COURSE:
1816             $string = get_string('course');
1817         break;
1819         case CONTEXT_GROUP:
1820             $string = get_string('group');
1821         break;
1823         case CONTEXT_MODULE:
1824             $string = get_string('modulename', basename($component));
1825         break;
1827         case CONTEXT_BLOCK:
1828             $string = get_string('blockname', 'block_'.$component.'.php');
1829         break;
1831         default:
1832             error ('This is an unknown context!');
1833         return false;
1834       
1835     }
1836     return $string;
1839 /** gets the list of roles assigned to this context
1840  * @param object $context
1841  * @return array
1842  */
1843 function get_roles_used_in_context($context) {
1845     global $CFG;
1847     return get_records_sql('SELECT distinct r.id, r.name 
1848                               FROM '.$CFG->prefix.'role_assignments ra,
1849                                    '.$CFG->prefix.'role r 
1850                              WHERE r.id = ra.roleid 
1851                                AND ra.contextid = '.$context->id.' 
1852                              ORDER BY r.sortorder ASC');
1855 /** this function is used to print roles column in user profile page. 
1856  * @param int userid
1857  * @param int contextid
1858  * @return string
1859  */
1860 function get_user_roles_in_context($userid, $contextid){
1861     global $CFG;
1862     
1863     $rolestring = '';
1864     $SQL = 'select * from '.$CFG->prefix.'role_assignments ra, '.$CFG->prefix.'role r where ra.userid='.$userid.' and ra.contextid='.$contextid.' and ra.roleid = r.id';
1865     if ($roles = get_records_sql($SQL)) {
1866         foreach ($roles as $userrole) {
1867             $rolestring .= '<a href="'.$CFG->wwwroot.'/user/index.php?contextid='.$userrole->contextid.'&amp;roleid='.$userrole->roleid.'">'.$userrole->name.'</a>, ';
1868         }   
1869         
1870     }
1871     return rtrim($rolestring, ', ');
1875 /**
1876  * Checks if a user can override capabilities of a particular role in this context
1877  * @param object $context
1878  * @param int targetroleid - the id of the role you want to override
1879  * @return boolean
1880  */
1881 function user_can_override($context, $targetroleid) {
1882     // first check if user has override capability
1883     // if not return false;
1884     if (!has_capability('moodle/role:override', $context)) {
1885         return false;  
1886     }
1887     // pull out all active roles of this user from this context(or above)
1888     if ($userroles = get_user_roles($context)) {
1889         foreach ($userroles as $userrole) {
1890             // if any in the role_allow_override table, then it's ok
1891             if (get_record('role_allow_override', 'roleid', $userrole->roleid, 'allowoverride', $targetroleid)) {
1892                 return true;
1893             }
1894         }
1895     }
1896     
1897     return false;
1898   
1901 /**
1902  * Checks if a user can assign users to a particular role in this context
1903  * @param object $context
1904  * @param int targetroleid - the id of the role you want to assign users to
1905  * @return boolean
1906  */
1907 function user_can_assign($context, $targetroleid) {
1908     
1909     // first check if user has override capability
1910     // if not return false;
1911     if (!has_capability('moodle/role:assign', $context)) {
1912         return false;  
1913     }
1914     // pull out all active roles of this user from this context(or above)
1915     if ($userroles = get_user_roles($context)) {
1916         foreach ($userroles as $userrole) {
1917             // if any in the role_allow_override table, then it's ok
1918             if (get_record('role_allow_assign', 'roleid', $userrole->roleid, 'allowassign', $targetroleid)) {
1919                 return true;
1920             }
1921         }
1922     }
1923     
1924     return false; 
1927 /**
1928  * gets all the user roles assigned in this context, or higher contexts
1929  * this is mainly used when checking if a user can assign a role, or overriding a role
1930  * i.e. we need to know what this user holds, in order to verify against allow_assign and
1931  * allow_override tables
1932  * @param object $context
1933  * @param int $userid
1934  * @return array
1935  */
1936 function get_user_roles($context, $userid=0) {
1938     global $USER, $CFG, $db;
1940     if (empty($userid)) {
1941         if (empty($USER->id)) {
1942             return array();
1943         }
1944         $userid = $USER->id;
1945     }
1947     if ($parents = get_parent_contexts($context)) {
1948         $contexts = ' AND ra.contextid IN ('.implode(',' , $parents).')';
1949     } else {
1950         $contexts = ' AND ra.contextid = \''.$context->id.'\'';
1951     }
1953     return get_records_sql('SELECT *
1954                              FROM '.$CFG->prefix.'role_assignments ra
1955                              WHERE ra.userid = '.$userid.
1956                              $contexts);
1959 /**
1960  * Creates a record in the allow_override table 
1961  * @param int sroleid - source roleid
1962  * @param int troleid - target roleid
1963  * @return int - id or false
1964  */
1965 function allow_override($sroleid, $troleid) {
1966     $record->roleid = $sroleid;
1967     $record->allowoverride = $troleid;
1968     return insert_record('role_allow_override', $record);
1971 /**
1972  * Creates a record in the allow_assign table 
1973  * @param int sroleid - source roleid
1974  * @param int troleid - target roleid
1975  * @return int - id or false
1976  */
1977 function allow_assign($sroleid, $troleid) {
1978     $record->roleid = $sroleid;
1979     $record->allowassign = $troleid;
1980     return insert_record('role_allow_assign', $record);
1983 /**
1984  * gets a list of roles assignalbe in this context for this user
1985  * @param object $context
1986  * @return array
1987  */
1988 function get_assignable_roles ($context) {
1990     $role = get_records('role');
1991     $options = array();
1992     foreach ($role as $rolex) {
1993         if (user_can_assign($context, $rolex->id)) {
1994             $options[$rolex->id] = $rolex->name;
1995         }
1996     }
1997     return $options;
2000 /**
2001  * gets a list of roles that can be overriden in this context by this user
2002  * @param object $context
2003  * @return array
2004  */
2005 function get_overridable_roles ($context) {
2007     $role = get_records('role');
2008     $options = array();
2009     foreach ($role as $rolex) {
2010         if (user_can_override($context, $rolex->id)) {
2011             $options[$rolex->id] = $rolex->name;
2012         }
2013     } 
2014     
2015     return $options;  
2016   
2020 /**
2021  * who has this capability in this context
2022  * does not handling user level resolving!!!
2023  * i.e 1 person has 2 roles 1 allow, 1 prevent, this will not work properly
2024  * @param $context - object
2025  * @param $capability - string capability
2026  * @param $fields - fields to be pulled
2027  * @param $sort - the sort order
2028  * @param $limitfrom - number of records to skip (offset)
2029  * @param $limitnum - number of records to fetch 
2030  */
2031 function get_users_by_capability($context, $capability, $fields='u.*', $sort='', $limitfrom='', $limitnum='') {
2032     
2033     global $CFG;
2034     
2035     // first get all roles with this capability in this context, or above
2036     $possibleroles = get_roles_with_capability($capability, CAP_ALLOW, $context);
2037     $validroleids = array();
2038     foreach ($possibleroles as $prole) {
2039         $caps = role_context_capabilities($prole->id, $context, $capability); // resolved list
2040         if ($caps[$capability] > 0) { // resolved capability > 0
2041             $validroleids[] = $prole->id;
2042         }
2043     }
2044     
2045     /// the following few lines may not be needed
2046     if ($usercontexts = get_parent_contexts($context)) {
2047         $listofcontexts = '('.implode(',', $usercontexts).')';
2048     } else {
2049         $sitecontext = get_context_instance(CONTEXT_SYSTEM, SITEID);
2050         $listofcontexts = '('.$sitecontext->id.')'; // must be site  
2051     }
2052     
2053     $roleids =  '('.implode(',', $validroleids).')';
2054     
2055     $select = ' SELECT '.$fields;
2056     $from   = ' FROM '.$CFG->prefix.'user u LEFT JOIN '.$CFG->prefix.'role_assignments ra ON ra.userid = u.id ';
2057     $where  = ' WHERE (ra.contextid = '.$context->id.' OR ra.contextid in '.$listofcontexts.') AND u.deleted = 0 AND ra.roleid in '.$roleids.' ';
2059     return get_records_sql($select.$from.$where.$sort, $limitfrom, $limitnum);  
2063 ?>