Skip to content

Moodle Assignment Database Schema

Introduction

This page gives an overview of assignment submission plugims within the assignment module.

Overview of an assignment submission plugin

An assignment submission plugin is used to display custom form fields to a student when they are editing their assignment submission. It also has full control over the display the submitted assignment to graders and students. Plugins participate in all assignment features including backup/restore, upgrades from 2.2, offline grading, group assignments and blind marking.

  • An assignment submission plugin can add settings to the module settings page.

  • An assignment submission plugin can show a summary of the submission to students and graders.

  • An assignment submission plugin can add form fields to the student submission page.

History

Assignment submission plugins were added with the assignment module rewrite for Moodle 2.3.

Template

A great example is the "onlinetext" submission plugin included with Moodle core.

File structure

The files for a custom submission plugin sit under "mod/assign/submission/<pluginname>". A plugin should not include any custom files outside of it's own plugin folder.

Note: The plugin name should be no longer than 11 characters - this is because the database tables for a submission plugin must be prefixed with "assignsubmission_" + pluginname (17 chars + X) and the table names can be no longer than 28 chars (thanks oracle). If a plugin requires multiple database tables, the plugin name will need to be shorter to allow different table names to fit under the 28 character limit.

All examples in this document exclude the required copyright and license information from source files for brevity.

version.php

To start with we need to tell Moodle the version information for our new plugin so that it can be installed and upgraded correctly. This information is added to version.php as with any other type of Moodle plugin. The component name must begin with "assignsubmission_" to identify this as a submission plugin.

See version.php for more information.

defined('MOODLE_INTERNAL') || die(); $plugin->version = 2012112900; $plugin->requires = 2012112900; $plugin->component = 'assignsubmission_file';

settings.php

The settings file allows us to add custom settings to the system wide configuration page for our plugin.

All submission settings should be named 'assignsubmission_pluginname/settingname' in order for the setting to be associated with the plugin.

All submission plugins should include one setting named 'default' to indicate if the plugin should be enabled by default when creating a new assignment.

This example from the submission_file plugin also checks to see if there is a maxbytes setting for this moodle installation and if found, it adds a new admin setting to the settings page. The name of the setting should begin with the plugin component name ("assignsubmission_file") in this case. The strings are specified in this plugins language file.

// Note: This is on by default. $settings->add(new admin_setting_configcheckbox('assignsubmission_file/default', new lang_string('default', 'assignsubmission_file'), new lang_string('default_help', 'assignsubmission_file'), 1)); if (isset($CFG->maxbytes)) { $name = new lang_string('maximumsubmissionsize', 'assignsubmission_file'); $description = new lang_string('configmaxbytes', 'assignsubmission_file'); $element = new admin_setting_configselect('assignsubmission_file/maxbytes', $name, $description, 1048576, get_max_upload_sizes($CFG->maxbytes)); $settings->add($element); }

lang/en/submission_pluginname.php

The language file for this plugin must have the same name as the component name (e.g. "submission_file.php"). It should at least define a string for "pluginname". For example:

$string['pluginname'] = 'Awesome submissions';

db/access.php

This is where any additional capabilities are defined if required. This file can be omitted if there are no capabilities added by the plugin.

See Activity_modules#access.php for more information.

$capabilities = array( 'assignsubmission/dungeon:master' => array( 'riskbitmask' => RISK_XSS, 'captype' => 'write', 'contextlevel' => CONTEXT_COURSE, 'archetypes' => array( 'editingteacher' => CAP_ALLOW, 'manager' => CAP_ALLOW ), 'clonepermissionsfrom' => 'moodle/course:manageactivities' ), );

db/upgrade.php

This is where any upgrade code is defined.

See Activity_modules#upgrade.php for more infomation.

function xmldb_submission_file_upgrade($oldversion) { global $CFG, $DB, $OUTPUT; $dbman = $DB->get_manager(); if ($oldversion < 2012091800) { // Put upgrade code here // Savepoint reached. upgrade_plugin_savepoint(true, 2012091800, 'assignsubmission', 'file'); } return true; }

db/install.xml

This is where any database tables required to save this plugins data are defined. File submissions define a table that links to submission and contains a column to record the number of files.

<?xml version="1.0" encoding="UTF-8" ?> <XMLDB PATH="mod/assign/submission/file/db" VERSION="20120423" COMMENT="XMLDB file for Moodle mod/assign/submission/file" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../../../../../lib/xmldb/xmldb.xsd" > <TABLES> <TABLE NAME="assignsubmission_file" COMMENT="Info about file submissions for assignments"> <FIELDS> <FIELD NAME="id" TYPE="int" LENGTH="10" NOTNULL="true" SEQUENCE="true"/> <FIELD NAME="assignment" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/> <FIELD NAME="submission" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/> <FIELD NAME="numfiles" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="The number of files the student submitted."/> </FIELDS> <KEYS> <KEY NAME="primary" TYPE="primary" FIELDS="id" COMMENT="The unique id for this submission info."/> <KEY NAME="assignment" TYPE="foreign" FIELDS="assignment" REFTABLE="assign" REFFIELDS="id" COMMENT="The assignment instance this submission relates to"/> <KEY NAME="submission" TYPE="foreign" FIELDS="submission" REFTABLE="assign_submission" REFFIELDS="id" COMMENT="The submission this file submission relates to."/> </KEYS> </TABLE> </TABLES> </XMLDB>

db/install.php

This example is from the submission_comments plugin. It shows how to run custom code on installation of the plugin. In this case it makes the comments plugin the last of the three submission plugins installed by default.

/** * Code run after the plugin database tables have been created. */ function xmldb_assignsubmission_comments_install() { global $CFG, $DB, $OUTPUT; // do the install require_once($CFG->dirroot . '/mod/assign/locallib.php'); // set the correct initial order for the plugins $assignment = new assignment(); $plugin = $assignment->get_submission_plugin_by_type('comments'); if ($plugin) { $plugin->move('down'); $plugin->move('down'); } return true; }

locallib.php

This is where all the functionality for this plugin is defined. We will step through this file and describe each part as we go.

class assign_submission_file extends assign_submission_plugin {

All submission plugins MUST define a class with the component name of the plugin that extends assign_submission_plugin.

get_name()

public function get_name() { return get_string('file', 'assignsubmission_file'); }

Get name is abstract in submission_plugin and must be defined in your new plugin. Use the language strings to make your plugin translatable.

get_settings()

The "get_settings" function is called when building the settings page for the assignment. It allows this plugin to add a list of settings to the form. Notice that the settings are prefixed by the plugin name which is good practice to avoid conflicts with other plugins.

save_settings()

The "save_settings" function is called when the assignment settings page is submitted, either for a new assignment or when editing an existing one. For settings specific to a single instance of the assignment you can use the assign_plugin::set_config function shown here to save key/value pairs against this assignment instance for this plugin.

get_form_elements()

The get_form_elements function is called when building the submission form. It functions identically to the get_settings function except that the submission object is available (if there is a submission) to associate the settings with a single submission. This example also shows how to use a filemanager within a submission plugin. The function must return true if it has modified the form otherwise the assignment will not include a header for this plugin.

save()

The "save" function is called to save a user submission. The parameters are the submission object and the data from the submission form. This example calls file_postupdate_standard_filemanager to copy the files from the draft file area to the filearea for this submission, it then uses the event api to trigger an assessable_file_uploaded event for the plagiarism api. It then records the number of files in the plugin specific "assignsubmission_file" table.

get_files()

If this submission plugin produces one or more files, it should implement "get_files" so that the portfolio API can export a list of all the files from all of the plugins for this assignment submission. This is also used by the offline grading feature in the assignment.

view_summary()

The view_summary function is called to display a summary of the submission to both markers and students. It counts the number of files submitted and if it is more that a set number, it only displays a count of how many files are in the submission - otherwise it uses a helper function to write the entire list of files. This is because we want to keep the summaries really short so they can be displayed in a table. There will be a link to view the full submission on the submission status page.

view()

The view function is called to display the entire submission to both markers and students. In this case it uses the helper function in the assignment class to write the list of files.

can_upgrade()

public function can_upgrade($type, $version) { $uploadsingle_type ='uploadsingle'; $upload_type ='upload'; if (($type == $uploadsingle_type || $type == $upload_type) && $version >= 2011112900) { return true; } return false; }

The can_upgrade function is used to identify old "Assignment 2.2" subtypes that can be upgraded by this plugin. This plugin supports upgrades from the old "upload" and "uploadsingle" assignment subtypes.

upgrade_settings()

This function is called once per assignment instance to upgrade the settings from the old assignment to the new mod_assign. In this case it sets the maxbytes, maxfiles and alwaysshowdescription configuration settings.

upgrade()

The "upgrade" function upgrades a single submission from the old assignment type to the new one. In this case it involves copying all the files from the old filearea to the new one. There is a helper function available in the assignment class for this (Note: the copy will be fast as it is just adding rows to the files table). If this function returns false, the upgrade will be aborted and rolled back.

get_editor_fields()

public function () { return array('onlinetext' => get_string('pluginname', 'assignsubmission_comments')); }

This example is from assignsubmission_onlinetext. If the plugin uses a text-editor it is ideal if the plugin implements "get_editor_fields". This allows the portfolio to retrieve the text from the plugin when exporting the list of files for a submission. This is required because the text is stored in the plugin specific table that is only known to the plugin itself. If a plugin supports multiple text areas it can return the name of each of them here.

get_editor_text()

This example is from assignsubmission_onlinetext. If the plugin uses a text-editor it is ideal if the plugin implements "get_editor_text". This allows the portfolio to retrieve the text from the plugin when exporting the list of files for a submission. This is required because the text is stored in the plugin specific table that is only known to the plugin itself. The name is used to distinguish between multiple text areas in the one plugin.

get_editor_format()

This example is from assignsubmission_onlinetext. For the same reason as the previous function, if the plugin uses a text editor, it is ideal if the plugin implements "get_editor_format". This allows the portfolio to retrieve the text from the plugin when exporting the list of files for a submission. This is required because the text is stored in the plugin specific table that is only known to the plugin itself. The name is used to distinguish between multiple text areas in the one plugin.

is_empty()

If a plugin has no submission data to show - it can return true from the is_empty function. This prevents a table row being added to the submission summary for this plugin. It is also used to check if a student has tried to save an assignment with no data.

get_file_areas()

A plugin should implement get_file_areas if it supports saving of any files to moodle - this allows the file areas to be browsed by the moodle file manager.

copy_submission()

Since Moodle 2.5 - a students submission can be copied to create a new submission attempt. Plugins should implement this function if they store data associated with the submission (most plugins).

format_for_log()

The format_for_log function lets a plugin produce a really short summary of a submission suitable for adding to a log message.

delete_instance()

The delete_instance function is called when a plugin is deleted. Note only database records need to be cleaned up - files belonging to fileareas for this assignment will be automatically cleaned up.

lib.php

This file is the entry point to many standard Moodle APIs for plugins. An example is that in order for a plugin to allow users to download files contained within a filearea belonging to the plugin, they must implement the componentname_pluginfile function in order to perform their own security checks.

See File_API for more information. Example:

function assignfeedback_file_pluginfile($course, $cm, context $context, $filearea, $args, $forcedownload) { global $USER, $DB; if ($context->contextlevel != CONTEXT_MODULE) { return false; } require_login($course, false, $cm); $itemid = (int)array_shift($args); $record = $DB->get_record('assign_grades', array('id'=>$itemid), 'userid,assignment', MUST_EXIST); $userid = $record->userid; if (!$assign = $DB->get_record('assign', array('id'=>$cm->instance))) { return false; } if ($assign->id != $record->assignment) { return false; } // Check is users feedback or has grading permission. if ($USER->id != $userid and !has_capability('mod/assign:grade', $context)) { return false; } $relativepath = implode('/', $args); $fullpath = "/{$context->id}/assignfeedback_file/$filearea/$itemid/$relativepath"; $fs = get_file_storage(); if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { return false; } // Download MUST be forced - security! send_stored_file($file, 0, 0, true); }

Useful classes

A submission plugin has access to a number of useful classes in the assignment module. See the phpdocs (or the code) for more information on these classes.

assign_plugin

This abstract class is the base class for all assignment plugins (feedback or submission plugins).

assign_submission_plugin

This is the base class all assignment submission plugins must extend. It contains a small number of additional function that only apply to submission plugins.

assign

This is the main class for interacting with the assignment module.

It contains public functions that are useful for listing users, loading and updating submissions, loading and updating grades, displaying users etc.

Other features

Add calendar events

Moodle 3.1


From Moodle 3.1 onwards, submission plugins can add events to the Moodle calendar without side effects. These will be hidden and deleted in line with the assignment module. For example:

// Add release date to calendar $calendarevent = new stdClass(); $calendarevent->name = get_string('calendareventname', 'assignsubmission_something'); $calendarevent->description = get_string('calendareventdesc', 'assignsubmission_something'); $calendarevent->courseid = $courseid; $calendarevent->groupid = 0; $calendarevent->userid = $userid; $calendarevent->modulename = 'assign'; $calendarevent->instance = $instanceid; $calendarevent->eventtype = 'something_release'; // For activity module's events, this can be used to set the alternative text of the event icon. Set it to 'pluginname' unless you have a better string. $calendarevent->timestart = $releasedate; $calendarevent->visible = true; $calendarevent->timeduration = 0; calendar_event::create($calendarevent);

This code should be placed in the "save_settings()" method of your assign_submission_plugin class.

Moodle is a web application used in educational settings. While this chapter will try to give an overview of all aspects of how Moodle works, it focuses on those areas where Moodle's design is particularly interesting:

Moodle provides a place online where students and teachers can come together to teach and learn. A Moodle site is divided into courses. A course has users enrolled in it with different roles, such as Student or Teacher. Each course comprises a number of resources and activities. A resource might be a PDF file, a page of HTML within Moodle, or a link to something elsewhere on the web. An activity might be a forum, a quiz or a wiki. Within the course, these resources and activities will be structured in some way. For example they may be grouped into logical topics, or into weeks on a calendar.

Moodle can be used as a standalone application. Should you wish to teach courses on software architecture (for example) you could download Moodle to your web host, install it, start creating courses, and wait for students to come and self-register. Alternatively, if you are a large institution, Moodle would be just one of the systems you run. You would probably also have the infrastructure shown in Figure 13.2.

Moodle focuses on providing an online space for teaching and learning, rather than any of the other systems that an educational organisation might need. Moodle provides a basic implementation of the other functionalities, so that it can function either as a stand-alone system or integrated with other systems. The role Moodle plays is normally called a virtual learning environment (VLE), or learning or course management system (LMS, CMS or even LCMS).

Moodle is open source or free software (GPL). It is written in PHP. It will run on most common web servers, on common platforms. It requires a database, and will work with MySQL, PostgreSQL, Microsoft SQL Server or Oracle.

The Moodle project was started by Martin Dougiamas in 1999, while he was working at Curtin University, Australia. Version 1.0 was released in 2002, at which time PHP4.2 and MySQL 3.23 were the technologies available. This limited the kind of architecture that was possible initially, but much has changed since then. The current release is the Moodle 2.2.x series.

13.1. An Overview of How Moodle Works

A Moodle installation comprises three parts:

  1. The code, typically in a folder like or . This should not be writable by the web server.
  2. The database, managed by one of the supported RDMSs. In fact, Moodle adds a prefix to all the table names, so it can share a database with other applications if desired.
  3. The folder. This is a folder where Moodle stores uploaded and generated files, and so needs to be writable by the web server. For security reasons, the should be outside the web root.

These can all be on a single server. Alternatively, in a load-balanced set-up, there will be multiple copies of the code on each web server, but just one shared copy of the database and , probably on other servers.

The configuration information about these three parts is stored in a file called in the root of the folder when Moodle is installed.

Request Dispatching

Moodle is a web applications, so users interact with it using their web browser. From Moodle's point of view that means responding to HTTP requests. An important aspect of Moodle's design is, therefore, the URL namespace, and how URLs get dispatched to different scripts.

Moodle uses the standard PHP approach to this. To view the main page for a course, the URL would be , where is the unique id of the course in the database. To view a forum discussion, the URL would be something like . That is, these particular scripts, or , would handle these requests.

This is simple for the developer. To understand how Moodle handles a particular request, you look at the URL and start reading code there. It is ugly from the user's point of view. These URLs are, however, permanent. The URLs do not change if the course is renamed, or if a moderator moves a discussion to a different forum. (This is a good property for URLs to have, as explained in Tim Berners-Lee's article Cool URIs don't change.)

The alternative approach one could take is to have a single entry point . The single script would then dispatch the requests in some way. This approach adds a layer of indirection, which is something software developers always like to do. The lack of this layer of indirection does not seem to hurt Moodle.

Plugins

Like many successful open source projects, Moodle is built out of many plugins, working together with the core of the system. This is a good approach because at allows people to change and enhance Moodle in defined ways. An important advantage of an open source system is that you can tailor it to your particular needs. Making extensive customisations to the code can, however, lead to big problems when the time comes to upgrade, even when using a good version control system. By allowing as many customisations and new features as possible to be implemented as self-contained plugins that interact with the Moodle core through a defined API, it is easier for people to customise Moodle to their needs, and to share customisations, while still being able to upgrade the core Moodle system.

There are various ways a system can be built as a core surrounded by plugins. Moodle has a relatively fat core, and the plugins are strongly-typed. When I say a fat core, I mean that there is a lot of functionality in the core. This contrasts with the kind of architecture where just about everything, except for a small plugin-loader stub, is a plugin.

When I say plugins are strongly typed, I mean that depending on which type of functionality you want to implement, you have to write a different type of plugin, and implement a different API. For example, a new Activity module plugin would be very different from a new Authentication plugin or a new Question type. At the last count there are about 35 different types of plugin. (There is a full list of Moodle plugin types.) This contrasts with the kind of architecture where all plugins use basically the same API and then, perhaps, subscribe to the subset of hooks or events they are interested in.

Generally, the trend in Moodle has been to try to shrink the core, by moving more functionality into plugins. This effort has only been somewhat successful, however, because an increasing feature-set tends to expand the core. The other trend has been to try to standardise the different types of plugin as much as possible, so that in areas of common functionality, like install and upgrade, all types of plugins work the same way.

A plugin in Moodle takes the form of a folder containing files. The plugin has a type and a name, which together make up the "Frankenstyle" component name of the plugin. (The word "Frankenstyle" arose out of an argument in the developers' Jabber channel, but everyone liked it and it stuck.) The plugin type and name determine the path to the plugin folder. The plugin type gives a prefix, and the foldername is the plugin name. Here are some examples:

Plugin typePlugin nameFrankenstyleFolder
mod (Activity module)
mod (Activity module)
block (Side-block)
qtype (Question type)/shortanswer
quiz (Quiz report)

The last example shows that each activity module is allowed to declare sub-plugin types. At the moment only activity modules can do this, for two reasons. If all plugins could have sub-plugins that might cause performance problems. Activity modules are the main educational activities in Moodle, and so are the most important type of plugin, thus they get special privileges.

An Example Plugin

I will explain a lot of details of the Moodle architecture by considering a specific example plugin. As is traditional, I have chosen to implement a plugin that displays "Hello world".

This plugin does not really fit naturally into any of the standard Moodle plugin types. It is just a script, with no connection to anything else, so I will choose to implement it as a "local" plugin. This is a catch-all plugin type for miscellaneous functionality that does not fit anywhere better. I will name my plugin , to give a Frankensyle name of , and a folder path of . (The plugin code can be downloaded.)

Each plugin must contain a file called which defines some basic metadata about the plugin. This is used by the Moodle's plugin installer system to install and upgrade the plugin. For example, contains:

<?php $plugin->component = 'local_greet'; $plugin->version = 2011102900; $plugin->requires = 2011102700; $plugin->maturity = MATURITY_STABLE;

It may seem redundant to include the component name, since this can be deduced from the path, but the installer uses this to verify that the plugin has been installed in the right place. The version field is the version of this plugin. Maturity is ALPHA, BETA, RC (release candidate), or STABLE. Requires is the minimum version of Moodle that this plugin is compatible with. If necessary, one can also document other plugins that this one depends on.

Here is the main script for this simple plugin (stored in ):

<?php require_once(dirname(__FILE__) . '/../../config.php'); // 1 require_login(); // 2 $context = context_system::instance(); // 3 require_capability('local/greet:begreeted', $context); // 4 $name = optional_param('name', '', PARAM_TEXT); // 5 if (!$name) { $name = fullname($USER); // 6 } add_to_log(SITEID, 'local_greet', 'begreeted', 'local/greet/index.php?name=' . urlencode($name)); // 7 $PAGE->set_context($context); // 8 $PAGE->set_url(new moodle_url('/local/greet/index.php'), array('name' => $name)); // 9 $PAGE->set_title(get_string('welcome', 'local_greet')); // 10 echo $OUTPUT->header(); // 11 echo $OUTPUT->box(get_string('greet', 'local_greet', format_string($name))); // 12 echo $OUTPUT->footer(); // 13

Line 1: Bootstrapping Moodle

require_once(dirname(__FILE__) . '/../../config.php'); // 1

The single line of this script that does the most work is the first. I said above that contains the details Moodle needs to connect to the database and find the moodledata folder. It ends, however, with the line . This:

  1. loads all the standard Moodle libraries using ;
  2. starts the session handling;
  3. connects to the database; and
  4. sets up a number of global variables, which we shall meet later.

Line 2: Checking the User Is Logged In

require_login(); // 2

This line causes Moodle to check that the current user is logged in, using whatever authentication plugin the administrator has configured. If not, the user will be redirected to the log-in form, and this function will never return.

A script that was more integrated into Moodle would pass more arguments here, to say which course or activity this page is part of, and then would also verify that the user is enrolled in, or otherwise allowed to access this course, and is allowed to see this activity. If not, an appropriate error would be displayed.

13.2. Moodle's Roles and Permissions System

The next two lines of code show how to check that the user has permission to do something. As you can see, from the developer's point of view, the API is very simple. Behind the scenes, however, there is a sophisticated access system which gives the administrator great flexibility to control who can do what.

Line 3: Getting the Context

$context = context_system::instance(); // 3

In Moodle, users can have different permissions in different places. For example, a user might be a Teacher in one course, and a Student in another, and so have different permissions in each place. These places are called contexts. Contexts in Moodle form a hierarchy rather like a folder hierarchy in a file-system. At the top level is the System context (and, since this script is not very well integrated into Moodle, it uses that context).

Within the System context are a number of contexts for the different categories that have been created to organise courses. These can be nested, with one category containing other categories. Category contexts can also contain Course contexts. Finally, each activity in a course will have its own Module context.

Line 4: Checking the User Has Permission to Use This Script

require_capability('local/greet:begreeted', $context); // 4

Having got the context—the relevant area of Moodle—the permission can be checked. Each bit of functionality that a user may or may not have is called a capability. Checking a capability provides more fine-grained access control than the basic checks performed by . Our simple example plugin has just one capability: .

The check is done using the function, which takes the capability name and the context. Like other functions, it will not return if the user does not have the capability. It will display an error instead. In other places the non-fatal function, which returns a Boolean would be used, for example, to determine whether to display a link to this script from another page.

How does the administrator configure which user has which permission? Here is the calculation that performs (at least conceptually):

  1. Start from the current Context.
  2. Get a list of the Roles that the user has in this Context.
  3. Then work out what the Permission is for each Role in this Context.
  4. Aggregate those permissions to get a final answer.

Defining Capabilities

As the example shows, a plugin can define new capabilities relating to the particular functionality it provides. Inside each Moodle plugin there is a sub-folder of the code called . This contains all the information required to install or upgrade the plugin. One of those bits of information is a file called that defines the capabilities. Here is the file for our plugin, which lives in :

<?php $capabilities = array('local/greet:begreeted' => array( 'captype' => 'read', 'contextlevel' => CONTEXT_SYSTEM, 'archetypes' => array('guest' => CAP_ALLOW, 'user' => CAP_ALLOW) ));

This gives some metadata about each capability which are used when constructing the permissions management user interface. It also give default permissions for common types of role.

Roles

The next part of the Moodle permissions system is roles. A role is really just a named set of permissions. When you are logged into Moodle, you will have the "Authenticated user" role in the System context, and since the System context is the root of the hierarchy, that role will apply everywhere.

Within a particular course, you may be a Student, and that role assignment will apply in the Course context and all the Module contexts within it. In another course, however, you may have a different role. For example, Mr Gradgrind may be Teacher in the "Facts, Facts, Facts" course, but a Student in the professional development course "Facts Aren't Everything". Finally, a user might be given the Moderator role in one particular forum (Module context).

Permissions

A role defines a permission for each capability. For example the Teacher role will probably ALLOW , but the Student role will not. However, both Student and Teacher will allow .

The roles are normally defined globally, but they can be re-defined in each context. For example, one particular wiki can be made read-only to students by overriding the permission for the capability for the Student role in that wiki (Module) context, to PREVENT.

There are four Permissions:

  • NOT SET/INHERIT (default)
  • ALLOW
  • PREVENT
  • PROHIBIT

In a given context, a role will have one of these four permissions for each capability. One difference between PROHIBIT and PREVENT is that a PROHIBIT cannot be overridden in sub-contexts.

Permission Aggregation

Finally the permissions for all the roles the user has in this context are aggregated.

  • If any role gives the permission PROHIBIT for this capability, return false.
  • Otherwise, if any role gives ALLOW for this capability, return true.
  • Otherwise return false.

A use case for PROHIBIT is this: Suppose a user has been making abusive posts in a number of forums, and we want to stop them immediately. We can create a Naughty user role, which sets and other such capabilities to PROHIBIT. We can then assign this role to the abusive user in the System context. That way, we can be sure that the user will not be able to post any more in any forum. (We would then talk to the student, and having reached a satisfactory outcome, remove that role assignment so that they may use the system again.)

So, Moodle's permissions system gives administrators a huge amount of flexibility. They can define whichever roles they like with different permissions for each capability; they can alter the role definitions in sub-contexts; and then they can assign different roles to users in different contexts.

13.3. Back to Our Example Script

The next part of the script illustrates some miscellaneous points:

Line 5: Get Data From the Request

$name = optional_param('name', '', PARAM_TEXT); // 5

Something that every web application has to do is get data from a request (GET or POST variables) without being susceptible to SQL injection or cross-site scripting attacks. Moodle provides two ways to do this.

The simple method is the one shown here. It gets a single variable given the parameter name (here ) a default value, and the expected type. The expected type is used to clean the input of all unexpected characters. There are numerous types like , , , and so on.

There is also a similar function, which like other functions stops execution and displays an error message if the expected parameter is not found.

The other mechanism Moodle has for getting data from the request is a fully fledged forms library. This is a wrapper around the HTML QuickForm library from PEAR. (For non-PHP programmers, PEAR is PHP's equivalent of CPAN.) This seemed like a good choice when it was selected, but is now no longer maintained. At some time in the future we will have to tackle moving to a new forms library, which many of us look forwards to, because QuickForm has several irritating design issues. For now, however, it is adequate. Forms can be defined as a collection of fields of various types (e.g. text box, select drop-down, date-selector) with client- and server- side validation (including use of the same types).

Line 6: Global Variables

if (!$name) { $name = fullname($USER); // 6 }

This snippet shows the first of the global variables Moodle provides. makes accessible the information about the user accessing this script. Other globals include:

  • : holds the commonly used configuration settings.
  • : the database connection.
  • : a wrapper around the PHP session.
  • : the course the current request relates to.

and several others, some of which we will encounter below.

You may have read the words "global variable" with horror. Note, however, that PHP processes a single request at a time. Therefore these variables are not as global as all that. In fact, PHP global variables can be seen as an implementation of the thread-scoped registry pattern (see Martin Fowler's Patterns of Enterprise Application Architecture) and this is the way in which Moodle uses them. It is very convenient in that it makes commonly used objects available throughout the code, without requiring them to be passed to every function and method. It is only infrequently abused.

Nothing is Simple

This line also serves to make a point about the problem domain: nothing is ever simple. To display a user's name is more complicated than simply concatenating , , and . The school may have policies about showing either of those parts, and different cultures have different conventions for which order to show names. Therefore, there are several configurations settings and a function to assemble the full name according to the rules.

Dates are a similar problem. Different users may be in different time-zones. Moodle stores all dates as Unix time-stamps, which are integers, and so work in all databases. There is then a function to display the time-stamp to the user using the appropriate timezone and locale settings.

Line 7: Logging

add_to_log(SITEID, 'local_greet', 'begreeted', 'local/greet/index.php?name=' . urlencode($name)); // 7

All significant actions in Moodle are logged. Logs are written to a table in the database. This is a trade-off. It makes sophisticated analysis quite easy, and indeed various reports based on the logs are included with Moodle. On a large and busy site, however, it is a performance problem. The log table gets huge, which makes backing up the database more difficult, and makes queries on the log table slow. There can also be write contention on the log table. These problems can be mitigated in various ways, for example by batching writes, or archiving or deleting old records to remove them from the main database.

13.4. Generating Output

Output is mainly handled via two global objects.

Line 8: The Global

$PAGE->set_context($context); // 8

stores the information about the page to be output. This information is then readily available to the code that generates the HTML. This script needs to explicitly specify the current context. (In other situations, this might have been set automatically by .) The URL for this page must also be set explicitly. This may seem redundant, but the rationale for requiring it is that you might get to a particular page using any number of different URLs, but the URL passed to should be the canonical URL for the page—a good permalink, if you like. The page title is also set. This will end up in the element of the HTML.

Line 9: Moodle URL

$PAGE->set_url(new moodle_url('/local/greet/index.php'), array('name' => $name)); // 9

I just wanted to flag this nice little helper class which makes manipulating URLs much easier. As an aside, recall that the function call above did not use this helper class. Indeed, the log API cannot accept objects. This sort of inconsistency is a typical sign of a code-base as old as Moodle's.

Line 10: Internationalisation

$PAGE->set_title(get_string('welcome', 'local_greet')); // 10

Moodle uses its own system to allow the interface to be translated into any language. There may now be good PHP internationalisation libraries, but in 2002 when it was first implemented there was not one available that was adequate. The system is based around the function. Strings are identified by a key and the plugin Frankenstyle name. As can be seen on line 12, it is possible to interpolate values into the string. (Multiple values are handled using PHP arrays or objects.)

The strings are looked up in language files that are just plain PHP arrays. Here is the language file for our plugin:

<?php $string['greet:begreeted'] = 'Be greeted by the hello world example'; $string['welcome'] = 'Welcome'; $string['greet'] = 'Hello, {$a}!'; $string['pluginname'] = 'Hello world example';

Note that, as well as the two string used in our script, there are also strings to give a name to the capability, and the name of the plugin as it appears in the user interface.

The different languages are identified by the two-letter country code ( here). Languages packs may derive from other language packs. For example the (French Canadian) language pack declares (French) as the parent language, and thus only has to define those strings that differ from the French. Since Moodle originated in Australia, means British English, and (American English) is derived from it.

Again, the simple API for plugin developers hides a lot of complexity, including working out the current language (which may depend on the current user's preferences, or the settings for the particular course they are currently in), and then searching through all the language packs and parent language packs to find the string.

Producing the language pack files and co-ordinating the translation effort is managed at http://lang.moodle.org/, which is Moodle with a custom plugin (). It uses both Git and the database as a backend to store the language files with full version history.

Line 11: Starting Output

echo $OUTPUT->header(); // 11

This is another innocuous-looking line that does much more than it seems. The point is that before any output can be done, the applicable theme (skin) must be worked out. This may depend on a combination of the page context and the user's preferences. was, however, only set on line 8, so the global could not have been initialised at the start of the script. In order to solve this problem, some PHP magic is used to create the proper object based on the information in the first time any output method is called.

Another thing to consider is that every page in Moodle may contain blocks. These are extra configurable bits of content that are normally displayed to the left or right of the main content. (They are a type of plugin.) Again, the exact collection of blocks to display depends, in a flexible way (that the administrator can control) on the page context and some other aspects of the page identity. Therefore, another part of preparing for output is a call to .

Once all the necessary information has been worked out, the theme plugin (that controls the overall look of the page) is called to generate the overall page layout, including whatever standard header and footer is desired. This call is also responsible for adding the output from the blocks at the appropriate place in the HTML. In the middle of the layout there will be a where the specific content for this page goes. The HTML of this layout is generated, and then split in half after the start of the main content . The first half is returned, and the rest is stored to be returned by .

Line 12: Outputting the Body of the Page

echo $OUTPUT->box(get_string('greet', 'local_greet', format_string($name))); // 12

This line outputs the body of the page. Here it simply displays the greeting in a box. The greeting is, again, a localised string, this time with a value substituted into a placeholder. The core renderer provides many convenience methods like to describe the required output in quite high-level terms. Different themes can control what HTML is actually output to make the box.

The content that originally came from the user () is output though the function. This is the other part of providing XSS protection. It also enables the user of text filters (another plugin type). An example filter would be the LaTeX filter, which replaces input like with an image of the equation. I will mention, but not explain, that there are actually three different functions (, , and ) depending on the particular type of content being output.

Line 13: Finishing Output

echo $OUTPUT->footer(); // 13

Finally, the footer of the page is output. This example does not show it, but Moodle tracks all the JavaScript that is required by the page, and outputs all the necessary script tags in the footer. This is standard good practice. It allows users to see the page without waiting for all the JavaScript to load. A developer would include JavaScript using API calls like .

Should This Script Mix Logic and Output?

Obviously, putting the output code directly in , even if at a high level of abstraction, limits the flexibility that themes have to control the output. This is another sign of the age of the Moodle code-base. The global was introduced in 2010 as a stepping stone on the way from the old code, where the output and controller code were in the same file, to a design where all the view code was properly separated. This also explains the rather ugly way that the entire page layout is generated, then split in half, so that any output from the script itself can be placed between the header and the footer. Once the view code has been separated out of the script, into what Moodle calls a renderer, the theme can then choose to completely (or partially) override the view code for a given script.

A small refactoring can move all the output code out of our and into a renderer. The end of (lines 11 to 13) would change to:

$output = $PAGE->get_renderer('local_greet'); echo $output->greeting_page($name);

and there would be a new file :

If the theme wished to completely change this output, it would define a subclass of this renderer that overrides the method. determines the appropriate renderer class to instantiate depending on the current theme. Thus, the output (view) code is fully separated from the controller code in , and the plugin has been refactored from typical legacy Moodle code to a clean MVC architecture.

13.5. Database Abstraction

The "Hello world" script was sufficiently simple that it did not need to access the database, although several of the Moodle library calls used did do database queries. I will now briefly describe the Moodle database layer.

Moodle used to use the ADOdb library as the basis of its database abstraction layer, but there were issues for us, and the extra layer of library code had a noticeable impact on performance. Therefore, in Moodle 2.0 we switched to our own abstraction layer, which is a thin wrapper around the various PHP database libraries.

The Class

The heart of the library is the class. This defines the interface provided by the global variable, which gives access to the database connection. A typical usage might be:

$course = $DB->get_record('course', array('id' => $courseid));

That translates into the SQL:

SELECT * FROM mdl_course WHERE id = $courseid;

and returns the data as a plain PHP object with public fields, so you could access , , etc.

Simple methods like this deal with basic queries, and simple updates and inserts. Sometimes it is necessary to do more complex SQL, for example to run reports. In that case, there are methods to execute arbitrary SQL: