Moodle 2.0 Plugin
Moodle 1.9 Plugin
Developer documentation
Release notes

Developer notes for the A.nnotate Plugin

Connecting Moodle to the A.nnotate service poses a number of technical problems that could be resolved in a variety of ways. Here we explain the problems and why particular solutions were adopted for the plugin.


There are two main requirements: first the plugin should add link buttons next to resources in various pages; second, when the user presses the button, the file should be displayed by the remote server. In addition, plugin installation should be as simple as possible and robust against upgrades of Moodle.

The main issues are first, how to get the buttons in the right place, and second, how to get the file to the server.

Adding buttons to resources in Moodle

One natural choice would be to use a filter to process the html as it is generated, spot resource links, and add the extra button next to them.

Unfortunately, this is not a good solution in Moodle 1.9 because:

The first problem may be resolved in Moodle 2.0, but the performance costs will remain.

Another option is to insert our own JavaScript on the page, and let the JavaScript modify the page once it has been sent to the client. This means that the user must have JavaScript enabled to see the buttons, but, since the document display process itself is JavaScript dependent, this does not reduce the functionality and may even be a benefit: if the user wouldn't be able to see the document in A.nnotate, they won't be shown the buttons.

Loading JavaScript on selected pages

The next question then is how to get our JavaScript on the page. Essentially, we just need our own PHP script to be loaded on pages that need the A.nnotate links. Note that it doesn't matter where the script is loaded - if we have a filter that we are sure will be called, we can just put the script in the first time it it called, but there is no guarantee the filter will be called at all. We can't insert the script element when the PHP is loaded because this is typically before the header is written, but we can call Moodle's require_js to tell it to put the JavaScript in.

This works fine for the main A.nnotate JavaScript, but we also need some settings from the plugin configuration to be available to JavaScript. Moodle 2.0 has facilities specifically for this, but in 1.9, there is no mechanism provided for getting data into the script. It could be done by declaring a JavaScript array in an inline script, but, as above, this is not an option when the script is loaded before the header is written.

Another solution is to put the data in cookies from PHP and let the JavaScript get it out again. And yet another option is to load JavaScript scripts with require_js conditionally on the settings of certain variables. Since A.nnotate only needs a couple of boolean settings, we adopt the last method here for simplicity and keep the cookie route available if more data needs to be passed.

Although the filter mechanism can be made to work for the course summary page, it suffers a couple of major drawbacks:

To avoid these problems, the plugin is loaded via Moodle's "customscripts" mechanism. This allows the Moodle administrator to create a parallel directory hierarchy to the main Moodle hierarchy, usually under $CFG->moodledata/customscripts, and add PHP files there to be loaded when the corresponding page under the main Moodle hierarchy is loaded. So, for example, when a user accesses .../course/view.php if there is a folder called course in the custom scripts folder containing a file view.php then this will also be loaded. This provides the ideal mechanism for loading A.nnotate wherever it is needed. The plugin is still packaged as a filter, and benefits from the usual filter configuration screen, but it is loaded from a custom script.

Each custom script contains exactly the same php:
 global $CFG;

In summary, the approach for adding buttons next to resources is to:

Transferring files to the A.nnotate server

The first decision is whether to push or pull the file. A.nnotate can accept files via http POST, or can fetch files from a specified URL. The latter is generally more robust, since, for example, it can easily be repeated if the first transfer fails. Also, by initiating the transfer from the receiving server, it is clear the server wants the file, rather than making the server accept potentially large posts before knowing what they are. It is preferable therefore to have A.nnotate fetch the file from the Moodle server if possible. However, this raises a security problem: since the connection is coming from A.nnotate server, not from the session where the user is logged in, there will be no session cookies set for A.nnotate, and we won't know whether the request is trusted and Moodle should send a particular file. This means we can't use the normal Moodle file.php to send the file, since it requires an authenticated session. However we also cannot just send a file on request as that would open a security hole.

One option would be to transfer the PHP session id to the remote server so it can access the source server as the original user and get the file that way. Using this route, the plugin would send the Moodle session id to A.nnotate which would then call back to Moodle with the session variable in the GET fields of the request. However, by default Moodle strips session variables from GET part of a requests as a security measure and only accepts them as cookies.

Although this behavior can be changed in Moodle with the "useids" configuration setting, rather than complicating the installation process, the solution adopted for A.nnotate is based on that used by other cloud services such as Box.net which need to securely release documents to remote services. The providing service generates an obscure temporary URL for the resource and gives it to the receiving service. If it then receives a request for that URL within a specified time it ships out the file.

The A.nnotate plugin keeps track of such requests by setting up temporary files with randomly generated codes containing the paths to files to be served. It then uses its own file.php to check a request against the temporary file, and, if the request is valid, it sends the resource.