Moodle Theme Building : Role Descriptor

Moodle Theme Building : Role Descriptor

This post is part of a series for documenting the work I did building a new Moodle Theme for our VLE’s. More to follow…

We have many different user roles in our VLE. I wanted a way to show this clearly at the top just under the user’s name at the top.

How I set this up….

This wasn’t a simple task as it turned out I needed to override a core library output renderer in order to complete the task. The renderer function I needed to override can be found in /lib/outputrenderers.php

https://github.com/moodle/moodle/blob/master/lib/outputrenderers.php

I created a copy of the user_menu function from this file and copied it into my theme’s renderer override class.

https://docs.moodle.org/dev/Output_renderers
https://docs.moodle.org/dev/Overriding_a_renderer

My Theme’s renderer override class lives in : /theme/nameoftheme/renderers.php
I’m extending the core_renderer here Line 517 of lib/outputrenderers.php so the class should have “… extends core_renderer” when it’s declared.

I copied in the user_menu function into the theme’s override class so I can override it.
On what would be line 3371 of the original file I replace the role section with a call to a function I’ve created in the override class called show_user_roles (see further down). This function should return a HTML String that describes the roles the current user is in.

defined('MOODLE_INTERNAL') || die();

class theme_nameoftheme_core_renderer extends core_renderer {

     // Original Function in /lib/outputrenderers.php 
     public function user_menu($user = null, $withlinks = null) {

          ...

          // fetch all role information....
          $usertextcontents .= $this->show_user_roles($user, $opts);

          ...

     }

}
// Original user_menu function
public function user_menu($user = null, $withlinks = null) {

     ...

     // Role. (Line 3371 of original)
     if (!empty($opts->metadata['asotherrole'])) {
          $role = core_text::strtolower(preg_replace('#[ ]+#', '-', trim($opts->metadata['rolename'])));
          $usertextcontents .= html_writer::span(
               $opts->metadata['rolename'],
                'meta role role-' . $role
          );
     }

     ...

}
public function show_user_roles($user = null, $opts = null) {
	// fetch all role information....
	$systemcontext = context_system::instance();
	$allRoles = role_fix_names(get_all_roles(), $systemcontext, ROLENAME_ORIGINAL);
	$usertextcontents = '';
        
	$usertextcontents .= html_writer::start_span('roles d-flex justify-content-end');

		
	// Role.
	if (!empty($opts->metadata['asotherrole'])) { // Logged in as....
		$role = core_text::strtolower(preg_replace('#[ ]+#', '-', trim($opts->metadata['rolename'])));
		$usertextcontents .= html_writer::span(
		$opts->metadata['rolename'],
			'meta role role-' . $role
		);
	} else {
		if(is_siteadmin()) { // Is Site Administrator
			$usertextcontents .= html_writer::span(
				'Site Administrator',
				'meta role role-admin'
			);
		} else { // possibly another role....
					
			$ismanagerorlecturer = false;
			foreach($allRoles as $key => $role) {
					
				if(user_has_role_assignment($user->id,$key)) {
					
					if(in_array($role->shortname, array(...))) {
						// skip these ones :).
					} else {
						if(in_array($role->shortname, array('manager','editinglecturer','teacher','visitinglecturer') ) ) {
							$ismanagerorlecturer = true;
						}
							
						// some lectureres are listed as students as well which seems sort of pointless to display this...
						if($role->shortname != 'student' || ($role->shortname == 'student' && !$ismanagerorlecturer)) {
							$usertextcontents .= html_writer::span(
								$role->localname,
									'meta role role-' . $role->shortname
							);
						}
					}
				}
			}
		}
	}
        
	$usertextcontents .= html_writer::end_span();
        
	return $usertextcontents;
}

That’s quite a bit so here’s a breakdown of what this script is doing:

  • Line 4 : Fetch all the user roles used on the site ($allRoles)
  • Line 5 : Create a string to store the html in ($usertextcontents = ”;)
  • Line 11 : Check if the current user is masquerading as another role e.g. a Lecturer viewing as a “Student”
    (if (!empty($opts->metadata[‘asotherrole’])) {)
    • Line 12 : turn the role name into a class (i.e. remove spaces etc)
    • Line 13 : Create a span for the role.
  • Line 17 : If current user isn’t masquerading….
    • Line 18 : Check if current user is a Site Admin….
      • Line 19 : Create a span for this role.
    • Line 23 : All users except the site admin….
      • Line 25 : This is a check to see if the user is a manager / lecturer since they may have multiple roles so we might want to skip some!
      • Line 26 : cycle through $allRoles
      • Line 28 : Check if current user has this role
      • Line 30 : This line I’m checking to see if this is one of the roles we should skip
        (we have some extra roles to set certain permissions for a custom course activity so we don’t want to display these since no one will care much about these)
      • Line 33 : Checking to see if the current user is in a manager / lecturer role in which case we set the flag to true.
      • Line 38 : If the role isn’t “Student” OR if the role is “Student” AND the user isn’t a manager or lecturer create a span for this role.
  • Line 52 – function returns the finished HTML string.

Handling a user with Multiple Roles

But what if the user has multiple roles?

When I initially wrote this I didn’t have the bit where I check for manager / lecturer or the bit where I skip some of the extra roles.

For some users where they were in multiple different roles on the site made it display weirdly as there wasn’t enough space.

screenshot of original issue

If they are viewing as a specific role we won’t see the additional roles (this is handled on line 11)

To deal with this. I first added the check for manager / lecturer since those roles we don’t really care that they are also in a student role.

I also hid some of the irrelevant roles we don’t care about by skipping them in the loop.

This reduced the list down from about 10 roles to maybe 3 but it was still displaying in a column.

I added some bootstrap classes (boost and my theme uses BootStrap) and CSS to handle this.

$usertextcontents .= html_writer::start_span('roles d-flex justify-content-end');

https://getbootstrap.com/docs/4.6/utilities/flex/

screenshot of the fixed issue
.roles {
	column-gap: 5px;
	.role:after {
		content: ',';
		display: block;
		float: right;
	}
	.role:last-child:after {
		display: none;
	}
}

This may need further improvements if the user is a member of even more roles but so far it hasn’t been an issue for me. I will revisit this if it becomes and issue.

What about Mobile?

On mobile I’m collapsing it down to make it more minimal.

Further Improvements?

I’m wondering if it’s a bit wasteful to fetch $allRoles everytime and if there is someway I can cache it (like set_transient does on WordPress). It won’t change very often.

There may be further ways to optimise the code and I’m open to suggestions.

Documentation Links

Moodle Documentation : Roles
Moodle Output Renderers
Moodle Documentation : Overriding a renderer

Comments are closed.