0% found this document useful (0 votes)
496 views281 pages

Moodle 39+ Plugin Development-Podia

Uploaded by

Juan Fortuna
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
496 views281 pages

Moodle 39+ Plugin Development-Podia

Uploaded by

Juan Fortuna
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 281

Introduction to

Moodle 3.9+ Plugin Development

A comprehensive introduction to plugin


development for Moodle, the world’s leading
learning environment, for PHP developers.

Benjamin C Ellis
Introduction to Moodle 3.9+ Plugin
Development

Copyright © 2021 Benjamin Charles Ellis

ISBN 978-1-8383570-0-9

All rights reserved. This book or any portion thereof may not be reproduced or used in any
manner whatsoever without the express written permission of the publisher except for the
use of brief quotations in a book review.
Although the author and publisher have made every effort to ensure that the information in
this book is correct, the author and publisher do not assume and hereby disclaim any
liability to any party for any loss, damage or disruption caused by errors or omissions,
whether such errors or omissions result from negligence, accident or any other cause.
All product and company names are trademarks™ or registered® trademarks of their
respective holders. Use of them does not imply any affiliation with or endorsement by
them. Where these designations have been mentioned in the book, and both the publisher
and author are aware of these, these mentions have been printed with initial capitals.
However, the publisher and author cannot guarantee the accuracy of such information.

First Published, 2021

Mukudu Publishing
http://mukudu.net/publishing/
About the Author
After many years as an informal programmer, web developer, system and network
manager, Benjamin Ellis formally became a professional web developer over 22 years ago
for what was originally a small educational publishing company in Birmingham, United
Kingdom. This move cemented his eight years’ experience in education administration and
finance during the introduction of Local Management of Schools (LMS) and his many
previous years in public service.
Benjamin's original web development was in Perl and Mod-Perl, working on major projects
that included Education Authority portal software, school websites, learning environments
that pre-dated Moodle 1.0 in 2002/2003, and later, in areas such as education content
delivery and single sign-on technology – including Shibboleth – within such giants as
Capita Services plc.
Benjamin specialised in PHP and Moodle plugin and integration development after
working on a very large migration project for one of the UK's leading universities at the
beginning of 2012. This was followed by a stint at a private education provider before
deciding to set up his own company in January 2013. Since then, Benjamin has worked on
many Moodle and Totara projects for partner organisations, educational authorities and
organisations, as well as organisations for staff and client training.
Despite writing documentation and numerous briefing papers and delivering presentations,
this is Benjamin’s first book project.
Table of Contents
Preface....................................................................................................................................1
What This Book Covers.......................................................................................................2
Conventions..........................................................................................................................3
Reader Feedback, Piracy, and Errata....................................................................................3
Example Code......................................................................................................................4
Chapter 1 Introduction to Moodle and Learning Environments.............................................5
About Moodle.......................................................................................................................6
Courses, Site, and User Home Pages.................................................................................6
Users, Roles, Enrolments, and Authentication..................................................................7
Grouping, Groups, and Cohorts.........................................................................................8
Navigation and Blocks.......................................................................................................8
Activities, Resources, Repositories, Grading, and Reporting............................................9
Completions, Competencies, and Rewards........................................................................9
Summary............................................................................................................................9
Chapter 2 Moodle Development Overview..........................................................................11
Legacy Issues.....................................................................................................................11
Accessibility.......................................................................................................................12
User Interface.....................................................................................................................12
Output – Themes, Layouts, Renderers, and Templates......................................................13
Languages and Internationalisation....................................................................................14
Core Hacking......................................................................................................................15
Frankenstyle.......................................................................................................................15
Plugins and APIs................................................................................................................15
Roles, Permissions, and Capabilities..................................................................................15
GDPR and Privacy.............................................................................................................17
Chapter 3 Moodle APIs and Plugin Types...........................................................................19
APIs....................................................................................................................................19

i
Plugin Types.......................................................................................................................22
Chapter 4 Plugin Development Background........................................................................25
Moodle Development Guidelines.......................................................................................25
Class Autoloading and Namespaces...................................................................................26
Third-Party PHP Libraries..................................................................................................26
Zend....................................................................................................................................27
PEAR..................................................................................................................................28
JavaScript Framework........................................................................................................29
Callbacks............................................................................................................................31
Chapter 5: Common APIs.....................................................................................................33
Moodlelib API....................................................................................................................33
String API...........................................................................................................................35
Page API.............................................................................................................................37
Output API.........................................................................................................................38
Renderers and Templates.................................................................................................40
weblib.php........................................................................................................................41
outputcomponents.php.....................................................................................................42
File API..............................................................................................................................45
Form API............................................................................................................................48
Form Elements.................................................................................................................51
Advanced File Elements..................................................................................................60
Data Manipulation API......................................................................................................64
Cross-DB Compatibility..................................................................................................69
Other Database-Related Functionality.............................................................................71
Data Definition API............................................................................................................72
The Database Manager Class...........................................................................................72
The XMLDB Developer Tool..........................................................................................74
Admin Settings API............................................................................................................75
Upgrade API.......................................................................................................................78
Access API.........................................................................................................................80

ii
Enrolment API....................................................................................................................83
Navigation API...................................................................................................................84
Events API and Logging....................................................................................................88
Logging............................................................................................................................89
Task API.............................................................................................................................90
Privacy API........................................................................................................................91
Web and External Functions APIs.....................................................................................91
External Functions Uploads and Downloads...................................................................93
Administration.................................................................................................................94
Other External Integration with Moodle..........................................................................95
Chapter 6 Plugin Development Essentials............................................................................97
Development Environment Setup......................................................................................97
SSL...................................................................................................................................97
Site Development Configuration.....................................................................................98
Development Test Data..................................................................................................100
IDEs...............................................................................................................................101
Community Development Tools......................................................................................102
Code Checker.................................................................................................................102
Moodle PHPdoc Checker...............................................................................................102
Code Skeleton Templates.................................................................................................102
The Plugin Skeleton Generator......................................................................................102
Cross Reference and Version Differences Site..............................................................104
Moosh.............................................................................................................................104
Chapter 7 Common Plugin Requirements..........................................................................105
The File Masthead............................................................................................................105
Plugin Naming Convention AKA Frankenstyle...............................................................106
Required Files...................................................................................................................106
version.php.....................................................................................................................106
Language File (lang/en/plugintype_pluginname.php)...................................................107
lib.php............................................................................................................................107

iii
index.php........................................................................................................................108
db/install.xml..................................................................................................................108
README/README.md...............................................................................................108
Installing and Uninstalling Plugins..................................................................................108
Moodle Caching...............................................................................................................109
Chapter 8 Developing Plugins............................................................................................111
Block Plugins...................................................................................................................111
Developing a Block Plugin............................................................................................111
Course Format..................................................................................................................119
Developing a Course Format.........................................................................................119
Repository........................................................................................................................133
Developing a Repository Plugin....................................................................................133
Activities and Resources (Modules).................................................................................145
Developing Modules......................................................................................................145
Third-Party Libraries...................................................................................................160
Caching........................................................................................................................161
Autoloading vs locallib.php.........................................................................................163
Viewing........................................................................................................................169
Backup and Restore.....................................................................................................171
Sub-plugins, the File API, and JavaScript........................................................................181
Developing an Assignment Feedback Plugin................................................................181
Enrolment.........................................................................................................................202
Developing an Enrolment Plugin...................................................................................203
Authentication..................................................................................................................217
Developing an Authentication Plugin............................................................................217
Privacy Considerations................................................................................................221
Reporting..........................................................................................................................222
Developing a Report Plugin...........................................................................................222
Local Plugins....................................................................................................................228
Developing a Local Plugin.............................................................................................229
Web and External Services for Plugins............................................................................239

iv
Developing an External Service Plugin.........................................................................239
Users and Capabilities.................................................................................................242
External Functions API................................................................................................244
Consuming Web Services..............................................................................................253
Chapter 9 Other Plugin Development Issues......................................................................254
Custom Scripts – When Plugins Won’t Do......................................................................254
Backup..............................................................................................................................254
Using Git..........................................................................................................................255
Using Git Sub-Modules for Plugins...............................................................................256
Testing..............................................................................................................................257
Unit Testing....................................................................................................................257
Acceptance Testing........................................................................................................257
Publishing Your Plugin....................................................................................................258
Final Words.........................................................................................................................260
Index........................................................................................................................................i

v
Preface
This book aims to introduce PHP developers to Moodle, the world's leading Learning
Management System (LMS) or, in other words, Virtual Learning Environment (VLE). It
will use Moodle 3.9 (Long Term Support) primarily, with some reference to earlier
versions – particularly Moodle 3.5 (LTS) – and to new versions available at the time of
writing. Moodle 3.9 is expected to be supported until May 2023 and is likely to be used by
many organisations until the next long-term support release.
Moodle development involves two types of developers. The first type includes those who
work for Moodle Pty Ltd aka Moodle.com – known as Moodle HQ in our circles – which is
a commercial organisation supported by financial subscriptions of Moodle Partners –
medium to large commercial companies that make money from Moodle services and public
donations. These developers work mainly from three sites across the world, maintain core
Moodle functionality, the APIs, the plugin hooks, and standard Moodle plugins, some of
which started life as extensions that were developed by third-party developers and later
integrated into the core code.
The third-party developers are the second type of developers and are the ones at whom this
book is aimed. These developers work in organisations worldwide or on their own
developing custom plugins, some of which are released into the community via the Moodle
plugins directory. However, many plugins are not released, either because they bestow a
commercial advantage or are too specialised, e.g. legacy integration. This kind of
development is the area in which I, the author, operate.
This book is not aimed at Moodle theme developers, though there are numerous references
to theme development and a section on themes included to inform the plugin developer of
theme-related issues. Nor is it a guide to PHP programming as such. As they always said in
the Perl world, ‘there are many ways to skin a cat’ (is that politically correct?), and so you
will bring your own preferences, mannerisms, and style of programming to your
Introduction to Moodle 3.9+ Plugin Development

developments. I like to think of myself as a developer rather than a programmer, and I


suspect you will notice the difference in some of my code examples.

What This Book Covers


Chapter 1: Introduction to Moodle and Learning Environments: A general overview of the
history of Moodle and other learning applications and the current operating environment.
We also take a high-level look at what Moodle does.
Chapter 2: Moodle Development Overview: An overview of some issues worth knowing
about before jumping into developing plugins, such as legacy issues, accessibility,
languages and internationalisation, roles, permissions and capabilities, and other important
issues such as 'core hacking' and naming conventions aka Frankenstyle.
Chapter 3: Moodle APIs and Plugin Types: A high-level view of APIs and plugin types
that will be elaborated on later in the book.
Chapter 4: Plugin Development Background: Development-related background
information, such as the Moodle development guidelines, class autoloading and
namespaces, third-party PHP Libraries including Zend and PEAR, as well as the JavaScript
framework.
Chapter 5: Common APIs: A long chapter looking at the common APIs that plugin
developers will encounter in their day-to-day work. Since a single plugin will generally use
several APIs, this chapter serves to inform the developer of what is available and helps
them avoid having to re-invent the wheel.
Chapter 6: Plugin Development Essentials: Essentials for plugin development, including
the setup of the development environment and touching on SSL and IDEs. We look at
generating test data, configuring Moodle for development, and some of the available
development tools.
Chapter 7: Common Plugin Requirements: We explore the required files and other
common requirements for all plugins, such as version.php and lib.php, which allow the
plugin to be installed and integrated into Moodle.
Chapter 8: Developing Plugins: A long chapter where we examine and develop several
common plugin types, including blocks, enrolments, authentication, and many others.
Some other concepts of plugin development are also discussed.
Chapter 9: Other Plugin Development Issues: The final chapter discusses other things that
haven't been covered or that need elaboration, including the use of version control,
particularly git, acceptance and unit testing, and publishing your plugin via the Moodle

2
Preface

plugins directory.

Conventions
Class names, configuration and global variables, command lines and code in the text will
appear in this format: $CFG->dirroot.
Directories and filenames are denoted in this format: /lib/forms/select.php. Where there is a
leading slash, the path is from the Moodle’s document root, i.e. $CFG->dirrroot. If
not, it is relative to the plugin's directory.
Code in PHP files is in mono font and appears as below.

/path/to/file.php
// HEADER STUFF HERE //

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

$plugin->version = 2020061500;
$plugin->requires = 2020061500; // Requires M3.9
$plugin->component = 'report_plugins';

Navigation in the user interface is shown in text in this format: ‘Site administration-
>Development > Third party libraries’ and is generally followed by the relative URL path
from the Moodle installation's document root, i.e. $CFG->dirrroot.

Reader Feedback, Piracy, and Errata


Feedback from readers is welcomed, so please take the time to review the book on the
normal bookselling channels; I will endeavour to read all reviews. If, however, you would
like to point out a specific issue unsuitable for general reviews, such as mistakes and/or
omissions, please email [email protected] and specify the title of this book and the
details of the issue. We will do our best to address any relevant issues.
Online piracy of copyrighted material is rife, and we would be very grateful if you would
notify us at the email address mentioned should you come across any infringements of our
copyright. This helps protect the author to continually update this book with later versions
of Moodle.

3
Introduction to Moodle 3.9+ Plugin Development

Example Code
I considered not publishing the example plugins code in order to allow the developer to
attempt to write and run the code. While the curse of 'Copy and Paste' would mean that
retention of some of the concepts would be lost, after further consideration, I decided it
would be better to leave it to the reader's discretion. The code for each of the plugins
developed in the ‘Developing Plugins’ chapter (Chapter 8) can be found in the git
repository at https://github.com/Mukudu-Publishing/.

4
Chapter 1
Introduction to Moodle and Learning
Environments
Learning Environments, Virtual Learning Environments (VLE), and Managed Learning
Environments (MLE) are generally web-based course management and delivery
applications. Their intention is always to deliver online learning materials and to track
student participation and progress. There have been several such systems over the years,
many of them commercial as well as open-source systems. Moodle is one such application,
having started life as the released Moodle 1.0 in August 2002.
Over the years, there has been some consolidation in the market, with most commercial
systems now absorbed into Blackboard Inc.'s (https://www.blackboard.com/) offerings; this
is the largest competitor to Moodle. Blackboard Inc. purchased Moodlerooms, then a
Moodle Partner, and developed an open-source Moodle-based SaaS solution, which, after
they lost their partner status, was rebranded as 'Blackboard Open LMS'. In March 2020,
they sold 'Open LMS' (https://www.openlms.net/) to another company challenging
Moodle’s position, the UK's Learning Technologies Group plc (LTG)
(https://www.ltgplc.com/), which just recently added another large Moodle partner, e-
Think Education (https://ethinkeducation.com/), to previous related acquisitions. New
open-source and commercial offerings appear on the market now and again with a view to
gaining market share with innovations that place a greater emphasis on BOD (bring your
device) and mobile delivery of learning.
Totara Learn (https://www.totaralearning.com/en/products/totara-learn-lms), formerly
Totara LMS, was initially developed by two leading Moodle Partners building Moodle
extensions suitable to the commercial enterprise training market. It is now a separate
organisation with several other products on offer. Despite announcing that development
Introduction to Moodle 3.9+ Plugin Development

was to start to diverge from the Moodle base, so far Totara Learn's development still
appears to be based on Moodle core code.
Moodle.org is now marketing Moodle workspace (https://moodle.com/workplace/) as
competition to Totara Learn. Is Moodle Workspace open source? Well, the answer is:
‘Moodle Workplace is not being distributed in the same way as Moodle core. Moodle
Workplace is only available via Moodle Partners so that we can ensure a high-quality
experience with Moodle Workplace.’
This mirrors Totara Learn's model where the code is said to be open source but not freely
available.
For more information about Moodle, please visit the 'About Moodle' page
(https://docs.moodle.org/39/en/About_Moodle).

About Moodle
Chances are that if you attended tertiary education – i.e. post-secondary education such as
colleges and universities in many Western and some other countries, you will have used a
learning environment for most or part of your time with the institution. It is also likely that
it would have been Moodle as it has been a leading learning environment for quite a few
years. With over 200,000 registered sites (most development and test sites will not be
registered) with over 33 million courses and used by nearly 260 million users in over 240
countries and territories, it is not hard to see why. Visit Moodle site's 'Statistics'
(https://stats.moodle.org/) and the 'Registered Moodle Sites' pages
(https://stats.moodle.org/sites/) for more information.
This section is to refresh and update those of you who have worked with Moodle before
and to provide a background for those who have not used it or any other learning
environment previously.
Being a learning environment, Moodle is built around delivering courses.

Courses, Site, and User Home Pages


Each course has its page or area in the environment. There are two special types of course
pages: if you like, the site's home page – usually a course with an id of 1 which serves as
the gateway or the site's home page and the user's dashboard, a specialised place which is
the user's home page.
First, courses are organised into categories, which make it easier to organise and manage.
Categories are more of an administrative than a general usage feature. This allows the

6
Chapter 1 Introduction to Moodle and Learning Environments

institution to organise the courses according to faculty, subjects, etc.


Second, courses come in different formats to suit the subject and may cover a whole year, a
whole subject, a semester or term and, in some cases, just a single activity. By default,
Moodle comes with a topics format, a weekly format, a single activity, and a social format.
The choice can be extended by installing or developing course format plugins.

Generally, Moodle pages, including course pages, are two- or three-column pages with a
top ‘navbar’, but remember they are theme-dependant. However, all themes will have some
means of displaying the content that would appear on a page. The standard Moodle theme
allows users to hide the side columns, if so required. Themes is another area where Moodle
can be extended with theme plugins.
Moreover, courses contain activities and resources, jointly called modules and blocks – a
type of page widget. The latter usually appears in one of the side regions, with the rest of
the page being known as the content region.
To deliver learning, we need users.

Users, Roles, Enrolments, and Authentication


The ultimate beneficiaries of the learning environment are learners who require one or
more instructors. It is feasible that an instructor could also be a learner; for example, the
institution may provide professional development courses for the instructors, where they
will be learners, in the same environment. Moodle uses contexts and user roles to manage
this kind of situation and allows for the definition of other roles. Usually, not all instructors
teach all courses and neither do all students take all courses, so we have the concept of
enrolments. Both instructors and learners are course participants and can be enrolled in

7
Introduction to Moodle 3.9+ Plugin Development

courses in many different ways, usually manually which is the default. This is another area
where Moodle can be extended by plugins. Not all users are course participants though, as
courses may be created by other users and categories managed by yet other users. These
users will have access to the courses but cannot participate. At the top of the user hierarchy
are the ‘managers’, who might only manage particular categories and the ‘site
administrators’ who are generally responsible for the whole of the site administration and
well-being.
Roles, in effect, are a collection of what we generally refer to as permissions. Moodle's
implementation of this can be confusing, but each action any user can potentially do is
called a capability and whether the user can or cannot carry out the action defines the
permission. The permission can be inherited from another 'role' or explicitly allowed or
denied. Some actions only make sense in the context in which they are being performed;
for example, adding an activity to a course only makes sense in the context of a particular
course. Moodle has several contexts where capabilities are relevant. Bespoke roles will,
therefore, be a bespoke collection of capabilities and their permissions.
To identify users and to ensure proper authorisation for actions within the platform, users
are required to authenticate. Moodle does have a concept of a 'guest' user which can be
disallowed. There are several ways that a user can be authenticated, such as via Active
Directory or some other Single Sign-On technology, and the site can define several means
of authentication, though each user is limited to authenticating in a single way.
Authentication is another area where Moodle can be extended by plugins.
Users do have some user-specific tools at their disposal, such as calendars and favourites,
that plugins can use to enhance the user experience.

Grouping, Groups, and Cohorts


Within courses, learners can be grouped for whatever reason. Groupings of groups are also
possible and this allows the instructor and/or the course manager to differentiate users and
deliver slightly different learning experiences. Cohorts are a special kind of group allowing
instructors and learners to be grouped at the site level for enrolments.

Navigation and Blocks


A typical user of the platform will have access to a multitude of areas – particularly courses
and user areas such as their calendar. Navigating the environment is via the specialised
navigation blocks which are context-aware and which for example, display course-specific
navigation for the course being viewed at the time. Plugins can tap into the navigation
when required. Other blocks, which are also plugins, provide other useful widget-like
functionality, such as a calendar overview, say for the week or month. Moodle can be

8
Chapter 1 Introduction to Moodle and Learning Environments

extended further by external block plugins.


The special course pages – the site and user home pages – can also house blocks in their
‘content’ region and many of the blocks common in these areas are developed specifically
for display in the content region, for example, the ‘course overview’ block that ships with
Moodle.

Activities, Resources, Repositories, Grading, and Reporting


The meat of the course is found in modules, used to refer to both course activities and
resources. This can be thought of as where learning is delivered. Resources are static
learning assets such as files, links, and activities. Moodle ships with about 23 modules,
such as assignments, lessons, and quizzes, and seven of these are resource modules such as
files and books. Moodle can be extended by installing or developing additional module
plugins.
All modules can use external repositories to house their assets; for example, a SCORM
activity can be saved on a Google Drive. Plugin developers can extend the range of
repositories shipped with Moodle, which include Google Drive, Dropbox, Flickr and
Office365.
Activity modules allow submissions and feedback and are usually graded by the instructor.
All these areas can be extended by external plugins as can the reporting tools for reporting
and tracking learner progress. Activities can usually also be completed.

Completions, Competencies, and Rewards


Generally, when the relevant course activities have been completed, where necessary, the
course can also be marked as completed. These completions, both at activity and course
level, can feed into learner competencies. Learner engagement and encouragement is
enhanced by badges, both at activity and course level, and usually at course level by
certificates. This functionality can also be extended by plugins.

Summary
There is more to Moodle than what has been described in this section and some other areas
will be covered throughout the book. If you want to understand Learning Environments and
Moodle in particular, then you can find lots of good videos on YouTube by searching
phrases such as 'What is LMS?' or 'What is Moodle?'.

9
Chapter 2
Moodle Development Overview
Moodle was designed to be extendable; it says it on the box: ‘Modular Object-Oriented
Dynamic Learning Environment’. Its core functionality includes the API hooks to allow
third-party developers to extend the system. Apart from these API hooks, the core also
takes care of the core functionality which includes:
 Category and Course Management
 User and Group Management
 Permissions and Capabilities
In Moodle parlance, extensions are referred to as plugins.
The APIs allow a developer to even extend some of the core functionality, such as
authentication and enrolments.

Legacy Issues
Moodle, however, does suffer from one major deficiency and that is that it has quite a lot of
legacy code which the core team have been slowly attempting to replace in line with
modern PHP development practices such as autoloading. This legacy issue runs quite deep,
especially since backward compatibility is fairly important. For example, the forms API is
based on QuickForms v1 and there is backward compatibility for support for the now-
defunct YUI (Yahoo! User Interface) JavaScript libraries.
Some older APIs that were created before object-oriented programming use only callbacks
to hook into different processes. Some new callbacks have been defined in later versions of
Introduction to Moodle 3.9+ Plugin Development

Moodle but the current guidance is to avoid these in favour to allow other ways of intra-
component communication such as using events (see the ‘Communication Between
Components’ documentation page
(https://docs.moodle.org/dev/Communication_Between_Components)).

Accessibility
Moodle has always stipulated that all interfaces should be able to fall back to a screen
reader default. While this has made it difficult to support some of the newer HTML
innovations such as responsiveness and HTML5, the core developers follow several
standards. These include the ARIA which defines semantic information about widgets,
structures, and behaviours, and allows assistive technologies to convey appropriate
information to persons with disabilities.
An accessibility audit resulted in a list of issues to be addressed (see Tracker issue MDL-
67688 https://tracker.moodle.org/browse/MDL-67688). Many of these were addressed in
Moodle 3.9 and back-ported where possible. They will most certainly influence the current
ongoing user interface work for Moodle 4.0.
In general, everything in Moodle should work with JavaScript turned off in your browser.
This is important for accessibility and to keep things in line with the principles of
unobtrusive JavaScript and progressive enhancement. Features that are not available
without JavaScript must be compliant with all accessibility standards.
Accessibility is worth keeping in mind when developing interface-heavy plugins. I can see
a time when the community-released third-party plugins will need to address this issue
seriously. See the 'Accessibility' page (https://docs.moodle.org/39/en/Accessibility) for
more details.

User Interface
According to many people, Moodle's interface may seem a little dated for the modern web
user. There has been some development to support responsive design, such as the Bootstrap
theme, but, in principle, the developer cannot always depend on responsive functionality
for plugins since the choice of the template in use is configurable by the administrator.
Saying this, I have seen some very fancy and innovative themes. Whether or not they all
address accessibility issues, as discussed earlier, is questionable however. Clients using
such themes will generally expect their custom plugins to fit in.
Moodle's interface is delivered via themes which define page layouts. The new core default
theme, Boost (https://docs.moodle.org/39/en/Boost_theme ), uses Bootstrap 4, a built-in

12
Chapter 2 Moodle Development Overview

SASS compiler, automated RTL support, preset files, and has new templates. In fact, theme
developers are encouraged to base their own on this theme.
The release of Moodle 4.0 is being delayed, but will include major revisions of the user
interface and we should expect to see a lot of the more modern features becoming an
integral part of the user interface.
This book does not cover the development of themes for Moodle, as this is a subject that
would require its own book. We will, however, touch on the subject a few times in this
book.

Output – Themes, Layouts, Renderers, and Templates


Visit the ‘Themes Overview’ Moodle documentation page
(https://docs.moodle.org/dev/Themes_overview) for a detailed overview of themes and
layouts. This book only covers areas pertaining to developers. Themes can be applied at
different levels: site-wide, category, course, and activity , and can be designed for specific
devices such as mobile phones or tablets. They are normally either based on other themes
or the base theme and will usually incorporate a number of the 18 or so possible layouts
which define the structure of a viewable page:

Layout Purpose
Base The base layout – not usually used directly but as a base for other
themes.
Standard The standard layout with blocks and recommended where other
layouts don’t fit.
Course The course layout.
coursecategory Used for browsing through course categories.
Incourse Mainly used by modules, as the layout while browsing through the
course.
Frontpage The frontpage layout.
Admin For administration pages.
mydashboard The user’s default dashboard layout.
Mypublic User’s public page.
Login Login page layout.

13
Introduction to Moodle 3.9+ Plugin Development

Popup Pop-up pages layout – with no navigation, no blocks or header.


Frametop Used for legacy frame layouts only. No blocks and minimal footer.
Embedded Used for things like iframes and objects embedded in pages.
maintenance A basic page usually used during an upgrade and install, maybe with a
message to visit the website or to try later.
Print Printing page layout.
redirect A redirection layout.
report Reports base layout.
secure Used for safebrowser and securewindow.

Where the theme does not define a layout, Moodle, by default, uses the one defined in the
theme’s hierarchy or the base definition. Most of the themes will be based on the standard
core Boost theme as recommended. The Boost theme is intended to be the starting point for
building a new theme. It ‘supports all the latest theme features and it tries to stay as true to
the Bootstrap CSS framework as possible’.
When a plugin needs to display visual output within a page or layout, the recommended
method is to use a renderer (https://docs.moodle.org/dev/Output_renderers). Renderers,
introduced in Moodle 2.0 with the introduction of the global $OUTPUT object, can be used
for other non-display content generation such as emails and data exports. They may also
use templates to generate output.
Moodle supports templates written in mustache
(http://mustache.github.io/mustache.5.html), which claim to be ‘Logic-less templates’.
Templates should not contain any logic, which should be defined in the code and passed as
variables to the renderer which, in turn, passes them to the template.
It is worth noting that theme designers can also define renderers and templates as well as
override plugin renderers.

Languages and Internationalisation


Moodle supports over 80 languages by keeping the language strings and locale information
in language packs. English, the Australian version, is the default language for code,
comments, and documentation.
Support for languages is conducted through the Strings API, which will be covered later in

14
Chapter 2 Moodle Development Overview

the book. The API handles internationalisation issues and provides “general string
functions like substr(), strlen() etc. for multibyte safe, string operations and uses
mbstring or iconv for UTF-8 strings and falls back to typo3”.
Plugins define their language strings in the required lang/ subdirectory with the lang/en/
subdirectory for Australian English being the default. Other translations are not necessary
if the plugin is approved for inclusion in the Moodle Plugins Directory
(http://moodle.org/plugins), as the English version is sent to the translators automatically.

Core Hacking
Before Moodle 2.0, and, particularly with Moodle 1.9, core hacking, which is changing the
core Moodle source code, was generally accepted despite the potential problems with
upgrading. Now it is not regarded as good practice. The accepted solution is either to report
issues to the core team or, better still, to provide them with patches to resolve issues.
Alternatively, you can develop a plugin to achieve your aims.

Frankenstyle
Frankenstyle is the term used for the naming convention that is used to uniquely identify
plugins and core components. It is the file path of the plugin separated by an underscore
which is determined partly by the type of plugin. So, a repository plugin called
‘googledrive’ will have the Frankenstyle name of repository_googledrive and will
be installed in the repository folder. Core components, though not plugins and not residing
in a core folder, are named core_component. There are some notable exceptions,
including the legacy support for course modules which reside in the /mod folder but are
not, for the most part, required to follow the Frankenstyle naming convention.
A list of Frankenstyle prefixes for current plugins can be found on the ‘Plugin Types’ page
(https://docs.moodle.org/dev/Plugin_types). A list of the names for core components can be
found on the ‘Frankenstyle’ page (https://docs.moodle.org/dev/Frankenstyle).

Plugins and APIs


Generally, a plugin will use one or more APIs to achieve its aims. For example, an
authentication plugin could use both the Authentication API and the Enrolment API to
enrol the authenticated user onto a default course such as an introduction course.

Roles, Permissions, and Capabilities


This can be a very confusing aspect of Moodle.

15
Introduction to Moodle 3.9+ Plugin Development

Roles such as manager, teacher, and student are, in essence, a collection of permissions
relating to what right a user has to a particular capability in a given context. Capabilities
are possible actions, for example, ‘edit a course’. A user can be allowed permission to the
capability, can inherit the permissions from a higher context or be denied explicitly.
The four permissions are:
 Not set
 Allow
 Prevent
 Prohibit
Permissions can also be overridden in some contexts.
Most capabilities only make sense in particular contexts; for example, managing course
completions only makes sense in the context of a course.
Moodle has six contexts, namely:
 System – site-wide contexts for roles such as manager and course creators
 User – user-related capabilities
 Course category – a collection of course permissions
 Course – course-related
 Module – course activity permissions

16
Chapter 2 Moodle Development Overview

 Block – blocks related permissions


There is an additional permission if you will, and that is for site administrators. Users
assigned to this special role are not restricted at all.
Since an administrator or manager can manage and change roles and permissions as
required, relying on roles for plugin permissions is not a good idea. Generally, plugins
should rely on a capability to determine if a user has permission to perform an action. The
plugin can rely on a core-defined capability such as moodle/site:config or define its
capabilities using the Access API as described later. Creating a specific role for a plugin is
generally not a good idea, though webservice plugins will normally require a separate role
to be defined.
If you need to identify users with a specific role such as ‘student’, it is best to define a
capability that is allowed for students specifically and check for that capability in your
code. This will be discussed further in the ‘Access API’ section in Chapter 5.
For more information about this subject, see the Moodle documentation ‘Roles and
Permissions’ page (https://docs.moodle.org/39/en/Roles_and_permissions).

GDPR and Privacy


The General Data Protection Regulation (GDPR) is a regulation in EU law on data
protection and privacy in the European Union (EU) and the European Economic Area
(EEA) which came into effect on 25th May, 2018. It gave citizens the right to:
 Request information about their personal data being held
 Access all of their data
 Have all the data deleted and be forgotten
New functionality was made available in Moodle 3.5 to assist service operators in
becoming compliant with the new regulations. The Privacy API allows plugin developers
to describe the personal data stored and provides the means for that data to be reported on
and managed on a per-user basis. We shall look at the API in a later chapter.
For more information, please see the Moodle documentation's ‘General Data Protection
Regulation’ page (https://en.wikipedia.org/wiki/General_Data_Protection_Regulation).

17
Chapter 3
Moodle APIs and Plugin Types
While this short chapter only introduces the available APIs and plugins, these two areas
will be covered in more detail later in the book. This will give you an idea of the range of
development concepts for Moodle development.

APIs
API Short Name Description
Access API access The ‘Permissions’ API, which determines what the
user is allowed to do and provides a way to define
new capabilities
Activity completion completion Functions handling course activity completions
API
Activity module APIs mod Functions to develop and work with course
activities
Admin settings API admin Used for managing configuration options for
plugins and core components
Advanced grading grading Allows plugins to add more advanced grading
API interfaces (such as rubrics) that can produce grades
for the grade book
Analytics API analytics Creates prediction models and generates insights
Availability API availability Controls access to course activities and sections
Introduction to Moodle 3.9+ Plugin Development

Backup API backup Functionality used to backup course data for course
activity plugins
Badges API badges Manages and works with user badges
Cache API cache Provides ways to work with the Moodle Universal
Cache (MUC) to use a cache in plugin code
Calendar API calendar Manages events in the calendar for user, groups,
courses or the whole site
Charts API charts Methods intended to provide a simple and yet
modern interface to generate dynamic charts.
Check API check Allows the management of security, performance
or health checks to your site
Comment API comment Saves and retrieves user comments, allowing
plugins to use user commenting
Competency API competency Lists and adds evidence of competencies to
learning plans, learning plan templates,
frameworks, courses, and activities
Custom fields API Configures and adds custom fields for different
entities
Data definition API ddl Manages tables and fields in the database
Data manipulation dml Manipulates table contents in a cross-db
API compatible, consistent and safe manner
Editor API Controls HTML text editors
Enrolment API enrol Works with course enrolments
Events API event Defines or handles internal ‘events’, the
recommended form of inter-plugin
communication; also forms the basis for logging
Experience API xAPI Lets plugins generate and handle xAPI standard
statements
External functions external Creates methods that can be accessed by external
API programs, such as web services
Favourites API Marks and manages items as favourites for a user
File API files Functionality to manage the storage of files

20
Chapter 3 Moodle APIs and Plugin Types

Form API form Defines and handles user data via web forms
Gradebook API grade Reads from and writes to the grade book
Groups API group Works with course user groups
Lock API lock Synchronises processing between multiple
requests, even for separate nodes in a cluster
Logging API log Logging stores for Moodle, for event logging, see
Events API earlier in this table
Media API media Functionality to embed media items such as audio,
video, and flash
Message API message Functionality for user messaging based on their
preferences
Moodlelib API core Miscellaneous general-purpose functions and
defined constants
My profile API Adds items on the user’s profile page
Navigation API navigation Adds and removes items for the navigation
OAuth 2 API oauth2 Configures and manages external systems using
OAuth 2
Output API output Functionality to render the HTML for all parts of
the page
Page API page Defines page setup and requirements such as
JavaScript and provides the structure for the
Output API mentioned above
Payment API payment Deals with payments
Plagiarism API plagiarism Functionality for activity modules to send files and
data to external services to have them checked for
plagiarism
Portfolio API portfolio Functionality to add portfolio interfaces and allow
users to package up data to send to their portfolios
Preference API preference A simple way to store and retrieve preferences for
users
Privacy API privacy Allows compliance with regulation such as the
GDPR in Europe; also provides functionality to

21
Introduction to Moodle 3.9+ Plugin Development

describe the personal data that you store and


provides the means for that data to be discovered,
exported, and deleted
Question API question The Question API encompassing the question bank
and the question engine can be used by activity
plugins to use questions from the question bank
Rating API rating Lets the plugin create AJAX rating interfaces so
that users can rate items in your plugin.
RSS API rss Creates secure RSS feeds of data in activity
modules
Search API search Allows components/plugins to index contents in a
search engine and query the search engine for
results
String API string Language text strings used in the user interface to
handle language translations that might be
available
Tag API tag Handles content tagging
Task API task Scheduled and one-off tasks
Testing API test Contains the Unit Test API (PHPUnit) and
Acceptance Test API
Time API time Translates and displays times between users
Upgrade API upgrade Installs and upgrades functionality
User-related APIs user User-related functions relating to sorting and
searching lists of users
Web services API webservice Exposes particular functions – usually external
functions as web services (see External functions
API earlier in this table)

Plugin Types
This is a list of the most commonly used plugin types supported by Moodle 3.9:

Plugin type Description


Activity modules Course activity plugins commonly called modules, e.g. forum,

22
Chapter 3 Moodle APIs and Plugin Types

quiz, etc; we develop an example of one of these plugins in the


‘Developing Plugins’ chapter (Chapter 8)
Admin tools Utility scripts for various site administration and maintenance
tasks; should only be used if the plugin is unsuitable as a
different plugin type
Assignment feedback Assignment module feedback types to allow tutors to provide
plugins student feedback; we develop an example of one of these
plugins in the ‘Developing Plugins’ chapter (Chapter 8)
Assignment submission Assignment module submission types to allow students to
plugins make assignment submissions.

Authentication plugins User Authentication; we develop an example of one of these


plugins in the ‘Developing Plugins’ chapter (Chapter 8)
Blocks Page widgets/blocks that are displayed inline; we develop an
example of one of these plugins in the ‘Developing Plugins’
chapter (Chapter 8)
Calendar types Define how dates are displayed
Course formats Different ways of displaying course content, e.g single topic,
weekly, etc we develop an example of one of these plugins in
the ‘Developing Plugins’ chapter (Chapter 8)
Editors Alternative text editors for editing content
Enrolment plugins Ways to control course enrolments; we develop an example of
one of these plugins in the ‘Developing Plugins’ chapter
(Chapter 8)
Gradebook export Exports grades in various formats
Gradebook import Imports grades in various formats
Gradebook reports Display/edit grades in various layouts and reports
Local plugins Generic plugins for local customisations; we develop several
examples of these types of plugins in the ‘Developing Plugins’
chapter (Chapter 8)
LTI services & sources Allow the implementation of LTI services as described by the
IMS LTI specification; for more details, please see the
documentation

23
Introduction to Moodle 3.9+ Plugin Development

Media players Pluggable media players


Messaging consumers Represent various targets where messages and notifications can
be sent to (email, sms, jabber, etc.)
Plagiarism plugins External services to process submitted files and content
Portfolio plugins Connect to external portfolio services
Question types Different types of questions (e.g. multiple-choice, drag-and-
drop) that can be used in quizzes and other activities
Reports Provide useful views of data for admins and teachers; we
develop an example of one of these plugins in the ‘Developing
Plugins’ chapter (Chapter 8)
Repository plugins Connect to external sources of files; we develop an example of
one of these plugins in the ‘Developing Plugins’ chapter
(Chapter 8)
SCORM reports Analysis of SCORM activity module attempts

A list of current plugins can be found on the Moodle documentation’s ‘Plugin Types’ page
(https://docs.moodle.org/dev/Plugin_types).

24
Chapter 4
Plugin Development Background
Before jumping into the development of plugins, there are several things that a plugin
developer needs to be aware of.

Moodle Development Guidelines


Whilst there is no obligation on plugin developers to follow the core Moodle development
guidelines, I consider it good practice – particularly if the intention is to submit the plugin
to the Plugins Directory. Since this book is not about coding styles and standards, I refer
you to the relevant Moodle documentation pages, ‘Coding Style’
(https://docs.moodle.org/dev/Coding_style), ‘Coding Guidelines’
(https://docs.moodle.org/dev/Category:Coding_guidelines), and the ‘SQL_coding_style’
(https://docs.moodle.org/dev/SQL_coding_style). Note, though, that some of the points (for
example, deprecating strings) are related to core functionality development rather than
plugin development.
There are two developer plugins that will help with coding guidelines:
 Code checker, which integrates with eclipse/phpstorm –
https://moodle.org/plugins/local_codechecker
 Moodle PHPdoc checker – https://moodle.org/plugins/local_moodlecheck
I cover both of these in a bit more detail later in this book. It is also worthwhile reading the
‘Security’ page (https://docs.moodle.org/dev/Security) for some further guidelines.
Introduction to Moodle 3.9+ Plugin Development

Class Autoloading and Namespaces


From Version 2.6, Moodle has supported autoloading and namespaces – see the ‘Automatic
Class Loading’ page (https://docs.moodle.org/dev/Automatic_class_loading). The class
files must exist in the plugin’s classes folder or its subfolders and must follow certain rules
to be autoloaded. Each class should reside in its own file and the class name should follow
the Frankenstyle convention, but the file name must be the class name without the
Frankenstyle prefix. For example, the webservice_bulkenrol_checkinput class
would be in the file /web service/bulkenrol/classes/checkinput.php.
Namespaces are designed to improve code organisation and the directory structure in the
classes/ should match the namespace structure. For example, using the previous example,
the namespace would be webservice_bulkenrol.
Namespaces are not required for any new code and there is no requirement to move non-
namespaced classes to namespaces. However, doing so appears to be the flavour of the
month with new and updated developments in the core. In some cases, the use of
autoloading classes is mandated, such as for raising events and, in other cases, such as
developing external (web) services. These subjects will be revisited in the development
chapters later in the book.
Classes are cached and, therefore, when developing code, you will need to refresh the
cache whenever you make changes. The easiest way to avoid this is to add the following
line to the Moodle configuration file:
$CFG->debug = (E_ALL | E_STRICT);

Third-Party PHP Libraries


A third-party library refers to any library where the code is not part of Moodle. You can
view a list of the currently included libraries by navigating to ‘Site administration-
>Development->Third party libraries’ (/admin/thirdpartylibs.php). You should do this
before considering including libraries with your plugin. According to the documentation,
Moodle can only use libraries that use a GPL compatible
(https://en.wikipedia.org/wiki/License_compatibility#GPL_compatibility) licence,
specifically one compatible with the GNU GPLv3.
Since the Moodle documentation appears to be aimed at core developers, the position for
third-party plugin developers is unclear from the documentation. It is possible that your
plugin may require a different version of an installed library or even libraries that have
incompatible licences. It may be that, in these cases, if the plugin is not for general release,
i.e. a proprietary plugin development, even proprietary licences could be integrated.
However, I am not a licence expert and, if something like this is an issue, your client might

26
Chapter 4 Plugin Development Background

like to seek relevant legal advice.


For more information, please see the documentation’s ‘Third-Party Libraries’ page
(https://docs.moodle.org/dev/Third_Party_Libraries).
If, however, you find that you need a third-party library, you should follow the process
described in the Moodle documentation. Part of this process is the inclusion of a
thirdpartylibs.xml file in your plugin’s folder. Please see the documentation’s ‘Plugin
Files’ page (https://docs.moodle.org/dev/Plugin_files#thirdpartylibs.xml) for more on this.
The file is in the format:
<?xml version="1.0"?>
<libraries>
<library>
<location>path/to/library</location>
<name>library name</name>
<version>library version</version>
<license>licence type</license>
<licenseversion>
licence type version
</licenseversion>
</library>
</libraries>

In order for the library paths to be excluded from the plugin checks that are carried out
when the plugin is installed, you need to update the exclusions by running grunt
ignorefiles on the command line after any changes. Grunt (https://gruntjs.com/) is a
build tool that runs in a Node.js (https://nodejs.org/en/), a JavaScript runtime environment.
Neither of these is installed with Moodle, although the Gruntfile.js is included in the
source. Many core JavaScript modules (AMD and YUI) and Less CSS are processed using
the Grunt tool before they are released.
However, it is not clear from the documentation how a third-party plugin's exclusion can be
generally updated on a live site. The ‘Third Party Libraries’ page, ‘Site administration-
>Development->Third party libraries’ (/admin/thirdpartylibs.php) does not depend on the
exclusion file, nor does it have any impact on the use of the libraries in your code. I advise
you to ignore the grunt step unless you have issues installing your plugin.

Zend
The Zend Framework is a collection of libraries/packages for professional PHP
development. Its use in Moodle, however, has been limited to web services and MNet

27
Introduction to Moodle 3.9+ Plugin Development

functionality and it was removed without deprecation in Moodle 3.1. The current advice is
to find alternatives for what you need to do.

PEAR
PEAR is a framework and distribution system for reusable PHP components. If you look at
the list on the ‘Third-Party Libraries’ page, ‘Site administration->Development->Third
party libraries’ (/admin/thirdpartylibs.php), you will notice several PEAR libraries listed.

A very important word of warning: these libraries have not been updated in a while (see
https://tracker.moodle.org/browse/MDL-52465), and some code, particularly
HTML::QuickForm, has been ‘hacked’ quite extensively. The advice in the
README.txt file is never to use these classes directly but, instead, to build wrapper
classes to isolate Moodle code from internal PEAR implementations. However, things have
moved on from 2005! At that time, all the libraries were included unmodified. In fact, it
would have been possible to delete the /lib/pear/ folder and the code would use the
standard PEAR libraries if installed. Now you can use other installed PEAR components
for your plugin as-is.
To do this, you need to be certain that PEAR and the relevant libraries are installed. This
may be less problematic if you are developing the plugin for a single client and you can
ensure the environment to some extent. I recommend doing the PEAR check-in on your
db/install.php file and not letting the install go ahead if this is missing and if your plugin
depends extensively on the library.

28
Chapter 4 Plugin Development Background

db/install.php
try {
if (@include_once('System.php')) {
if(class_exists('System')) {
echo 'PEAR is installed!';
}else{
throw new Exception(
'System.php cannot be called.');
}
}else{
throw new Exception('PEAR is not installed');
}
}catch (Exception $e) {
echo $e->getMessage();
}

All the modifications to the included PEAR libraries are documented in the
README_MOODLE.txt in the /lib/pear/ folder.
The rule that third-party libraries should have GNU GPLv3 compatible licences is not
strictly followed for the included PEAR libraries. There is an interesting note added on 2
April, 2006 by Martin Dougiamas to the README.txt file about this matter.
I leave it up to you as to whether or not you choose to use PEAR in your developments.

JavaScript Framework
Moodle had been using the Yahoo User Interface library (YUI) framework for many years,
but since Moodle 2.9 there has been a push to use AMD JavaScript modules and JQuery, as
there is no further development taking place on the YUI library which started life in 2005.
Since Moodle 2.9, Moodle has supported JavaScript modules written using the
Asynchronous Module Definition (AMD) API, which is defined on its wiki site
(https://github.com/amdjs/amdjs-api/wiki/AMD) in the following way: ‘The Asynchronous
Module Definition (AMD) API specifies a mechanism for defining modules such that the
module and its dependencies can be asynchronously loaded. This is particularly well suited
for the browser environment where synchronous loading of modules incurs performance,
usability, debugging, and cross-domain access problems’. For more information about
Moodle’s support for modules, please visit the ‘JavaScript Modules’ page
(https://docs.moodle.org/dev/JavaScript_Modules).
Moodle includes jQuery as an AMD module – see the ‘jQuery’ page

29
Introduction to Moodle 3.9+ Plugin Development

(https://docs.moodle.org/dev/jQuery). This means that JQuery is available to all AMD


JavaScript and it can be listed as a dependency of your module or required to load it.
Additionally, AJAX (Asynchronous JavaScript and XML) functionality is available.
As for AJAX, Moodle prefers the use of JSON rather than XML. AJAX functionality is
available via the JavaScript module named core/ajax, which can directly call existing
and new webservice functions. Please see the ‘AJAX’ page
(https://docs.moodle.org/dev/AJAX) and the ‘Web Services’ page
(https://docs.moodle.org/dev/Web_services) for more information.
However, the core AJAX library, which lives in /lib/amd/src/ajax.js, is designed to be used
with web services. If you want to use standard AJAX, use $.ajax where $ is JQuery.
In Moodle 3.9, jQuery 3.4.1 is standard. The jQueryUI libraries are not recommended for
use with Moodle, particularly for core functionality, for several reasons, including that CSS
conflicts with the Bootstrap. It is available as an AMD module named jqueryui if you
need or want to use it.
Incorporating JavaScript in your plugin has become complex, and even more so since
Moodle 3.8 for which, even in developer mode, your JavaScript is served minimised. The
instructions in the installation of the requirements to include JavaScript are adequate on the
‘JavaScript Modules’ page (https://docs.moodle.org/dev/Javascript_Modules), but I did
have some issues on Ubuntu 18.04 after installing NodeJS.
If you get the error npm: Depends: node-gyp (>= 0.10.9) but it is not
going to be installed when you attempt to install npm, then the following
command lines might be useful:
sudo apt-get install nodejs-dev node-gyp libssl1.0-dev
sudo apt-get install npm
cd /var/www/moodlesite/html
npm install
sudo npm install -g grunt-cli

The fallback is to use the $PAGE->requires-js(‘path/to/js/file’) call,


though it is not advised. Sometimes it may be the only way you can incorporate third-party
JavaScript. To help during development, add $CFG->cachejs = false; to
your /config.php file.
Moodle provides several core modules for use in your JavaScript – see Moodle’s ‘Useful
Core Javascript Modules’ page
(https://docs.moodle.org/dev/Useful_core_Javascript_modules). All the core modules are
defined in the /lib/amd/scr/ directory.

30
Chapter 4 Plugin Development Background

There are several pages relating to AMD, JQuery, and general JavaScript on the Moodle
docs site which are worth reading when you need to solve an issue. Otherwise, sticking
with the details on the ‘JavaScript Modules’ page
(https://docs.moodle.org/dev/Javascript_Modules) may suffice. It would take another book
to cover the subject entirely.
We will look at an example of the use of JavaScript modules later in this book.

Callbacks
Callbacks are one way that plugins integrate with the rest of the Moodle system. These are
functions that are defined within the plugin, and usually required to be in the plugin’s
lib.php file that is called by the core for each plugin. The callbacks are usually defined
within the relevant API, such as the File and the Navigation APIs, two ones that you will
most likely use. For example, every plugin that handles files is expected to implement a
pluginfile() callback function in the form <PLUGINNAME>_pluginfile()
which ‘sends’ the requested file. There will be an example of this in our ‘Developing
Plugins’ chapter (Chapter 8).
The Navigation API callbacks, such as extend_navigation_course(),
extend_navigation(), and extend_navigation_user() let your plugin add
options to the navigation trees.
Since most of the callbacks are defined by the relevant APIs, which will be covered next,
we will leave this subject for now. For more information and to get a better understanding,
please visit the Moodle documentation’s ‘Callbacks’ page
(https://docs.moodle.org/dev/Callbacks).

31
Chapter 5: Common APIs

Chapter 5:
Common APIs
This chapter provides a summary of the most commonly-used APIs when developing
plugins. More complete documentation for these and others not covered in this book can be
found in the development section of the Moodle website –
https://docs.moodle.org/dev/Main_Page. A link to the specific API's documentation page is
given at the end of each summary, but the comprehensiveness of the documentation is
inconsistent. While it can be helpful to read the documentation page to get a better
overview, I have found that looking over the code is also worthwhile, as the PHP Doc
comments are usually very informative.

Moodlelib API
The Moodlelib API is a sort of catch-all of functionality that is necessary, but not supplied,
by other categorised API functionality. A couple of classes, and many miscellaneous
functions and constants are defined in this API. The API is housed in the
/lib/moodlelib.php file.
Constants include:
 Time-related such as HOURSECS, DAYSECS, DAYMINS;
 Parameter type constants such as PARAM_TEXT, PARAM_BOOL,
PARAM_ALPHANUM;
 Value-related such as VALUE_REQUIRED, VALUE_OPTIONAL,
NULL_NOT_ALLOWED;
Chapter 5: Common APIs

 Blog levels such as BLOG_USER_LEVEL, BLOG_COURSE_LEVEL;


 Password policy such as PASSWORD_LOWER, PASSWORD_UPPER,
PASSWORD_DIGITS;
 Plugin feature constants such as FEATURE_GRADE_HAS_GRADE,
FEATURE_PLAGIARISM, FEATURE_GROUPS;
 Activity module types such as MOD_ARCHETYPE_RESOURCE,
MOD_ARCHETYPE_OTHER, MOD_ARCHETYPE_ASSIGNMENT;
 A few others relating to web services, emailing, and course displays.
Class definitions are:
 emoticon_manager – core support for plugins that have to deal with emoticons;
 lang_string – special class to handle the generation of strings, as we shall see
in the ‘String API’ section;
 partial – helper function to do partial function binding.
Some useful functions are:
 clean_param() and related functions such as required_param() and
optional_param() to manage input parameters;
 require_login() to check if the user is logged in and related functions such as
require_logout(), require_course_login(), and
require_admin();
 set_config() and get_config() to set and get configuration variables;
 get_string(), print_string(), and get_string_manager() to work
with strings – more about this when we look at the String API;
 purge_caches() and other caching functions;
 set_user_preference() and other user preference functions;
 userdate() which returns a formatted string date based on the user and other
date time functions such as format_time(), dayofweek(),
date_format_string(), and get_time_interval_string();
 fullname() to return the user’s name in the correct format and other user data
field functions such as get_all_user_name_fields(), as well as some user
Chapter 5: Common APIs

record functions such as create_user_record() to create a bare-bones user


record, and delete_user();
 get_enabled_auth_plugins() and get_auth_plugin() and other
functions for user authentication;
 reset_password_and_mail() and other password-related functions such as
check_password_policy() and generate_password();
 delete_course() and remove_course_contents() and other course-
related functions;
 email_to_user() and get_mailer() and other email-related functions
such as generate_email_signoff();
 clean_filename() and other file-related functionality such as
get_file_storage(), get_directory_list(), and
get_file_packer();
 Getting lists – get_list_of_charsets(), get_list_of_plugins(),
and get_list_of_themes();
 get_emoticon_manager() – to get an instance of the emoticon_manager;
 A number of general useful functions such as fix_utf8() – to ensure value is
valid utf8, is_number() – to determine if a value is a number, and
format_float() – to format numbers for display.
It is worth having a look at the /lib/moodlelib.php script to become aware of the
functionality it provides. Some of these functions, such as get_string(), are used very
frequently.

String API
The String API's main purpose is to handle the string output, particularly for Languages
and Internationalisation purposes as discussed in the ‘Moodle Development Overview’
chapter (Chapter 2), but it also provides some general multi-byte safe text-based
functionality. If a third-party plugin is approved for inclusion in the Moodle Plugins
Directory (http://moodle.org/plugins), then the default English (Australian) language file is
automatically registered with AMOS, the Automated Manipulation Of Strings
(https://docs.moodle.org/dev/AMOS_manual) tool, allowing translations to be made.
Plugins define their strings in language files in their lang/ subdirectory. The default English

35
Introduction to Moodle 3.9+ Plugin Development

strings file goes into the lang/en/ folder and the file’s name should be the Frankenstyle
name of the plugin, e.g. enrol_bulkenrol.php. Other languages, if required, live in separate
directories based on their ISO 639-1 language codes, such as lang/de/ for German, with the
optional inclusion of two-letter country codes such as lang/en_us/ for US English.
Strings are defined as an associative array that the plugin defines in the file, simply as:
$string[‘mystringname’] = ‘My string name’;
These values are added to the array so do not reset the $string variable. When the
plugin wants to retrieve the string, a call to get_string(‘mystringname’,
‘mypluginname’) is all that is needed. If, for example, the site is Swahili and a Swahili
translation is available, the Swahili term will be returned. The function
print_string(‘mystringname’, mypluginname) will echo the string directly.
Run time parameters can be passed to the calls for substitution in the returned string. The
parameter $a is passed as the third parameter to the calls and can be a scalar, an array or
even an object. The string then can be defined with {$a} or {$a->parametername},
e.g.
$string[‘mystringname’] = ‘My full name is {$a->fullname}’;
A word of warning: for this to work, the string must be defined in single quotes.
Another frequent use for the API is to help tooltips in forms. When defined, the user can
hover over a help/information icon and get a more helpful description of the input required.
The help string is defined by appending '_help' to the string name. The above
$string[‘mystringname’] would then become
$string[‘mystringname_help’]. There are further options such as supplying a
link to further help and an administration description which are described on the relevant
Moodle documentation page (see https://docs.moodle.org/dev/String_API#Help_strings).
The functions get_string() and print_string() are two of a few wrappers for
the methods provided by the string manager class, which is one of the two classes regarded
as part of the String API. As mentioned before, the wrapper functions reside within
Moodlelib API. Apart from the methods called by the wrappers, the string manager
provides some other useful functions such as:
 string_exists() – check if a string is defined
 get_list_of_countries() – a localised list of all countries
 get_list_of_languages() – a localised list of languages

36
Chapter 5: Common APIs

 get_list_of_currencies() – a localised list of known currencies


 translation_exists() – check if the translation exists
Renamed in Moodle 2.6, the String API declares the core_text class, which specifically
assists in dealing with utf8 text. The class consists of several static methods of use that feel
familiar as a PHP developer, for example:
 core_text::asort()
 core_text::strlen()
 core_text::strpos()
 core_text::strrpos()
 core_text::strtolower()
 core_text::strtotitle()
 core_text::strtoupper()
 core_text::substr()
The class is defined in the /lib/textlib.class.php file and you can examine it to get an idea of
other functions.
For more information, please see the Moodle documentation's ‘String API’ page
(https://docs.moodle.org/dev/String_API).

Page API
Think of the global variable $PAGE as the structure into which the output from the Output
API, discussed in the next section, offloads the page's content – usually HTML. The page
includes the theme and layout, the JavaScript and CSS requirements, titles, headings,
navigation position, and things like blocks to display. In other words, the scaffolding for
the actual output.
The $PAGE variable is an instance of the moodle_page class defined in the
/lib/pagelib.php file. To display a page as part of the standard user interface (UI), you will
need to set the URL for the page and the context in which the page exists. The URL is used
quite extensively by the core code and specifically for the navigation menus. Sometimes
the context is magically determined, usually in the case where a call to the function
require_login() or require_course_login() is made, which check and
force the user to login to the system.

37
Introduction to Moodle 3.9+ Plugin Development

As mentioned earlier, in the ‘Roles, Permissions, and Capabilities’ section of the ‘Moodle
Development Overview’ chapter (Chapter 2), Moodle has six contexts, namely System,
User, Course Category, Course, Module, and Block.
The $PAGE->set_pagelayout() method sets the page layout mentioned earlier in
the ‘Output – Themes, Layouts, Renderers and Templates’ section in the Moodle
Development Overview chapter (Chapter 2).
The $PAGE->set_title() method sets the page’s title, i.e. the text in the <title>
HTML tags, and the $PAGE->set_heading() sets the title for the page. The display of
this is theme-dependant. The four methods mentioned so far are the ones you will most
commonly see in pages that display output.
There are some other advanced methods, including $PAGE->set_course() and
$PAGE->set_cm (course module). Both of these automatically set the page context as
well.
You can also get information with properties and methods, such as with $PAGE-
>course, $PAGE->cm, $PAGE->title, as well as $PAGE->headerprinted,
which enables you to determine if the header has been printed, and $PAGE->navbar and
$PAGE->navigation, which let you access the navigation components discussed later
in the ‘Navigation API’ section.
We will see more of the Page API and the $PAGE variable later in the book. For more
information, please visit the Moodle’s Page API documentation page at
https://docs.moodle.org/dev/Page_API.

Output API
Together with Themes and the Page API described in the previous section, this API
handles some of the Moodle output. It provides several generic functions and encompasses
additional output components, JavaScript output, and renderers and templates. There are
several files in the /lib folder relating to the Moodle output, many of which you will not
need to go through.

File Description
outputcomponents.php Classes representing HTML elements, used by
global $OUTPUT methods (see upcoming
outputcomponents.php section)
weblib.php Library of functions for web output library of all

38
Chapter 5: Common APIs

general-purpose Moodle PHP functions and


constants that produce HTML output (see
upcoming weblib.php section)
Theme Related
outputlib.php Functions for generating the HTML that should
output
Javascript
outputrequirementslib.php Library functions to facilitate the use of JavaScript
in Moodle, providing two classes:
• theme_config – represents the
configuration variables of a Moodle theme
• xhtml_container_stack – keeps
track of which HTML tags are currently
open
outputactions.php Classes representing JavaScript event handlers,
used by output components. The file defines a
couple of generic functions and two classes.
• page_requirements_manager –
tracks all the things that are needed by the
current page and is usually accessed via
$PAGE->requires to load JavaScript
and CSS
• YUI_config – The YUI framework
configuration class as discussed in the
‘JavaScript Framework’ sect. in Chapter 4
outputfragmentrequirementslib.php Library functions to facilitate the use of JavaScript,
housing the
fragment_requirements_manager class,
which extends
page_requirements_manager to facilitate
the capture of HTML to create a fragment to be
inserted elsewhere
Rendering (see ‘Renderers and Templates’ section that follows)
outputfactories.php Interface and classes for creating renderers
outputrenderers.php Classes for rendering HTML output, including the

39
Introduction to Moodle 3.9+ Plugin Development

renderer base class that is available via the


$OUTPUT global

For more information on the Output API, please see the Moodle documentation's ‘Output
API’ page (https://docs.moodle.org/dev/Output_API) and the ‘Output Functions’ page
(https://docs.moodle.org/dev/Output_functions).

Renderers and Templates


A renderer works alongside themes and templates to display the output for a component of
Moodle. This idea was introduced in Moodle 2.0 along with the global $OUTPUT class
variable, which points to Moodle's core renderer. Moodle 2.8 introduced support for
renderer auto-loading and Moodle 2.9 introduced the concept of templates. The idea of
renderers is that they should not have any logic other than that to generate the display of
snippets of HTML. However, quite a few core components do not follow this 'suggestion'
too closely. Prior to templates, many renderers generated HTML using the html_writer
class, though there is still a lot of PHP-generated HTML.
Being a fairly new introduction, it is mainly in the core code that you will find templates in
use. Most plugins will still be using html_writer and vanilla PHP for HTML generation.
Moodle uses Mustache (http://mustache.github.io/), described as Logic-less templates, as
its template engine. Mustache is written as HTML with additional tags used to format the
display of the data. Mustache tags are made of two or sometimes three opening and closing
curly braces {{somethinginhere}}. For more information about writing templates,
please see the Version 5 manual at http://mustache.github.io/mustache.5.html.
Plugin templates should be saved in the plugin’s templates folder and must have a
.mustache extension. Renderers use templates via the render_from_template()
method in the form $this->output-
>render_from_template($templatename, $data) where $data is the
context referred to in the Mustache’s documentation and is the data to be used by the
template. Visit the Moodle ‘Templates’ documentation page
(https://docs.moodle.org/dev/Templates) for information on the how and the where, and
coding styles. In this book, templates are not covered in further detail.
Renderers are defined in renderer.php files, either in the plugin’s top folder or in the
classes/ subdirectory, following the Moodle’s auto-loading rules, as previously discussed in
the ‘Class Autoloading and Namespaces’ section in the ‘Plugin Development Background’
chapter (Chapter 4) or as a required/included file. The renderer is called by the global
$PAGE->get_renderer() method and the plugin’s renderer class normally extends

40
Chapter 5: Common APIs

the core plugin_renderer_base class. There is a sort of unwritten rule about naming
the renderer variable $output (lower case) to show its relation to the global $OUTPUT.
Code in the renderer should not refer to the two globals $PAGE or $OUTPUT, but access
the core functionality via $this->page and $this->output variables.
We shall meet renderers again later in this book.

weblib.php
This library defines several classes, the base progress_trace, several extensions to
output progress status (such as html_progress_trace), and the very useful
moodle_url class.
The moodle_url class provides useful functions to work with Moodle URLs:
 URL output – escaped or not – out(), raw_out()
 Methods to set and get parameters – params(), remove_params(),
remove_all_params(), param()
 Dealing with query strings – get_query_string(),
out_omit_querystring()
 General URL functionality – get_path(), get_param(), get_scheme(),
get_host(), get_port()
 Useful functions to create complicated internal URLs, including those for the File
API’s file manager class – make_file_url(), make_pluginfile_url(),
make_webservice_pluginfile_url(), make_draftfile_url(),
make_legacyfile_url()
 Many more functions – please view the source code for these
We will see this class quite often throughout the rest of this book.
The library also has several useful functions that are not part of the classes. A few are
mentioned here:
 s() – adds quotes to HTML characters
 p() – prints quotes to HTML characters
 format_text() – when given text in a variety of format coding, this function
returns the text as safe HTML

41
Introduction to Moodle 3.9+ Plugin Development

 clean_text() – cleans raw text, removing nasties


 get_local_referer() – returns the cleaned local URL of the
HTTP_REFERER, less the URL query string parameters if required
 html_to_text() – when given HTML text, it converts it into plain text
 is_https() – check if the site is running under SSL
 is_in_popup() and close_window() – check if in a pop-up and close the
pop-up window respectively
 me() – returns the name of the current script, WITH the query string portion and
qualified_me(), which guesses the full URL of the current script
 notice() – prints a message and exits
 redirect() – redirects the user to another page, after printing a notice
 strip_links() – when given a string, it replaces all <a>.*</a> by .* and
returns the string
 text_to_html() – when given plain text, it converts it into HTML as neatly as
possible
 validate_email() – validates an email address

outputcomponents.php
This file defines a number of classes representing HTML elements extensively used by
global $OUTPUT methods, but available for use generally. The classes are as follow:

Class Methods Description


Interfaces
renderable{} 0 methods Interface marking other classes as suitable
for renderer_base::render()
templatable{} 1 method Functions to export the renderer data in a
format that is suitable for a mustache
template
Pictures/Icons
user_picture{} 4 methods A data structure representing a user picture

42
Chapter 5: Common APIs

help_icon{} 3 methods A data structure representing a help icon


pix_icon_font{} 3 methods A data structure representing an icon font
pix_icon_fontawesome{} 0 methods A data structure representing an icon
subtype
pix_icon{} 3 methods A data structure representing an icon
subtype
image_icon{} 0 methods A data structure representing an activity
icon
pix_emoticon{} 1 method A data structure representing an emoticon
image
Simple Form Classes
file_picker{} 1 method A data structure representing a file picker;
more information in the ‘File API’ and
‘Form API’ sections later in this book
single_button{} 5 methods A data structure representing a simple form
with only one button
single_select{} 7 methods Simple form with just one select field that
gets submitted automatically
url_select{} 7 methods Simple URL selection widget
HTML/JS strings
js_writer{} 5 methods Simple Javascript output class – YUI
JavaScript only; methods include
function_call(), object_init(),
set_variable()
html_writer{} 28 Simple html output class – methods include
methods tag(), start_tag(), end_tag(),
link(), etc
html_table{} 1 method Holds all the information required to render
a HTML table; outputted by
html_writer::table()
html_table_cell{} 1 method Component representing a table cell
html_table_row{} 1 method Component representing a table row

43
Introduction to Moodle 3.9+ Plugin Development

HTML Bars
progress_bar{} 8 methods Progress bar class; manages the display of a
progress bar
paging_bar{} 3 methods Component representing a paging bar
initials_bar{} 2 methods Component representing initials bar, i.e. bar
with alphabet ABCDE...
Moodle Blocks
block_contents{} 2 methods This class represents how a block appears
on a page. during output, each block
instance is asked to return a block_contents
object as discussed later in the ‘Developing
Plugins’ chapter (Chapter 8)
block_move_target{} 1 method Represents a target for where a block can go
when it is being moved
Custom Menus (Theme specific)
custom_menu{} 4 methods Used to operate a custom menu; this menu
is built using $CFG-
>custommenuitems and is a structured
collection of custom_menu_item nodes
– see next
custom_menu_item{} 15 Used to represent one item within a custom
methods menu that may or may not have children
Tabs
tabtree{} 2 methods Stores and outputs tabs list of tab objects –
see next
tabobject{} 5 methods Stores one tab
Action Menu
action_menu{} 18 An action menu; the primary actions are
methods displayed permanently and the secondary
attributes are displayed within a drop-down
menu
action_link{} 6 methods Data structure describing html link with
special action attached

44
Chapter 5: Common APIs

action_menu_filler{} 1 method action_link extension: an action menu


filler
action_menu_link{} 2 methods action_link extension: an action menu
action
action_menu_link_primary{} 1 method action_menu_link extension: a
primary action menu action
action_menu_link_secondary{} 1 method action_menu_link extension: a
secondary action menu action
Preferences (Theme Specific)
preferences_groups{} 1 method Represents a set of preferences groups
preferences_group{} 1 method Represents a group of preferences page
links
Miscellaneous
context_header{} 2 methods Renderable for the main page header

File API
Moodle 2.0 introduced a new file system for Moodle. There were several good reasons to
change from the previous system where the files were used as files on the server. One of
these reasons was that the files were beholden to the rules of the server's operating system.
The change to the new file system was a significant one for developers and users pre-
Moodle 2. Note that the API is specifically for files that are part of the site's content.
Related to the this API is the repository plugins functionality
(https://docs.moodle.org/dev/Repository_plugins), which deals with file stores outside the
site’s files system. We will cover both later in the ‘Developing Plugins’ chapter (Chapter
8).
At the basic level, the contents of the files are still stored on disk in obfuscated folders and
files in the /moodledata/filedir/ directory, but file details are stored in a database table
named mdl_files. Moreover, files in this file system all belong to a component/plugin
and it is usually this that determines the access and the delivery of the files to the user.
Whilst working with files, the user gets a ‘draft area’ until the commitment is made to
‘save’ the file.
It is possible to work out where the contents of a particular file are if you have access to the
files table. So, not all is lost if you need to do something unconventional. The files are

45
Introduction to Moodle 3.9+ Plugin Development

stored based on the SHA1 hash of their content in folders that relate to the first four
characters in the hash. For more information about the internals of the API, please see the
‘File API Internals’ page (https://docs.moodle.org/dev/File_API_internals).
For plugins, the usual process when working with files is:
1 To create a draft area for the user;
2 If the file is an existing file, as in the case of it being about to be replaced or edited,
to copy the file into the draft area. Otherwise, use the draft area-id to, for example,
create or get the new file. In the ‘Form API’ section later in the book, we will see
how the draft id is used by the forms file manager;
3 Once the user commits to saving the file, to copy the draft file(s) back to the
component’s file area;
4 When any user attempts to access the file, to deliver the file after doing relevant
access checks.
Your plugin's files need to be stored in its file area. The concept is similar to directories but
not quite the same, as you can still use paths. A file area is defined by:
 A contextid – the context in which the file is being saved. See Access API for a
discussion of contexts;
 The Plugin’s Frankenstyle name;
 A file area type – this is for the plugin to determine. For example, you could have
an images type and another for non-image files. Most simple plugins just have one
type;
 A unique itemid – again something the plugin determines, it might be the record
id of a related record in another table; otherwise use 0.
When creating the link for the user to access the file, usually your plugin will call
moodle_url::make_pluginfile_url(), which expects the following parameters:
 contextid
 component
 area
 itemid
 pathname

46
Chapter 5: Common APIs

 filename
 forcedownload
Note, the first four of these parameters relates to the file area and the next two relate to the
file's name and the path, which is not translated to the physical storage. The last parameter
is used to force the user to download the file. The resultant link will be in the format:
/pluginfile.php/$contextid/$component/$filearea/arbitrary/extra/information/file.ext
Moodle has several files relating to the delivery of files: file.php, draftfile.php,
userfile.php, and pluginfile.php which is of interest to us. All components that store and
deliver files have to define a callback <pluginname>_pluginfile() function to
deliver the requested file in its lib.php file. This is the callback function that
/pluginfile.php will call when it has identified the relevant plugin.
The callback should expect the following parameters:
 course – the course object (will be null if not applicable)
 cm – the course module object (will be null if not applicable)
 context – the context (will be the context based on the context id – could be
system context)
 filearea – the name of the file area
 args – extra arguments (itemid, path) (your pluginname_pluginfile()
function will have to decide what it does with these)
 forcedownload – whether or not to force download
 options – additional options affecting the file serving – again, the plugin decides
what to do with these; usually, it is not defined
The callback is expected to return false if the file is not found. Otherwise, it should just
send the file in the normal way using the File API’s send_stored_file() function
after processing.
The file request processing will:
 Check context is relevant
 Check the file area
 Check user is logged in – if applicable

47
Introduction to Moodle 3.9+ Plugin Development

 Check for any required capabilities as per the Access API


 Identify $itemid, $filepath, $filename from the forward slashed separated
$args parameter
 Attempt to retrieve the file from the file store and return results – either false or
using the send_stored_file() function
Many of the File API’s functions are defined in the /lib/filelib.php file. Additional
functionality is defined in the filestorage (/lib/filestorage/file_storage.php) class for
low-level file activities (such as retrieving the file contents), the filebrowser class
(/lib/filebrowser/file_browser.php) for browsing files in code, and the forms user interfaces
for file uploads discussed in the ‘Form API’ chapter later in the book.
There is so much that can be achieved with this API, including browsing, moving, deleting
folders and files and, if you require this functionality, it is worth looking at the source code
and at some examples in some of the core plugins to get a feel for what you will need to do.
We shall meet this API again later on and even see examples of where you might, in some
cases, have to use the global $_FILES variable.
For more information, see the Moodle’s documentation ‘File API’ page
(https://docs.moodle.org/dev/File_API).

Form API
The Moodle Form API is strongly based on the PHP Extension and Application Repository
(PEAR) HTML QuickForm Version 3.2
(https://pear.php.net/manual/en/package.html.html-quickform.php) libraries. This is an
example of the legacy issues I mentioned earlier in the ‘Legacy Issues’ section of the
‘Moodle Development Overview’ chapter (Chapter 2). The PEAR libraries have been re-
written for PHP5, which is currently at Version 2.2.0. The library can be found in the
/lib/pear/HTML/QuickForm/ directory, but you should rarely need to go there.
Most forms in Moodle – if they use this API – are extensions of the moodleform class
which is defined in the /lib/formslib.php file. The class is described as a ‘wrapper that
separates QuickForm’s syntax from Moodle code’ and does not extend any of the
QuickForm classes. Instead, there is a property $this->_form, which is an instance of
the MoodleQuickForm class, which, in turn, does extend the PEAR
HTML_QuickForm_DHTMLRulesTableless class.
The major exception is for activity modules, which tend to extend the moodleform_mod
class. This class, which is an extension of the moodleform class, extends the

48
Chapter 5: Common APIs

functionality for module-specific functionality, such as when defining $this-


>standard_intro_elements() to add standard form elements to the module’s
configuration edit form.
The elements of the form are defined in the abstract definition() method which must
always be overridden by your object. This function is called when the form class is
instantiated and, many times, may be the only method required for your forms to work.
Adding form elements mostly requires using the MoodleQuickForm addElement()
method and it makes sense to create a variable for $this->_form as in this example:
public function definition() {
$mform = $this->_form;
$mform->addElement('text', 'email',
get_string('email'));

The other useful property for your definition function is $this->_customdata, which
is an array that holds custom data passed to the form's constructor, if defined. There are
several possible parameters passed to the constructor:
$myform = new myform_class($action, $customdata,
$method, $target, $attributes, $editable,
$ajaxformdata);

All have default values, so there is no need to define them unless necessary:
$action The action attribute for the form – if empty this defaults to auto-
detect the current URL. If the value is a moodle_url object, then
it outputs parameters as hidden variables.
$customdata If your form’s definition() method needs access to data (such
as $course, $cm, etc.) to construct the form definition, then
pass it in this array. You can also use globals.
$method If you set this to anything other than 'post', then _GET and _POST
will be merged and used as incoming data for the form.
$target Target frame – don't use it if you don't need to, as the target
attribute is deprecated in xHTML strict.
$attributes You can pass a string or an array of HTML attributes here.
$editable Whether the form is editable or not, if not, no input fields are
created or displayed.

49
Introduction to Moodle 3.9+ Plugin Development

$ajaxformdata Forms submitted via AJAX must pass their data here, instead of
relying on _GET and _POST.

We will discuss adding elements to the form in the next section, but there is a special add
elements method add_action_buttons(), which adds action buttons to the form. The
function can take up to two parameters: the first, a boolean value relating to whether to
include a cancel button, and the second, a label for the submit button if the default ‘Save
Changes’ will not do.
The most useful methods of the form in everyday use are as follow (but, remember, there
are many more, so it is worth having a look at the moodleform class code):
 display(), render() – print or get the form HTML
 set_data() – apply existing database table data values to the form elements; the
fields must match for this to work, otherwise, you can pass the data via the
$customdata parameter and then apply logic within the form to use that data to
set the initial element value
 validation() – function to validate the submitted data; errors mean the form is
re-displayed with errors highlighted and sometimes this is also used to do additional
processing work with files
 get_data() – a special wrapper method to get the submitted data or null if no
data has been submitted or validation fails
 is_cancelled() – checks if the form has been cancelled
 is_submitted() – check if the form has been submitted.
Generally, the process is as follows:
1 Initiate the form, e.g. $myform = new myformclass(null,
$customparms, ...).
2 Optionally read data from the database and apply it to the form using the
set_data() method.
3 Check if the form has been cancelled using the is_cancelled() method and
act accordingly – usually by redirecting the user.
4 Check if data has been submitted with the get_data() method and process if
necessary.

50
Chapter 5: Common APIs

5 If not cancelled or submitted, display the form with the display() method.
See the Moodle documentation's ‘lib/formslib.php Usage’ page
(https://docs.moodle.org/dev/lib/formslib.php_Usage) for another description of this
process.

Form Elements
As expected, the Form API supports the standard HTML form elements. In addition to the
‘Basic’ elements, the API supports what is termed ‘Advanced elements’ and ‘Custom
elements’. ‘Advanced’ elements are core Moodle custom elements that provide additional
functionality to standard HTML elements. Handling files is a special case here and is
discussed in the following section.
‘Custom elements’ are element extensions and are created by calling the
registerElementType() of the MoodleQuickForm class, passing in the element
type. They include paths for type's class definition and the element's class name. You will
need to define the class and its methods – in most cases, extending an existing
MoodleQuickForm class. The custom element can then be used like any other element.
As mentioned earlier, elements are defined in the form’s definition() method.
Working with elements uses the MoodleQuickForm class methods, mainly
addElement(). The MoodleQuickForm object is accessed via the classes $this-
>_form variable (also mentioned earlier) and will be referred to as $mform for the rest
of this chapter.
It can be a little confusing, but understand that $this refers to your form class, which
extends the moodleform class, while $mform is a pointer to the MoodleQuickForm
object that provides the functionality to work with the actual form elements. In this chapter,
I will specify which is the correct form for each method.
Normally, there are several steps to defining an element for a form:
1 First add the element to the form using the $mform->addElement(). All
elements expect at least three parameters: the element type, the form name for the
element, and the text for the form's prompt (usually by calling String API's
get_string() function). Also, all the elements optionally expect an options
array parameter, which contains the attributes for the element. The actual order
where this options parameter is expected varies depending on the element’s
definition requirements. The connected $mform->createElement() is used
in some cases; see the method $mform->addGroup() notes later.

51
Introduction to Moodle 3.9+ Plugin Development

2 Set the element’s type with $mform->setType() by specifying the input’s


parameter type constants, such as PARAM_TEXT, PARAM_INT as discussed in the
‘Moodlelib API’ section in this chapter. UI elements, such as header, do not require
this to be set.
3 Optionally, specify some text for the help popup/tooltip using the $mform-
>addHelpButton() method. We touched on this functionality in the ‘String
API’ section. The function accepts three arguments, namely:
3.1 The name of the form element for which the help button is to be added. This is
not a button, but rather a help icon that is displayed next to the element's text;
3.2 The identifier for the help string title and contents. Both the title and the
contents should be defined in the plugin's strings file. Moodle gets the title from
the string with the identifier specified and the contents from the identifier
specified with '_help' appended. For example, if the identifier is the username,
the title value is $string[‘username’] and the help contents is
$string[‘username_help’];
3.3 The plugin/component’s name.
4 Optionally, set the elements default value using $mform->setDefault().
Usually, the default value of the elements is set by the $this->set_data()
method, but this requires that the form fields mirror the database record field
names. This function allows you to use a different value.
// HEADER STUFF HERE //

require_once($CFG->libdir . '/formslib.php');

class simple_form extends moodleform {

function definition() {
$mform = $this->_form; // MoodleQuickForm obj.

// Access the passed in parameters.


$param1 = $this->_customdata['param1'];
$param2 = $this->_customdata['param2'];

$attributes = array('size'=>'64');
// Add the element to the form.
$mform->addElement('text', 'name',

52
Chapter 5: Common APIs

get_string('myprompt', 'myplugin'),
$attributes);
// Set the PARAM type for the element
$mform->setType('name', PARAM_TEXT);
// Add a help icon) to pop-up some help.
$mform->addHelpButton('name', 'namehelp',
'myplugin');
// Set the default value of the element.
$mform->setDefault('name', $param1);
// Add a required rule for the element.
$mform->addRule('name', null, 'required', null,
'client');
}
}

You will notice the $mform->addRule() function in the example. This allows you to
specify rules (aside from other defined rules which I will discuss next). Several rules can be
applied, with those most often used including:
 required
 maxlength
 email
 alphanumeric
 numeric
The $mform->addRule() method requires at least three parameters, but takes a total of
seven. These are:
1 The element’s name
2 The error message to display if the input is invalid – usually obtained via a
get_string() call
3 The rule type as per the examples above
4 Optionally, the data required by the rule, such as the maximum length for the
maxlength rule
5 Optionally, to perform validation, either ‘server’, which is the default, or ‘client’
6 Optionally, a boolean value for client-side validation, determining whether the form

53
Introduction to Moodle 3.9+ Plugin Development

should be reset if there is an error – defaults to false


7 Whether to force the rule to be applied, even if the target form element does not
exist – defaults to false
For more information about rules, please see the Moodle documentation’s addRule
paragraph on the ‘formslib.php_Form_Definition’ page
(https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#addRule).
Loosely related to the rules are two other useful methods: $mform->disabledIf()
and $mform->hideif(), which both accept the same parameters that allow you to
conditionally hide or disable any group or individual element depending on conditions
provided as one of the parameters. For more information, please read the documentation on
the Moodle page.
The use of the $mform->createElement() method is usually related to grouping
form elements, which simply involves grouping elements that will have a single label and
are included on one line in the form. The first parameter for the $mform->addGroup()
method is an array of references to created elements, optionally followed by:
 group name – default null
 group label – default empty string
 string to separate elements – default null
 boolean – default true that specifies whether the group name should be used in the
form element name, e.g. group[element]
// A group of radio buttons.
$grpdradios = array();
$grpdradios[] = & $mform->createElement('radio',
'yesno','', get_string('yes'), 1);
$grpdradios[] = & $mform->createElement('radio',
'yesno', '', get_string('no'), 0);
$mform->addGroup($grpdradios, 'radioar', '', array(' '),
false);

Previously I mentioned the moodleform_mod class’


standard_intro_elements() method, which added standard fields to the activity
plugin’s edit forms. Another useful method, also previously mentioned, $this-
>add_action_buttons() adds the common action buttons to the form with a little
control facilitated via the two optional parameters:

54
Chapter 5: Common APIs

 A boolean parameter to specify whether to display a cancel button – default is


true
 The text for the submit button and the default is
get_string('savechanges') from the core strings
This ends the discussion on the form elements, except for the special File API-related
functionality which is covered in the next section.
The standard Moodle elements can be found in the following table. Parameter items in the
table refer to:
 $elementName – the name for the element in the form
 $elementLabel – the label text for the element
 $value/$values – the element’s value(s)
 $attributes – the HTML attributes for the element
 $text – the element’s text
 $options – any options the element requires

Element Notes
advcheckbox Checkbox with default behaviour modified for Moodle, so that it will
return '0' if not checked and '1' if checked
Parameters – $elementName, $elementLabel, $text,
$attributes, $values

autocomplete A select box that allows you to start typing to narrow the list of
options or search for results
Parameters – $elementName, $elementLabel,
$options, $attributes

button See helpful method add_action_buttons()


Parameters – $elementName, $value, $attributes

cancel See helpful method add_action_buttons()

55
Introduction to Moodle 3.9+ Plugin Development

Parameters – $elementName, $value, $attributes

checkbox Parameters – $elementName, $elementLabel, $text,


$attributes

cohort Form field type for choosing a cohort; allows auto-complete AJAX
searching for cohort
Parameters – $elementname, $elementlabel,
$options

course Form field type for choosing a course; allows auto-complete AJAX
searching for courses and can restrict by enrolment, permissions,
viewhidden, etc.
Parameters – $elementname, $elementlabel,
$attributes

date_selector Class for a group of elements used to input a date


Parameters – $elementName, $elementLabel,
$options, $attributes

date_time_selector Class for a group of elements used to input a date and time
Parameters – $elementName, $elementLabel,
$options, $attributes

defaultcustom Creates an element with a drop-down Default/Custom and an input


for the value supports text or date_selector
Parameters – $elementname, $elementlabel,
$options, $attributes

duration Class for a length of time; the values returned to PHP represent the
duration in seconds (an integer rounded to the nearest second)
Parameters – $elementName, $elementLabel,
$options, $attributes

editor Specialised text area; it adds the preferred editor form element for the
selected format

56
Chapter 5: Common APIs

Parameters – $elementName, $elementLabel,


$attributes, $options

filemanager Specialised file elements (see the next section)


Parameters – $elementName, $elementLabel,
filepicker $attributes, $options

filetypes File types and file type groups selection form element
Parameters – $elementname, $elementlabel,
$options, $attributes

float Customised text element when working with float numbers, which
take care of the fact that different languages use different symbols as
the decimal separator; submitted float numbers will be automatically
translated from the localised format into the computer format
Parameters – $elementName, $elementLabel,
$attributes

grading Class for a grading element; this is a wrapper for advanced grading
plugins. When adding the 'grading' element to the form, the developer
must pass an object of class gradingform_instance as
$attributes['gradinginstance']
Parameters – $elementName, $elementLabel,
$attributes

group Class for a form element group (see addGroup() method)


Parameters – $elementName, $elementLabel,
$elements, $separator, $appendName

hidden Parameters – $elementName, $value, $attributes

html Add arbitrary HTML – usually to add the start and end of a HTML
div
Parameters – $elementName, $value

htmleditor Specialised text area that handles HTML input

57
Introduction to Moodle 3.9+ Plugin Development

Parameters – $elementName, $elementLabel,


$options, $attributes

listing Class for a listing form element, which is a simple customisable


‘select’ without the input type=select
Parameters – $elementName, $elementLabel,
$attributes, $options

modgrade Specialised custom class for a drop-down element to select the grade
for an activity – used in configuration update form
Parameters – $elementname, $elementlabel,
$options, $attributes

modvisible Specialised custom drop-down form element to select visibility in an


activity configuration update form
Parameters – $elementName, $elementLabel,
$attributes, $options

password Parameters – $elementName, $elementLabel,


$attributes

passwordunmask A password element with an option to show the password in plain


text
Parameters – $elementName, $elementLabel,
$attributes

questioncategory Specialised custom class for a drop-down element to select a question


category
Parameters – $elementName, $elementLabel,
$options, $attributes

radio Parameters – $elementName, $elementLabel, $text,


$value, $attributes

recaptcha Class for a recaptcha type element


Parameters – $elementName, $elementLabel,

58
Chapter 5: Common APIs

$attributes

searchableselector Extension of the autocomplete element


Parameters – $elementname, $elementlabel,
$options, $attributes

select Call the select’s setMultiple() method for a multi-select.


Setting the value of the selects requires the use of the element's
setSelected() method
Parameters – $elementName, $elementLabel,
$options, $attributes

selectgroups A ‘select’ element to create all options grouped in optgroups


Parameters – $elementName, $elementLabel,
$optgrps, $attributes, $showchoose

selectwithlink Specialised custom class for a ‘select’ type element with options
containing links
Parameters – $elementName, $elementLabel,
$options, $attributes, $linkdata

selectyesno Specialised custom class for a simple yes/no drop-down element


Parameters – $elementName, $elementLabel,
$attributes, $options

static Display a static text


Parameters – $elementName, $elementLabel, $text

submit See helpful method add_action_buttons().


Parameters – $elementName, $value, $attributes,
$primary, $options

tags Specialised custom class for editing tags, either standard or not

59
Introduction to Moodle 3.9+ Plugin Development

$options, $attributes

text Parameters – $elementName, $elementLabel,


$attributes

textarea Parameters – $elementName, $elementLabel,


$attributes

url Specialised custom class for a URL type element


Parameters – $elementName, $elementLabel,
$attributes, $options

warning Class for static warning type element


Parameters – $elementName, $elementClass, $text

header A pseudo-element used for adding headers to form and grouping


elements into collapsible field sets – theme dependant
Parameters – $elementName, $text

The header element is used for grouping the form elements into collapsible field sets, with
the text as the legend. This is dependent on the theme in use. By default, Moodle collapses
all field sets without a required element. To force a field set to be expanded, use $mform-
>setExpanded($elementname, true) and, in reverse, pass false as the second
argument.
A field set is automatically closed if you open a new one. However, if the following
elements are not to be enclosed by a visible field set, you can use the $mform-
>closeHeaderBefore() method, passing in the first element name of the following
elements.
You might also realise that there is no reset button definition. It’s unclear why there isn’t
one, but it would appear that most modern developers do not like resetting forms. Cancel
and then re-attempt, if required, seems to be the workflow of choice.
For more information regarding the Form API, please see the Moodle documentation's
‘Form API’ page (https://docs.moodle.org/dev/Form_API).

60
Chapter 5: Common APIs

Advanced File Elements


Moodle provides custom elements to work with file uploads. Whilst using the standard
HTML forms file uploads will work, you have to handle the global $_FILES variable
yourself. Later in this book, there is an example of doing such a thing but, for the most part,
you should use the custom fields, as they work so much better with the File API.
There are three custom elements to work with files:
1 filepicker – a more appropriate replacement for the HTML file element, but
really useful to get a file that is to be processed and then discarded, such as a CSV
upload
2 editor – this is actually a specialised text-area with an HTML editor (so not
strictly a file element), but it allows the use of files such as images, sounds, and
video within that HTML
3 filemanager – the recommended way to get uploaded files and save them to the
appropriate file area (please see File API for information about working with files)
All the file elements have an optional options array parameter; while it can contain just
about anything, the appropriate options for file elements are:
 subdirs (not applicable for file picker) – whether to include subdirectories
 maxbytes – maximum file size
 areamaxbytes (not applicable for file picker) – maximum file area size
 maxfiles (not applicable for file picker) – the maximum number of files
 accepted_types – see get_mimetypes_array() in
/lib/classes/filetypes.php. Defaults to ‘*’, which is all files. You can use file
extensions, e.g. array('.txt', '.jpg', 'audio') or, alternatively, file
types, such as array('audio', 'video', 'document')
 return_types – optional and best left to the defaults. Value is one or a
combination of file ‘types’ constants – FILE_EXTERNAL, FILE_INTERNAL,
FILE_REFERENCE or FILE_CONTROLLED_LINK
The editor also has specific editor options that we will discuss a little later.
Both the editor and the filemanager elements require that the files are saved and so
need to go through the process of going from the draft area to the appropriate file area. That
is not to say, however, that a file uploaded via the filepicker cannot be saved as a

61
Introduction to Moodle 3.9+ Plugin Development

Moodle file as well as being saved directly on the server’s file system.
The filepicker element is created by adding the element to the form:
$mform->addElement('filepicker', 'elementname',
$elementprompt, $attributes, $options);

When the form has been submitted, you can:


1 Get the file content:
$content = $mform->get_file_content($elementname);

The underlying PHP call is file_get_contents(), so the contents are


returned as a string. Watch out if this is a CSV file and you want to process it.
2 Get the filename:
$name = $mform->get_new_filename($elementname);

The editor element has several File API helper functions which remove the need for the
developer to manage getting files into draft areas and back to the relevant file area. The
editor is added to the form like this:
$mform->addElement('editor', 'elementname',
$elementprompt, $attributes, $options);

As mentioned before, the editor has some additional ‘specific’ options:


 enable_filemanagement – which, when true, will enable the user to use
files in the editor – default is false
 trusttext – if true, the submitted text is trusted. Otherwise, the default is
based on the $CFG->enabletrusttext and the capability
moodle/site:trustcontent
 context – the most local context, otherwise the default $PAGE->context
defaults to the system context
Before the form is displayed, the draft file area needs to be prepared with existing files, if
any. This is achieved with the following call:
$data = file_prepare_standard_editor(...);

And with the following parameters:

62
Chapter 5: Common APIs

 data – database object that includes the field that holds the editor contents field. It
must have not only the element’s field but also the field’s content format field – e.g.
$data->myhtml for the HTML content and $data->myhtmlformat field,
usually with a value FORMAT_HTML. Additionally, you can define a $data-
>myhtmltrust, which must exist in the database table. If this is a new record,
you have to create a new stdClass object with the property $id not set. Note,
the other form fields would also normally be defined in the data object as we make
a call to the form’s set_data() method to populate the form fields
 field – the name of the database field that holds the HTML text with embedded
media
 options – the element’s options as per our addElement() options
 context – context as per our addElement() options context
 component – the Frankenstyle plugin’s name
 filearea – the file area name where the files are to be stored as per the File API
 itemid – the item id (required if the item exists as per the File API)
The function returns an amended $data object, which then has to be passed to the form’s
set_data() method before displaying the form.
$mform->set_data($data);

When the form is submitted, the File API provides a function to update the files and move
them to the relevant file area:
$data = file_postupdate_standard_editor( ... )

The function takes the same parameters as the


file_prepare_standard_editor(), except for the $data parameter, which is
the submitted data. The function returns the $data object that is used to update or create
the table record.
One additional thing to remember with the editor’s input is that the URLs to the embedded
files are re-written with tokens (@@PLUGINFILE@@). So, if you want to display the
HTML at another point, you will need to use the
file_rewrite_pluginfile_urls() function in the form:
$myhtml = file_rewrite_pluginfile_urls($fieldhtml,
'pluginfile.php', $contextid, $pluginname,

63
Introduction to Moodle 3.9+ Plugin Development

$filearea, $itemid);

Unfortunately, the filemanager element is a bit more complicated.


Before displaying the form, the draft file area must be prepared. This is done by obtaining a
draft area-id and then using that to prepare the draft area:
// Get a draftitemid if the form has been submitted
// or 0 if not.
$draftitemid =
file_get_submitted_draft_itemid('elementname');

// Prepare an area – $draftitemid will be updated if the


// value was 0 as in a new record.
file_prepare_draft_area($draftitemid, $contextid,
$pluginname, $filearea,$entryid, $options);

The variable $draftitemid is passed by reference to the


file_prepare_draft_area() and so will change if this is a new submission. The
value is then used to populate the value of the filemanager element before the form is
displayed. If there are any existing files, they are displayed in the form element. The other
parameters have already been discussed.
When the form is submitted, the draft files are updated and then the code needs to call the
function to copy the draft files into the relevant file area:
if ($data = $mform->get_data()) {
.
.
file_save_draft_area_files($data->elementname,
$contextid, $pluginname, $filearea, $entryid,
$options);
}

That is it for the custom file elements. We will use the filemanager element later in
this book when we develop the Course Format plugin.
For more information about using the custom file elements, please see the Moodle
documentation’s ‘Using the File API in Moodle forms’ page
(https://docs.moodle.org/dev/Using_the_File_API_in_Moodle_forms).

64
Chapter 5: Common APIs

Data Manipulation API


This API defines the functionality to access database tables. All the functionality is
accessed via the global $DB object, which is an instance of the moodle_database class
and is initialised – including opening the connection – from the settings in the /config.php
file. Apart from the usual querying, updating, inserting, and deleting records in database
tables, the API provides many compatibility public methods, as Moodle supports several
databases.
Moodle tables are normally named with a prefix, the default being 'mdl_', which allows a
single database to hold tables from different applications, such as WordPress (which also
uses table name prefixes). When stipulating a table name in code, the prefix is omitted, as
the prefix is a configurable option in the /config.php file.
It is possible to control how database query results are handled by using the strictness
parameter which is supported by several methods that expect a single record. Passing the
constant IGNORE_MISSING as the strictness parameter will return a boolean false if a
record is not found or generate a debugging message if multiple records are found (this is
the default behaviour). Passing MUST_EXIST will, instead, throw an exception. There is
another constant, IGNORE_MULTIPLE, that will only return the first of multiple records,
but this is not recommended and may be deprecated in the future.
Just about all the data manipulation methods expect a placeholder ($params) parameter,
which is an array of values to fill placeholders in SQL statements. Using placeholders
avoids issues with SQL-injection and invalid SQL quoting and helps maintain cross-DB
compatible code. There is support for two types of placeholders. The more common
question marks SQL_PARAMS_QM and named placeholders SQL_PARAMS_NAMED must
be unique, even if the value passed is the same. The SQL_PARAMS_QM replaces the ‘?'
placeholders in the SQL and is single-dimensional. It must contain the same number of
items as placeholders in the SQL and replacement is sequential. The
SQL_PARAMS_NAMED is a multidimensional array, with the keys matching the
placeholders in the SQL. The SQL placeholders are the key names with a ‘:’ (colon)
prefix.
There are several ways to specify the conditions for SQL queries. The simplest form is a
multidimensional array $conditions, with the field name as the key and the value as
the field’s value. The array items are joined with the AND statement in the WHERE clause,
so all conditions must be met to generate a result. Whilst this may be the solution to a
greater proportion of queries, it is limited, so there are methods that accept a string
containing the WHERE conditions ($select) and others that will accept a full SQL
command ($sql).

65
Introduction to Moodle 3.9+ Plugin Development

Especially where a full SQL command is being used, it is advisable to use the SQL
compatibility methods to ensure the SQL is compatible with the supported databases. The
table name/s in the SQL must be enclosed in braces '{}' to support the prefix functionality.
If things get complicated, the low level execute() method will allow you to achieve
what you need to do.
The general record retrieval methods are not optimal, as they load all the returned records
into memory. Where there is a large number of records returned, it is best to use the
recordset methods that return an iterator which must be closed when no longer required.
For databases that support it, you can use transactions. Moodle handles the process if the
database does not support transactions, so you can use this functionality without worrying.
To begin a transaction, call $DB->start_delegated_transaction(), which
returns a transaction object on which the allow_commit() method will commit to the
transaction. Rollbacks are handled via the transaction object's rollback() method,
which expects the exception as the parameter.
Delegated database transactions can be nested; the outermost transaction will only be
committed if all the nested delegated transactions commit successfully. Any rollback in the
nested transactions will roll back all the transactions.
If you are having problems with your database queries whilst developing, you can enable
additional debugging information by calling the $DB->set_debug() method, passing a
boolean true parameter.
In the following table, the parameters quoted translate to the following:
 $table: the database table without the prefix
 $conditions: the multidimensional array mentioned earlier
 $select: the WHERE clause string
 $sql: an SQL command with brace-enclosed table names
 $params: the placeholder values array
 $field/$fields: $field name(s)
 $sort: a comma separated field list or ‘*’
 $limitfrom: limit start number
 $limitnum: limit number of records

66
Chapter 5: Common APIs

 $strictness: strictness constant as discussed earlier


 $countitem: the count string to be used in the SQL call – default is COUNT('x')
 $newvalue: the new value for the field
 $dataobject/$dataobjects: standard class objects representing a table
record – the keys mirror the field names
 $bulk: set to true if further operations can be expected – defaults to false
 $returnid: defaults to true – should the record’s id be returned?

Getting a single record


get_record()
PARAMS: $table, $conditions, $fields, $strictness
get_record_select()
PARAMS: $table, $select, $params, $fields,
$strictness
get_record_sql()
PARAMS: $sql, $params, $strictness
Getting multiple records
get_records()
PARAMS: $table, $conditions, $sort, $fields,
$limitfrom, $limitnum
get_records_select()
PARAMS: $table, $select, $params, $sort, $fields,
$limitfrom, $limitnum
get_records_sql()
PARAMS: $sql, $params, $limitfrom, $limitnum
get_records_list()
PARAMS: $table, $field, $values, $sort, $fields,
$limitfrom, $limitnum
Getting data as key/value pairs in an associative array
get_records_menu()
PARAMS: $table, $conditions, $sort, $fields,
$limitfrom, $limitnum

67
Introduction to Moodle 3.9+ Plugin Development

get_records_select_menu()
PARAMS: $table, $select, $params, $sort, $fields,
$limitfrom, $limitnum
get_records_sql_menu()
PARAMS: $sql, $params, $limitfrom, $limitnum
Counting records that match the given criteria
count_records()
PARAMS: $table, $conditions
count_records_select()
PARAMS: $table, $select, $params, $countitem
count_records_sql()
PARAMS: $sql, $params
Checking if a given record exists
record_exists()
PARAMS: $table, $conditions
record_exists_select()
PARAMS: $table, $select, $params
record_exists_sql()
PARAMS: $sql, $params
Getting a particular field value from one record
get_field()
PARAMS: $table, $field, $conditions, $strictness
get_field_select()
PARAMS: $table, $return, $select, $params,
$strictness
get_field_sql()
PARAMS: $sql, $params, $strictness
Getting field values from multiple records
get_fieldset_select()
PARAMS: $table, $return, $select, $params
get_fieldset_sql()
PARAMS: $sql, $params

68
Chapter 5: Common APIs

Setting a field value


set_field()
PARAMS: $table, $field, $newvalue, $conditions
set_field_select()
PARAMS: $table, $newfield, $newvalue, $select,
$params
Deleting records
delete_records()
PARAMS: $table, $conditions
delete_records_select()
PARAMS: $table, $select, $params
Inserting records
insert_record()
PARAMS: $table, $dataobject, $returnid, $bulk
insert_records()
PARAMS: $table, $dataobjects
Updating records
update_record()
PARAMS: $table, $dataobject, $bulk
Using record sets
get_recordset()
PARAMS: $table, $conditions, $sort, $fields,
$limitfrom, $limitnum
get_recordset_select()
PARAMS: $table, $select, $params, $sort, $fields,
$limitfrom, $limitnum
get_recordset_sql()
PARAMS: $sql, $params, $limitfrom, $limitnum
get_recordset_list()
PARAMS: $table, $field, $values, $sort, $fields,
$limitfrom, $limitnum

69
Introduction to Moodle 3.9+ Plugin Development

Cross-DB Compatibility
The following $DB methods ensure that SQL statements are compatible between the
supported databases. Please view the Moodle documentation’s page for examples of the
use of each of the functions. They are mentioned here to make you aware that they exist.

Function Notes
get_in_or_equal Constructs 'IN()' or '=' SQL fragment and returns an SQL
snippet and a parameter array to specify if a value is IN the
given list of items.
sql_bitand Returns snippet to be used to perform bitwise operations.
sql_bitnot
sql_bitor
sql_bitxor
sql_cast_char2int Returns the SQL to be used to CAST one CHAR column to
sql_cast_char2real INTEGER or a REAL number. Ensure the CHAR column
you're trying to cast contains real numbers or the database
will throw an error!
sql_ceil Returns the cross-DB correct CEIL (ceiling) SQL
expression applied to the field name. Note CEIL($fldname)
is the default.
sql_compare_text Returns the snippet to be used to compare one TEXT (clob)
column with a VARCHAR column, because some databases
don't support this type of comparison.
sql_concat Returns a snippet to do CONCAT between the field names
sql_concat_join passed and with sql_concat_join(), using passed in
character(s) as the separator.
sql_equal Returns an equal (=) or not equal (<>) snippet. Caution
advised.
sql_fullname Returns the proper snippet to concatenate user’s first name
and last name as a full name.
sql_intersect Returns the snippet to find the intersection of two or more
queries.
sql_isempty Returns the snippet to query whether one field is empty or

70
Chapter 5: Common APIs

sql_isnotempty not.
sql_length Returns the snippet to be used to calculate the length of
characters of the field.
sql_like Returns 'LIKE' snippet of a query and/or escape the LIKE
sql_like_escape special characters such as '_' or '%'.

sql_modulo Returns the snippet to be used to perform module '%'


operation – remainder after division.
sql_null_from_clause Returns an empty FROM clause.
sql_order_by_text Returns the snippet to be used to order by one TEXT (clob)
column. Caution recommended.
sql_position Returns the snippet for searching one string with the
location of another.
sql_regex Returns the driver-specific snippet syntax for matching
regex.
sql_regex_supported Checks if this database driver supports regex syntax when
searching.
sql_substr Returns the proper snippet used to extract substrings.

Other Database-Related Functionality


Apart from the methods of the global moodle_database object, there are a few useful
database-related functions defined in the /lib/datalib.php. These include:
 get_course() and get_courses(), which return course information. The
functions are provided to save database resources in cases where the data may have
already been retrieved. The get_site() function returns the $course object
of the top-level site;
 get_all_instances_in_course() to get all the active instances of a
particular activity module in a given course,
get_all_instances_in_courses() for all the active instances of a
particular module in given courses, and get_course_mods() for all the activity
modules in a course. There are also several other course module functions defined;
 get_admin() to get the ‘main’ administration user, usually the default user with
the id 2, and get_admins() to get all the site administrator records;

71
Introduction to Moodle 3.9+ Plugin Development

 A few user related functions, such as get_users(),


get_users_listing(), and get_users_confirmed();
 A little debugging gem, print_object(), which dumps a given object's
information for debugging purposes.
It is worth glancing over the /lib/datalib.php file to understand what other functionality is
available.
For more information about the Data Manipulation API, please see the Moodle
documentation’s ‘Data Manipulation API’ page
(https://docs.moodle.org/dev/Data_manipulation_API).

Data Definition API


The Data Definition API is what you use to create, change, and delete tables and fields in
the database, particularly during upgrades. The initial definition for tables for plugins is
done from the plugin’s db/install.xml file. This is the file that is used to create the tables
when the plugin is first installed and usually always contains the most up-to-date
definitions. Upgrades are handled by the Upgrade API, which we will discuss later. They
tend to use this API to manage table schema changes.
Moodle provides a tool, the XMLDB, to assist with both the initial and upgrade files. It is
highly recommended that developers use the XMLDB to generate code for table schemas.
This is because the tool is designed to create tables that will work in all the databases that
Moodle supports. Be warned though the upgrade code generated by this tool will need
modification. We will discuss this tool in the next section.
Plugins must use this API to manage table schemas, as the high-level abstraction will
ensure that the code will work with all of Moodle's supported databases.

The Database Manager Class


All the functionality for this API is via the public methods of the database manager
class defined in the /lib/ddl/database_manager.php file. You get an instance of this class
by calling the global $DB->get_manager() method.
All the objects used for data manipulation, tables, fields, and indexes are extensions of the
xmldb_object class defined in the /lib/xmldb/xmldb_object.php file – namely the
xmldb_table, xmldb_field, and xmldb_index. In most cases, it is the objects you
pass to the methods. The following is an example:
$dbman = $DB->get_manager();

72
Chapter 5: Common APIs

// Define table.
$table = new xmldb_table('mynewtool');

// Adding fields to table.


$table->add_field('id', XMLDB_TYPE_INTEGER, '10', null,
XMLDB_NOTNULL, XMLDB_SEQUENCE, null);
$table->add_field('userid', XMLDB_TYPE_INTEGER, '10',
null, XMLDB_NOTNULL, null, null);
$table->add_field('status', XMLDB_TYPE_INTEGER, '2',
null, XMLDB_NOTNULL, null, '0');

// Adding keys to the table.


$table->add_key('primary', XMLDB_KEY_PRIMARY,
array('id'));

// Conditionally launch create table


if (!$dbman->table_exists($table)) {
$dbman->create_table($table);
}

Some useful functions:


 Tables
◦ table_exists()
◦ create_table()
◦ drop_table()
 Fields
◦ field_exists()
◦ add_field()
◦ drop_field()
◦ change_field_type()
◦ change_field_default()
◦ rename_field()
 Indexes

73
Introduction to Moodle 3.9+ Plugin Development

◦ index_exists()
◦ find_index_name()
◦ add_index()
◦ drop_index()
For more information about this API, please visit the Moodle documentation's ‘Data
Definition API’ page (https://docs.moodle.org/dev/Data_definition_API).

The XMLDB Developer Tool


The XMLDB is the Moodle tool for developers to create and manage their plugin’s
database tables. The use of this tool is highly recommended, as it generates the right code
to manage tables in all the database systems that Moodle supports and makes your plugin
more portable. The tool is accessed by navigating to ‘Site administration->Development-
>XMLDB editor’ (/admin/tool/xmldb/).
If you are a phpmyadmin (https://www.phpmyadmin.net/) user, the tool feels very light in
comparison. For MySQL and mariaDB installations, the tool has an option [New table from
MySQL], which will create the XML from an existing table in the Moodle database and so,
if you are inclined, you can use phpmyadmin to manage your tables. Be warned though,
you might not get what you expect if you go down this road.
You should use the tool during the initial development of your plugin. All you have to do is
provide a web server writeable db folder in the skeleton of your installed plugin code. You
have to install your plugin to be able to use the tool.

74
Chapter 5: Common APIs

When you access the tool, a list of all the plugins is presented with the following options
alongside each:
 Create – creates a new db/install.xml file, with the initial table in true Frankenstyle
style called plugintype_pluginname with a primary field ‘id’ in it. This is only
available if a db/install.xml file does not exist. The plugin’s folder db must exist.
 Load – if the db/install.xml file does exist, this will load the file into a working
area for changes to be made.
 Edit – available once the db/install.xml file has been loaded and allows the
management of the plugin’s database tables.
 Save – an opportunity to save any changes, if you have not already done so, in the
Edit functionality.
 Doc – generates a document of the plugin tables, which you can use for
documentation.
 XML – displays the install.xml in the browser.
 Revert – undoes any changes in this session.
 Unload – unloads the db/install.xml file from the working area. We are done with
changes.
 Delete – as it says on the box, deletes the db/install.xml file. It does not remove
existing tables.
To avoid writing the upgrade code during initial development, I usually find it easier to
uninstall the plugin and reinstall it to recreate the tables as required. Only after the initial

75
Introduction to Moodle 3.9+ Plugin Development

release do I use the upgrade functionality to change the tables.


The tool is fairly easy to use and very basic. The Edit option allows you to manage tables,
keys, and indexes. The tool differentiates between keys, which have to be one of
primary, unique, foreign or foreign-unique, and indexes, which are not
one of those. This is where you can ‘import’ an existing MySQL table via the [New table
from MySQL] option.
It has the functionality to view the SQL and PHP code required for upgrades. If you are
deleting, you want to grab the code before making the changes, but, otherwise, get the PHP
code after making the change.
More information about the XMLDB tool can be found on the Moodle ‘XMLDB
Documentation’ page (https://docs.moodle.org/dev/XMLDB_Documentation).

Admin Settings API


Many of the plugins you will develop will require some settings to change the behaviour of
the plugin, e.g. number of result on pages, URLs to web services, and much more.
A plugin’s configuration settings are defined in its settings.php file and, for most plugins,
the global $settings variable is already defined and the script can add additional
elements to the settings forms.
if ($ADMIN->fulltree) {
require_once($CFG->dirroot.'/mod/forum/lib.php');

$settings->add(new admin_setting_configselect(
'forum_displaymode',
get_string('displaymode', 'forum'),
get_string('configdisplaymode', 'forum'),
FORUM_MODE_NESTED,
forum_get_layout_modes()));
}

The admin settings are a kind of extension to the Form API, which follows a similar format
and accepts similar inputs for the different elements. For example, for a checkbox, you
would define an admin_setting_configcheckbox object which relates to the
checkbox element. The available classes are all defined in /lib/adminlib.php:
 admin_setting_configcheckbox
 admin_setting_configcheckbox_with_advanced

76
Chapter 5: Common APIs

 admin_setting_configcheckbox_with_lock Form API


 admin_setting_configcolourpicker
 admin_setting_configdirectory
 admin_setting_configduration
 admin_setting_configduration_with_advanced
 admin_setting_configempty
 admin_setting_configexecutable
 admin_setting_configfile
 admin_setting_confightmleditor
 admin_setting_configiplist
 admin_setting_configmixedhostiplist
 admin_setting_configmulticheckbox
 admin_setting_configmulticheckbox2
 admin_setting_configmultiselect
 admin_setting_configmultiselect_modules
 admin_setting_configpasswordunmask
 admin_setting_configpasswordunmask_with_advanced
 admin_setting_configportlist
 admin_setting_configselect
 admin_setting_configselect_with_advanced
 admin_setting_configselect_with_lock
 admin_setting_configstoredfile
 admin_setting_configtext
 admin_setting_configtextarea
 admin_setting_configtext_with_advanced
 admin_setting_configtext_with_maxlength

77
Introduction to Moodle 3.9+ Plugin Development

 admin_setting_configthemepreset
 admin_setting_configtime
For some plugins, such as local plugins, the global $settings variable is not set and, in
this case, the settings must be added to the global $ADMIN settings.
if ($hassiteconfig) {

$settings = new admin_settingpage('local_plugin',


new lang_string('pluginname',
'local_plugin'));

$ADMIN->add('localplugins', $settings);

// Enable plugin
$settings->add(
new admin_setting_configcheckbox(
'local_plugin/enabled',
new lang_string('enabled', 'local_plugin'),
new lang_string('enabled_help',
'local_plugin'),
0, 1, 0));
}

Optionally, if you need either more control over the input form or to store the input
separately, an admin external page can be used to create a link to a page that uses the Form
API to manage the settings. You can still save the input as settings if you want to, by using
the Moodlelib API’s set_config() function. There is a disadvantage to this, however,
as highlighted on the Moodle documentation’s ‘Admin Settings’ page
(https://docs.moodle.org/dev/Admin_settings).
if ($hassiteconfig) {
$ADMIN->add('courses', new admin_externalpage(
'local_plugin',
get_string('pluginname', 'local_plugin'),
new moodle_url(
'/local/local_plugin/configsettings.php'))
);
}

The piece of code above is an extension to the navigation and another function of the

78
Chapter 5: Common APIs

settings file. It is discussed in more detail in the ‘Navigation API’ section. We will come
across settings later in our plugin development chapter.
For more information, please see the Moodle documentation’s ‘Admin Settings’ page
(https://docs.moodle.org/dev/Admin_settings).

Upgrade API
The Upgrade API provides functionality to manage plugin upgrade changes. Its main
function is to support database table schema changes when a plugin is upgraded. Generally,
the plugin upgrade code will usually, but not necessarily, make extensive use of the Data
Definition API to update the tables.
In a nutshell, Moodle keeps track of plugins and their versions. A check of the plugins is
done whenever a site administrator logs in or visits the notification page.
When the plugin is installed, the tables are created from the definitions in the
db/install.xml. This is why this file must contain the latest definitions of the plugin’s
tables. For installation steps not covered by the db/install.xml file, the developer can
provide code in the db/install.php file.
If the plugin is installed, the version stipulated in the version.php file is compared against
the last version installed. If the new version is greater than the installed one, the code in
db/upgrade.php is run.
The db/upgrade.php should contain code that is specific to each upgrade change in a single
function called xmldb_plugintype_pluginname_upgrade(), where
plugintype is the type and the pluginname is the name as per Frankenstyle
conventions. When the upgrade API calls the function, it passes the old version number
which the upgrade code should use to determine what changes need to be made to the
schema. For example:
function xmldb_plugintype_pluginname_upgrade(
$oldversion) {
.
.
// Add a new column to the mdl_myqtype_options
if ($oldversion < 10) {
// Code to add the column,
// generated by the XMLDB editor.
}
// Additional changes for this new version -
// NOTE do not use else if as code for version 10

79
Introduction to Moodle 3.9+ Plugin Development

// may have to be run if user is upgrading from


// original
if ($oldversion < 11) {
// Code for additional changes,
// generated the XMLDB editor.
}
// Save the version – upgrade steps completed and
// reset timeouts. Avoids double executions – where
// plugintype is the plugin type
upgrade_plugintype_savepoint(11);
return true;
}

There is also support for the uninstallation of a plugin in the db/uninstall.php file.
For a fuller explanation of this API, please visit the Moodle documentation's 'Upgrade API'
page (https://docs.moodle.org/dev/Upgrade_API).

Access API
We have touched on this subject earlier in the book in the ‘Roles, Permissions, and
Capabilities’ section in the ‘Moodle Development Overview’ chapter (Chapter 2). The API
has functions to determine what the current user is allowed to do and allows plugins to
define capabilities related to its functionality.
Before diving into API, let’s look at how plugins can define capabilities. Plugin capabilities
are defined in the plugin’s db/access.php file using a $capabilities array with each
capability name – an array key – in the
‘plugintype/pluginname:capabilityname’ format. For example:
$capabilities = array(
/* Add, edit or remove catalogue enrol instance. */
'enrol/catalogue:config' => array(
'riskbitmask' => RISK_SPAM,
'captype' => 'write',
'contextlevel' => CONTEXT_COURSE,
'archetypes' => array(
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW,
)
)

80
Chapter 5: Common APIs

);

Each capability is defined by an array with the following keys:


 riskbitmask – associated risks defined as constants. These do not have to be
defined for no risk:
◦ RISK_SPAM – allows user to add visible content to the site and/or send
messages to other users
◦ RISK_PERSONAL – provides access to private personal information
◦ RISK_XSS – allows user to submit content including files that are not cleaned
◦ RISK_CONFIG – allows user to change global configuration, bypassing sanity
checks
◦ RISK_MANAGETRUST – allows the management of trust bitmasks of other
users
◦ RISK_DATALOSS – addresses the risk of the user being able to destroy large
amounts of information that cannot easily be recovered
 captype – read or write capability type
 contextlevel – specified as context level constant. We have covered contexts
earlier in this book. This declares the typical context level at which this capability is
checked:
◦ CONTEXT_SYSTEM
◦ CONTEXT_USER
◦ CONTEXT_COURSECAT
◦ CONTEXT_COURSE
◦ CONTEXT_MODULE
◦ CONTEXT_BLOCK
 archetypes – an array that specifies defaults for roles based on archetypes. The
use of CAP_ALLOW is recommended for these. Archetypes are defined in {role}
table as:
◦ manager

81
Introduction to Moodle 3.9+ Plugin Development

◦ coursecreator
◦ editingteacher
◦ teacher
◦ student
◦ guest
◦ user
◦ frontpage
 clonepermissionsfrom – (optional) copies the permissions for each role
from the current settings of another capability. For example:
◦ 'clonepermissionsfrom' => 'moodle/quiz:attempt'
If your plugin needs to identify users with a specific role, such as student, it is best to
specifically define a capability that is allowed for students and check for that capability in
your code as mentioned earlier.
Since the concept of permissions revolves around contexts, the API provides functionality
to handle them by defining classes and methods such as:
 context_system::instance(), context_user::instance(),
context_coursecat::instance(),
context_course::instance(), context_module::instance(),
and context_block::instance() to get context classes
 $context->get_parent_contexts()
 $context->get_child_contexts()
The API provides functions to check the user’ s capabilities:
 has_capability() – checks whether a user has a particular capability in a
given context
 has_any_capability() – checks if the user has any one of several
capabilities from a list
 has_all_capabilities() – checks if the user has all the capabilities in a
list
 get_users_by_capability() – returns users with the specified capability in

82
Chapter 5: Common APIs

the specified context (use sparingly)


It also provides some shortcut functions to determine permissions:
 is_viewing()
 is_guest()
 is_siteadmin() – very useful
 isguestuser()
 isloggedin()
 is_enrolled() and get_enrolled_users() overlap with Enrolment API.
Additionally, it provides some quick check functions:
 require_login() overlap with Moodlelib API
 require_capability()
We will come across this API later in the book. For more information, please see the
Moodle documentation ‘Access API’ page (https://docs.moodle.org/dev/Access_API).

Enrolment API
Enrolled users are users who fully participate in a course. It is important to remember that
roles and enrolments are separate, although an enrolled user will have a role within the
context of the course. It is possible to have access but not be enrolled.
The Enrolment API defines both the enrol_plugin abstract class that all enrolment
plugins extend and several general enrolment functions. The plugin development will be
covered later in this book.
The API is defined in the /lib/enrollib.php file. Useful functions include:
 Plugin-Specific
◦ enrol_get_plugins() – returns all the enrolments plugins, optionally
only enabled ones
◦ enrol_get_plugin() – returns an instance of a plugin’s class, as opposed
to instances of the enrolments methods as below
◦ enrol_get_instances() – returns the instances in a particular course.
For some enrolment plugins it is possible to add multiple instances, especially

83
Introduction to Moodle 3.9+ Plugin Development

in cases where each of the instances has a different setting


◦ enrol_is_enabled() – checks if a particular enrolment plugin is enabled
 User-Specific
◦ enrol_get_my_courses() – lists courses current user is enrolled in
◦ enrol_get_users_courses() – lists courses a particular user is enrolled
in
◦ is_enrolled() – checks of the user is enrolled in the course
◦ get_enrolled_users(), count_enrolled_users(), and
enrol_get_course_users() – gets user information for a course
◦ get_enrolled_with_capabilities_join(),
get_enrolled_sql(), and get_enrolled_join() – returns SQL
snippets to be used to extract enrolment data for developers
For more information, please see the Moodle documentation’s ‘Enrolment API’ page
(https://docs.moodle.org/dev/Enrolment_API).

Navigation API
The Navigation API allows the manipulation of the site's navigation. The user interface
interpretation of the navigation is what the user sees. It is contextually based on the
location of the user in the system and is displayed using the navigation and settings blocks.
Access to the navigation objects is gained using the global $PAGE variable via the
properties $PAGE->navigation, $PAGE->settingsnav, and $PAGE->navbar,
which specifically refers to breadcrumb navigation. The blocks and breadcrumb display
read data from the objects and, based on other $PAGE properties such as $PAGE->url,
$PAGE->context, $PAGE->course or $PAGE->cm (course module), display a
relevant navigation menu.
All three navigation objects, the global, the settings, and the navbar, are extensions of the
navigation_node class which implements the renderable interface. Each item
added to the navigation is a node object for various types. Navigation is organised in a tree
structure of nodes of root->parent->children.
The navigation_node class defined in /lib/navigationlib.php provides a handful of
useful methods to work with the navigation trees. The most likely steps are to find a
particular node in the tree and then add a new node specifically for your plugin.

84
Chapter 5: Common APIs

Some useful methods are:


 find(), get(), find_active_node(), and
search_for_active_node() – to locate a specific node
 add(), add_node(), and create() – to create a node or add a child node
 has_siblings(), has_children(), get_siblings(), and
get_children_key_list() – to work with other nodes
All of the class extensions have specific additional methods relating to their speciality.
There are two node types, defined as constants NODETYPE_LEAF (an end node) and
NODETYPE_BRANCH (a container/collection of leaves). Each node will also have one of
the following:
 TYPE_ROOTNODE: system node
 TYPE_SYSTEM: system node
 TYPE_CATEGORY: category node
 TYPE_MY_CATEGORY: category displayed in MyHome navigation node
 TYPE_COURSE: course node
 TYPE_SECTION: course structure node
 TYPE_ACTIVITY: activity node
 TYPE_RESOURCE: resource node
 TYPE_CUSTOM: custom node
 TYPE_SETTING: setting node
 TYPE_SITE_ADMIN: site admin branch node
 TYPE_USER: setting node
 TYPE_CONTAINER: setting node
 TYPE_UNKNOWN: unknown node

85
Introduction to Moodle 3.9+ Plugin Development

There are three ways to work with navigation:


1 Using the plugin’s settings.php file – usually for navigation options that are always
available and particularly for entries in the ‘Administration / Site administration’
tree. This will be discussed later;
2 Dynamically during code execution. Note, though, that the navigation structure is
only generated after the $PAGE object is defined;
3 Via callbacks, where the core makes specific calls to plugins to allow the
opportunity to modify the navigation tree. There are two special cases of this, for
course activity modules and for local plugins – also discussed later in this section.
For the latter two, the code would be similar to this:
// Find the node I want to add to.
$coursenode = $PAGE->navigation->find(
$courseid, navigation_node::TYPE_COURSE);
// Optional – Create a container/folder/branch.
$mynode = $coursenode->add(get_string('setting'),
new moodle_url('/a/link/if/you/want/one.php'),
navigation_node::TYPE_CONTAINER);
// Add a link aka a new leaf to my branch

86
Chapter 5: Common APIs

$thingnode = $mynode->add(get_string('Name of thing'),


new moodle_url('/a/link/if/you/want/one.php'));
// Make it the active link in the navigation.
$thingnode->make_active();

Moodle provides a handful of callbacks for plugins to modify the navigation as they are
being loaded. All the callbacks are expected to be defined in the plugin’s lib.php file.
Activity modules and local plugins have two special additional callbacks: one to extend the
global navigation and the other to extend the settings navigation.
The modules’ callbacks are used whenever the user is viewing a page within the module
and should only extend the navigation for the module. The relevant module node is passed
into the functions:
 function {modulename}_extend_navigation(
${modulename}node, $course, $module, $cm)
 function {modulename}_extend_settings_navigation(
$settings, ${modulename}node)
The local plugins’ callbacks are:
 function local_{pluginname}_extend_navigation(
global_navigation $nav)
 function local_{pluginname}_extend_settings_navigation(
settings_navigation $nav, context $context)
These and the other plugins can also have callbacks for the following:
1 Extend the course navigation:
<component>_extend_navigation_course()
2 Extend the user settings navigation:
<component>_extend_navigation_user_settings()
3 Extend the category settings navigation:
<component>_extend_navigation_category_settings()
4 Extend the frontpage settings navigation:
<component>_extend_navigation_frontpage()
5 Extend the user profile navigation:
<component>_extend_navigation_user()
The plugin’s settings.php file’s primary purpose is to define the settings for the plugin, as

87
Introduction to Moodle 3.9+ Plugin Development

discussed in the ‘Admin Settings API’ section of this chapter. However, it can also be used
to extend the navigation – particularly the Site Administration navigation. The global
$ADMIN is an instance of the admin_root class defined in /lib/adminlib.php, which is
available for the settings file and represents the administration settings navigation. The
most common use in the settings is to modify the navigation by adding an external page
link as below:
if ($hassiteconfig) {
$ADMIN->add('courses', new admin_externalpage(
'local_plugin',
get_string('pluginname', 'local_plugin'),
new moodle_url(
'/local/local_plugin/configsettings.php'))
);
}

The class’ add() method sort of does both the find and the add for your node. In the
example, the link is added to the course’s folder/branch. We use this method later in the
plugin development chapter.
Unfortunately, I have no examples in this book of the other uses of the Navigation API. For
more information about working with Moodle’s navigation, please refer to the
documentation's ‘Navigation API’ page (https://docs.moodle.org/dev/Navigation_API).

Events API and Logging


The new Events API (https://docs.moodle.org/dev/Events_API), developed in Moodle 2.6
and required in Moodle 2.7, extended the previous 'legacy' events and logging APIs.
Moodle 2.7 and later versions still have support for the 'legacy' systems, but this will
eventually be deprecated. With events, other plugins can capture particular events and take
action, e.g. when a new user is added or a course deleted. Components that capture events
define observers for the particular event, and, when the event is triggered, the observer can
examine the event payload. However, it cannot change the event or the event's data. It is
one-way communication. For example, it could capture the \core\event\
user_created event and automatically create an account for the user on an external
examinations system.
Observers are described in the component’s db/events.php file in the $observers array
and have the following keys:
 eventname – fully qualified event class name, e.g. \core\event\

88
Chapter 5: Common APIs

user_created or * for all events


 callback – function, method, etc.; preferably an autoloaded class method, but
see include file
 includefile – (optional) file to be included before calling the observer to allow
the callback to be available
 priority – (optional) defaults to 0; observers with higher priority are notified
first
 internal – (optional) defaults to true; non-internal observers are not called
during database transactions, but, instead, after a successful commit of the
transaction
So, for our example:
$observers = array(
array(
'eventname' => '\core\event\user_created',
'callback' => 'register_new_exam_user',
'includefile' =>
'/local/myplugin/registeruser.php'
)
);

Observers must never cause a fatal error, but can raise exceptions which are caught and
sent to the logs.
You can view all the available, core, and plugin events for your Moodle instance by
navigating to ‘Site administration->Reports->Events list’ (/report/eventlist/index.php). All
these events have been defined by the dispatchers.
Dispatchers define the events in classes defined in the namespaced scripts defined in the
classes/event folder. Each event is expected to be defined in a separate class file and is a
unique identifier for each event. So, the \core\event\user_created event is
defined in the /lib/classes/event/user_created.php file.
All event definitions are classes extending the \core\event\base class, and are
triggered by creating a new instance and then executing the trigger() method. In
naming the class, the convention is like some_object_action, where action is a
verb such as created, added, deleted or attempted. A list of useful and recommended verbs
is defined on the Moodle ‘Events API’ page

89
Introduction to Moodle 3.9+ Plugin Development

(https://docs.moodle.org/dev/Events_API#Verb_list).
While events should attempt to contain as much information as possible, in some cases this
is not possible (as in a record deletion). There is therefore a way to snapshot the record and
for the observer to retrieve the data via the two defined methods, namely
add_record_snapshot() and get_record_snapshot(). It is recommended
that these methods are used in all cases of deletions. The snapshot is not saved; it is solely
for the use of observers during the event.
In other cases, the properties of the event should be adequate for most uses. This is notably
true for the optional objectid, which relates to the record’s id in the objecttable
property, which relates to the record’s table. When defined, these two properties will allow
the record to be identified, but the observer must be able to manage the possibility that the
record may no longer exist by the time its processing takes place.
While most of the other properties of the event are automatically computed or optional, one
other useful property is other, which will be a JSON encoded scalar or array data
variable.

Logging
Using the core logging functionality may be something you may find you have to do with
your plugin. You could, of course, develop your logging and reporting within your plugin,
but why do so if you don’t need to? Logging now uses the Events API and the additional
files required for the logging events are:
 classes/event/<eventname>.php, where <eventname> is your event
name. You will need a file for each event you want to log, e.g. record update,
update failed, etc.
There is an example of using the Events API to log actions later on in the ‘Developing
Plugins’ chapter (Chapter 8). Since there is a bit more to Moodle events, for more
information please visit the documentation's 'Events API’ page
(https://docs.moodle.org/dev/Events_API).

Task API
There are times when you need to do some processing outside the user interactions.
Moodle provides the facility for plugins to run scheduled tasks, sometimes referred to as
cron jobs, aka Linux parlance. Before Moodle 2.7, there were different ways to hook into
the Moodle cron, but, with the development of the Task API, there is a consistent way to
hook into the scheduled tasks. Additionally, site administrators have more control over

90
Chapter 5: Common APIs

when the tasks run and sometimes can even run the scheduled tasks outside the set
schedule. It is also possible to develop ad-hoc tasks within plugins.
Scheduled tasks are created by extending \core\task\scheduled_task and need to
reside in your plugin’s classes/task/ directory. To assist the autoloading functionality to
pick up the class, you will need to add it to your plugin’s namespace. Only two functions
are required for the class – get_name() and execute(). The get_name() function
returns the human-readable task name that is displayed in the tasks list and the
execute() function does what it says on the box. The return value for execute() is
irrelevant and is ignored. If you need to raise an error, you will have to raise an exception
which is dealt with as per the API. See the ‘Failures’ section on the Moodle ‘Task API’
page (https://docs.moodle.org/dev/Task_API#Failures).
The additional files required to hook into scheduled tasks are:
 classes/task/<classname>.php, where <classname> is the name of the class in
the file and is an extended class of \core\task\scheduled_task
 db/tasks.php, where the fully namespaced class name and the default schedule are
defined
We make use of the Task API later in this book in one of the sections in the ‘Developing
Plugins’ chapter (Chapter 8).

Privacy API
We have previously touched on the issue of the EU’s GDPR (General Data Protection
Regulation) and Privacy considerations in the relevant section in the ‘Moodle Development
Overview’ chapter (Chapter 2). The API allows plugin developers to describe the personal
data stored and provides the means for that data to be discovered, exported, and deleted.
Plugins should implement one metadata provider and, optionally, one or more request
providers. A metadata provider describes the data and the purpose that it stores, and a
request provider acts upon user requests such as the Right to be Forgotten and a Subject
Access Request.
Many Moodle plugins do not store any personal data, as they typically either display data
from other areas of Moodle or simply add functionality. If such a plugin is to define a
metadata provider, it should define a null provider. One has to be careful here because
plugins that cause data to be stored elsewhere in the Moodle sub-system would be regarded
as data stores, as the logic to determine what data belongs to any particular user would
normally reside in its functionality.

91
Introduction to Moodle 3.9+ Plugin Development

Since, in most cases, third-party plugins do not store user data per se, this API is not
covered in much depth here. It is most likely that course activity modules will need to
implement privacy functionality.
I have mentioned a null provider for plugins not storing data. This is implemented as a \
core_privacy\local\metadata\null_provider class, the opposite of which
is implementing a core_privacy\local\metadata\provider to indicate and
describe the storage of personal data. The core_privacy\manager class defined in
the /privacy/classes/manager.php file provides the glue between all the different
components and manages the core privacy functionality.
If you find that your plugin does, in fact, store data, then there is quite extensive inline
documentation in the class file.
For the rest of the plugins, the null provider is simple to implement. This book includes
an example of such an implementation in the ‘Developing Plugins’ chapter (Chapter 8).
For more information, please visit the documentation's ‘Privacy API’ page
(https://docs.moodle.org/dev/Privacy_API).

Web and External Functions APIs


It is easy to get confused when discussing web services in Moodle. Unfortunately, the
documentation, to me at least, is not very coherent.
The Web Services API refers to the API to support the development of webservice
protocols, meaning you can develop additional protocols if you so wish. We will not be
covering the development of new protocols in this book, but if you would like more
information about this, you can find it in Moodle documentation’s ‘Webservice Protocols’
(https://docs.moodle.org/dev/Webservice_protocols).
Out of the box, Moodle supports:
 REST – representational state transfer returning JSON or XML. The server is not
RESTful
 SOAP – Simple Object Access Protocol
 XML-RPC – Extensible Markup Language (XML) Remote Procedure Calls (RPC)
The External Service API allows any plugin to expose external services through a web
service. So, for example, your enrolment plugin may allow an external system to query
enrolments via the XML-RPC protocol. Many of the core Moodle plugins, including over
20 activity modules and a few enrolment ones, actually provide external services. A list of

92
Chapter 5: Common APIs

the core services can be found in the core webservice functions section on the API’s
functions documentation page
(https://docs.moodle.org/dev/Web_service_API_functions#Core_web_service_functions).
In developing external services for your plugins, you will be exposed not only to the Web
Services API (https://docs.moodle.org/dev/Web_services_API), but to the External
Functions API (https://docs.moodle.org/dev/External_functions_API), and quite possibly
exporter class functionality (https://docs.moodle.org/dev/Exporter), all of which overlap
considerably. However, these are not covered in this book. If your plugin is solely to
provide functionality for an external application, then you will need to create a local plugin
for the service.
To create an external service, you will have to define the functionality in your plugin’s
db/services.php file. The file will define at least one or, optionally, two arrays. The first
required array declares your webservice functions, where each declaration uses an array of
values to reference a usually external function. Previously, the referenced functions would
have resided in the externallib.php file of the plugin, but now each function must be
defined in a class file in the classes/external folder following the conventions discussed in
the ‘Class Autoloading and Namespaces’ section of the ‘Plugin Development Background’
chapter (Chapter 4).
The other array named services is optional as it can be defined in the User Interface.
The services array pre-builds the services, so the Moodle administrator doesn't need to.
While I can imagine that this chapter will be a little confusing without examples, later in
the book we will develop an external service as a local plugin in the ‘Developing Plugins’
chapter (Chapter 8). This will help explain the discussion here.
The best Moodle documentation pages covering this subject are:
• External functions API (https://docs.moodle.org/dev/External_functions_API)
• Adding a web service to a plugin
(https://docs.moodle.org/dev/Adding_a_web_service_to_a_plugin)

External Functions Uploads and Downloads


Moodle’s support for file uploads is reliant on the File API; thus, to deal with the issue for
external service plugins, there is an entry point at /webservice/upload.php, which requires
the webservice token for authentication where files can be posted. The files are saved in the
token user’s draft area and the results – including errors – are returned in JSON format.
Positive results will return an array of file object details, including the item id, which will
be the same for each file uploaded at the same time. If the solution requires separate file

93
Introduction to Moodle 3.9+ Plugin Development

uploads, then the client can extract the first file’s itemid and stipulate it as an optional
itemid parameter with the next file upload. The other optional parameter to this endpoint
is filepath, where the client can specify the path in the draft area that the file needs to
be placed.
The webservice client can then pass the itemid(s) to the relevant service to allow the
plugin to retrieve and process and/or save it to the plugin’s file area. However, if you do
not have the luxury of getting the other system to make two or more API calls, then you
can always revert to manipulating the standard $_FILES global variable and get the client
to upload the files directly to the webservice function.
Downloads are managed though the /webservice/pluginfile.php endpoint and require the
webservice token for authentication. It works a lot like the standard /pluginfile.php, as
discussed in the ‘File API’ section of the ‘Common APIs’ chapter (Chapter 5).
For both these methods, the relevant rights have to be granted via the service settings – by
editing the relevant service on the ‘Site administration->Plugins->Web services->External
services’ (/admin/settings.php?section=externalservices) page. One or both settings can be
programmatically set in the services definition.

94
Chapter 5: Common APIs

There are another two core Moodle web services available since Moodle 2.x to specifically
upload/download files: core_files_upload and core_files_get_files, which
are defined in /lib/db/services.php.
For more information about file support, please see Moodle documentation’s ‘Web
Services File Handling’ page (https://docs.moodle.org/dev/Web_services_files_handling).

Administration
Moodle rightly gives a lot of control to site administrators so that they can manage web and
external services, which adds to the administrative complexity. There are up to eight steps
to follow to activate a webservice for the first time, depending on the user requirements.
We will not go into the steps involved here, but I mention it because you will have to go
through the process to develop the external service you wish to provide. You can find the
steps laid out quite nicely on the Moodle site by navigating to ‘Site administration-
>Plugins->Web services->Overview’ (/admin/settings.php?

95
Introduction to Moodle 3.9+ Plugin Development

section=webservicesoverview). For documentation, please see the Moodle ‘Using Web


Services’ page (https://docs.moodle.org/39/en/Using_web_services).

Other External Integration with Moodle


Apart from web and external services, there are several other ways of integrating external
systems. The SCORM activity module enables AICC (Aviation Industry Computer-based
Training Committee) HACP (HTTP-based AICC/CMI Protocol) functionality. There are a
few authentication plugins that communicate with external systems, including
LDAP/Active Directory, external database, Shibboleth, and Oauth2.
We have not covered the exporter class functionality in this book, but it can be used in
conjunction with the External Services to export consistent data. Please see the Moodle
documentation’s ‘Exporter’ page (https://docs.moodle.org/dev/Exporter) for more
information.

96
Chapter 6
Plugin Development Essentials

Development Environment Setup


To get the most out of this book, it would be a good idea to install a development instance
of Moodle 3.9 or 3.10 somewhere; I tend to install locally on my laptop. I prefer to use git
to install Moodle but you have several options. Please see the page on ‘Setting Up
Development Environment’
(https://docs.moodle.org/dev/Setting_up_development_environment) for instructions.
Alternatively, if you are comfortable with Docker, you might want to check out 'Docker
Containers for Moodle Developers’ at https://github.com/moodlehq/moodle-docker.

SSL
Seriously consider running your development web servers with SSL. You can attempt self
certificates or create your own CA authority – see ‘Be your own Certificate Authority
(CA)’ (http://www.g-loaded.eu/2005/11/10/be-your-own-ca/). Since I find myself firing up
lots of development servers, I installed Sam Gleske’s ‘My Internal Certificate Authority’
scripts (https://github.com/samrocketman/my_internal_ca) and added my script to install
the certificates for Apache. As a result, I no longer get messages like this:
Chapter 6 Plugin Development Essentials

Site Development Configuration


Whilst it is possible to use the user interface to put Moodle into development and
debugging mode by navigating to ‘Site administration->Development->Debugging’
(https://moodle39.local/admin/settings.php?section=debugging), there are several useful
settings for development that can be defined in the /config.php.

After installation, there will be a file called /config-dist.php. This is a full Moodle
configuration file with a lot of commented out lines, but also a lot of explanations about
each configuration option. In it, there is a section entitled ‘SETTINGS FOR
DEVELOPMENT SERVERS’, which has configuration options for development servers. I
normally copy the whole section into the live /config.php file and uncomment several lines
for my use. I end up with these lines:
/config.php
@error_reporting(E_ALL | E_STRICT);

99
Introduction to Moodle 3.9+ Plugin Development

@ini_set('display_errors', '1');
$CFG->debug = (E_ALL | E_STRICT);
$CFG->debugdisplay = 1;
$CFG->noemailever = true;
$CFG->cachejs = false;

The email settings are important if you are going to be using live user data (not
recommended) for development and testing. The most annoying issue with these settings is
that if $CFG->debugdisplay is set, it will always produce debugging information and
break the flow of your displays. You can avoid this by setting your $CFG->debug to
DEBUG_MINIMAL or DEBUG_NONE before the emailing functionality is called and then
changing it back afterwards.
A few more useful settings are:
 The $CFG->divertallemailsto and $CFG-
>divertallemailsexcept settings are another way to manage emails whilst
developing;
 The $CFG->cachejs and $CFG->cachetemplates settings help when
dealing with caching, though I have rarely ever used them. The $CFG->cachejs
setting is important if you are compiling JavaScript for your plugin as, since
Moodle 3.8, all JavaScript is served minimised. Setting this to false means
Moodle will send the browser the corresponding source map files and the modules
will appear in the browser's source tree as separate modules.
Plugins can also define or check for custom configuration variables. For example, a useful
development setting is the $CFG->tool_generator_users_password, which
allows you to define a password for all the users generated by the ‘Make test course’ tool
discussed in the next section.
In reality, most UI configuration settings can be overridden in the configuration file. For
example, another useful setting for development is removing the requirements of the
password policy by setting $CFG->passwordpolicy to false, which means you can
have simple passwords for your created test users.
Your configuration file will then have the following lines:
/config.php
// Config Tool user password and remove password policy
$CFG->passwordpolicy = 0; // Remove password policy
$CFG->tool_generator_users_password = "Pa55w0rd";

100
Chapter 6 Plugin Development Essentials

Development Test Data


In many cases, developing a plugin requires test data. I prefer to try and somewhat replicate
the environment I am developing for – usually a subset of obfuscated live data but, in many
cases, this is not possible. You will likely have to create the test data. Using backup
strategies, you could have a way to re-use your test data across your projects.
One tool that helps can be found by navigating to ‘Site administration->Development-
>Make test course’ (/admin/tool/generator/maketestcourse.php), where you can set up
various size test courses with users. Please do so now in preparation for Chapter 8.
All the courses are created in the Miscellaneous (default) category, so you have to
manually manage the site structure. Additionally, depending on the course size you select,
you will get a lot of duplicate names and similar email addresses. You can predefine the
password for the generated users in the /config.php with the $CFG-
>tool_generator_users_password setting, as mentioned earlier.

Do not select ‘XS’, as this only creates one user. The users created have usernames in the
format [email protected], where NNNNNN is a zero-led number
for each student in that course. To make it easier to test by logging in, it is worth setting the
passwords of all users to a single memorable one by defining the $CFG-
>tool_generator_users_password variable in the /config.php file, as mentioned
earlier.
Additionally, you can manually add some other users to the mix. Set these users’
passwords to the same as those for the students to make life easier. If you have used a
simple password, you will need to navigate to the ’Site administration->Security->Site

101
Introduction to Moodle 3.9+ Plugin Development

security settings’ (/admin/settings.php?section=sitepolicies) page and disable the


‘Password Policy’ setting or set $CFG->passwordpolicy to 0/false in the
/config.php file, as mentioned earlier.
For instructions to add a new user, please see the ‘Add a New User’ Moodle documentation
page (https://docs.moodle.org/39/en/Add_a_new_user).
 Editing Teacher [email protected]
 Assistant Teacher [email protected]
 Course Creator [email protected]
 Manager Manager [email protected]
Next, you need to assign roles to these users. The Course Creator and Manager roles are
created in system context, i.e. site-wide. This can be done on the ‘Site administration-
>Users->Permissions->Assign system roles’ (/admin/roles/assign.php?contextid=1) page.
The roles for the Teacher (teacher role) and Assistant Teacher (non-editing teacher role)
are done in the course. Navigate to the course and select ‘Participants’ from the menu, then
click the ‘Enrol users’ button to enrol them. For detailed instructions to assign roles, please
visit the Moodle documentation’s ‘Assign Roles’ page
(https://docs.moodle.org/39/en/Assign_roles).
You can, of course, change data directly in the {user} database table. I often change the
username and email addresses to help when searching for users, for example, for enrolment
purposes and when you want to find a particular user profile. I select a set of users, say
those with an id less than 10 and greater than 2 (guest and main admin users) as
teachers/tutors, and change their usernames and email addresses to something like
‘teacherXX’ and [email protected], where XX is the record’s id. The rest of the
users become studentXX with the resultant email address. This makes it easier for me to
log in, enrol, and view users’ profiles. Needless to say, you do not have to do this and you
might find something that works better for you.

IDEs
The Moodle documentation’s page ‘Setting Up Development Environment’
(https://docs.moodle.org/dev/Setting_up_development_environment) has information about
setting up different IDEs for Moodle development. I use Eclipse PHP and so many of my
screenshots in the following chapters will display that IDE. I recommend you work with an
IDE you are comfortable with, as many will provide the same features. Alternatively, you
may choose a good text editor.

102
Chapter 6 Plugin Development Essentials

Community Development Tools


There are a few tools that are available to Moodle developers. Some of these are included
in the core, such as the ‘DBXML Editor’ and the ‘Third-Party Libraries’ page that are
covered elsewhere in this book. Others can be found in the Development area of the Site
Administration functionality – ‘Site administration->Development’ (/admin/category.php?
category=development). There are another two recommended developer tools: the ‘code
checker’ and the ‘Moodle PHPdoc checker’, both of which are available on the
Development page when installed. To ensure you do not include these tools in your
production code, you can exclude them from git commits in your local repository.

Code Checker
This is a recommended plugin for plugin development. Its purpose is to help you follow
Moodle's coding style. It can be installed in several ways, with the easiest being to
download it from https://moodle.org/plugins/local_codechecker and install it by navigating
to ‘Site administration->Plugins->Install plugins’ (/admin/tool/installaddon/index.php).
Running code checker is simple and, once you have used it a few times, you will notice
you will start to code differently in order to avoid having to fix code later.

Moodle PHPdoc Checker


Moodle PHPdoc Checker is another recommended plugin for plugin development. This one
checks styles of PHP docs in Moodle source files. It can be installed in several ways, with
the easiest being to download it from https://moodle.org/plugins/local_moodlecheck and
install it by navigating to ‘Site administration->Plugins->Install plugins’ page
(/admin/tool/installaddon/index.php).
Running PHPdoc Checker is simple and, once you have used it a couple of times, you will
get used to what is required whilst writing your code.

Code Skeleton Templates


There are a few plugin skeleton templates to get developers off to a flying start, although
they tend to come and go. There used to be one for activity plugins, but it has disappeared.
Another one is for blocks and can be found at https://github.com/danielneis/moodle-
block_newblock.

The Plugin Skeleton Generator


Initially implemented as a GSOC (Google Summer of Code) 2016 project, the ‘Plugin

103
Introduction to Moodle 3.9+ Plugin Development

Skeleton Generator’ is a tool to assist developers in generating a new Moodle plugin code
skeleton. The more powerful way to use it appears to be via the CLI (command-line
interface) using YAML recipe files, but there is a web interface.
However, the tool is not designed for users new to Moodle development, as you still
require the knowledge of Moodle plugin development to use it effectively. Hopefully, this
book will provide that knowledge.

You can find the plugin at Moodle plugin skeleton generator


(https://moodle.org/plugins/tool_pluginskel).

104
Chapter 6 Plugin Development Essentials

For more information, please see the Moodle documentation on the ‘Plugin Skeleton
Generator’ page (https://docs.moodle.org/39/en/Plugin_skeleton_generator).

Cross Reference and Version Differences Site


Mukudu’s Moodle Cross Reference and Version Differences Site (http://xref-diff.mukudu-
dev.net/) is useful for some tasks that your IDE may not provide. In many cases,
developing plugins may require working in all supported (and many times unsupported)
versions of Moodle. The site allows you to compare versions of API code and allows you
to conduct quick searches for Moodle classes, functions, variables, constants, and
namespaces. Additionally, if you suspect you need a core item, you can use the browser’s
search function to find it on the pages that list all the items, e.g. functions, on one page.

Moosh
Though not a development tool, Moosh (https://moosh-online.com/), which stands for
MOOdle SHell, is a command-line tool that allows you to perform most common Moodle
tasks. Aimed at Moodle administrators, it is useful for developers to generate and manage
testing data. I suggest that if you are not familiar with Moodle, then, when generating test
courses, activities, and other data, you use the user interface (UI) for the task in order to get
some familiarity. When you need to generate larger amounts of data, CSV files, some shell
scripting, and Moosh may be the least painful way to do so. This tool also includes some
other very useful functions that can save you time.

105
Chapter 7
Common Plugin Requirements

The File Masthead


All Moodle plugin code (PHP) files should have a full GPL copyright statement right at the
top, with no blank line after the opening PHP tag:
// This file is part of Moodle – http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.

This should be followed as per the coding style guidelines by a SEPARATE docblock
with the appropriate PHP doc tags. The @package, @copyright, and @license tags
are required, for example:
/**
* A silly quote block for a bit of light entertainment
*
Chapter 7 Common Plugin Requirements

* @package block_sillyquote
* @copyright 2021 The Silly Corp. (who paid for it)
* @license http://www.gnu.org/copyleft/gpl.html
* GNU GPL v3 or later
*/

See the ‘Files’ section on the Moodle documentation’s ‘Coding Style’ page
(https://docs.moodle.org/dev/Coding_style#Files) for a more detailed explanation.

Plugin Naming Convention AKA Frankenstyle


Plugin naming should follow the Frankenstyle convention that was covered in Chapter 2.

Required Files
Several files are recommended for plugins. Many of them are common and usually work in
the same way, no matter the type of plugin. For more information on these files, please
visit the Moodle ‘Plugin Files’ documentation page
(https://docs.moodle.org/dev/Plugin_files). Many plugins will install with a minimum of
files, but will be limited in scope. Some of the other required/recommended files will be
covered in the ‘Developing Plugins’ chapter (Chapter 8).
There are some exceptions (in terms of files that are required which depend on the plugin
being developed), but most plugins will require the following files:

version.php
All plugins require this file in the root of the plugin’s directory. At a minimum, the file
must define:
 $plugin->version – The format is YYYYMMDDXX, where XX is an incremental
counter for the given year (YYYY), month (MM), and date (DD) of the plugin
version's release. This variable triggers Moodle’s upgrade process when a new or
upgraded version is detected;
 $plugin->component – Used during the installation and upgrading, this is the
full Frankenstyle component name.
You will see that the vast majority of plugins will also define $plugin->requires,
which relates to the Moodle version required by the plugin as defined by the $version
variable (minus the decimal part) in /version.php file. Versions of Moodle lower than this
will not install the plugin. Released with Moodle 3.9 and related to it are the variables

107
Introduction to Moodle 3.9+ Plugin Development

$plugin->supported and $plugin->incompatible, which provide more


control in terms of where the plugin can be installed.
Another useful variable is the associative array $plugin->dependencies, which
specifies other modules that the plugin depends on. This one is not useful for core plugins,
but very useful when you have developed a suite of plugins that have dependencies on one
another.
EXAMPLE
$plugin->version = 2020061500;
$plugin->requires = 2020061500; // Requires M3.9
$plugin->component = 'mod_d4activity';
$plugin->dependencies = array(
'local_d4core' => ANY_VERSION);

For more information on these and other variables, please see the Moodle documentation's
‘version.php’ page (https://docs.moodle.org/dev/version.php).

Language File (lang/en/plugintype_pluginname.php)


This file defines the language strings used by the plugin. This English language strings file
is required. Other languages can be defined, but if the intention is to release the plugin via
the Plugins Directory (discussed in the ‘Publishing Your Plugin’ section later in Chapter 9),
translations are submitted to the Moodle language translators by automatic registration with
AMOS – Automated Manipulation Of Strings
(https://docs.moodle.org/dev/AMOS_manual) – service as mentioned previously. The
$string['pluginname'] variable is the only variable expected. The name of the file
follows the Frankenstyle convention and the usual exception for activity modules applies,
i.e. just pluginname.php – without the mod_ prefix.

lib.php
The lib.php is not necessary for authentication, block or local plugins. It provides the
interface into the plugin hooks via callbacks and is usually an extension of the relevant
plugin class. Each plugin type will have a minimum of functions that must be implemented
in the relevant file. Since Moodle loads all plugins' lib.php files, it is recommended that the
file is kept to a minimum, with other internal functionality provided in auto-loaded classes
or in a locallib.php file (no longer recommended). Requiring or including the locallib.php
from your lib.php kind of defeats the purpose of keeping it minimal, so some sort of
organisation is required. An examination of autoloading classes vs locallib.php can be
found in the ‘Developing Modules’ section of the ‘Developing Plugins’ chapter (Chapter

108
Chapter 7 Common Plugin Requirements

8).
Sometimes, the API requires the lib.php to be a class definition, for example, for course
formats. So, you might require additional means to provide the additional library functions
outside the class definition. An example of the issues around this is covered in a section in
the ‘Developing Plugins’ chapter (Chapter 8).

index.php
Though neither required nor specified in the documentation, it is a good idea to have an
index.php in the plugin’s home folder at least. A lot of plugins use this file to list instances
of the plugin or to provide administrative functionality outside the Moodle settings
functionality. At best, display a blank page to avoid users attempting to view the plugin’s
files – though your web server should be configured not to display the directory listing.

db/install.xml
Though not strictly required and usually not necessary for some plugin types, I suspect that,
for most plugins, database objects (tables, fields, indexes, and keys) will need defining and
so this file must be present.
To ensure that your plugin is portable, it is highly advised that you define the original
schema using the XMLDB tool described in the ‘XMLDB Developer Tool’subsection of
the ‘Data Definition API’ section in the ‘Common APIs’ chapter (Chapter 5). However,
once the original schema is defined for existing plugins, any changes to your table schemas
will have to be coded into the db/upgrade.php file as per the Upgrade API. The XMLDB
tool also assists with the code to successfully upgrade the schema. Until you have released
the plugin, you can safely ignore the db/upgrade.php file and deal directly with the
database or use the uninstall and install functionality to update tables’ schemas.

README/README.md
If you are using git as your repository for your plugins, you should have a readme file.

Installing and Uninstalling Plugins


As Moodle provides several ways to install plugins, please see the ‘Installing Plugins’
(https://docs.moodle.org/39/en/Installing_plugins) page for the various options. Whilst just
developing locally with a local installation of Moodle, I generally create the code skeleton
in the relevant web server folder and work from there. To get some changes registered by
Moodle core, you can either up the plugin's version number or uninstall it without deleting
the folder on disk. With Linux, you can play around with the plugin folder's permissions so

109
Introduction to Moodle 3.9+ Plugin Development

that the webserver user cannot delete the folder. As a side note, I also create a git
submodule in the folder.

Moodle Caching
When developing plugins, it is always a good idea to clear the Moodle caches when you
make changes – unless you increase the plugin’s version and upgrade the site, as this resets
some, if not all, of the caches. Caching is usually the issue if you do not see your changes.
This is particularly true of changes to the plugin’s strings. The caches can be cleared in two
ways. You can navigate to ‘Site administration->Development->Purge caches’
(/admin/purgecaches.php) or you can run the /admin/cli/purge_caches.php script on the
command line.

110
Chapter 8
Developing Plugins
This section of the book looks at some common plugin development.

Block Plugins
Moodle blocks are boxes of content that can be displayed on content pages – similar in
concept to widgets. Usually, the blocks are displayed in the right or left column of the page
(known as regions), but a small number also use the 'content' region on the Dashboard. In
later versions of Moodle, blocks can be dockable.
Blocks are the easiest plugins to develop, but have limited capabilities and you cannot
count on them being displayed in the theme, even if it is enabled. However, usually, if you
are asked to develop a block, there is a commitment to ensuring users get the benefit of it.
There are three types of blocks. The block type BLOCK_TYPE_TEXT (based on the
block_base class) is the default, block type BLOCK_TYPE_LIST (derived from the
block_list), and BLOCK_TYPE_TREE (derived from the block_tree class). The
difference between the blocks is about how the content is defined. The types are defined as
constants.

Developing a Block Plugin


We will now attempt to develop a simple block plugin. This block will display the latest
course activities of a certain type that a user has not yet attempted on their Dashboard page.
So, to get started with our new_activity block, which we are going to call, we need
three files to install the plugin in the /blocks/new_activity/ directory:
Chapter 8 Developing Plugins

 version.php: see the ‘version.php’ subsection in the ‘Required Files’ section of


Chapter 7
 lang/en/block_new_activity.php: please see the String API for more on this file; the
only requirement is that the $string['pluginname'] variable is defined
 block_new_activity.php: this file, which should be named
block_blockname.php, where blockname is your block’s name, contains
your block object, which is an extension of one of three classes (types) that resides
in the block/moodleblock.class.php. Only the init() function is required
 OPTIONAL README
A note here is that blocks do not need to define the lib.php file. With these three files only,
you can install the block into a Moodle instance. However, errors are generated whenever a
course is put in editing mode if you do not also have a db/access.php.
Formatted as previously discussed, our version.php file looks like this:
/blocks/new_activity/version.php
// HEADER STUFF HERE //

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

$plugin->version = 2020061500;
$plugin->requires = 2020061500; // Requires M3.9
$plugin->component = 'block_new_activity';

Every time we make significant changes to our code, we will update the $plugin-
>version.
Our starting lang/en/block_new_activity.php file looks like this:
/blocks/new_activity/lang/en/block_new_activity.php
// HEADER STUFF HERE //

$string['pluginname'] = 'New Activity';

Our main file block_new_activity.php is going to display a list and so we are extending the
block_list class rather than block_base or choosing block_tree. The init()
function is required to be able to install the plugin and so our file looks like this:

113
Introduction to Moodle 3.9+ Plugin Development

/blocks/new_activity/block_new_activity.php
// HEADER STUFF HERE //

class block_new_activity extends block_list {


public function init() {
$this->title = get_string('pluginname',
'block_new_activity');
}
}

In this case, init() just sets the title of the plugin to the string we have defined in the
lang/en/block_new_activity.php file.
With these files, we can now install the block. Please see the ‘Installing and Uninstalling
Plugins’ section in the ‘Common Plugin Requirements’ chapter (Chapter 7).
Now that the block is installed, when you attempt to add it to the test course page (see
Managing blocks – https://docs.moodle.org/39/en/Managing_blocks), an error is displayed
if you are in development mode – which you should be. This error is generated because we
have not defined the permissions for the block.

To do this we need to add the db/access.php file. This file defines the permissions and
capabilities for the plugin as discussed in the ‘Access API’ section of the ‘Common APIs’
chapter (Chapter 5). Moodle defined the capabilities addinstance and
myaddinstance for blocks, which makes it possible to control the use of individual
blocks. We need to define these two capabilities in our file like this:
/blocks/new_activity/db/access.php
// HEADER STUFF HERE //

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

114
Chapter 8 Developing Plugins

$capabilities = array(
'block/new_activity:myaddinstance' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_SYSTEM,
'archetypes' => array(
'user' => CAP_ALLOW,
),
'clonepermissionsfrom' =>
'moodle/my:manageblocks'
),

'block/new_activity:addinstance' => array(


'riskbitmask' => RISK_SPAM | RISK_XSS,
'captype' => 'write',
'contextlevel' => CONTEXT_BLOCK,
'archetypes' => array(
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
),
'clonepermissionsfrom' =>
'moodle/site:manageblocks'
),
);

Of note, the capability myaddinstance relates to the Dashboard. If your block is not
suitable for use on the dashboard, you still need to define the capability and then disable
use in the applicable_formats() function as we shall do later.
By adding capabilities, we need to define the language strings that will be displayed when
the capabilities are being assigned and so we update the lang/en/block_new_activity.php
by adding the following lines:
/blocks/new_activity/lang/en/block_new_activity.php
$string['new_activity'] = 'New Activity';
$string['new_activity:addinstance'] = 'Add a new New
Activity block';
$string['new_activity:myaddinstance'] = 'Add a new New
Activity block to the My Moodle page';

Now, we increase the plugin’s version and we can upgrade the plugin. If you are not
automatically redirected to the ‘Plugins Check’ page, then navigate to ‘Site Administration-

115
Introduction to Moodle 3.9+ Plugin Development

>Notifications’ (/admin/index.php). After upgrading, you could install the block in a


course if you want to, but don't do this just yet.
As mentioned a little earlier, there is a way to restrict where the block can be added. It is
possible that a block is only sensible on a course page and not on the Dashboard and vice
versa. The default case is that blocks can be used in courses and on the front-page, but not
in activities. There is a fairly detailed explanation of the rules in the documentation (see
https://docs.moodle.org/dev/Blocks/Appendix_A#applicable_formats.28.29 and
https://docs.moodle.org/dev/Blocks#Authorized_Personnel_Only), but for our purposes,
we only want to use the block on the Dashboard page, so we add the
applicable_formats() function to our block_new_activity.php file.

/blocks/new_activity/block_new_activity.php
public function applicable_formats() {
return array(
'my' => true,
);
}

With just the init() and applicable_formats() functions in


block_new_activity.php, the block plugin does not do much. The main function of block
plugins is the get_content() function that generates, but doesn’t display, the block’s
content in the $this->content object. The way the content is structured is determined
by the block type. $this->content is expected to have the following member
variables:

Member Applies to Description


text BLOCK_TYPE_TEXT A string of arbitrary length which can contain
HTML
footer BLOCK_TYPE_TEXT A string of arbitrary length displayed below the
BLOCK_TYPE_LIST main block content using a smaller font size and
which can also contain HTML
items BLOCK_TYPE_LIST A numerically indexed array of strings, normally
a fully qualified HTML <a> tag
BLOCK_TYPE_TREE An array of tree_item objects. It is not clear in
the documentation and in the code what class the
tree_item is, but it appears to be a
navigation_node

116
Chapter 8 Developing Plugins

icons BLOCK_TYPE_LIST A numerically indexed array of fully qualified


HTML <img> tag strings which represent the
images displayed before each $this-
>content->items

One thing about $this->content is that it can affect the page performance,
particularly if several blocks are active, and so we always check if the content has already
been generated before we generate the content.
/blocks/new_activity/block_new_activity.php
public function get_content() {
// Check if content has already been generated.
if ($this->content !== null) {
return $this->content;
}
// Define the content object.
$this->content = new stdClass;
$this->content->footer = '';
$this->content->items = array();
$this->content->icons = array();

//Get All the courses that this user is enrolled on.


if ($mycourses = enrol_get_my_courses()) {
foreach ($mycourses as $mycourse) {
$this->content->items[] =
$mycourse->fullname;
}
}

// Message if the user is editing the page.


if ($this->page->user_is_editing()) {
$this->content->footer .= '<br/>' .
html_writer::tag('div',
get_string('editingmessage',
'block_new_activity'));
}
}

For a full explanation of the block functions, see the ‘Blocks – Appendix A’ page

117
Introduction to Moodle 3.9+ Plugin Development

(https://docs.moodle.org/dev/Blocks/Appendix_A) or examine the


/block/moodleblock.class.php file.
We need to define a language string:
/blocks/new_activity/lang/en/block_new_activity.php
$string['editingmessage'] = 'You have editing switched
on.';

Each ‘copy’ of the block that is activated on a page is called an instance. Each instance can
be separately configured and we can add our own ‘specialised’ configuration settings.
Let’s say we want the user to set the title of the block to anything they want, we would then
need to create an edit_form.php. This file creates a class that extends
block_edit_form and uses the Form API, as discussed in the relevant section in the
‘Common APIs’ chapter (Chapter 5), to define form fields for the additional settings. The
form is merged with Moodle’s default block settings form.
/blocks/new_activity/edit_form.php
// HEADER STUFF HERE //

class block_new_activity_edit_form extends


block_edit_form {

protected function specific_definition($mform) {


// Section hdr title according to language file.
$mform->addElement('header', 'config_header',
get_string('blocksettings', 'block'));

// Change the title of the block.


$mform->addElement('text', 'config_title',
get_string('blocktitle',
'block_new_activity'));
$mform->setType('config_title', PARAM_TEXT);
}
}

All your field names need to be prefixed with ‘config_’; otherwise, they will not be saved.
The saved field values will be available to your code via the $this->config object,
whose members are the name you set without the prefix, i.e. the above will be available as
$this->config->title. The $this->config variable is not available in the

118
Chapter 8 Developing Plugins

init() function only.


Two other things have to happen to make our instance settings work as expected. These are
defining the ‘blocktitle’ language string in the lang/en/block_new_activity.php file
and adding another function in the block_new_activity.php file. In the language file, add
the following line:
/blocks/new_activity/lang/en/block_new_activity.php
$string['blocktitle'] = 'Your title for this block';

If you were to update the version of the plugin and then attempt to change its title, the
change would be saved and re-editing the block settings would reveal the value you input,
but it would not be displayed in the block. This is because we have set the title in the
init() function and we have not changed it anywhere else. Since $this->config is
not available in that function, we have to do this in a function called
specialization(). This function is called before anything else after the configuration
has been loaded. So, if we add this function, the issue is resolved.
/blocks/new_activity/block_new_activity.php
public function specialization() {
if (isset($this->config)) {
if (!empty($this->config->title)) {
$this->title = $this->config->title;
}
}
}

It is possible to define global settings – ones that affect all the block instances – and to
define and use database tables, but these and other aspects of plugin development will be
covered in the following chapters.
After all the changes we have made, we need to increase the plugin’s version in
version.php and upgrade. If you navigate to a test course and attempt to add this block, it
will not appear in the list, as, if you remember, we limited the location to the Dashboard in
the applicable_formats() function, where it will be available. To make the block
available on all users’ dashboards you need to add it to the ‘Default Dashboard page’, by
navigating to ‘Site Administration->Appearance->Default Dashboard page’
(/my/indexsys.php) and adding the block to the page. Click on the ‘Reset Dashboard for all
users' button to reset the dashboard for all user accounts.
Log in as one of the test students and then go ahead and try configuring the block to see the

119
Introduction to Moodle 3.9+ Plugin Development

settings form in action. Please see the 'Managing Blocks’ page


(https://docs.moodle.org/39/en/Managing_blocks) for more information.
We have a working block plugin; so, we will now move onto another type of plugin.

Course Format
Course formats are plugins that determine the page layout of courses in both view and
editing modes. The four core formats are usually adequate for most installations, but
sometimes a separate format is required – search the Plugins Directory
(https://moodle.org/plugins/?q=type:format) for examples of the many course formats that
have been developed by the Moodle community.
The four core course formats are:
 Weekly format
 Single activity format
 Social format
 Topics format
Course format plugins reside in the /course/format/ directory and the plugin needs all the
usual required files. An additional file required for course formats is a file named
/course/format/<FORMATNAME>/format.php, where <FORMATNAME> is your
plugin’s name. This file defines the layout and is included by /course/view.php.
Since Moodle 2.4, the lib.php (which is, as for most plugins, the glue between the Moodle
core and your code) must have a class called format_FORMATNAME that extends the
class format_base, which is defined in the /course/format/lib.php file.
The recommended way of creating a new course format is to copy an existing one. As this
is too easy for us, we will do it the hard way – though there will be some copying and
pasting involved.

Developing a Course Format


We are going to develop a simple course format called bannertopics that is similar to
the topics format, but which adds a banner to each section. A default banner is provided
where one is not provided. This would be a good time to make a couple of banners
including the default one. The default one will live in a folder called pix, and you will see
the reason for this later. I suggest 468 x 60 png files.
Since Moodle 2.4, there have been some features of the API that require the use of a

120
Chapter 8 Developing Plugins

renderer, as covered in the ‘Renderers and Templates’subsection in the ‘Output API’


section of the ‘Common APIs’ chapter (Chapter 5). Whilst we will not be using all of those
features for our plugin, we will use a renderer to display the course.
So, our initial files (all residing in the /course/format/bannertopics/ directory) are the usual
ones required plus our format file, for now:
 version.php
 lang/en/format_bannertopics.php
 lib.php
 format.php
 classes/renderer.php
 README – for GitHub (optional)
In the version.php file:
/course/format/bannertopics/version.php
// HEADER STUFF HERE //

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

$plugin->version = 2020061500;
$plugin->requires = 2020061500; // Requires M3.9.
$plugin->component = 'format_bannertopics';

In the lang/en/format_bannertopics.php file:


/course/format/bannertopics/lang/en/format_bannertopics.php
// HEADER STUFF HERE //

$string['pluginname'] = 'Banner Topics format';

In lib.php, we define our class extension. Note the need to require the format lib.php, as
below:
/course/format/bannertopics/lib.php
// HEADER STUFF HERE //

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

121
Introduction to Moodle 3.9+ Plugin Development

require_once($CFG->dirroot. '/course/format/lib.php');

// Class Definition PHP Docs here //


class format_bannertopics extends format_base {

}
For now, both format.php and classes/renderer.php are empty files.
/course/format/bannertopics/format.php
// HEADER STUFF HERE //

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

/course/format/bannertopics/classes/renderer.php
// HEADER STUFF HERE //

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

It is now actually possible to install the plugin. This would allow you to use the XMLDB
Tool to define any tables your plugin would require. As you do not have any additional
tables for this plugin, you can go ahead and install it.
Create a course with your new format (see the ‘Adding_a_new_course’
(https://docs.moodle.org/39/en/Adding_a_new_course page on how to do this). You will
get a blank page if you ensured that you created the file in this format. You will get errors
if you mistakenly created the course in the default ‘Topics’ format and then changed back
to this format. By default, Moodle creates a single section (topic in our case) in a new
course – Section 0. While some course formats may not use sections, ours does, so we let
the API know in our class (lib.php) by adding a function:
/course/format/bannertopics/lib.php
public function uses_sections() {
return true;
}

We also need to let the user know what the section is called in our format by defining it in
our language file (lang/en/format_bannertopics.php):
/course/format/bannertopics/lang/en/format_bannertopics.php
$string['sectionname'] = 'BannerTopic';

122
Chapter 8 Developing Plugins

Before Moodle 3.2, if you wanted a default ‘Announcements’ forum created, you had to do
some elaborate stuff. Now all you need to do is define at supports_news() function in
your format class and return true. So, add the following function to lib.php:
/course/format/bannertopics/lib.php
public function supports_news() {
return true;
}

We need to render the page to see the sections and their contents. This means turning our
attention to the format.php and classes/renderer.php files.
For our simple course format, the format.php file calls the renderer to display the course
page. Thus, the code – a simplified copy of the topics format – looks like this:
/course/format/bannertopics/format.php
// HEADER STUFF HERE //

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

// Retrieve course format option fields ...


// ... and add them to the $course object.
$course = course_get_format($course)->get_course();

$renderer = $PAGE->get_renderer('format_bannertopics');

$renderer->print_multiple_section_page($course,
null, null, null, null);

The renderer is an extension of the format_section_renderer_base class defined


in the course/format/renderer.php file, but could easily be based on the standard
plugin_renderer_base class. The base class defines three abstract functions that we
need to provide code for, so we need the three functions added in (copied from the Topics
format). Our renderer file looks like this:
/course/format/bannertopics/classes/renderer.php
// HEADER STUFF HERE //

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

123
Introduction to Moodle 3.9+ Plugin Development

// Extending the class in format renderer.


require_once($CFG->dirroot .
'/course/format/renderer.php');

class format_bannertopics_renderer
extends format_section_renderer_base {

protected function start_section_list() {


return html_writer::start_tag('ul',
array('class' => 'topics'));
}

protected function end_section_list() {


return html_writer::end_tag('ul');
}

protected function page_title() {


return get_string('topicoutline');
}
}

We can now increase the plugin version in version.php, navigate to ‘Site Administration’,
and upgrade the site.
Returning to the course, you will notice that there is no ‘Announcements’ forum, and the
page remains blank. Why is that? Well, we added the supports_news() function in
lib.php after creating the new course. If you created another course at this point, it would
have the forum in the first section. Go ahead and edit the course settings – change the
record slightly and save it. You will now have the forum appear.
Now turn editing on and attempt to add a new section to your course. You will get an error:
Invalid get_string() identifier: 'hidefromothers' or component 'format_bannertopics' ...
The strings hidefromothers and showfromothers are required for the section
editing menu. Just add them to the language file:
/course/format/bannertopics/lang/en/format_bannertopics.php
$string['hidefromothers'] = 'Hide topic';
$string['showfromothers'] = 'Show topic';

If you reload the course page, the error is still displayed. Since strings are cached, you

124
Chapter 8 Developing Plugins

either have to increase the plugin’s version or clear the cache as per the ‘Moodle caching’
section in the ‘Common Plugin Requirements’ chapter (Chapter 7). The new section has
been created though.
Now would be a good time to add our default banner to the plugin. Save a 468x60 png
image file as pix/defaultbanner.png, if you have not already done so.
If you navigate back to the course and switch editing off, you get the new section being
displayed, but with an error ‘Notice: Undefined property: stdClass::$coursedisplay in
/course/format/renderer.php on line XXX'.
This is caused by the fact that the function $renderer-
>print_multiple_section_page called in format.php expects the topics format
option coursedisplay to be defined. This is a bit naughty, as it is possible for formats
like ours not to have defined any options; it may be caused by a bug in the core code.
Whilst creating a course, you will find that all the default course formats have various
options; for example, the Topics and Weeks formats define numsections,
hiddensections, and coursedisplay options. We did not define any for our
course, as we were happy to go with the defaults. However, if you examine the code, the
only default is the single section created when we created our course. There are several
functions relating to course format options, specifically:
 course_format_options()
 create_edit_form_elements()
 edit_form_validation()
 update_course_format_options()
It is worth examining the topics format code for examples of how some of these functions
work. I am going to cheat and set coursedisplay in format.php, as I am going to use
the section formatting instead. Alternatively, I could just copy the relevant functions from
the topics class into the lib.php file. The constant variables for the course display are
defined in the /lib/moodlelib.php file and there are two options
COURSE_DISPLAY_SINGLEPAGE and COURSE_DISPLAY_MULTIPAGE, which are
kind of self-explanatory. In our format.php we add the following lines after the line we get
the course object:
/course/format/bannertopics/format.php
$course = course_get_format($course)->get_course();

// HACK – add coursedisplay to the course options.

125
Introduction to Moodle 3.9+ Plugin Development

$course->coursedisplay = COURSE_DISPLAY_SINGLEPAGE;

$renderer = $PAGE->get_renderer('format_bannertopics');

If you refresh the course page, there is no longer an error. Now we want to set a banner for
each section in our course, so we are going to define an option for the banner file for each
section. We start by creating the function section_format_options() in lib.php.
The function will define the field to hold the banner information, using the Form API
conventions, and it is passed a boolean true value if the call is being made for an editing
form:
/course/format/bannertopics/lib.php
public function section_format_options(
$foreditform = false) {
// Are we editing the form?
if ($foreditform && !isset(
$sectionformatoptions['sectionbanner']['label'])
) {
$label = new
lang_string('sectionnbannerprompt',
'format_bannertopics');
$sectionformatoptions = array(
'sectionbanner' => array(
'default' => '',
'type' => PARAM_FILE,
'label' => $label,
'element_type' => 'filemanager',
'element_attributes' => array(
'maxfiles' => 1,
'accepted_types' =>
array('image'),
'return_types'=> FILE_INTERNAL,
),
),
);
}else{
// Display default banner when rendering.
$sectionformatoptions = array();
}

126
Chapter 8 Developing Plugins

return $sectionformatoptions;
}

For the option form’s prompt, we need to define a language string


sectionbannerprompt in the language file:

/course/format/bannertopics/lang/en/format_bannertopics.php
$string['sectionnbannerprompt'] = 'Select Banner File
(png recommended)';

Increase the plugin’s version in the version.php file and then upgrade, so that our latest
changes are reflected.
Going back to the course, you will notice that no banners are being displayed yet. Turn
editing back on and edit the new section. The editing form now has a filemanager field
– ‘Select Banner File (png recommended)’.

Dealing with the Moodle forms’ filemanager element takes some getting used to – see
the ‘Custom File Elements’subsection in the ‘Form API’ section of the ‘Common APIs’

127
Introduction to Moodle 3.9+ Plugin Development

chapter (Chapter 5) – but we need to save the uploaded banner to an area where we can
find it to edit the options and display the banner.
To save the banner after uploading, in order for us to access it later, we override the
update_section_format_options() function in lib.php, which is passed an array
variable with the edit form’s submitted data. The banner file is moved into a file area that
can be accessed at some point in the future. Please refer back to the ‘File API’ and ‘Form
API’ sections in the ‘Common APIs’ chapter (Chapter 5) for more information on this.
/course/format/bannertopics/lib.php
public function update_section_format_options($data) {
$courseid = $this->courseid;
$ctxtid = context_course::instance($courseid)->id;
file_save_draft_area_files(
$data['sectionbanner'],
$ctxtid,
'format_bannertopics',
'banners',
$data['id'],
array('subdirs' => 0)
);
return parent::update_section_format_options($data);
}

Now, using the File API, we can retrieve the banner image to be displayed. We are going to
display the banners before the section title.
To be a bit more efficient, I defined the default and uploaded banners at the time the course
format is loaded in the class/renderers.php file’s __construct() function, which
needed two additional properties of the class to hold the values:
/course/format/bannertopics/class/renderers.php
private $defaultbanner = '';
private $bannerfiles = array();

In the __construct() function, we call the parent function to ensure all the necessary
processing before we define our own variables:
/course/format/bannertopics/class/renderers.php
public function __construct(moodle_page $page, $target)
{

128
Chapter 8 Developing Plugins

global $COURSE;
parent::__construct($page, $target);

// Define our default image only once.


$pixpath =
'/course/format/bannertopics/pix/defaultbanner.png';
$bimage = new moodle_url($pixpath);
$this->defaultbanner = $bimage->out(false);
$coursectx = context_course::instance($COURSE->id);
$contextid = $coursectx->id;

// Get all the uploaded banners in one step.


$fs = get_file_storage();
$bannerfiles = $fs->get_area_files(
$contextid,
'format_bannertopics',
'banners',
false,
null,
false
);
foreach ($bannerfiles as $bannerfile) {
$bannerimg = moodle_url::make_pluginfile_url(
$bannerfile->get_contextid(),
$bannerfile->get_component(),
$bannerfile->get_filearea(),
$bannerfile->get_itemid(),
$bannerfile->get_filepath(),
$bannerfile->get_filename(),
false
);
$this->bannerfiles[$bannerfile->get_itemid()] =
$bannerimg;
}
}

The default banner, which has been saved in the pix folder, is defined using the core
moodle_url() function to ensure the correct URL. The information about the uploaded
banners is retrieved using the File API’s file storage class and their retrievable URLs are
then computed again using moodle_url() functionality.

129
Introduction to Moodle 3.9+ Plugin Development

Since both section head functions, section_title() and


section_title_without_link(), will display the banner, I defined another
function for both to call to return the HTML to the appropriate section’s banner. The
function requires that the section object be passed to it.
/course/format/bannertopics/class/renderers.php
private function get_sectionbanner_html($section) {
$html = '';

if (empty($this->bannerfiles[$section->id])) {
$bannerimage = $this->defaultbanner;
}else{
$bannerimage = $this->bannerfiles[$section->id];
}
$html = html_writer::img($bannerimage, 'banner');
$bannerhtml = html_writer::div($html,
'text-center');

return $bannerhtml;
}

Note that both the section head functions can now call $this-
>get_sectionbanner_html():

/course/format/bannertopics/class/renderers.php
public function section_title($section, $course) {
$cf = course_get_format($course);
$sthtml =
$cf->inplace_editable_render_section_name(
$section);
$sectiontitle =
$this->get_sectionbanner_html($section);
$sectiontitle .= $this->render($sthtml);
return $sectiontitle;
}

public function section_title_without_link($section,


$course) {
$cf = course_get_format($course);
$sthtml =

130
Chapter 8 Developing Plugins

$cf->inplace_editable_render_section_name(
$section, 0);
$sectiontitle = $this->get_sectionbanner_html(
$section);
$sectiontitle .= $this->render($sthtml);
return $sectiontitle;
}

If you now view the course, the section will have the default banner displayed. Now add a
new section, edit it, and upload a banner image file. You will notice that the new banner is
not displayed, and that only the image alt text is. Why is this? Well, for plugins that
manage files, the plugins have to define a function <plugin>_pluginfile(), where
<plugin> is the plugin’s Frankenstyle name, in their lib.php file to retrieve and deliver
the files as per the File API. Our lib.php, however, defines our format class and, in direct
violation of Moodle coding guidelines, we are forced to define a function outside the class,
but in the same file.
The code in our format_bannertopics_pluginfile() function is based on
Moodle’s ‘File API – Serving Files to Users’ page
(https://docs.moodle.org/dev/File_API#Serving_files_to_users) example. Make sure the
function is outside the format_bannertopics class for it to work.

/course/format/bannertopics/lib.php
function format_bannertopics_pluginfile($course, $cm,
$context, $filearea, $args, $forcedownload,
array $options=array()) {

// Leave this line out if itemid is null ...


// ... in make_pluginfile_url().
$itemid = array_shift($args); // 1st item $args.

// Extract the filepath from the $args array.


$filename = array_pop($args); // Last item.
if (!$args) {
$filepath = '/'; // $args empty default '/'.
} else {
// $args now has elements of the filepath.
$filepath = '/'.implode('/', $args).'/';
}

131
Introduction to Moodle 3.9+ Plugin Development

// Retrieve the file from the Files API.


$fs = get_file_storage();
$file = $fs->get_file(
$context->id,
'format_bannertopics',
$filearea,
$itemid,
$filepath,
$filename
);
if (!$file) {
return false; // The file does not exist.
}

// Send the file back with a cache lifetime ...


// ... of 1 day and no filtering.
send_stored_file($file, 86400, 0,
$forcedownload, $options);
}

Alternatively, you could define the format class in a classes/ file and require it in the
lib.php file. I went with classes/bannertopics.class.php. Once done, the sections with an
uploaded banner will display that banner instead of the default one.
Now attempt to edit the new section (the one with your uploaded banner) and you will
notice that the uploaded file appears to have disappeared. There is still one more thing we
need to do: retrieve the file for editing.
If you read through the documentation, it is very confusing as to where is the best place to
do this. Two likely candidates, section_format_options() and
create_edit_form_elements() methods in the format_bannertopics class,
do not work. After a little experimentation, the only place that allows you to set the value
of the file field, at least for uploaded files, is the class’ get_format_options()
method.
This method is optionally called with a section parameter that allows us to determine that
the call is in relation to a section. The function sets up the draft space for the editing and
sets the field default value to point to the draft area. See the ‘Custom File Elements’
subsection in the ‘Form API’ section of the ‘Common APIs’ chapter (Chapter 5) for how
this works. This is what our method looks like:

132
Chapter 8 Developing Plugins

/course/format/bannertopics/lib.php
OR /course/format/bannertopics/classes/bannertopics.class.php
public function get_format_options($section = null) {
global $PAGE;
$savedoptions =
parent::get_format_options($section);

// Bug somewhere that PAGE->context is not set.


If (!($PAGE->context)) {
$PAGE->set_context(
context_course::instance($this->courseid));
}

if ($PAGE->user_is_editing() && $section !== null) {


$draftitemid = 0;
$ctxid = context_course::instance(
$this->courseid)->id;
$itemid = required_param('id', PARAM_INT);

file_prepare_draft_area(
$draftitemid,
$ctxid,
'format_bannertopics',
'banners',
$itemid,
array('subdirs' => 0)
);

$savedoptions['sectionbanner'] =
$draftitemid;
}
return $savedoptions;
}

Now any editing user can upload new or updated banners.


As mentioned earlier, it is recommended that when developing course formats, you start
with a copy of an existing format – the closest to the one you wish to emulate. We have not
done so in this example in order to explore some of the functionality. Our course format
would need further work to make it suitable for release, but we are going to leave it here.

133
Introduction to Moodle 3.9+ Plugin Development

Repository
Repository plugins allow Moodle to use resources from external resources or file stores.
There are several repositories bundled with core Moodle including Dropbox, Flickr,
Google Drive, Microsoft OneDrive, Amazon S3, and many more.
We are going to develop a repository using Slideshare by LinkedIn. Our plugin will allow
the course creator the opportunity to include a Slideshare resource in their course. This
plugin requires the use of an API key, which you can get at the LinkedIn’s ‘Developers &
API’ page (https://www.slideshare.net/developers).
For more information about Repository Plugins, please visit the Moodle ‘Repository
Plugins’ page (https://docs.moodle.org/dev/Repository_plugins).

Developing a Repository Plugin


As usual, we will start with some skeleton files to allow us to install the plugin. The
plugin’s directory is /repository/slideshare/. The usual version.php looks like this:
/repository/slideshare/version.php
// HEADER STUFF HERE //

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

$plugin->version = 2020061500;
$plugin->requires = 2020061500; // Requires M3.9.
$plugin->component = 'repository_slideshare';

We also need the lang/en/repository_slideshare.php file. In addition to the required


pluginname string, repository plugins need to define a configplugin string and a
pluginname_help string. Our file, then, looks like this:

/repository/slideshare/lang/en/repository_slideshare.php
// HEADER STUFF HERE //

$string['pluginname'] = 'Slideshare repository';


$string['configplugin'] = 'Config for the Slideshare
repository';
$string['pluginname_help'] = 'A Slideshare search
repository';

With just those two files in the folder, you can install the plugin, so go ahead and install it.

134
Chapter 8 Developing Plugins

However, we also need the lib.php file that defines our class
repository_slideshare, which is an extension of the base repository class
defined in /repository/lib.php. The only function that has to be overridden is
get_listing(). This is the guts of the repository plugin and returns the files that will
be displayed in the file picker. The function get_listing() must expect two
parameters: $path, which identifies the current path, and $page, which is the page
number for the file list. For now, we just create an empty function in our lib file:
/repository/slideshare/lib.php
// HEADER STUFF HERE //

class repository_slideshare extends repository {


public function get_listing($path = '',$page = ''){
return array('list' => array());
}
}

To ensure the relevant users can see the repository, we need to define a capability to view
in db/access.php. Students will never see the repository when they use the file picker.
/repository/slideshare/db/access.php
// HEADER STUFF HERE //

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

$capabilities = array(
'repository/slideshare:view' => array(
'captype' => 'read',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'coursecreator' => CAP_ALLOW,
'teacher' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
)
)
);

We also need to add a language string for the capability.

135
Introduction to Moodle 3.9+ Plugin Development

/repository/slideshare/lang/en/repository_slideshare.php
$string['slideshare:view'] = 'View the Slideshare
repository';

Another useful file is the repository’s icon. This 16x16 pixel file pix/icon.png is displayed
in the file picker; all I’ve done is to minimise the Slideshare icon.
Moodle defines four types of file link, which are constants defined in the
/repository/lib.php file. Repositories can support one or more of these file types by
returning a combination of the types from the class’ supported_returntypes()
method.
Files that are uploaded to Moodle’s internal file system are of the type FILE_INTERNAL.
Where the file remains in the external repository and is accessed from there as a link, it is
defined as of the FILE_EXTERNAL type. A variation of this type is the
FILE_REFERENCE type, which refers to files that remain in the external repository, but
may be cached locally. Provision has to be made for regularly updating the cache, possibly
via tasks functionality.
Though not mentioned in the release notes, the FILE_CONTROLLED_LINK type is
introduced in Version 3.3. The documentation explains that ‘the file remains in the external
repository’. By ‘uploading’ it, ownership of the file (in the remote system) is changed, so
that the system account in the external repository becomes the new owner of the file. Later,
if the file is accessed, the system account is responsible for granting access to users.
The supported types will define the actions that users will be presented with in the file
picker. Additionally, course activities that do not use certain file types will not present the
user with the repositories that do not support the required types. For example, the URL
resource (mod_url) only allows FILE_EXTERNAL links, so the file picker will only list
repositories that support this.
Our repository plugin will only link to the external Slideshare resource,
FILE_EXTERNAL, so our supported_returntypes() method in lib.php looks like
this:
/repository/slideshare/lib.php
public function supported_returntypes() {
return FILE_EXTERNAL;
}

It is also possible to restrict the type of files acceptable to the repository via the
supported_filetypes() method, which returns an array of mime types, e.g.

136
Chapter 8 Developing Plugins

'image/gif', 'image/jpeg', 'image/png' or a grouping such as 'web_image'. Groupings and the


relevant mime types are defined in /lib/classes/filetypes.php and are limited to:
 archive – we can extract files from this archive
 audio – file that can be imported as audio in the text editor
 document – used for portfolio format
 image – an image that we can parse using GD to find its dimensions
 presentation – used for portfolio format
 spreadsheet – used for portfolio format
 video – a file that can be imported as a video in the text editor
 web_image – an image that can be included as <img> in HTML
 html_audio
 html_track
 html_video
 web_audio
 web_file
 web_image
 web_video
We will not be using this functionality, but, rather, using the default, which is all file types.
Apart from the get_listing() function, (according to the documentation) you have to
override the __construct() method (however, I have found that you do not have to do
this). If you do, it should call the parent method to ensure that certain class variables are
initialised, including $this->options. The options array variable includes two
possible options passed in by the calling function:
 ajax – bool, true if the user is using the AJAX filepicker
 mimetypes – an array of accepted mime types or * for all types
These two options have any other plugin specific options added to them.
Repository plugins have options as opposed to the settings that are usually specified in the
settings.php file. Instead, there are several specific lib.php functions to deal with options.

137
Introduction to Moodle 3.9+ Plugin Development

Two types of options are possible, global and instance options. Global settings are site-
wide, whereas instance options are applied to the particular instance of the plugin. When a
repository is added to a course, an instance of the plugin is created and specific settings can
be applied; for example, in our case, we could have a separate API key for each course.
In our plugin, we are going to have the API keys as global settings and set default files per
page setting. Our local settings will allow the course manager to override the default files
per page setting as well as set the parameters for the Slideshare search. Since you do not
have to have any of these options, all the relevant functions are optional.
By default, a system-wide instance of a repository is created when the repository is
enabled. If you want to allow either or both course-specific and user repository instances,
you have to specify both enablecourseinstances and enableuserinstances
as options. There are three ways to do this:
1 Define $string[‘enablecourseinstances’] and
$string[‘enableuserinstances’] in your plugin’s language file;
2 Add them as items in the get_type_option_names() function’s return array.
Note, you must not define the form fields for these options in the
type_config_form() function;
3 Several core repositories use the db/install.php to create the original repository
instance by constructing an instance of the repository_type class. The options
can be defined in the array passed as the second parameter to the constructor. Look
at some of the files for examples.
We will just take the first option and declare them in the language file:
/repository/slideshare/lang/en/repository_slideshare.php
$string['enablecourseinstances'] = 'Allow course
administrators to add a repository instance into the
course';
$string['enableuserinstances'] = 'Allow users to add a
repository instance into the user context';

As we are going to have global options, the first function to override is


get_type_option_names(), which must be statically declared and must call the
parent function, which returns an array with a single item called pluginname. The new
options – apikey, apisecret, and filesperpage – are added to the array and
returned.

138
Chapter 8 Developing Plugins

/repository/slideshare/lib.php
public static function get_type_option_names() {
$myoptions = array(
'apikey',
'apisecret',
'filesperpage'
);
return array_merge(parent::get_type_option_names(),
$myoptions);
}

To set these options, we define the form elements in order to allow the administrator to set
the values. The form fields are defined in the statically declared type_config_form()
function that should expect the Moodle form in passed parameter $mform. To avoid PHP
warnings, the parent function stipulates an additional parameter $classname, with a
default value of 'repository', which you need to replicate.
The first thing in this function is to call the parent function so that default fields are set and
then, using the standard Form API conventions, you define the form fields to collect the
global options:
/repository/slideshare/lib.php
public static function type_config_form($mform,
$classname = 'repository') {

parent::type_config_form($mform);

$currentoptions = (array)
get_config('repository_slideshare');

$mform->addElement('text', 'apikey',
get_string('apikeyprompt',
'repository_slideshare'),
array('size' => '25'));
if (!empty($currentoptions['apikey'])) {
$mform->setDefault('apikey',
$currentoptions['apikey']);
}
$mform->addHelpButton('apikey', 'apikeyprompt',
'repository_slideshare');

139
Introduction to Moodle 3.9+ Plugin Development

$mform->setType('apikey', PARAM_TEXT);

$mform->addElement('text', 'apisecret',
get_string('apisecretprompt',
'repository_slideshare'),
array('size' => '25'));
if (!empty($currentoptions['apisecret'])) {
$mform->setDefault('apisecret',
$currentoptions['apisecret']);
}
$mform->setType('apisecret', PARAM_TEXT);

$mform->addElement('text', 'filesperpage',
get_string('filesperpageprompt',
'repository_slideshare'),
array('size' => '5'));
if (empty($currentoptions['filesperpage'])) {
$mform->setDefault('filesperpage', 20);
} else {
$mform->setDefault('filesperpage',
$currentoptions['filesperpage']);
}
$mform->setType('filesperpage', PARAM_INT);
}

The third function, which should also be statically declared, is


type_form_validation(). This function gives you the chance to validate the input
from the user just like standard forms. We are not using it in our plugin.
To support the form, we need to define a few more language strings in
lang/en/repository_slideshare.php and add in the strings for the course and local instances:
/repository/slideshare/lang/en/repository_slideshare.php
$string['apikeyprompt'] = 'Enter the API key.';
$string['apikeyprompt_help'] = 'Enter the key you have
been issued by Slideshare.';
$string['apisecretprompt'] = 'Enter Shared Secret Key';
$string['filesperpageprompt'] = 'Default results per
page.';

At the moment our repository is a global one. To enable ‘instance’ creating and

140
Chapter 8 Developing Plugins

management, we need to define our instance functions.


The three methods mirror the ones we have just looked at –
get_instance_option_names(), instance_config_form() (which expects
a Moodle form $mform parameter), and the validation method
instance_form_validation() (which we will not bother with for our plugin). We
define the option resultsperpage to override our global filesperpage setting and
search term, which we will use in our Slideshare API call, and define the form
variables as before:
/repository/slideshare/lib.php
public static function get_instance_option_names() {
return array(
'searchterm',
'resultsperpage'
);
}

public static function instance_config_form($mform) {


$mform->addElement('text', 'searchterm',
get_string('searchtermprompt',
'repository_slideshare'),
array('size' => '25'));
$mform->setType('searchterm', PARAM_TEXT);
$mform->addRule('searchterm',
get_string('required'), 'required');

$mform->addElement('text', 'resultsperpage',
get_string('resultsperpageprompt',
'repository_slideshare'),
array('size' => '5'));
$mform->setDefault('resultsperpage', get_config(
'repository_slideshare','filesperpage'));
$mform->setType('resultsperpage', PARAM_INT);
}

We need to define some more strings:


/repository/slideshare/lang/en/repository_slideshare.php
$string['searchtermprompt'] = 'Enter Search Term';
$string['resultsperpageprompt'] = 'Number of results

141
Introduction to Moodle 3.9+ Plugin Development

per page';

NOTE: You cannot call $this from static methods. If you need to access the non-static
variables, you may have to store the values in the __construct() method into
private/protected static variables.
Now would be a good time to bump up the plugin’s version and upgrade.
Navigate to ‘Site administration->Plugins->Repositories->Manage repositories’ and make
the repository active by selecting ‘Enabled and Visible’ in the relevant select field. This
will bring up our global options page.

Fill in the form with the relevant details and allow course administrators to add a repository
instance into the course.
Now we can return to our get_listing() method. In this method, we call the
Slideshare API and the response is XML – please see the Slideshare API documentation
(https://www.slideshare.net/developers/documentation) for more details. Notice I used the
PHP simplexml_load_file() to fetch the file and parse the XML. Of course, I
could also have used either the functions file() or file_get_contents() and then
parsed the XML response. For simple GET API requests, these work well. We will look at
more elaborate API consumption functionality later in the book.
Once we have the parsed contents, it is simply a matter of creating the response required by

142
Chapter 8 Developing Plugins

the file picker. For a full list of all the possible variations that can be set, please see the
relevant section on Moodle’s ‘Repository Plugins’ page
(https://docs.moodle.org/dev/Repository_plugins#get_listing). Remember, we have defined
get_listing() already, so our replacement function looks like this:

/repository/slideshare/lib.php
public function get_listing($path = '', $page = '') {

$options = $this->options;
$config = get_config($options['type']); // BUG.

$ts = time();
$query = $options['searchterm'];
$page = $page ? $page : 1;
$items = $options['resultsperpage'] ??
$config->filesperpage;

$apiurl = 'https://www.slideshare.net';
$apiurl .= '/api/2/search_slideshows';

$requestparams = array(
'api_key' => $config->apikey,
'ts' => $ts,
'hash' => sha1($config->apisecret . $ts),
'q' => $query,
'page' => $page,
'items_per_page' => $items
);
$requesturl = $apiurl . '?' .
http_build_query($requestparams, null, '&');

$contents = simplexml_load_file($requesturl);

if ($contents === false) {


if (empty($errors = libxml_get_errors())) {
return array('list' => array());
} else {
print_error('parseerror',
'repository_slideshare');
}

143
Introduction to Moodle 3.9+ Plugin Development

}else{
if (empty(
$contents->SlideShareServiceError)) {
$meta = (array) $contents->Meta;
$pages = (int) $meta['TotalResults'];
$results = array(
'dynload' => true,
'page' => $page,
'pages' =>
$pages > $items ? -1 : '',
'norefresh' => true,
'nosearch' => true,
'nologin' => true,
'list' => array()
);

foreach ($contents->Slideshow
as $slideshow) {

// Turn to array for simplicity.


$slideshow = (array) $slideshow;
$sizes = preg_replace('/[\[\]]/',
'', $slideshow['ThumbnailSize']);

list($w, $h) = explode(',', $sizes);


$results['list'][] = array(
'title' => $slideshow['Title'],
'datemodified' =>
strtotime(
$slideshow['Created']),
'datecreated' => strtotime(
$slideshow['Updated']),
'thumbnail' =>
$slideshow['ThumbnailURL'],
'thumbnail_width' => $w,
'thumbnail_height' => $h,
'url' => $slideshow['URL'],
);
}
return $results;

144
Chapter 8 Developing Plugins

} else {
print_error('apierror',
'repository_slideshare',
null,
$contents->
SlideShareServiceError->Message);
}
}
}

We also need to add a couple more language strings for the possible errors.
/repository/slideshare/lang/en/repository_slideshare.php
$string['apierror'] = 'An API error occurred -
the message is "{$a}"';
$string['parseerror'] = 'Error parsing the response
from Slideshare API';

Bump up your version and upgrade your plugin before moving on.
Now log out and then log in as the teacher user, and navigate to the original test course. A
new option 'Repositories' appears on the 'Course Settings' (gear icon) menu. Selecting it
allows you to create a course-specific repository. The form displayed is the one we have
defined in the instance_config_form() method.
After entering the settings, users creating resources and activities that allow links in the
course (e.g. URL resources) can choose them from the Slideshare results.

145
Introduction to Moodle 3.9+ Plugin Development

This is where we are going to leave the repository plugin. Please note that it does need
more development before it can be used, as adding one of the resources to what we have
here would result in an error. However, you should now have a good idea of how to
develop this type of plugin.

Activities and Resources (Modules)


Courses and their contents, activities and resources, are the heart of Moodle and referred to
as ‘modules’ in plugin talk. Resources are usually static objects, such as documents, that
support learning, whilst activities are, well, activities. We are going to develop a resource
that displays a section (snippet) from Wikipedia in courses.
For more information about course modules, please visit the Moodle ‘Activity Modules’
page (https://docs.moodle.org/dev/Activity_modules).

Developing Modules
We are going to develop a plugin called mod_wikipediasnippet, which will reside in
the /mod/wikipediasnippet folder of the site. As usual, we will start with the minimum
required files to install. Unfortunately, modules will not allow you to install without the
db/install.xml file, so you cannot count on using the XMLDB tool to create your plugin’s
database tables.
Our version.php file looks like this:

146
Chapter 8 Developing Plugins

/mod/wikipediasnippet/version.php
// HEADER STUFF HERE //

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

$plugin->version = 2020061500;
$plugin->requires = 2020061500;
$plugin->component = 'mod_wikipediasnippet';

With lang/en/wikipediasnippet.php (note that it is not


lang/en/mod_wikipediasnippet.php), as well as the standard pluginname string, you
also need modulename and modulenameplural defined. We might as well throw in
the help hint as well:
/mod/wikipediasnippet/lang/en/wikipediasnippet.php
// HEADER STUFF HERE //

$string['pluginname'] = 'Wikipedia Snippet';


$string['modulename'] = 'Wikipedia Snippet';
$string['modulenameplural'] = 'Wikipedia Snippets';
$string['modulename_help'] = 'Allows managers to include
fragments of Wikipedia pages';

Even though pluginname and modulename are the same, you have to define each one
separately.
Unfortunately, you need to create a module table and will have to define a basic
db/install.php to install the plugin. You cannot use the XMLDB tool this time. The
minimum fields are:
 id – the id field
 course – the course id
 name – the name of the instance
 intro – the description
 introformat – the format of the intro text – usually HTML
 timecreated – the time record created
 timemodified – last modified

147
Introduction to Moodle 3.9+ Plugin Development

However, we are going to define additional fields at the same time. It makes sense to start
by copying and modifying a simple file from the other modules, such as mod_folder or
mod_url. Once you have installed your skeleton plugin, you can then use the XMLDB
tool to change the initial table and add the additional tables you might require. For more
information about the db/install.xml file, please see the ‘Data Definition API’ section in
the ‘Common APIs’ chapter (Chapter 5).
/mod/wikipediasnippet/db/install.xml
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="mod/wikipediasnippet/db" VERSION="20200423"
COMMENT="XMLDB file for Moodle mod/wikimediasnippet"
xmlns:xsi=
"p://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation=
"../../../lib/xmldb/xmldb.xsd"
>
<TABLES>
<TABLE NAME="wikipediasnippet"
COMMENT="wikipediasnippet main table">
<FIELDS>
<FIELD NAME="id" TYPE="int" LENGTH="10"
NOTNULL="true" UNSIGNED="true"
SEQUENCE="true"/>
<FIELD NAME="course" TYPE="int" LENGTH="10"
NOTNULL="true" UNSIGNED="true"
SEQUENCE="false"
COMMENT="Course activity belongs to"/>
<FIELD NAME="name" TYPE="char" LENGTH="255"
NOTNULL="true" SEQUENCE="false"
COMMENT="name field for moodle instances"/>
<FIELD NAME="intro" TYPE="text" LENGTH="big"
NOTNULL="false" SEQUENCE="false"
COMMENT="Intro for the activity"/>
<FIELD NAME="introformat" TYPE="int" LENGTH="4"
NOTNULL="true" UNSIGNED="true" DEFAULT="0"
SEQUENCE="false"
COMMENT="Format of the intro field – HTML"/>
<FIELD NAME="wikiurl" TYPE="char" LENGTH="255"
NOTNULL="true" SEQUENCE="false"
COMMENT="Wikipedia URL"/>

148
Chapter 8 Developing Plugins

<FIELD NAME="noimages" TYPE="int" LENGTH="3"


NOTNULL="true" DEFAULT="0" SEQUENCE="false"
COMMENT="Content include images?"/>
<FIELD NAME="nolinks" TYPE="int" LENGTH="3"
NOTNULL="true" DEFAULT="0" SEQUENCE="false"
COMMENT="Content include links?"/>
<FIELD NAME="includecitations" TYPE="int"
LENGTH="3" NOTNULL="true" DEFAULT="0"
SEQUENCE="false"
COMMENT="Include citations?"/>

<FIELD NAME="timecreated" TYPE="int" LENGTH="10"


NOTNULL="true" UNSIGNED="true"
SEQUENCE="false"/>
<FIELD NAME="timemodified" TYPE="int"
LENGTH="10" NOTNULL="true" UNSIGNED="true"
DEFAULT="0"
SEQUENCE="false"/>
</FIELDS>
<KEYS>
<KEY NAME="primary" TYPE="primary" FIELDS="id"/>
</KEYS>
<INDEXES>
<INDEX NAME="course" UNIQUE="false"
FIELDS="course"/>
</INDEXES>
</TABLE>
</TABLES>
</XMLDB>

Our additional fields are:


 wikiurl – the Wikipedia section URL
 noimages – exclude images in the snippet?
 nolinks – exclude links in the snippet?
 includecitations – include citations in the snippet?
You will see why these are included later.
Additionally, for display purposes, you need a 24x24 icon – pix/icon.svg or pix/icon.png.

149
Introduction to Moodle 3.9+ Plugin Development

Now go ahead and install the plugin.


If you attempt to add the plugin to a course, it will not appear in the modules available to
use. We need to add functionality to make this work as a resource. All our functionality
will be defined in a lib.php file. Additionally, we will need a form mod_form.php for use
when adding, editing, and deleting our module. The form will work with the three
minimum functions we need to define in the lib file –
wikipediasnippet_add_instance(),
wikipediasnippet_update_instance(), and
wikipediasnippet_delete_instance(). But first, we want Moodle to know that
our plugin is a resource and what features our plugin supports in the
wikipediasnippet_supports() function. The features possible are defined in
lib/moodlelib.php and are:
 FEATURE_ADVANCED_GRADING – true if the module supports advanced grading
methods
 FEATURE_BACKUP_MOODLE2 – true if the module supports backup/restore for
the moodle2 format
 FEATURE_COMPLETION_HAS_RULES – true if the module has custom
completion rules
 FEATURE_COMPLETION_TRACKS_VIEWS – true if the module has code to
track whether somebody has viewed it
 FEATURE_CONTROLS_GRADE_VISIBILITY – true if the module controls the
grade visibility over the grade book
 FEATURE_GRADE_HAS_GRADE – true if the module can provide a grade
 FEATURE_GRADE_OUTCOMES – true if the module supports outcomes
 FEATURE_GROUPINGS – true if the module supports groupings
 FEATURE_GROUPS – true if the module supports groups
 FEATURE_IDNUMBER – true (which is the default) if the module wants support
for setting the ID (the number for grade calculation purposes)
 FEATURE_MOD_ARCHETYPE – type of module
 FEATURE_MOD_INTRO – true if the module supports intro editor
 FEATURE_MODEDIT_DEFAULT_COMPLETION – true if the module has default

150
Chapter 8 Developing Plugins

completion
 FEATURE_NO_VIEW_LINK – true if the module has no 'view' page (like label)
 FEATURE_PLAGIARISM – true if the module supports plagiarism plugins
 FEATURE_SHOW_DESCRIPTION – true if the module can show description on
the course’s main page
 FEATURE_USES_QUESTIONS – true if the module uses the question bank
Firstly, we want Moodle to know that we are a resource, so we need to return the relevant
response for the FEATURE_MOD_ARCHETYPE feature. There are four defined responses
for the feature, which are also defined in the lib/moodlelib.php file:
 MOD_ARCHETYPE_ASSIGNMENT – assignment module archetype
 MOD_ARCHETYPE_OTHER – unspecified module archetype
 MOD_ARCHETYPE_RESOURCE – resource-like type module
 MOD_ARCHETYPE_SYSTEM – system (not user-addable) module archetype
The last one is obviously for internal Moodle use. In our case, we return
MOD_ARCHETYPE_RESOURCE. Additionally, since we have defined an intro field in our
db/install.xml, we might as well support the FEATURE_MOD_INTRO feature. For all other
features, we return null from our function. So, we now have our skeleton lib.php file:
/mod/wikipediasnippet/lib.php
// HEADER STUFF HERE //

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

function wikipediasnippet_supports($feature) {
switch($feature) {
case FEATURE_MOD_INTRO:
return true;
case FEATURE_MOD_ARCHETYPE:
return MOD_ARCHETYPE_RESOURCE;
default:
return null;
}
}

151
Introduction to Moodle 3.9+ Plugin Development

function wikipediasnippet_add_instance(
$wikipediasnippet, $mform) {
}

function wikipediasnippet_update_instance(
$wikipediasnippet, $mform){
}

function wikipediasnippet_delete_instance($id) {
}

Both add_instance and update_instance get the data from the form (as an object
ready to be used on the database table) and the form itself. We will now deal with the
mod_form.php form to define what we need for our module. Our class extends a
specialised moodle_form called moodleform_mod and defined in
/course/moodleform_mod.php. Our class is named
mod_wikipediasnippet_mod_form, which is the format expected by the core.

/mod/wikipediasnippet/mod_form.php
// HEADER STUFF HERE //

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

require_once($CFG->dirroot .
'/course/moodleform_mod.php');

/**
* Module instance settings form.
*/
class mod_wikipediasnippet_mod_form extends
moodleform_mod {

/**
* Defines forms elements.
*/
public function definition() {

$mform = $this->_form;

// Adding the "general" fieldset ...

152
Chapter 8 Developing Plugins

// ... where all the common settings are showed.


$mform->addElement('header', 'general',
get_string('general', 'form'));

// Adding the standard "name" field.


$mform->addElement('text', 'name',
get_string('wikipediasnippetname',
'wikipediasnippet'),
array('size'=>'64'));
$mform->setType('name', PARAM_CLEAN);

$mform->addRule('name', null, 'required',


null, 'client');
$mform->addRule('name',
get_string('maximumchars', '', 255),
'maxlength', 255, 'client');
$mform->addHelpButton('name',
'wikipediasnippetname',
'wikipediasnippet');

// The standard "intro" & "introformat" flds.


$this->standard_intro_elements();

// Adding the rest of wikipediasnippet settings.


$mform->addElement('header', 'specific',
get_string('ws_formsection',
'wikipediasnippet'));

//wikipedia url
$mform->addElement('text', 'wikiurl',
get_string('wikis_url', 'wikipediasnippet'),
array('size'=>'80'));
if (!empty($this->_customdata['wikiurl'])) {
$mform->setDefault('wikiurl',
$this->_customdata['wikiurl']);
}
$mform->setType('wikiurl', PARAM_URL);
$mform->addRule('wikiurl', null, 'required',
null, 'client');
$mform->addElement('static', 'label1', '',

153
Introduction to Moodle 3.9+ Plugin Development

get_string('wikis_url_help',
'wikipediasnippet'));

// Include images?
$mform->addElement('checkbox', 'noimages',
get_string('wikis_excludeImages',
'wikipediasnippet'));
if (!empty( $this->_customdata['noimages'])) {
$mform->setDefault('noimages',
$this->_customdata['noimages']);
}
$mform->setType('noimages', PARAM_INT);
$mform->addElement('static', 'label2', '',
get_string('wikis_excludeImages_help',
'wikipediasnippet'));

// Include links?
$mform->addElement('checkbox', 'nolinks',
get_string('wikis_excludeLinks',
'wikipediasnippet'));
if (!empty( $this->_customdata['nolinks'])) {
$mform->setDefault('nolinks',
$this->_customdata['nolinks']);
}
$mform->setType('nolinks', PARAM_INT);
$mform->addElement('static', 'label3', '',
get_string('wikis_excludeLinks_help',
'wikipediasnippet'));

// Include citations?
$mform->addElement('checkbox',
'includecitations',
get_string('wikis_includecitations',
'wikipediasnippet'));
if (!empty(
$this->_customdata['includecitations'])){
$mform->setDefault('includecitations',
$this->_customdata['includecitations']);
}
$mform->setType('includecitations', PARAM_INT);

154
Chapter 8 Developing Plugins

$mform->addElement('static', 'label4', '',


get_string('wikis_includecitations_help',
'wikipediasnippet'));

// Add standard elements, common to all modules.


$this->standard_coursemodule_elements();

// Add standard buttons, common to all modules.


$this->add_action_buttons();
}
}

You will notice some form fields relate to the table fields, e.g. the Wikipedia snippet URL,
but you will probably notice three different calls:
 $this->standard_intro_elements(): this adds an editor to allow the
user to enter a description and, since we have said we support the
FEATURE_MOD_INTRO feature, we should include this field;
 $this->standard_coursemodule_elements(): all modules should
include this in the form, as this collects the standard data for modules. In short, it
adds the following sections to the form:
◦ Common module settings
◦ Restrict access
◦ Tags
◦ Competencies
 $this->add_action_buttons(): this adds the submit buttons with the
option to return to the course or to display the module or to cancel the whole
operation. Again, all modules should use this functionality in order to be consistent
with other modules.
We do have to define some new language strings to support our new form and,
additionally, for our capabilities definitions, which we will require to add an instance of our
module. Capabilities are defined in the db/access.php file and we will need at least two
capabilities for our module, namely wikipediasnippet:addinstance and
wikipediasnippet:view.

155
Introduction to Moodle 3.9+ Plugin Development

/mod/wikipediasnippet/db/access.php
// HEADER STUFF HERE //

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

$capabilities = array(

'mod/wikipediasnippet:addinstance' => array(


'riskbitmask' => RISK_XSS,
'captype' => 'write',
'contextlevel' => CONTEXT_COURSE,
'archetypes' => array(
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW
),
'clonepermissionsfrom' =>
'moodle/course:manageactivities'
),

'mod/wikipediasnippet:view' => array(


'captype' => 'read',
'contextlevel' => CONTEXT_MODULE,
'archetypes' => array(
'user' => CAP_ALLOW
)
)
);

And in the language file we add:


/mod/wikipediasnippet/lang/en/wikipediasnippet.php
$string['pluginadministration'] = 'Wikipedia Snippet
Administration';
$string['wikipediasnippetname'] = 'Name';
$string['wikipediasnippetname_help'] = "Name of this
snippet's instance";
$string['wikipediasnippetdesc'] = 'Description';
$string['wikipediasnippetdesc_help'] = "Description of
this snippet's instance";
$string['ws_formsection'] = 'Wikipedia Snippet

156
Chapter 8 Developing Plugins

Settings';
$string['wikis_url'] = 'Wikipedia URL';
$string['wikis_url_help'] = 'including fragment #anchor
<br/> enter #toc for Table of Contents, #preamble for
the introductory text and #infobox for the information
table on the wikipedia page';
$string['wikis_excludeImages'] = 'No Images?';
$string['wikis_excludeImages_help'] = 'Select so that
images on the wikipedia page are stripped out';
$string['wikis_excludeLinks'] = 'No Links?';
$string['wikis_excludeLinks_help'] = 'Select so that any
links on the wikipedia page are stripped out -
leaving just text';
$string['wikis_includecitations'] = 'Include
Citations?';
$string['wikis_includecitations_help'] = 'Select so that
citatations are not stripped out of the snippet.';
$string['wikis_preview_title'] = 'Snippet Preview';
$string['wikis_submit'] = 'Grab Snippet';
$string['wikis_error'] = 'Errors';
$string['wikipediasnippet:addinstance'] = 'Add Wikipedia
Snippets to courses.';
$string['wikipediasnippet:view']= 'View Wikipedia
Snippets';

Increase the version in the plugin’s version file and upgrade the Moodle database for the
capabilities to be incorporated.
Now we can add an instance of our resource module to a course.

157
Introduction to Moodle 3.9+ Plugin Development

Selecting our plugin, we now have a module form with our additional fields visible:

If you have not already done so, now might be a good time to create users – particularly a
‘teacher’ user to ensure the plugin is working as expected. See the ‘Development

158
Chapter 8 Developing Plugins

Environment Setup’ section in the ‘Plugin Development Essentials’ chapter (Chapter 6) for
details about setting up a test course and user.
Don’t fill out the form yet; click the ‘Cancel’ button instead. Now we can go back to our
lib.php file and fill in the functions.
All the data from the form is passed into the add_instance and update_instance
functions as the first parameter. If you need access to the form object, it is the second
parameter. Whilst most of the data will be irrelevant to our plugin, passing the object to the
database layer should not be a problem. Only your data is saved to your table(s). In our
wikipediasnippet_add_instance() function, we only have to add the time
created and the time modified to the data before creating our record.
The function is a bit more elaborate in that, in addition to the time modified field, we also
have to check whether any of the flags – for images and links – have been turned off and
amend the data accordingly.
These are the functions (remember they are replacing the ones already in the file):
/mod/wikipediasnippet/lib.php
function wikipediasnippet_add_instance(
$wikipediasnippet, $mform) {
global $DB;

$thistime = time();
$wikipediasnippet->timecreated = $thistime;
$wikipediasnippet->timemodified = $thistime;

if (empty($wikipediasnippet->nolinks)) {
$wikipediasnippet->nolinks = 0;
}
if (empty($wikipediasnippet->noimages)) {
$wikipediasnippet->noimages = 0;
}
if (empty($wikipediasnippet->includecitations)) {
$wikipediasnippet->includecitations = 0;
}

return $DB->insert_record('wikipediasnippet',
$wikipediasnippet);
}

159
Introduction to Moodle 3.9+ Plugin Development

function wikipediasnippet_update_instance(
$wikipediasnippet, $mform){
global $DB;

$wikipediasnippet->timemodified = time();

$wikipediasnippet->id = $wikipediasnippet->instance;

if (empty($wikipediasnippet->nolinks)) {
$wikipediasnippet->nolinks = 0;
}
if (empty($wikipediasnippet->noimages)) {
$wikipediasnippet->noimages = 0;
}
if (empty($wikipediasnippet->includecitations)) {
$wikipediasnippet->includecitations = 0;
}

return $DB->update_record('wikipediasnippet',
$wikipediasnippet);
}

The next required function wikipediasnippet_delete_instance() is only


passed the record’s id, which allows you to remove all the related records in your tables.
In our case, just remove the record in the wikipediasnippet table. Now $DB-
>delete_records() always returns true for some databases, so, if you want to be
pedantic, you could check if the record exists first and, if not, return false. We won't bother
doing this. Again, this is a replacement for the function already defined in the file.
/mod/wikipediasnippet/lib.php
function wikipediasnippet_delete_instance($id) {
global $DB;
return $DB->delete_records('wikipediasnippet',
array('id' => $id));
}

Before we move on to creating the resource, we can think of enhancing the saving
functionality. Many REST API services expect clients to cache results, at least for a short
time, to avoid overloading the service. This is even more relevant in our case, when you
consider that, if you are in a classroom, there will be 30 or so requests made in a short

160
Chapter 8 Developing Plugins

period. Additionally, the editor will likely want to check the results before letting anyone
use the resource. So, we are going to add some caching functionality to our functions.
Before this though, we need to collect the results. For this, we are going to use a third-party
library. The library is Mukudu's php-class_wikipediasnippet class, available
from the GitHub repository (https://github.com/Mukudu/php-class_wikipediasnippet). Save
the files in the classes/wikipediasnippet/ folder.

Third-Party Libraries
Though obviously written for Moodle core developers, the details on the Moodle ‘Third
Party Libraries’ documentation page (https://docs.moodle.org/dev/Third_Party_Libraries)
are worth noting and following where possible. If you intend to release your plugin, you
will also need to pay heed to the licensing requirement.
Note that the location of the library files is not stipulated, but the use of an XML file
thirdpartylibs.xml, describing the library and its location, is required (see
https://docs.moodle.org/dev/Plugin_files#thirdpartylibs.xml), as well as two other files – a
Moodle readme, which we will create, and a changes file, which we are not going to cover
here.
I am going to use the classes folder for our third-party library. This means that if you
continue to develop your plugin, such as by adding events, your classes can all reside in
one folder. I am going to put the GitHub library in classes/wikipediasnippet/ folder. There
is no prescribed format for this, so my classes/wikipediasnippet/moodle_readme.txt file is:
/mod/wikipediasnippet/classes/wikipediasnippet/moodle_readme.txt
Moodle Third-Party Library Readme
==================================
Mukudu/php-class_wikipediasnippet
1. Download the library from
https://github.com/Mukudu/php-class_wikipediasnippet/arc
hive/master.zip
2. Unzip the files and copy the contents of the php-
class_wikipediasnippet-master folder to the
classes/wikipediasnippet/ folder.

And my thirdpartylibs.xml is a little short on detail, but looks like this:


/mod/wikipediasnippet/thirdpartylibs.xml
<?xml version="1.0" encoding="UTF-8"?>
<libraries>

161
Introduction to Moodle 3.9+ Plugin Development

<library>
<location>classes/wikipediasnippet</location>
<name>
Mukudu/php-class_wikipediasnippet Class
</name>
</library>
</libraries>

We could, if we wanted to, wrap the third-party library in a Moodle class, in order for us to
take advantage of auto-loading; we will discuss this later.
This should now allow us to retrieve the Wikipedia snippet. However, before we go there,
let’s look at the caching. We will also need to use the caching with the viewing
functionality, so we will build it in a separate class.

Caching
There are several aspects to caching. We will not be looking at cache back-ends (see
https://docs.moodle.org/39/en/Caching#Cache_backends) – things like the file system, PHP
session, Memcached, and memory – or cache stores (see
https://docs.moodle.org/39/en/Caching#Cache_stores) – things like MongoDB, APC user
cache (APCu), and Redis. Moodle supports caching plugins, so you could develop a
caching mechanism. Both the caching backends and stores will be configured by the
system managers and administrators, and if you are working on a clustered Moodle
installation, it can get interesting – please see the ‘Server Cluster’ page
(https://docs.moodle.org/39/en/Server_cluster) for more.
In normal operation, the developer does not get any say in where the data gets cached; we
just decide what type of cache we need and, using the API, cache the data. Sometimes
referred to as the ‘The Moodle Universal Cache’ (MUC), the developer’s view of caches is
of three types and defined in /cache/classes/store.php:
 Request cache – MODE_REQUEST – alive for the duration of the request and
cleared at the end of every request
 Session cache – MODE_SESSION – user-specific and alive for the duration of the
user's session
 Application – MODE_APPLICATION – a shared cache that all users can access,
making common data accessible
In our case, we need an application cache, as we want to share the information we retrieve
from Wikipedia.

162
Chapter 8 Developing Plugins

It is possible, but not recommended, to create ad-hoc caches in code. Instead, we will
create our defined cache – an application cache – as recommended. To get started, we need
a definition in db/caches.php, which looks like this:
/mod/wikipediasnippet/db/caches.php
// HEADER STUFF HERE //

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

$definitions = array(
'wikipediadata' => array(
'mode' => cache_store::MODE_APPLICATION,
'simplekeys' => true
)
);

And in the language file, we add another line:


/mod/wikipediasnippet/lang/en/wikipediasnippet.php
$string['cachedef_wikipediadata'] = 'Wikipedia Snippet
Data Cache';

For details of other properties you can set, have a look at the ‘Definition’ section on the
'Cache API – Quick_reference’ page (see https://docs.moodle.org/dev/Cache_API_-
_Quick_reference#Create_a_definition).
We could have attempted to use the ttl property to manage the amount of time that our
cached pages will live, but it comes with this warning:
‘It is strongly recommended that you don't make use of this and instead try to create
an event driven invalidation system. Not all cache stores will support this natively
and there are undesired performance impacts if the cache store does not.’
You would not expect Wikipedia pages to change very often unless they are new pages, so
we can happily cache pages for days if we want to. I am going to go with 24 hours.
To get our cache definition recognised, we need to increase the plugin version and upgrade
Moodle.
Now, in our code, we can set, update, and delete cache objects. Since we have set the
simplekeys property in our definition, we cannot use the snippet URL as our data key.
Additionally, we have different options, whether links and/or images are included. Also,

163
Introduction to Moodle 3.9+ Plugin Development

we need to determine how old the data is and whether we need to renew the cache. You
might think of a better way of doing this, but I am going to use the database record id field
as the key to our data, which will be an array of the timestamp and the actual Wikipedia
data.
For more information about caching, please see the ‘Cache API’ page
(https://docs.moodle.org/dev/Cache_API). It may be worth having a read of the ‘Caching’
page (https://docs.moodle.org/39/en/Caching) for general background about how it all
works together.

Autoloading vs locallib.php
Since we will be requiring functionality in both the lib.php and view.php files, we will need
to use another file that can be included by both scripts. Many plugin developers, including
me, have used the locallib.php file for such purposes and there is nothing wrong with that.
A developer I have recently worked with suggested that the alternative to using an
autoloading class for such purposes is to declare the public methods as static. I like this
idea, so that’s where my preference lies now. Additionally, we can load the third-party
library in this new file which we will save as classes/snippet.php. Generally, the only thing
we will want to do is get the snippet from Wikipedia and return it to the caller, but I have
added in two other ‘helper’ variables. This is the contents of our class:
/mod/wikipediasnippet/classes/snippet.php
// HEADER STUFF HERE //

namespace mod_wikipediasnippet;

class snippet {

public static $cachettl = 24 * 60 * 60;


public static $lasterror = null;

public static function get($snippeturl,


$nolinks = true, $noimages = false,
$includecitations = false, $debug = false) {
// Load the 3rd party library.
require_once(
'wikipediasnippet/wikipediasnippet.inc.php'
);
$getter = new \WikipediaSnippet();
if ($debug) {

164
Chapter 8 Developing Plugins

$getter->setdebugging();
}
$content =
$getter->getWikiContent($snippeturl,
$nolinks, $noimages,
$includecitations, $debug);

if ($getter->error) {
self::$lasterror = $getter->error;
return null;
}
return $content;
}
}

Now we can go back to lib.php and update our functions to incorporate getting the content
and caching it. In wikipediasnippet_add_instance() we have the added
advantage of being able to check there is content from Wikipedia before saving the
module’s record. So, we get the content, save the form data, and then use the new record’s
id as the key for our cache item. Alongside the content, we also cache the creation time as
an array item. We call cache::make('mod_wikipediasnippet',
'wikipediadata') to get our cache to work with and use its set() method to save
our cache item.
Moodle expects either the new record’s id, or false returned. If there is an issue, an
alternative to returning false, which will give the user a generic error, is to throw an
exception and provide better error details. Our function uses the core print_error()
function to throw a moodle_error exception. This is our function now:

/mod/wikipediasnippet/lib.php
function wikipediasnippet_add_instance(
$wikipediasnippet, $mform) {
global $DB;

$thistime = time();
$wikipediasnippet->timecreated = $thistime;
$wikipediasnippet->timemodified = $thistime;

if (empty($wikipediasnippet->nolinks)) {
$wikipediasnippet->nolinks = 0;

165
Introduction to Moodle 3.9+ Plugin Development

}
if (empty($wikipediasnippet->noimages)) {
$wikipediasnippet->noimages = 0;
}

// Get the content.


if ($snippet = mod_wikipediasnippet\snippet::get(
$wikipediasnippet->wikiurl,
$wikipediasnippet->nolinks,
$wikipediasnippet->noimages)) {

$id = $DB->insert_record('wikipediasnippet',
$wikipediasnippet);

// Cache the content.


$cache = cache::make('mod_wikipediasnippet',
'wikipediadata');
$cache->set($id, array(
'time' => $thistime,
'content' => $snippet
)
);
return $id;
} else {
$error =
mod_wikipediasnippet\snippet::$lasterror;
if ($error) {
print_error('contentgeterror',
'wikipediasnippet', null, $error);
} else {
print_error('nowikicontenterror',
'wikipediasnippet');
}
}
}

We need to update the plugin’s strings for the error reporting.


/mod/wikipediasnippet/lang/en/wikipediasnippet.php
$string['contentgeterror'] = 'Error getting content :

166
Chapter 8 Developing Plugins

"{$a}"';

In the wikipediasnippet_update_instance() function, we get the saved


database record, check if any of the relevant data has changed and, if not, check if we have
an expired cache entry before determining if we need to refresh the cache entry by getting
the Wikipedia content.
We get the cache with the cache::make('mod_wikipediasnippet',
'wikipediadata'), then we use the get() method passing the module’s record’s id
to get the relevant entry. We work out how old the cache item is and, if it is older than
expected, we get the new Wikipedia content and update the cache item with the current
time and the Wikipedia content. This is our function:
/mod/wikipediasnippet/lib.php
function wikipediasnippet_update_instance(
$wsnip, $mform){
global $DB;

$wsnip->timemodified = time();

$wsnip->id = $wsnip->instance;

if (empty($wsnip->nolinks)) {
$wsnip->nolinks = 0;
}
if (empty($wsnip->noimages)) {
$wsnip->noimages = 0;
}
if (empty($wsnip->includecitations)) {
$wsnip->includecitations = 0;
}

if ($record = $DB->get_record('wikipediasnippet',
array('id' => $wsnip->id))) {
$updated = false;
if ($wsnip->wikiurl ==
$record->wikiurl){
if ($wsnip->nolinks ==
$record->nolinks) {
if ($wsnip->noimages ==
$record->noimages) {

167
Introduction to Moodle 3.9+ Plugin Development

if (
$wsnip->includecitations
!= $record->includecitations
) {
$updated = true;
}
} else {
$updated = true;
}
}else{
$updated = true;
}
}else{
$updated = true;
}

$cache = cache::make('mod_wikipediasnippet',
'wikipediadata');
// Check if the cache is stale.
if (!$updated) {
$wid = $wsnip->id;
if ($cached = $cache->get($wid)) {
$ttl =
mod_wikipediasnippet\snippet::$cachettl;
if ((time() – $cached['time']) > $ttl) {
$updated = true;
}
} else {
$updated = true;
}
}

if ($updated) {
// Get the content.
if ($snippet =
mod_wikipediasnippet\snippet::get(
$wsnip->wikiurl,
$wsnip->nolinks,
$wsnip->noimages,
$wsnip->includecitations)) {

168
Chapter 8 Developing Plugins

// Cache the content.


$cache->set($wsnip->id,
array(
'time' => time(),
'content' => $snippet
)
);
}
}
} else {
return false;
}

return $DB->update_record('wikipediasnippet',
$wsnip);
}

Deleting the record and the cache entry is quite simple. We get our cache, check if the
cache item exists using the get() method and, if so, call the delete() method passing
the module instance’s id to both functions. Then we delete the database record:
/mod/wikipediasnippet/lib.php
function wikipediasnippet_delete_instance($id) {
global $DB;

$cache = cache::make('mod_wikipediasnippet',
'wikipediadata');
if ($cache->get($id)) {
$cache->delete($id);
}

return $DB->delete_records('wikipediasnippet',
array('id' => $id));
}

Cached items can be removed by clearing Moodle caches as per the ‘Moodle Caching’
section in the ‘Common Plugin Requirements’ chapter (Chapter 7).

169
Introduction to Moodle 3.9+ Plugin Development

Viewing
Now we can turn to viewing our new resource. The course module id is always required to
view the resource. The course module id is not our record's id, so we have to determine it
by getting the course and course module records, which is made easy by the core
get_course_and_cm_from_cmid() function. Then we will retrieve the record from
our table, mainly to ensure the record exists, and then check for a fresh cache item. If the
cache item is 'stale', we will update the cache. Then we display the cached content to the
user.
We could go to the trouble of defining a renderer and templates, which will be covered
elsewhere. But since we are getting HTML content, we just wrap the content in a HTML
<div>. Our file is view.php and looks like this:

/mod/wikipediasnippet/view.php
// HEADER STUFF HERE //

require('../../config.php');

$id = required_param('id', PARAM_INT);


list ($course, $cm) = get_course_and_cm_from_cmid($id,
'wikipediasnippet');
$snippetid = $cm->instance;
$snippetrecord = $DB->get_record('wikipediasnippet',
array('id'=> $snippetid), '*', MUST_EXIST);

// Protect the page.


require_course_login($course);

// Set up the page for display.


$PAGE->set_context(context_course::instance(
$course->id));
$PAGE->set_pagelayout('course');
$PAGE->set_heading($snippetrecord->name);
$PAGE->set_title($snippetrecord->name);
$PAGE->set_url(new
moodle_url('/mod/$snippetrecord->name/view.php'));

// Get cached item.


$updaterequired = false;
$cache = cache::make('mod_wikipediasnippet',

170
Chapter 8 Developing Plugins

'wikipediadata');
if ($cached = $cache->get($snippetrecord->id)) {
if ((time() – $cached['time']) >
mod_wikipediasnippet\snippet::$cachettl) {
$updaterequired = true;
} else {
$content = $cached['content'];
}
} else {
$updaterequired = true;
}

if ($updaterequired) {
// Get the content.
if ($content = mod_wikipediasnippet\snippet::get(
$snippetrecord->wikiurl,
$snippetrecord->nolinks,
$snippetrecord->noimages,
$snippetrecord->includecitations)) {
// Cache the content.
$cache->set($snippetrecord->id, array(
'time' => time(),
'content' => $content
)
);
} else {
if (!empty($cached['content'])) {
$content = $cached['content'];
}
}
}

echo $OUTPUT->header();
echo $OUTPUT->heading($snippetrecord->name);

echo $OUTPUT->box($snippetrecord->intro,
"generalbox center clearfix border");

if (!$content) {
if ($err =

171
Introduction to Moodle 3.9+ Plugin Development

mod_wikipediasnippet\snippet::$lasterror){
$errmsg = get_string('contentgeterror',
'wikipediasnippet', $err);
echo $OUTPUT->notification($errmsg, 'error');
} else {
echo $OUTPUT->notification(
get_string('nowikicontenterror',
'wikipediasnippet'), 'error');
}
} else {
echo \html_writer::tag('div', $content);
}

echo $OUTPUT->footer();

We need to add a further line to the plugin’s strings.


/mod/wikipediasnippet/lang/en/wikipediasnippet.php
$string['nowikicontenterror'] = 'Error: There is no
content to display';

Next, we increase the plugin’s version and attempt to add an instance of the resource in a
course. To get the best effect, you can select a page on Wikipedia with an anchor point, e.g.
https://en.wikipedia.org/wiki/Zimbabwe#Climate.
There is quite a lot more we can develop for our module plugin. For example, we could add
settings functionality to control things like the cache time. However, we are going to leave
the module at this, although I first want to cover module backups and restores, as many
users use course backups and restores regularly, e.g. when setting up new academic years.

Backup and Restore


Our plugin's table structure is very simple and so our backup and restore functionality will
be simple as well. If you have a look at the other modules’ files, you will see that it can
become quite elaborate. This functionality is to take a snapshot of your database for the
modules in the course that is being backed up in such a way that it can be restored in the
same Moodle site or another site. So, for example, your record ids are not included.
The backup files are stored in the backup/moodle2 folder. Older modules will have the old-
style backup and restore functionality in backup/moodle1.
In the ‘Setting up the environment’ on Moodle’s ‘Backup 2.0 for Developers’ page

172
Chapter 8 Developing Plugins

(https://docs.moodle.org/dev/Backup_2.0_for_developers#Setting_up_the_environment),
there are a couple of things that will make it easier to develop the backups without having
to go through the motions via the user interface. I recommend using these tricks whilst
developing.
First of all, we need to tell Moodle that we support the feature
FEATURE_BACKUP_MOODLE2, and so, in the wikipediasnippet_supports()
function in the lib.php file, we add a new line. Our function looks like this:
/mod/wikipediasnippet/lib.php
function wikipediasnippet_supports($feature) {

switch($feature) {
case FEATURE_MOD_INTRO:
return true;
case FEATURE_MOD_ARCHETYPE:
return MOD_ARCHETYPE_RESOURCE;
case FEATURE_BACKUP_MOODLE2:
return true;
default:
return null;
}
}

Our first two files are the backup files,


backup/moodle2/backup_wikipediasnippet_activity_task.class.php and
backup/moodle2/backup_wikipediasnippet_stepslib.php. These files aim to generate the
instructions to recreate our module's data in a new course. This is achieved with an XML
file and you will see how that works.
Our table has the following fields:
 id
 course
 name
 intro
 introformat
 wikiurl

173
Introduction to Moodle 3.9+ Plugin Development

 noimages
 nolinks
 includecitations
 timecreated
 timemodified
The backup_wikipediasnippet_stepslib.php defines the steps to store the data in the
required define_structure() function. I have left a bit of commentary in the source
code to give you an idea of what is involved:
/mod/wikipediasnippet/backup/moodle2/backup_wikipediasnippet_stepslib.php
// HEADER STUFF HERE //

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

/**
* Define the complete structure for backup,
* with file and id annotations.
*/
class backup_wikipediasnippet_activity_structure_step
extends backup_activity_structure_step {

protected function define_structure() {

// Are we including user information?


// If we are – uncomment below.
// $userinfo =
// $this->get_setting_value('userinfo');

// Define each element separated.


$snippet = new backup_nested_element(
'wikipediasnippet', array('id'),
array(
'name',
'intro',
'introformat',
'wikiurl',
'noimages',
'nolinks',

174
Chapter 8 Developing Plugins

'includecitations',
'timecreated',
'timemodified'
)
);

// Build the tree – no connected tables.

// Define the data sources.


$snippet->set_source_table('wikipediasnippet',
array('id' => backup::VAR_ACTIVITYID));

// Define id annotations – None.

// Define file annotations – No files.

// Return the root element ...


// ... wrapped into standard activity structure.
return
$this->prepare_activity_structure($snippet);
}
}

We know that the id and course fields will be changed for the new course. You will also
note that the course field is not defined, the reason for this being that the course id will be
defined by the restore functionality. We use the record’s id as an attribute for the new
backup_nested_element() object, which will act as an identifier for the record.
If the module worked with user data, then we would need to know whether the user has
requested that the backup includes user data. A call the to $this-
>get_setting_value('userinfo') function would tell us if this was the case. If
positive, it is important to annotate the user ids for the tables that hold the user data. There
are other important ids to annotate in order for the restore to know to map things to the
record, e.g. ids for groups and roles.
If our plugin had one to many connected tables, we would define the relationships in the
build tree section. Basically, you would define, for example, an answers element and
then the child element answer with the record structure for each record. This would look
something like this:
$answers = new backup_nested_element('answers');

175
Introduction to Moodle 3.9+ Plugin Development

$answer = new backup_nested_element('answer',


array('id'),
array(
'userid',
'question',
'time',
'answer1',
'answer2'
)
);

// Build the tree.


$snippet->add_child($answers);
$answers->add_child($answer);

The next process is to define the data source tables. Note the use of a
backup::VAR_ACTIVITYID property, which is what the restore will determine at the
time of restore. There are several other properties available, including
backup::VAR_COURSEID (obviously) and backup::VAR_PARENTID (relating to
the parent element’s id). Look at the backup page linked later in this section.
If our module used files, we would need to annotate the file areas involved so the relevant
file areas can be recreated on restore.
The resultant backup XML file generated by simple module looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<activity id="5" moduleid="113"
modulename="wikipediasnippet" contextid="378">
<wikipediasnippet id="5">
<name>Zimbabwe See Also</name>
<intro>
&lt;p dir="ltr" style="text-align: left;"&gt;
Some description.
&lt;br&gt;&lt;/p&gt;choice
</intro>
<introformat>1</introformat>
<wikiurl>
https://en.wikipedia.org/wiki/Zimbabwe#See_also
</wikiurl>
<noimages>1</noimages>

176
Chapter 8 Developing Plugins

<nolinks>1</nolinks>
<timecreated>1587981490</timecreated>
<timemodified>1587982730</timemodified>
</wikipediasnippet>
</activity>

However, before we can generate the backup XML files, we have to create the backup task.
Our task is an extension of the core backup_activity_task class in
backup/moodle2/backup_wikipediasnippet_activity_task.class.php. Generally, we only
need the define_my_steps() method that calls our steps’ functionality, but,
additionally, both the define_my_settings() and encode_content_links()
methods must be overridden. The define_my_settings() is empty in our case. The
encode_content_links() is passed the content to be backed up to allow the backup
to fix any links in the data that will need remapping on restore. You have to return the
updated content which, in our case, we did not have to do anything with:
/mod/wikipediasnippet/backup/moodle2/
backup_wikipediasnippet_activity_task.class.php
// HEADER STUFF HERE //

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

require_once('backup_wikipediasnippet_stepslib.php');

/**
* backup task that provides all the settings and
* steps to perform one complete backup of the activity.
*/
class backup_wikipediasnippet_activity_task
extends backup_activity_task {

/**
* Define (add) settings for this activity.
*/
protected function define_my_settings() {
// No particular settings for this activity.
}

/**
* Define (add) steps this activity can have.

177
Introduction to Moodle 3.9+ Plugin Development

*/
protected function define_my_steps() {
$step = new
backup_wikipediasnippet_activity_structure_step
(
'wikipediasnippet_structure',
'wikipediasnippet.xml'
);
$this->add_step($step);
}

/**
* Code transformations to perform in the activity
* in order to get transportable (encoded) links
*/
static public function encode_content_links(
$content) {
return $content;
}
}

Our module is very simple in that only one table is used and so the backup is not very
involved. For a fuller understanding of module backup, see the Moodle’s ‘Backup 2.0 for
Developers’ page (https://docs.moodle.org/dev/Backup_2.0_for_developers).
Now, we turn to restoring our simple module. Again, there is a useful script on the ‘Restore
2.0 for Developers’ page
(https://docs.moodle.org/dev/Restore_2.0_for_developers#Automatically_triggering_restor
e_in_code) to help whilst developing your backup and restore.
Naturally, the restore is a kind of reversal of the backups. Again, we have two files to deal
with – the task and the steps in
backup/moodle2/restore_wikipediasnippet_activity_task.class.php and
backup/moodle2/restore_wikipediasnippet_stepslib.php, respectively. There is an
additional process with restores for logs, if your plugin uses ones. We cover plugin logs
and events elsewhere.
Starting with backup/moodle2/restore_wikipediasnippet_stepslib.php, we have to define
how the structure in the define_structure() function relates to the backup XML
and process the data by inserting records into our table in the process_wikipedia()
function. If we had child database tables, each table would require a function to restore the

178
Chapter 8 Developing Plugins

records. Likewise, if we had file areas and files, we would have to handle that in the
after_execute() function.
Our records include time fields that we will want to update. We could have left both fields
out of the backup if we had wanted to and then we would just add them in here in the
process_wikipedia() function. If however, your module required a date that was the
offset of the course start date, you can call the class' apply_date_offset() function,
passing in the backed-up value that you want to offset. So, for example, if your module had
a date field that is 30 days after the original course creation date, then that function will
return a date 30 days after the restored course's start date.
These are our restore steps:
/mod/wikipediasnippet/backup/moodle2/restore_wikipediasnippet_stepslib.php
// HEADER STUFF HERE //

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

class restore_wikipediasnippet_activity_structure_step
extends restore_activity_structure_step {

protected function define_structure() {

// No user info for this plugin.


// $userinfo =
// $this->get_setting_value('userinfo');

$paths = array();
$paths[] = new restore_path_element(
'wikipediasnippet',
'/activity/wikipediasnippet');

// Return the paths wrapped into ...


// ... standard activity structure
return
$this->prepare_activity_structure($paths);
}

protected function process_wikipediasnippet($data) {


global $DB;

179
Introduction to Moodle 3.9+ Plugin Development

$data = (object)$data;
// Get the new course id.
$data->course = $this->get_courseid();

// If you need to set dates offset


// start/end dates call ...
// $data->timeopen =
// $this->apply_date_offset(
// $data->timeopen);

// We want to determine new times ...


// ... we could have not backed up the fields.
$data->timecreated = time();
$data->timemodified = time();

// Insert the record.


$newitemid =
$DB->insert_record('wikipediasnippet',data);
// Immediately after inserting record ...
// ... call this.
$this->apply_activity_instance($newitemid);
}
}

Now we set up the task. The log-related methods define_restore_log_rules()


and define_restore_log_rules_for_course() would be declared here. While
our task only requires the define_my_steps() method to call our steps, we are forced
to override three other functions define_my_settings(),
define_decode_contents(), and define_decode_rules(). It is interesting
that there is no define_encode_contents() method in the backup class!

/mod/wikipediasnippet/backup/moodle2/
restore_wikipediasnippet_activity_task.class.php
// HEADER STUFF HERE //

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

require_once('restore_wikipediasnippet_stepslib.php');

class restore_wikipediasnippet_activity_task

180
Chapter 8 Developing Plugins

extends restore_activity_task {

/**
* Define (add) settings for this activity.
*/
protected function define_my_settings() {

// No particular settings for this activity.


}

/**
* Define (add) steps this activity can have.
*/
protected function define_my_steps() {

// Call our steps definition.


$step = new
restore_wikipediasnippet_activity_structure_step
('wikipediasnippet_structure',
'wikipediasnippet.xml');

$this->add_step($step);

/**
* Define the contents in the activity that must be
* processed by the link decoder
*/
static public function define_decode_contents() {

return array();

/**
* Define the decoding rules for links belonging
* to activity to be executed by the link decoder

181
Introduction to Moodle 3.9+ Plugin Development

*/
static public function define_decode_rules() {
return array();
}
}

Have a play with backing up and restoring our module.


And that’s it for our resource module plugin. Activity module plugins are typically more
elaborate, as they interact with grades, events, and other Moodle APIs.
For a fuller understanding of module restore, see Moodle’s ‘Restore 2.0 for Developers’
page (https://docs.moodle.org/dev/Restore_2.0_for_developers).

Sub-plugins, the File API, and JavaScript


We are going to develop an assignment feedback plugin allowing the tutor to record audio
feedback for the student for an assignment submission.
 Activity modules
 HTML editors (since Moodle 2.4)
 Local plugins (since Moodle 2.6)
 Admin tools (since Moodle 2.6)
In some cases, the plugin has several types of sub-plugins. For example, the assignment
activity module has submissions and feedback sub-plugin types.
We are going to develop an assignment feedback plugin that will also have examples of the
use of the File API and some JavaScript functionality, including modules, JQuery, and
AJAX.

Developing an Assignment Feedback Plugin


We are going to develop an assignment feedback plugin allowing the tutor to record audio
feedback to the student for an assignment submission.
Our plugin is going to reside in the /mod/assign/feedback/audio/ folder. As usual, we start
with the version.php file:
/mod/assign/feedback/audio/version.php
// HEADER STUFF HERE //

182
Chapter 8 Developing Plugins

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

$plugin->version = 2020061500;
$plugin->requires = 2020061500; // Requires M3.9.
$plugin->component = 'assignfeedback_audio';

The settings.php file is required and must have one setting named default to indicate if
the plugin should be enabled by default when creating a new assignment. Note the use of
the lang_string class instead of the get_string() call.

/mod/assign/feedback/audio/settings.php
// HEADER STUFF HERE //

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

$settings->add(
new admin_setting_configcheckbox(
'assignfeedback_audio/default',
new lang_string('default',
'assignfeedback_audio'),
new lang_string('default_help',
'assignfeedback_audio'), 0)
);

Our language file lang/en/assignfeedback_audio.php defines the usual pluginname


string as well as the two used in the settings.php file and a further two, enabled and
enabled_help, which are required.

/mod/assign/feedback/audio/lang/en/assignfeedback_audio.php
// HEADER STUFF HERE //

$string['pluginname'] = 'Audio feedback';


$string['default'] = 'Enabled by default';
$string['default_help'] = 'If set, this feedback method
will be enabled by default for all new assignments.';
$string['enabled'] = 'Audio feedback';
$string['enabled_help'] = 'If enabled, the tutor will be
able to provide audio feedback ';

These three files only will allow us to install the plugin. Go ahead and install it, and in the

183
Introduction to Moodle 3.9+ Plugin Development

settings form, check the ‘Enabled by default’ checkbox.

At this stage, we could use the XMLDB Tool to define our table(s), but we do not require it
for this plugin.
Assignment feedback plugins extend the assign_feedback_plugin class, which is
defined in /mod/assign/feedbackplugin.php. The extended class name needs to be in the
format assign_feedback_<pluginname>, so in our case, we would expect
assign_feedback_audio to be in the plugin’s locallib.php file. Following on from
my preference of keeping my classes in the classes directory, I created the locallib.php file
with a single line requiring my class file in the classes/assign_feedback_audio.php file, but
you can use the locallib.php file if you prefer.
/mod/assign/feedback/audio/locallib.php
// HEADER STUFF HERE //

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

require_once('classes/assign_feedback_audio.php');

Our class needs to override the get_name() method of the class, so the file starts like
this:
/mod/assign/feedback/audio/classes/assign_feedback_audio.php
// HEADER STUFF HERE //

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

class assign_feedback_audio extends


assign_feedback_plugin {

public function get_name() {


return get_string('pluginname',

184
Chapter 8 Developing Plugins

'assignfeedback_audio');
}
}

At this point, if you created or edited a course assignment, you will have the option to
choose our plugin.

Go ahead and edit one of the test assignments in the test course; change its name so you
can find it again, select audio feedback, and save this.
Now if we wanted, we could get and save some settings for our plugin via the two
methods: get_settings(), which gets the Moodle form as a parameter, and
save_settings(), which gets the form data as its parameter. So, if we want to, say,
restrict the length of the feedback to between 1 and 60 minutes, we could do this:
/mod/assign/feedback/audio/classes/assign_feedback_audio.php
public function get_settings(MoodleQuickForm $mform) {
$mform->addElement('duration',
'assignfeedback_audio_timeallowed',
get_string('timeallowedprompt',
'assignfeedback_audio'),
array(
'defaultunit' => MINSECS,
'units' => array(MINSECS)
)
);

$mform->addHelpButton(
'assignfeedback_audio_timeallowed',

185
Introduction to Moodle 3.9+ Plugin Development

'timeallowedprompt',
'assignfeedback_audio');
$mform->setDefault(
'assignfeedback_audio_timeallowed', 1);
// Hide if feedback plugin is disabled.
$mform->hideIf('assignfeedback_audio_timeallowed',
'assignfeedback_audio_enabled',
'notchecked');
}

public function save_settings(stdClass $data) {


$this->set_config('duration',
$data->assignfeedback_audio_timeallowed);
return true;
}

We also have to define some strings for our settings form:


/mod/assign/feedback/audio/lang/en/assignfeedback_audio.php
$string['timeallowedprompt'] = 'Maximum recording time';
$string['timeallowedprompt_help'] = 'The time limit for
the feedback recording.';

We need to clear the cache to update the strings as per the ‘Moodle Caching’ section in the
‘Common Plugin Requirements’ chapter (Chapter 7).
Now we have an additional field when we select our audio plugin. Note that all the settings
for all the selected feedback plugins are shown. In the following illustration, both our
plugin and the comments feedback plugin have settings:

Now that we have the plugin available, we need to collect the feedback from the tutor. We

186
Chapter 8 Developing Plugins

do this by adding fields to the feedback form and then saving it. Three functions are
involved, namely get_form_elements_for_user(),
is_feedback_modified(), and save(). All three functions are passed the grade
object and the form data, with get_form_elements_for_user() additionally
getting the Moodle form and the user id.
We are going to use JavaScript’s MediaDevices interface (see
https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices) and its mediaRecorder
object (see
https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder/MediaRecorder)
wrapped in a JavaScript module, as previously covered in the ‘JavaScript Framework’
section in the ‘Plugin Development Background’ chapter (Chapter 4). I am not going to go
into the mechanics of the actual JavaScript to collect the audio input but I will point out a
few things.
The actual script can be downloaded from the plugin’s repository at
https://github.com/Mukudu-Publishing/moodle-assignfeedback_audio/blob/master/amd/
src/record-me.js. The file should be saved in the amd/src/ folder and ‘minimised’ with the
GRUNT (The JavaScript Task Runner) tool. This creates the minimised JavaScript for
browser delivery. Alternatively, just download all the relevant files from the repository
https://github.com/Mukudu-Publishing/moodle-assignfeedback_audio/tree/master/amd/.
The first few lines in the JavaScript file are:
/mod/assign/feedback/audio/amd/src/record-me.js
// HEADER STUFF HERE //
/**
* @module assignfeedback_audio/record-me
*/
define(['jquery', 'core/str', 'core/notification'],
function($, str, notification) {
return {
init: function(posturl, maxduration, gradeid,
contextid) {

The define line maps the ‘required’ modules to variables passing into the encompassing
function. These are:
 jquery mapped to $ – the JQuery object
 core/str mapped to str – one of the core JavaScript Modules – see the
‘Language Strings’ section on the ‘Useful Core JavaScript Modules’ page

187
Introduction to Moodle 3.9+ Plugin Development

(https://docs.moodle.org/dev/Useful_core_Javascript_modules#Language_strings_.
28core.2Fstr.29)
 core/notification mapped to notification – another core JavaScript
Module – see the ‘Notifications’ section on the ‘Useful Core JavaScript Modules’
page
(https://docs.moodle.org/dev/Useful_core_Javascript_modules#Notifications_.28co
re.2Fnotification.29)
‘Hey, where is the core AJAX Module?’ I hear you ask. Well, the core/ajax module is
developed to work with internal web services exclusively and so the script will use the
JQuery.ajax() functionality instead. AJAX is being used to send the audio file to the
server. With the support for uploading files from Moodle forms done via the custom file
manager functionality, it is easier to do it this way.
We have required the core/str module so we can pass strings into the module. The
additional strings required are defined in the usual place:
/mod/assign/feedback/audio/lang/en/assignfeedback_audio.php
$string['nomicrophineallowed'] = 'You have not allowed
access to your microphone.';
$string['feedbackremoved'] = 'The feedback data has been
cleared.';
$string['nomediarecordingsupport'] = 'Your browser does
not support audio recording';
$string['mediasavefailed'] = 'Your recording has not
been saved – Error';
$string['erroremptyfile'] = 'File is empty';
$string['maxtimeallowedreached'] = 'Max recording time
has been reached.';
$string['medianocapture'] = 'No recording was
captured.';
$string['ok'] = 'OK';

The amd/record_me.js’ init() line is the module’s only function call and it expects four
parameters url, maxduration, gradeid, and contextid, which are passed in from
the get_form_elements_for_user() function. The JavaScript module is loaded by
the line:
$PAGE->requires->js_call_amd('assignfeedback_audio/record-
me', 'init', $params)

188
Chapter 8 Developing Plugins

In this line, init is the function variable and $params is an array of the expected
parameters, which could also have just been a simple single dimension array. After loading
the module and passing it the required parameters, the function creates form elements,
including four buttons on the form to allow the tutor to record the feedback. Our function
looks like this:
/mod/assign/feedback/audio/classes/assign_feedback_audio.php
public function get_form_elements_for_user($grade,
MoodleQuickForm $mform, stdClass $data, $userid){
global $PAGE;

$params = array(); // To pass to javascript.


$posturl = new moodle_url(
'/mod/assign/feedback/audio/getrecording.php');
$params['posturl'] = $posturl->out(false);
$params['maxduration'] =
$this->get_config('duration');
$params['gradeid'] = $grade ? $grade->id : 0;
$params['contextid'] =
$this->assignment->get_context()->id;

$PAGE->requires->js_call_amd(
'assignfeedback_audio/record-me', 'init',
$params);

// Title.
$mform->addElement('header', 'general',
get_string('pluginname',
'assignfeedback_audio'));

// Maybe show existing feedback here?

$mform->addElement('static', 'description', '',


get_string('staticprompttext',
'assignfeedback_audio',
format_time(
$this->get_config('duration'))));

$mform->addElement('html',
\html_writer::div('',

189
Introduction to Moodle 3.9+ Plugin Development

'alert alert-danger feedbackinfoarea',


array(
'style' => 'display: none',
'role' => 'alert'))
);

$mform->addElement('hidden', 'fileref', '',


array('id' => 'id_fileref'));
$mform->setType('fileref', PARAM_TEXT);

$buttonarray=array();
$buttonarray[] =& $mform->createElement('button',
'startbtn',
get_string('startrecordingprompt',
'assignfeedback_audio'),
array('class' => 'btn btn-default startbtn',
'disabled' => false));
$buttonarray[] =& $mform->createElement('button',
'stopbtn',
get_string('stoprecordingprompt',
'assignfeedback_audio'),
array('class' => 'btn btn-default stopbtn',
'disabled' => false));
$buttonarray[] =& $mform->createElement('button',
'listenbtn',
get_string('listenrecordingprompt',
'assignfeedback_audio'),
array('class' => 'btn btn-default listenbtn',
'disabled' => false));
$buttonarray[] =& $mform->createElement('button',
'deletebtn',
get_string('removerecordingprompt',
'assignfeedback_audio'),
array('class' => 'btn btn-default deletebtn',
'disabled' => false));
$mform->addGroup($buttonarray, 'buttonar', '',
array(' '), false);

$mform->addElement('html', '<br />');

190
Chapter 8 Developing Plugins

return true;
}

Note we have not checked for existing feedback, but we will come back to that. We define
the additional language strings we need:
/mod/assign/feedback/audio/lang/en/assignfeedback_audio.php
$string['startrecordingprompt'] = 'Record Feedback
Audio';
$string['listenrecordingprompt'] = 'Listen to
recording';
$string['removerecordingprompt'] = 'Remove recording';
$string['staticprompttext'] = 'Maximum recording time
is {$a}';
$string['stoprecordingprompt'] = 'Stop Recording';
$string['stoplisteningprompt'] = 'Stop Listening';

Increase the plugin version and upgrade or clear the cache now to update the strings.
Now, if you log in as a teacher and click on the assignment and choose to grade it, you will
be taken to the grading page, where you will be presented with the feedback audio buttons.
Please see the Moodle ‘Using Assignment’ page
(https://docs.moodle.org/39/en/Using_Assignment) for more about the process.
The buttons allow the tutor to record an audio clip, stop recording, listen to the recording,
and delete the recording if they change their minds. Re-recording will replace previously
recorded data. It is at the point that the tutor stops the recording that the audio is sent back
to the server where it is saved as a draft. The draft file id is returned and is submitted when
the tutor saves the grading.

You might notice in the JavaScript that the recording deletion does not delete the draft file.
Since it is a draft file, Moodle housekeeping will delete the draft files at some point and so
we are saved that hassle.

191
Introduction to Moodle 3.9+ Plugin Development

The AJAX call from our module is to our responding script called getrecording.php. Since
this script is specifically dealing with AJAX requests, we tell Moodle about it by defining it
as such. Responses are expected to be JSON.
The sole purpose of getrecording.php is to receive the audio recording and save it to a draft
file, returning a reference for submission when and if the grading is updated. We know that
the audio will have to be served when required and that functionality will need to come
from a lib.php file as per the File API, so I have added the related functions to save the file
there. So, our getrecording.php looks like this:
/mod/assign/feedback/audio/getrecording.php
// HEADER STUFF HERE //

define('AJAX_SCRIPT', true);

require(__DIR__ . '/../../../../config.php');
require_once(__DIR__ . '/lib.php');

if ($action = optional_param('action', '', PARAM_TEXT)){


switch ($action) {
case 'save' :
// save file.
if (empty($_FILES)) {
send_ajax_error('nofilesent',
'assignfeedback_audio');
}
if (!$file = $_FILES['audiofile']) {
send_ajax_error('nofilesent',
'assignfeedback_audio');
}
if (!$gradeid = optional_param(
'gradeid', 0, PARAM_INT)) {
send_ajax_error('nogradeid',
'assignfeedback_audio');
}
if (!$contextid = optional_param(
'contextid', 0, PARAM_INT)) {
send_ajax_error('nocontextid',
'assignfeedback_audio');
}
try {

192
Chapter 8 Developing Plugins

$tempfileref = save_uploaded_audio(
$file, $gradeid, $contextid);
} catch ( moodle_exception $e ) {
send_ajax_error('moodleerrormsg',
'assignfeedback_audio',
$e->getMessage());
}
return_ajax_response(
array('fileref' => $tempfileref)
);
break;
default:
// Raise Error.
send_ajax_error('invalidactionspecified',
'assignfeedback_audio');
}
} else {
send_ajax_error('noactionspecified',
'assignfeedback_audio');
}

As usual, we add the necessary strings:


/mod/assign/feedback/audio/lang/en/assignfeedback_audio.php
$string['nofilesent'] = 'No file has been sent.';
$string['noactionspecified'] = 'No action has been
specified.';
$string['invalidactionspecified'] = 'Invalid action has
been specified.';
$string['nogradeid'] = 'Grade Id must be specified';
$string['nocontextid'] = 'Context Id must be specified';
$string['moodleerrormsg'] = '{$a}';

In lib.php, we have to define three functions to support the get recording script. The
send_ajax_error() and return_ajax_response() are simple functions to
communicate JSON responses back to the AJAX. The save_uploaded_audio()
function handles the uploaded audio and creates a Moodle draft file using the File API. The
advantage of a draft file is that, if the user does not save the grading form or the user
decides against leaving audio feedback, Moodle core functionality will clean out the file for
us.

193
Introduction to Moodle 3.9+ Plugin Development

The function gets a draft id number, creates a suitable filename (the submitted file name is
‘blob’ which is not very useful), loads the file storage class, and then recreates the
uploaded temp file in the Moodle file store. Our lib.php so far is:
/mod/assign/feedback/audio/lib.php
// HEADER STUFF HERE //

defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . '/filelib.php');

function send_ajax_error($strorstrname,
$plugin = 'assignfeedback_audio',
$params = array()) {

$errorcontent = array(
'status' => 'error',
'message' => get_string($strorstrname,
$plugin, (object) $params)
);
echo(json_encode($errorcontent));
exit(0);
}

function return_ajax_response($params) {
$params = (array) $params;
$params['status'] = 'success';
echo json_encode($params);
}

function save_uploaded_audio($file, $gradeid,


$contextid) {
if (file_exists($file['tmp_name'])) {
if (filesize($file['tmp_name'])) {

// Now we save file as a draft file.


$draftitemid =
file_get_unused_draft_itemid();
$filename = basename($file['tmp_name'],
'.webm') . '.webm';

194
Chapter 8 Developing Plugins

$fs = get_file_storage();

$filerecord = array(
'contextid' => $contextid,
'component' => 'user',
'filearea' => 'draft',
'itemid' => $draftitemid,
'filepath' => '/',
'filename' => $filename,
);
$fs->create_file_from_pathname($filerecord,
$file['tmp_name']);
return $draftitemid;
} else {
send_ajax_error('erroremptyfile',
'assignfeedback_audio');
}
} else {
send_ajax_error('nofilesent',
'assignfeedback_audio');
}
return null;
}

Whilst we are here, we may as well add another function to lib.php. The File API requires
plugins that work with files to define a <plugin>_audio_pluginfile() function
to serve the file. We have seen this functionality in a previous section. So, we can add our
assignfeedback_audio_pluginfile() function too:

/mod/assign/feedback/audio/lib.php
function assignfeedback_audio_pluginfile($course, $cm,
$context, $filearea, $args, $forcedownload,
array $options=array()) {

// Check the contextlevel is as expected.


if ($context->contextlevel != CONTEXT_MODULE) {
return false;
}

// Make sure the filearea is used by the plugin.

195
Introduction to Moodle 3.9+ Plugin Development

if ($filearea !== 'feedback') {


return false;
}

// User is logged in and has access to the course?


require_login($course, true);

$itemid = array_shift($args); // 1st item in $args.

// Extract the filepath from the $args array.


$filename = array_pop($args); // Last item in $args.
if (!$args) {
$filepath = '/'; // empty $args => '/'.
} else {
$filepath = '/' . implode('/', $args) . '/';
}

// Retrieve the file from the Files API.


$fs = get_file_storage();
$file = $fs->get_file($context->id,
'assignfeedback_audio',
$filearea, $itemid, $filepath, $filename);
if (!$file) {
return false; // The file does not exist.
}

// We can now send the file back to the browser.


send_stored_file($file, 1440, 0,
$forcedownload, $options);
}

Now we can swing back to our class file classes/assign_feedback_audio.php or


locallib.php (if that is what you have used). We are going to deal with the functionality
once the tutor has saved the grading form, namely the is_feedback_modified() and
save() functions mentioned earlier.
The assignment plugin will call our plugin’s is_feedback_modified() function to
find out if the feedback has been modified. We check this by determining if another
recording has been made by checking for the draft file id in the submitted data. If we return
true, then the assignment will call the save() function.

196
Chapter 8 Developing Plugins

The save() function retrieves the draft file using the submitted draft id and copies it into
our plugin's file area, ensuring that any existing audio file is deleted first, so we only ever
have one feedback audio. I have defined a private function to return the file attributes or
parameters, as we will be working with the saved file in several future functions. Here are
the additional functions:
mod/assign/feedback/audio/locallib.php
OR /mod/assign/feedback/audio/classes/assign_feedback_audio.php
public function is_feedback_modified(stdClass $grade,
stdClass $data) {
// We know we have a change if the fileref is set.
return (!empty($data->fileref));
}

public function save(stdClass $grade, stdClass $data) {


$contextid = $this->assignment->get_context()->id;

$fs = get_file_storage();
// Get the draft file submitted.
$files = $fs->get_area_files($contextid, 'user',
'draft', $data->fileref, null, false);
if ($draftfile = array_pop($files)) {
$newfilerecord = array(
'contextid' => $contextid,
'component' => 'assignfeedback_audio',
'filearea' => 'feedback',
'itemid' => $grade->id,
'filepath' => $draftfile->get_filepath(),
'filename' => $draftfile->get_filename()
);

// Should replace old recording before saving.


if (!$this->is_empty($grade)) {
$allparams =
$this->getfileparams($grade->id);
$params = array_slice($allparams, 0,
(count($allparams) – 2));
$fs->delete_area_files(...$params);
}
$fs->create_file_from_storedfile($newfilerecord,

197
Introduction to Moodle 3.9+ Plugin Development

$draftfile);
}

return true;
}

private function getfileparams($gradeid,


$includedirs = false) {
return array(
$this->assignment->get_context()->id,
'assignfeedback_audio',
'feedback',
$gradeid,
null,
$includedirs);
}

Now we need to deliver feedback to the student. Grade and record some feedback for a
student and note the student's details so you can log in as them, then navigate back to the
course.
The assignment plugin makes two specific calls to display the feedback –
view_summary() and view(). The view() function is called if the
view_summary() sets the passed in by reference $showviewlink variable to true,
meaning that there is more content to show. It is designed for lengthy feedback and an
example of its use can be found in the file feedback plugin. We are only going to display an
audio element for the user to listen to, so we will return the HTML to the
view_summary() call. This will result in the student’s view looking like this:

198
Chapter 8 Developing Plugins

Since we can listen to our feedback, we can go back and add some functionality to allow
the tutor to hear the recorded feedback, if it exists when they edit the grade. To support
both cases, I defined a private function render_audio_controls(), which gets the
audio file's URL and creates an HTML snippet for an audio control for display. Of course,
I could have used a renderer and a template, but this would be a bit overkill for now. The
URL is in the form
/pluginfile.php/385/assignfeedback_audio/feedback/2/phpxgdND4.webm, where
pluginfile.php is the functionality that will eventually call our
assignfeedback_audio_pluginfile() function in lib.php.
Our function view_summary() simply returns the output from
render_audio_controls(), and we will revisit our
get_form_elements_for_user() function to display the audio controls if there is
existing feedback. Before the view_summary() function is called, a call is made to the
is_empty() function to determine if there is any feedback to display. We check for an
existing audio file and return the result:
/mod/assign/feedback/audio/classes/assign_feedback_audio.php
public function is_empty(stdClass $submissionorgrade) {
$fs = get_file_storage();
$fileparams = $this->getfileparams(
$submissionorgrade->id);
return $fs->file_exists(...$fileparams);
}

private function render_audio_controls($gradeid,

199
Introduction to Moodle 3.9+ Plugin Development

$title = '') {

$content = '';
$fileparams = $this->getfileparams($gradeid);
$fs = get_file_storage();
if ($files = $fs->get_area_files(...$fileparams)) {
if ($feedbackfile = array_pop($files)) {
$feedbackfileurl =
\moodle_url::make_pluginfile_url(
$feedbackfile->get_contextid(),
$feedbackfile->get_component(),
$feedbackfile->get_filearea(),
$feedbackfile->get_itemid(),
$feedbackfile->get_filepath(),
$feedbackfile->get_filename()
);

// Audio player, could be a rendender.


$content = "
<div>
<audio controls src='$feedbackfileurl'>
Your browser does not support the audio tag.
</audio>
</div>";

if ($title) {
$content = "<div><h5>$title</h5></div>\n".
$content;
}
}
}
return $content;
}

public function view_summary(stdClass $grade,


& $showviewlink) {
return $this->render_audio_controls($grade->id);
}

The get_form_elements_for_user() function determines if the grading is being

200
Chapter 8 Developing Plugins

edited by checking for a grade id and then attempting to get the html content to add to the
form. The whole function now looks like this:
/mod/assign/feedback/audio/classes/assign_feedback_audio.php
public function get_form_elements_for_user($grade,
MoodleQuickForm $mform, stdClass $data, $userid){
global $PAGE;

$params = array(); // pass to javascript.


$posturl = new moodle_url(
'/mod/assign/feedback/audio/getrecording.php');
$params['posturl'] = $posturl->out(false);
$params['maxduration'] =
$this->get_config('duration');
$params['gradeid'] = $grade ? $grade->id : 0;
$params['contextid'] =
$this->assignment->get_context()->id;

$PAGE->requires->js_call_amd(
'assignfeedback_audio/record-me', 'init',
$params);

// Title.
$mform->addElement('header', 'general',
get_string('pluginname',
'assignfeedback_audio'));

// Show existing feedback.


if ($params['gradeid']) {
$divtitle = get_string(
'exisitingfeedbackprompt',
'assignfeedback_audio');
if ($html = $this->render_audio_controls(
$params['gradeid'], $divtitle)) {
$mform->addElement('html', $html);
}
}

$mform->addElement('static', 'description', '',


get_string('staticprompttext',

201
Introduction to Moodle 3.9+ Plugin Development

'assignfeedback_audio',
format_time($this->get_config('duration')))
);

$mform->addElement('html',
\html_writer::div('',
'alert alert-danger feedbackinfoarea',
array(
'style' => 'display: none',
'role' => 'alert'))
);

$mform->addElement('hidden', 'fileref', '',


array('id' => 'id_fileref'));
$mform->setType('fileref', PARAM_TEXT);

$buttonarray=array();
$buttonarray[] =& $mform->createElement('button',
'startbtn',
get_string('startrecordingprompt',
'assignfeedback_audio'),
array('class' => 'btn btn-default startbtn',
'disabled' => false));
$buttonarray[] =& $mform->createElement('button',
'stopbtn',
get_string('stoprecordingprompt',
'assignfeedback_audio'),
array('class' => 'btn btn-default stopbtn',
'disabled' => false));
$buttonarray[] =& $mform->createElement('button',
'listenbtn',
get_string('listenrecordingprompt',
'assignfeedback_audio'),
array('class' => 'btn btn-default listenbtn',
'disabled' => false));
$buttonarray[] =& $mform->createElement('button',
'deletebtn',
get_string('removerecordingprompt',
'assignfeedback_audio'),
array('class' => 'btn btn-default deletebtn',

202
Chapter 8 Developing Plugins

'disabled' => false));


$mform->addGroup($buttonarray, 'buttonar', '',
array(' '), false);

$mform->addElement('html', '<br />');

return true;
}

We add an additional string for the audio control’s title:


/mod/assign/feedback/audio/lang/en/assignfeedback_audio.php
$string['exisitingfeedbackprompt'] = 'Existing Audio
Feedback';

Now clear the cache to update the strings and return to grade the user you earlier graded
and recorded feedback for. You should now be able to hear your feedback.

Log in as the relevant student and see what the student will see when there is feedback.
This is where we are going to leave this rather long section. There is, as usual, much more
work to be done to this plugin in order for it to make it out there in the wild.

Enrolment
For standard users, access to courses is managed by enrolments. Moodle provides different
ways for enrolments to be managed. Enrolments and roles are separate, so you could be
enrolled and not have a specified role or you could have a role in the course and not be
enrolled. Usually, enrolment plugins will assign a role for the course. For an enrolment
method to be available in a course, the plugin must be enabled. Manual enrolments are
automatically enabled when a course is created.

203
Introduction to Moodle 3.9+ Plugin Development

For more information about enrolment plugins, please visit the Moodle ‘Enrolment
Plugins’ page (https://docs.moodle.org/dev/Enrolment_plugins).

Developing an Enrolment Plugin


We are going to develop an enrolment plugin called catalogue (note the British
spelling). This plugin will allow a user to browse all the courses that have ‘enabled’ the
plugin and enrol themselves as a student on a course. The plugin could be extended to
encompass e-commerce functionality, but we will not be going that far.
To kick-off, we are going to start with a set of skeleton files that will allow us to install the
plugin. To start, create a new folder called /enrol/catalogue and remember to watch out for
your file permissions.
The minimum required files for enrol plugins are version.php,
lang/en/enrol_catalogue.php, and lib.php.
Our version.php looks like this:
/enrol/catalogue/version.php
// HEADER STUFF HERE //

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

$plugin->version = 2020061500;
$plugin->requires = 2020061500; // Requires M3.9
$plugin->component = 'enrol_catalogue';

The English language file lang/en/enrol_catalogue.php looks like this:


/enrol/catalogue/lang/en/enrol_catalogue.php
// HEADER STUFF HERE //

$string['pluginname'] = 'Catalogue Enrolment';

The lib.php file is also required. The file must define a class derived from the
enrol_plugin class that is defined in lib/enrollib.php, as discussed in the ‘Enrolment
API’ section in the ‘Common APIs’ chapter (Chapter 5). Our lib.php file then looks like
this:
/enrol/catalogue/lib.php
// HEADER STUFF HERE //

204
Chapter 8 Developing Plugins

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

class enrol_catalogue_plugin extends enrol_plugin {


}

The format for the class name is important. The _plugin part of the name must be defined,
even though, by Frankenstyle standards, enrol_catalogue should have sufficed.
These three files alone will allow you to install the plugin, so go ahead and install. With the
enrol plugin needing to be enabled in the Moodle instance, this is done by navigating to
‘Site administration->Plugins->Enrolments->Manage enrol plugins’
(/admin/settings.php?section=manageenrols) and clicking on the eye icon. However, even
after this, the plugin cannot be activated in the course administration. We still have to make
it possible to add an instance of our plugin in the course, and this functionality has to be
provided by our class.
The obvious function to do this is can_add_instance(), which is passed the course
id. However, adding the function and returning true still does not work. You also have to
return true from another function, use_standard_editing_ui(). Since Moodle 3.1,
this function is now the recommended way for enrol plugins to define their add/edit
interfaces, so I will not go into what happens if you do not return true from that function.
In the can_add_instance() function, we want to allow only one instance of the
plugin per course and we need to check if the user has the capability to manage enrolments.
We will come back to this issue when we look at the capabilities in due course, but, for
now, we can use that moodle/course:enrolconfig capability.

/enrol/catalogue/lib.php
public function use_standard_editing_ui() {
return true;
}

public function can_add_instance($courseid) {


$context = context_course::instance($courseid,
MUST_EXIST);

// We do not want more than 1 instance in a course.


if (!enrol_is_enabled($this->get_name())) {
return false;
}

205
Introduction to Moodle 3.9+ Plugin Development

if (!has_capability('moodle/course:enrolconfig',
$context)) {
return false;
}

return true;
}

Now we have an option to add an instance of our plugin in the course editing functionality.
I suggest you use the course created earlier for the banner format or create a new course
and use that (you will see why later). Navigate to the course and select ‘More’ in the gear
icon (Course Management) drop-down. Then on the ‘Users’ tab, click on the ‘Enrolment
methods’ link. The ‘Add method’ select box will display the opportunity to select our
plugin.

Now if you attempt to create an instance of the plugin, you will get an error message:
Sorry, but you do not currently have permissions to do that
([[catalogue:config]]).
At this point, we have to look into the capabilities in order to proceed. As we wriggled out
of the check for the permission in the can_add_instance(), we may as well define
any others we will need at the same time. To define the capabilities required for this plugin,
we need to add a db/access.php file. This file is described in more detail in the ‘Access
API’ section in the ‘Common APIs’ chapter (Chapter 5).
Since we expect users to enrol themselves by choosing a course from our catalogue, we
need to allow them to enrol themselves. We do not have to specify the enrol capability,

206
Chapter 8 Developing Plugins

since all users will be able to enrol themselves. Unenrolment is a drastic step in Moodle, as
all user data is deleted as well. Hence, we should not allow users to unenroll themselves
but, instead, only let teachers/managers deal with unenrolments.
The manage capability allows the defined roles to manage the plugin’s enrolments in the
enrolment administration pages.
This is what the db/access.php file looks like:
/enrol/catalogue/db/access.php
// HEADER STUFF HERE //

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

$capabilities = array(
/* Add, edit or remove catalogue enrol instance. */
'enrol/catalogue:config' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_COURSE,
'archetypes' => array(
'editingteacher' => CAP_ALLOW,
'manager' => CAP_ALLOW,
)
),

/* Manage enrolments of users. */


'enrol/catalogue:manage' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_COURSE,
'archetypes' => array(
'manager' => CAP_ALLOW,
'editingteacher' => CAP_ALLOW,
)
),

/* Unenrol anybody (including self) – */


'enrol/catalogue:unenrol' => array(
'captype' => 'write',
'contextlevel' => CONTEXT_COURSE,
'archetypes' => array(
'manager' => CAP_ALLOW,

207
Introduction to Moodle 3.9+ Plugin Development

'editingteacher' => CAP_ALLOW,


)
),
);

You also need to define the capability strings in the language file:
/enrol/catalogue/lang/en/enrol_catalogue.php
$string['catalogue:config'] = 'Configure Catalogue
Enrolment';
$string['catalogue:manage'] = 'Manage the Catalogue
Enrolments';
$string['catalogue:unenrol'] = 'Catalogue Enrolment
Unenrol';

Changing your plugin capabilities requires that you update the version of the plugin. So,
increase the version and update the plugin in your Moodle site.
Now if you attempt to add an enrolment instance to a course, you will be presented with an
empty form.

When you click on the ‘Add method’ button, you will get an error
‘enrol_plugin::edit_instance_validation() is missing. This
plugin has no validation!’. Since we have used the
use_standard_editing_ui() function, we need to define form fields to collect the

208
Chapter 8 Developing Plugins

instance data in the edit_instance_form() function and, additionally, validate the


data in the edit_instance_validation() function. However, experimentation
proved that just overriding the edit_instance_validation() function and
returning an empty array removes the error message.
However, we are going to define a form field and validate the input. Our enrol plugin will
allow the administrator the opportunity to name the instance, borrowed from
enrol_self.

/enrol/catalogue/lib.php
public function edit_instance_form($instance, $mform,
$context) {
$nameattribs = array('size' => '20',
'maxlength' => '255');
$mform->addElement('text', 'name',
get_string('custominstancename', 'enrol'),
$nameattribs);
$mform->setType('name', PARAM_TEXT);
$mform->addRule('name',
get_string('maximumchars', '', 255),
'maxlength', 255, 'server');
}

public function edit_instance_validation($data, $files,


$instance, $context) {
$errors = array();
if (core_text::strlen($data['name']) > 255) {
$errors['name'] =
get_string('err_maxlength', 'form', 255);
}
return $errors;
}

If you have debugging still on, you will notice an error: ‘The enrolment plugin
'catalogue' should override the function
can_hide_show_instance()’ on the course’s ‘Enrolments Methods’ page.
For the can_hide_show_instance() function and to allow instances of the plugin to
be added to courses by course managers (Editing Teachers and Managers), we have the
config capability. Our can_hide_show_instance() function is:

209
Introduction to Moodle 3.9+ Plugin Development

/enrol/catalogue/lib.php
public function can_hide_show_instance($instance) {
$context = context_course::instance(
$instance->courseid);

if (!has_capability('enrol/catalogue:config',
$context)) {
return false;
}
return true;
}

We then update the can_add_instance() function to check for this capability:

/enrol/catalogue/lib.php
public function can_add_instance($courseid) {
$context = context_course::instance($courseid,
MUST_EXIST);

// we do not want more than 1 instance in a course


if (!enrol_is_enabled($this->get_name())) {
return false;
}

if (!has_capability('moodle/course:enrolconfig',
$context)
or !has_capability('enrol/catalogue:config',
$context)) {
return false;
}

return true;
}

Now that we have added our enrolment plugin to the course, it is time to provide a way for
students to browse our catalogue and choose to enrol in those courses that allow them to. I
am going to use index.php to display the catalogue. To simplify the example, we are not
going to use a renderer and templates to display the page.
Our file will do several things using the Enrolment API and will display the page using
both the Output and Page APIs, which I will not explain here, as their use is covered

210
Chapter 8 Developing Plugins

elsewhere.
To display the courses, we need to check that our enrolment plugin is enabled and, if so,
find all the courses that have an active instance of our plugin. If none are found, we inform
the user:
/enrol/catalogue/index.php
// Is our plugin enabled?
$enabledplugins = explode(',',
$CFG->enrol_plugins_enabled);
if (in_array('catalogue', $enabledplugins)) {
// Get all enabled instances of the plugin.
$instances = $DB->get_records('enrol', array(
'enrol'=>'catalogue',
'status'=>ENROL_INSTANCE_ENABLED
), 'courseid');
if (empty($instances)) {
// Inform user – no courses in the catalogue.
echo html_writer::tag('h3', get_string(
'nocoursesincat','enrol_catalogue'));
.
.

If we do have valid courses, we get all the user’s enrolments using the API’s
enrol_get_all_users_courses() function, then loop through the plugin
instances and discard any courses that the user is already enrolled on. For the leftover
courses, we get the details using the optimised get_course() function, then check if
the course is ‘visible’, before generating any output.
/enrol/catalogue/index.php
} else {
// Get all the user's course enrolments.
$myenrolments =
enrol_get_all_users_courses($USER->id);

foreach ($instances as $instance) {


// Is user enrolled already?
if (empty($myenrolments[$instance->courseid])) {
// Get the course details for display.
$course = get_course($instance->courseid);
// Make sure the course is available.

211
Introduction to Moodle 3.9+ Plugin Development

if ($course->visible) {
.
.

I decided to use a HTML table to display the results to the user. In the table, next to each
course, is a button that posts back to index.php.

The index.php has to check for the posted data and process the request. The request must
provide the course id, then, in code, we need to determine what the ‘student’ role id is and
determine and retrieve the plugin instance for the course before we can use the API’s
enrol_user() function. Since our plugin in lib.php has not overridden the
enrol_user() function, the default function deals with the enrolment.
So, this is the final index.php file:
/enrol/catalogue/index.php
// HEADER STUFF HERE //

require(__DIR__ . '/../../config.php');

require_login(); // Ensure the user is authenticated.

$errmsg = '';
if (optional_param('action', '', PARAM_TEXT) ==
'enrolme') {
// Get course id – we require this parameter.
$courseid = required_param('courseid', PARAM_INT);
$coursecontext = context_course::instance($courseid,
MUST_EXIST);

// Find the student role id.


$studentroleid = 0;

212
Chapter 8 Developing Plugins

$courseroles = get_all_roles($coursecontext);
foreach ($courseroles as $courserole) {
if ($courserole->archetype == 'student') {
$studentroleid = $courserole->id;
break;
}
}
if ($studentroleid) {
// Do the enrolment here.
$enrolplugin = enrol_get_plugin('catalogue');
// Get enrol plugin instance in course ...
// ... – could have used ...
// ... enrol_get_instances().
$instance = $DB->get_record('enrol', array(
'courseid'=>$courseid,
'status'=>ENROL_INSTANCE_ENABLED,
'enrol' => 'catalogue'
)
);
$enrolplugin->enrol_user($instance, $USER->id,
$studentroleid);
// Check- enrol user does not return results.
if (is_enrolled($coursecontext)) {
// Redirect to then newly enrolled course.
$courseurl =
new moodle_url('/course/view.php',
array('id' => $courseid));
redirect($courseurl);
}else{
$errmsg = get_string('failedenrol',
'enrol_catalogue');
}
}else{ // This should not happen – paranoid check.
$errmsg = get_string('nostudentrole',
'enrol_catalogue');
}
}

$PAGE->set_context(context_system::instance());
$PAGE->set_url(

213
Introduction to Moodle 3.9+ Plugin Development

new moodle_url('/enrol/catalogue/index.php'));
$PAGE->set_heading(get_string('catalogheader1',
'enrol_catalogue'));
$PAGE->set_pagelayout('standard');
$PAGE->set_title(get_string('pluginname',
'enrol_catalogue') . ': (' .
get_config('enrol_catalogue', 'version') .')') ;

// Generate the page.


echo $OUTPUT->header();

// Display error if one has been created.


if ($errmsg) {
echo $OUTPUT->notification($errmsg, 'error');
}
// Is our plugin enabled?
$enabledplugins = explode(',' ,
$CFG->enrol_plugins_enabled);
if (in_array('catalogue', $enabledplugins)) {
// Get all enabled instances of the plugins.
$instances = $DB->get_records('enrol', array(
'enrol'=>'catalogue',
'status'=>ENROL_INSTANCE_ENABLED
), 'courseid');
if (empty($instances)) {
// No courses in the catalogue.
echo html_writer::tag('h3', get_string(
'nocoursesincat','enrol_catalogue'));
} else {
// Set up the enrolment request for later.
$enrolurl = $PAGE->url;
$enrolurl->param('action', 'enrolme');

// Get all the user's course enrolments.


$myenrolments =
enrol_get_all_users_courses($USER->id);

$catcourses = new html_table(); // Html table.


foreach ($instances as $instance) {
// Is the user enrolled already?

214
Chapter 8 Developing Plugins

if (empty(
$myenrolments[$instance->courseid])) {
// Get the course details for display.
$course = get_course(
$instance->courseid);
// Make sure the course is available.
if ($course->visible) {
$enrolurl->param('courseid',
$course->id);
$enrolbutton =
new single_button($enrolurl,
get_string('enrolinvite',
'enrol_catalogue')
);
$catcourses->data[] =
new html_table_row(array(
new html_table_cell(
$course->fullname),
new html_table_cell(
$course->summary),
new html_table_cell(
$OUTPUT->render(
$enrolbutton))
));
}
}
}

if (count($catcourses->data)) { // Any data?


echo html_writer::tag('h3',
get_string('catalogheader2',
'enrol_catalogue'));
echo html_writer::table($catcourses);
}else{
// No data – inform the user about it.
echo html_writer::tag('h3',
get_string('nocoursesincat',
'enrol_catalogue'));
}
}

215
Introduction to Moodle 3.9+ Plugin Development

}else{
// Our plugin not enabled – inform the user.
echo html_writer::tag('h3',
get_string('plugindisabled',
'enrol_catalogue'));
}

echo $OUTPUT->footer();

We have defined several new strings and so we need to add them to our
lang/en/enrol_catalogue.php file:
/enrol/catalogue/lang/en/enrol_catalogue.php
$string['catalogheader1'] = 'Course Catalogue';
$string['catalogheader2'] = 'Enrol on these wonderful
courses.';
$string['plugindisabled'] = 'The Catalogue Enrolment
plugin has not be enabled.';
$string['nocoursesincat'] = 'There are currently no
courses available in the catalogue';
$string['enrolinvite'] = 'Enrol On this course';
$string['nostudentrole'] = 'Cannot enrol as a student on
the selected course';
$string['failedenrol'] = 'Sorry, failed to enrol you on
the selected course';

Users will need to be directed to the catalogue page. I used an HTML block added to the
‘Default Dashboard page’, by navigating to ‘Site Administration->Appearance->Default
Dashboard page’ (/my/indexsys.php), to provide the link to all users. You should click on
the ‘Reset Dashboard for all users’ button to reset the dashboard for the user accounts you
have previously used.
You can now increase the plugin version again and upgrade so that all our changes are
integrated. Now is a good time to enrol the teacher-user as a teacher in the relevant course.
Now if you log on as a student, use this functionality to enrol on the course. Remember the
plugin has to be active/enabled in the course to appear in the catalogue.
As the teacher-user, log in and attempt to manage the enrolled user’s enrolment in the
course’s ‘Participants’ page – navigate to the course and select ‘Participants’ in the ‘Course
Navigation’. Whilst it appears possible to change the user’s role, there are no icons to
manage the enrolment. We have defined the capabilities in the db/access.php file, so why

216
Chapter 8 Developing Plugins

is this?

For starters, try changing the student’s role to, say, teacher and removing the student role.
You will notice, after attempting to save the changed roles, that the student’s role remains.
This is because we did not override the API’s roles_protected() function in lib.php,
which, by default, returns true. If we add the function, the student role can be removed:

/enrol/catalogue/lib.php
public function roles_protected() {
return false;
}

Remember to revert the user to their student role. Unless you are developing an enrolment
plugin that relies on external sources to manage enrolments, you will likely want course
administrators to manage and be able to unenroll users.
To unenroll users, the API's functions allow_unenrol() and
allow_unenrol_user() must return true. By default, allow_unenrol_user()
returns the value of allow_unenrol(), so you can simply define
allow_unenrol() if you wish both to be true:

/enrol/catalogue/lib.php
public function allow_unenrol($instance) {
return true;
}

We could leave it at this, but it may be that the enrolment requires management, so we add
an allow_manage() function to enable that:

/enrol/catalogue/lib.php
public function allow_manage($instance) {
return true;
}

217
Introduction to Moodle 3.9+ Plugin Development

The allow_manage(), allow_unenrol(), and allow_unenrol_user()


functions rely on the relevant capabilities being defined.
Now it is possible to manage the enrolment on the ‘Participants’ page.
This is where we are going to leave the enrolment plugin development. Again, this is a
basic example and there is much more that can be done to make it more robust for release.
Since it could also provide additional functionality, it is worth examining other enrolment
plugins to get an idea of what can be achieved.

Authentication
Moodle allows the creation of plugins to manage the authentication process. That means
that Moodle can be integrated into other systems, including Single Sign-On environments.
There is even an OAuth2 API (https://docs.moodle.org/dev/OAuth_2_API). Authentication
plugins can be simple or as complex as required and the general advice is to look at the
auth_none plugin as a starting point.
We are going to develop a simple authentication plugin that uses a text file to store the user
details. The file can be generated with an external system or on the command line. If a user
is not found in the Moodle database, it is created and the password is set.
For more information about authentication plugins, please visit the Moodle ‘Authentication
Plugins’ page (https://docs.moodle.org/dev/Authentication_plugins).

Developing an Authentication Plugin


To start with, we will create some skeleton files in the /auth/textfile folder to allow us to
install our undeveloped plugin. As always, we need the version.php file:
/auth/textfile/version.php
// HEADER STUFF HERE //

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

$plugin->version = 2020061500;
$plugin->requires = 2020061500; // Requires M3.9
$plugin->component = 'auth_textfile';

We also need the language file, lang/en/auth_textfile.php, with our plugin name defined
for display purposes:

218
Chapter 8 Developing Plugins

/auth/textfile/lang/en/auth_textfile.php
// HEADER STUFF HERE //

$string['pluginname'] = 'Textfile Authentication';

Authentication plugins do not require a lib.php file, but do require an auth.php file, which
defines a class that extends auth_plugin_base from /lib/authlib.php, which your file
needs to require_once. Our class named auth_plugin_textfile will have two
required methods: __construct() (that sets the authtype property) and
user_login() (that returns true defined).

/auth/textfile/auth.php
// HEADER STUFF HERE //

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

require_once($CFG->libdir.'/authlib.php');

class auth_plugin_textfile extends auth_plugin_base {

public function __construct() {


$this->authtype = 'textfile';
}

public function user_login($username,$password) {


return true;
}
}

The plugin can now be installed. You will also need to enable your plugin. To do this,
navigate to ‘Site administration->Plugins->Authentication->Manage authentication’
(/admin/settings.php?section=manageauths) and click on the eye icon.

219
Introduction to Moodle 3.9+ Plugin Development

Authentication plugins are one of the easiest plugins to piggyback on. For example, instead
of extending auth_plugin_base, you could extend auth_plugin_manual and just
override the methods you need and let the manual plugin do all the heavy lifting. We are
not doing that with our plugin.
The manual authentication plugin is a good place to start to determine the necessary
functions to override when creating your plugin. The /lib/authlib.php file is well
documented and worth looking at to see what is possible or to determine default responses.
Our user tab-separated text file userdb.tsv of usernames – use email addresses (you will see
why later) – and md5 hashed passwords needs to be created in the Moodle data directory
and will need to be readable by the webserver user. I just created my file manually on the
command line.
As our plugin’s user data lives outside the Moodle database, we need to inform Moodle
that we are not internal, so we override the is_internal() function and return false.

/auth/textfile/auth.php
public function is_internal() {
return false;
}

As for the main function user_login(), we extend it to read the user file, parse the
contents, find our user, then compare the passwords, before returning true if all is in order.
/auth/textfile/auth.php
public function user_login($username, $password) {
global $CFG;
// Our user text db.
$userdb = $CFG->dataroot . '/userdb.tsv';
if (file_exists($userdb)) {

220
Chapter 8 Developing Plugins

$users = file($userdb); // Read file lines.


foreach ($users as $userdetails) {
// Split the line on tabs.
list($user, $passwd) =
explode("\t", trim($userdetails));
if ($user == $username) {
if (md5($password) === $passwd) {
return true;
}
}
}
}
return false;
}

We could authenticate into Moodle with only this, but we’re not going to just yet. Since all
Moodle has is a username and password, the user is prompted to provide the other required
details, such as names and email details. The email address is further complicated in that
Moodle sees the new email address as a change and goes through the process of verifying
the email address.
We can, at least, provide the email address. The authentication process calls the
update_user_record() function of the authenticating plugin as soon as the user is
authenticated. The default function update_user_record() is elaborate and, in turn,
it calls the get_userinfo() function if the user is a new account. We use that function
to return the email address. If your username is the email address, as in my case, we can
return the username as the email address in the expected array response.

221
Introduction to Moodle 3.9+ Plugin Development

We can also consider the other potential functions and see if we need to override them.
We do not have any configuration settings, so we override the is_configured()
method and return false;
The can_change_password() and change_password_url() methods are
connected. If users can change their password in the external user data store, you can return
true from can_change_password() and specify the change password URL in
change_password_url().
Here are our final functions:
/auth/textfile/auth.php
public function get_userinfo($username) {
return array(
'email' => $username
);
}

public function is_configured() {


return true;
}

Now attempt to log in as one of your created users.

222
Chapter 8 Developing Plugins

An extra word of caution : this is not a very secure way to authenticate users and I would
suggest never using this example as the basis for a production authentication module.

Privacy Considerations
This is a good place to revisit the Privacy API we covered in the ‘Common APIs’ chapter
(Chapter 5). We are going to define a null data provider for our plugin (defining our
plugin as not storing user data), as the data we are using is managed outside our control. To
do this, we must create a class called provider within our namespace. This is defined in
the classes/privacy/provider.php file. The class implements the \core_privacy\
local\metadata\null_provider interface and needs to define one method,
namely get_reason(), which returns a description of why the plugin does not store
user data.
So, our hook into the Privacy API looks like this:
/auth/textfile/classes/privacy/provider.php
// HEADER STUFF HERE //

namespace auth_textfile\privacy;

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

class provider implements


\core_privacy\local\metadata\null_provider {

public static function get_reason() : string {


return 'privacy:metadata';
}
}

Our language file needs an additional line to support our function:


/auth/textfile/lang/en/auth_textfile.php
$string['privacy:metadata'] = 'The Textfile
authentication plugin does not store any personal
data.';

Increase the plugin’s version and upgrade. And that’s all there is to it.
Of course, you could extend this authentication plugin to enrol users in default courses or
extend the user database to provide details of the courses the users should be enrolled in

223
Introduction to Moodle 3.9+ Plugin Development

and in what role. You get the drift. For a more elaborate example of what is possible with
authentication plugins, have a look at the Shibboleth or LDAP plugins.

Reporting
The legacy course reports and admin reports were combined in Moodle 2.2. There are other
reporting plugins for specific plugins, such as the SCORM and quiz modules, which will
not be covered in this book. Instead, we are going to develop a simple report. For more
information about report plugins, please see the Moodle documentation ‘Reports’ page
(https://docs.moodle.org/dev/Reports).

Developing a Report Plugin


We are going to develop a graphical report that will report the plugins we have installed in
our Moodle LMS. I suspect this won’t be very useful until you have a good number of non-
core plugins that you need to review regularly. Our plugin report_plugins will reside
in the /report/plugins/ folder.
As usual, we start with our version.php file:
/report/plugins/version.php
// HEADER STUFF HERE //

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

$plugin->version = 2020061500;
$plugin->requires = 2020061500; // Requires M3.9
$plugin->component = 'report_plugins';

And then our language file lang/en/report_plugins.php:


/report/plugins/lang/en/report_plugins.php
// HEADER STUFF HERE //

$string['pluginname'] = 'Site Plugins Report';

With just these two files, we can install our plugin. We are going to use index.php as the
entry point for our report, but we need to provide a way for the user to navigate to our
report. There are several ways to integrate reports depending on the ‘context’ of the report.
At user, course or course module levels, the Navigation API defines callbacks, as discussed
in the relevant section in the 'Common APIs' chapter (Chapter 5):

224
Chapter 8 Developing Plugins

 <plugin>_extend_navigation_user()
 <plugin>_extend_navigation_course()
 <plugin>_extend_navigation_module()
These would normally be expected to be in the plugin’s lib.php file. For system-wide
reports, this is achieved using the settings.php file, which is exactly what we need for our
plugin:
/report/plugins/settings.php
// HEADER STUFF HERE //

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

$ADMIN->add('reports',
new admin_externalpage('reportconfigplugins',
get_string('pluginname', 'report_plugins'),
"$CFG->wwwroot/report/plugins/index.php")
);

$settings = null;

Since this is a simple plugin, I am going to take the opportunity to include logging via the
Events API as covered in the ‘Logging’ subsection of the ‘Events API And Logging’
section of the 'Common APIs' chapter (Chapter 5). Dispatching events is fairly simple,
particularly for logging. Each event requires its class file in the classes/event/ folder to
follow ‘Class Autoloading and Namespaces’ rules as covered in the ‘Plugin Development
Background’ chapter (Chapter 4). We are going to define a ‘report viewed’ event in the
classes/event/report_viewed.php file.
The class will be in our namespace report_plugins\event and extend \core\
event\base. The minimum required function is:
 init() – which initialises the class and sets some of the required crud ('c'reate,
'r'ead, 'u'pdate or 'd'elete) and the edulevel properties. Here, you can also define
other properties, so the trigger does not need to pass in the property, for example, if
the context of the request never changes, e.g. system context.
To be useful, you will also need to define:
 get_name() – returns the name of the plugin/event

225
Introduction to Moodle 3.9+ Plugin Development

 get_description() – returns a description of the event – e.g. ‘user with


userid viewed this wonderful report’
 get_url() – the relevant URL for the generated event
So, our event code in classes/event/report_viewed.php looks like this:
/report/plugins/classes/event/report_viewed.php
// HEADER STUFF HERE //
namespace report_plugins\event;

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

// CLASS HEADER //
class report_viewed extends \core\event\base {

protected function init() {


$this->data['crud'] = 'r';
$this->data['edulevel'] = self::LEVEL_OTHER;
}

public static function get_name() {


return get_string('eventreportviewed',
'report_plugins');
}

public function get_description() {


return get_string('eventreportdesc',
'report_plugins', $this->userid);
}

public function get_url() {


return new \moodle_url(
'/report/plugins/index.php');
}
}

And in the language file:


/report/plugins/lang/en/report_plugins.php
$string['pluginenabled'] = 'Enabled';

226
Chapter 8 Developing Plugins

$string['plugininstalled'] = 'Installed';
$string['plugintypes'] = 'Plugin Types';
$string['pluginnumbers'] = 'Numbers';

Now, we can complete our report code in index.php. We are generating a bar graph of the
site’s plugins by plugin type using the Charts API
(https://docs.moodle.org/dev/Charts_API), which we have not covered in this book. Our
charts will show installed versus enabled plugin types. We use the plugin manager class to
get the plugin information. So, our file is like this:
/report/plugins/index.php
// HEADER STUFF HERE //

require(__DIR__ . "/../../config.php");

require_login();

if(!is_siteadmin()) { // Only site admins allowed.


print_error('nopermissions', 'core');
}

$context = context_system::instance();
$PAGE->set_context($context);
$PAGE->set_pagelayout('admin');
$PAGE->set_heading($SITE->fullname);
$PAGE->set_title(get_string('pluginname',
'report_plugins'));
$PAGE->set_url('/report/plugins/index.php');

$pm = core_plugin_manager::instance();

$allplugintypes = $pm->get_plugin_types();

$chart = new core\chart_bar();


$chart->set_stacked(true);
$labels = array();
$series1data = array();
$series2data = array();

foreach ($allplugintypes as $type => $typefolder) {

227
Introduction to Moodle 3.9+ Plugin Development

$installedplugins =
$pm->get_installed_plugins($type);
$enabledplugins = $pm->get_enabled_plugins($type);

$installed = empty($installedplugins) ? 0 :
count($installedplugins);
$enabled = empty($enabledplugins) ? 0 :
count($enabledplugins);

$labels[] = $type;
$series2data[] = $installed;
$series1data[] = $enabled;

$chart->add_series(new core\chart_series(
get_string('pluginenabled', 'report_plugins'),
$series1data));
$chart->add_series(new core\chart_series(
get_string('plugininstalled', 'report_plugins'),
$series2data));

$chart->get_xaxis(0, true)->set_label(
get_string('plugintypes', 'report_plugins'));
$chart->get_yaxis(0, true)->set_label(
get_string('pluginnumbers', 'report_plugins'));

$chart->set_labels($labels);

echo $OUTPUT->header();
echo $OUTPUT->heading(
get_string('pluginname', 'report_plugins'));

echo $OUTPUT->render_chart($chart);

// Trigger a logs viewed event.


$event = \report_plugins\event\report_viewed::create(
array('context' => $context));
$event->trigger();

228
Chapter 8 Developing Plugins

echo $OUTPUT->footer();

The lines in bold in the code are where the logging is being done.
Then, we also have to update our language file:
/report/plugins/lang/en/report_plugins.php
$string['eventreportviewed'] = 'Plugin report viewed';
$string['eventreportdesc'] = 'The user with id "{$a}"
viewed the site plugins report';

Now, increase the plugin’s version and upgrade before navigating to ‘Site Administration-
>Reports->Site Plugins Report’ (/report/plugins/index.php).

As we triggered the report viewed event in index.php, navigate to ‘Site administration-


>Reports->Live logs’ (/report/loglive/index.php) and you will see the event.

229
Introduction to Moodle 3.9+ Plugin Development

This is a very simple report, but it can get as complex as it needs to be. We could extend
the functionality in order for you to examine each plugin type, but we are going to leave it
alone for now.

Local Plugins
Local plugins are mostly suitable for things that do not fit standard plugin types. Local
plugins are always executed last, during install/upgrade as well as whenever the core
function call get_plugin_types() is made. This makes local plugins suitable for
‘supporting’ other types of plugins, including your custom ones. An example would be in
order to provide common functionality for a suite of standard plugins and, as event
handlers, for core functionality. The plugins can also be used to add administration settings
to any settings page, as they are loaded last when constructing the admin tree.
Of all the plugins, local plugins require the least number of required files and do not need
to have a user interface (UI). For more information, please visit the Moodle ‘Local Plugins’
page (https://docs.moodle.org/dev/Local_plugins).
We are going to cover two major areas with our local plugin: calling web services and
creating scheduled tasks (sometimes referred to as cron(s) – a Linux term).

Developing a Local Plugin


We are going to develop a local plugin local_udacity_sync, which calls a REST
web service and synchronises a selected Moodle category's courses with the 'Web
Development' track of courses from Udacity's (https://www.udacity.com/) catalogue. The
plugin’s folder is /local/udacity_sync/.
Before we start, create a new course category ‘Web Development’ for synchronised
courses. Please see the Moodle ‘Course Categories’ page
(https://docs.moodle.org/39/en/Course_categories) for information about managing
categories.

230
Chapter 8 Developing Plugins

The minimum files required to install a local plugin are version.php and the language file.
Our version file is as follows:
/local/udacity_sync/version.php
// HEADER STUFF HERE //

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

$plugin->version = 2020061500;
$plugin->requires = 2020061500; // Requires M3.9.
$plugin->component = 'local_udacity_sync';

Our lang/en/local_udacity_sync.php file looks like this:


/local/udacity_sync/lang/en/local_udacity_sync.php
// HEADER STUFF HERE //

$string['pluginname'] = 'Udacity Course Sync';

You can now install the plugin, but it does not do much. So, let’s create a new course
category for our course details to synchronise with.
Our next step is to create a setting that tells our plugin where to synchronise the courses to.
This is done in the settings.php file. We only want to have the settings available when the
user has the capability to change the site settings, so we check the variable
$hassiteconfig before we create the settings.

/local/udacity_sync/settings.php
// HEADER STUFF HERE //

if ( $hassiteconfig ){

// Our new settings.


$settings = new
admin_settingpage('local_udacity_sync',
get_string('settingstitle',
'local_udacity_sync') );

// Course Category list for the drop-down.


$crsecats =
core_course_category::make_categories_list('',

231
Introduction to Moodle 3.9+ Plugin Development

0, ' / ');

//Add a setting field to the settings for this page.


$settings->add(
new admin_setting_configselect(
'local_udacity_sync/categorysync',
get_string('catsyncprompt',
'local_udacity_sync'),
get_string('catsyncdesc',
'local_udacity_sync'),
1,
$crsecats
)
);

// Add to the admin settings for localplugins.


$ADMIN->add( 'localplugins', $settings);
}

The admin_settings classes are defined in the Admin Settings API, which we covered
in the ‘Admin Settings’ section of the ‘Common APIs’ chapter (Chapter 5). There is a
slight difference when applied to the local plugin. We define the settings name as
local_udacity_sync/categorysync, so we can later retrieve this value by calling
get_config(‘local_udacity_sync’, ‘categorysync’).
Our language file has also grown a little with the addition of new strings:
/local/udacity_sync/lang/en/local_udacity_sync.php
$string['settingstitle'] = 'Udacity Sync Settings';
$string['catsyncprompt'] = 'Sync Category';
$string['catsyncdesc'] = 'Select the category for the
courses to be syncronised to.';

Since we intend for our plugin to be a scheduled task, we are going to develop the
functionality using the Task API, covered in the relevant section in the ‘Common APIs’
chapter (Chapter 5). Until we finish the development, we will use the plugin’s index.php to
run the task. When we are happy, we will then add in the missing bits to make the plugin
integrate with the other tasks.
Scheduled tasks are created by subclassing \core\task\scheduled_task and need
to reside in your plugin's classes/task/ directory. To assist the autoloading functionality to

232
Chapter 8 Developing Plugins

pick up the class, you will need to add it to your plugin’s namespace. Only two functions
are required for the class – get_name() and execute().
Our skeleton classes/task/udacitysync.php file looks like this:
/local/udacity_sync/classes/task/udacitysync.php
// HEADER STUFF HERE //

namespace local_udacity_sync\task;

class udacitysync extends \core\task\scheduled_task {

public function get_name() {


return get_string('taskname',
'local_udacity_sync');
}

public function execute($testing = null) {


// Cron expects Exception to recognise errors.
return true;
}
}

A further addition to the language file is the task name string:


/local/udacity_sync/lang/en/local_udacity_sync.php
$string['taskname'] = 'Udacity Course Sync';

At this point, we are going to take a detour and add an index.php file, so we can test the
plugin without waiting for a scheduled task to run. I am not going to go into depth with the
code in this file, as we have come across similar code in other earlier parts of this chapter.
The important bits are the loading of the class and task execution, which are in bold text
below.
/local/udacity_sync/index.php
// HEADER STUFF HERE //

require('../../config.php');

// Protect the page.


require_login();

233
Introduction to Moodle 3.9+ Plugin Development

if(!is_siteadmin()) { // Only site admins.


print_error('nopermissions', 'core');
}

// Set up the page for display.


$PAGE->set_context(context_system::instance());
$PAGE->set_pagelayout('admin');
$PAGE->set_heading($SITE->fullname);
$PAGE->set_title($SITE->fullname . ': ' .
get_string('pluginname', 'local_udacity_sync'));
$PAGE->set_url(new moodle_url(
'/local/udacity_sync/configsettings.php'));
$PAGE->set_pagetype('admin-' . $PAGE->pagetype);

echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('pluginname',
'local_udacity_sync'));

$task = new local_udacity_sync\task\udacitysync();


echo html_writer::tag('div',
html_writer::tag('p', $task->get_name()));

try {
$task->execute(1);
} catch (Exception $e) {
echo $OUTPUT->notification($e->getMessage(),
'error');
echo $OUTPUT->footer();
die;
}
echo $OUTPUT->notification('Successfully executed.',
'success');
echo $OUTPUT->footer();

Now, we can get to the meat of the functionality in the classes/task/udacitysync.php file.
Moodle defines a cURL class in lib/filelib.php for plugins, which is a wrapper for the PHP
functionality. You could, of course, use PHP’s curl functionality if you chose to, but we
are going to use the Moodle class.
An alternative for simple HTTP GET calls is to use either the PHP functions file() or

234
Chapter 8 Developing Plugins

the file_get_contents() or, even where the results are XML, the
simplexml_load_file() call, which we used in the repository plugin development
section in this chapter.
Many REST webservice providers expect the client to cache the results for a while to
reduce the load and network traffic, which can be problematic when developing a client.
My strategy is usually to obtain the expected results manually and save them in a file,
which I will then use to develop the rest of the code. For this plugin, make a browser call to
the API URL https://www.udacity.com/public-api/v1/courses and save the results to
/local/udacity_sync/classes/task/catalog.json. Make sure the webserver user can read the
file.
You will notice a parameter in the call to execute the task in index.php. It is the flag for the
code to recognise it is a testing/developing call. There does not appear to be a way to pass
parameters to scheduled tasks in core Moodle functionality.
Before we tackle the execute() function, we define a few private variables:

/local/udacity_sync/classes/task/udacitysync.php
/** @var $apiurl – the REST endpoint */
private $apiurl =
'https://www.udacity.com/public-api/v1/courses';

/** @var $testingfile – the local file for testing */


private $testingfile = __DIR__ . '/catalog.json';

/** @var $track – the course tracks to sync */


private $track = 'Web Development';

Now, we can work on synchronising the courses. We parse the JSON response, loop
through the records, and find any courses that are on the ‘Web Developer’ track. Assuming
that the key field is the unique id field, we use that to match our existing courses to the
feed. We check if we have already created the course and, if we have, compare the fields to
check for any changes and, if there are any, we update the course or create a new course.
This is very straight forward and our execute() function looks like this:

/local/udacity_sync/classes/task/udacitysync.php
public function execute($testing = null) {
global $DB;

$catlog = null;

235
Introduction to Moodle 3.9+ Plugin Development

if (isset($testing)) {
// Check if we have a local JSON file and ...
// ... that instead – saves on API calls.
if (file_exists($this->testingfile)) {
$catlog = json_decode(
file_get_contents($this->testingfile));
if ($catlog === null) {
print_error('errorjsonparse',
'local_udacity_sync',
null,
$this->get_last_json_errormsg());
}
} // Else we are using curl to get data.
$testing = true;
} else {
$testing = false;
}

if (empty($catlog)) { //
// Moodle's RESTful cURL class.
$c = new \curl(
array(
// Use cache – not when developing :).
'cache' => $testing,
)
);
$requestparams = array(); // No params req.

if ($response = $c->get($this->apiurl,
$requestparams)) { // HTTP GET Method.
if (($catlog =
json_decode($response)) === null) {
print_error('Unable to parse file');
}
if ($testing &&
!file_exists($this->testingfile)) {
file_put_contents($this->testingfile,
$catlog);
}

236
Chapter 8 Developing Plugins

} else {
$ci = $c->get_info();
if ( (int) $ci['http_code'] >= 500) {
print_error('errorapicall',
'local_udacity_sync',
null,
$this->get_request_status(
$ci['http_code']));
} else {
print_error('errorservererror',
'local_udacity_sync',
null,
$this->get_request_status(
$ci['http_code']));
}
}
}

// Course Category to synchronise.


$synccategory = get_config('local_udacity_sync',
'categorysync');

foreach ($catlog->courses as $catcourse) {


if (!empty($catcourse->tracks)) {
if (in_array($this->track,
$catcourse->tracks)) {
$record = new \stdClass();
$record->idnumber = $catcourse->key;
$record->category = $synccategory;
$record->fullname = $catcourse->title;
$record->shortname = $catcourse->slug;
$record->summary = $catcourse->summary;
// Add the course image ...
// ... to the summary – being clever.
$record->summary .= "\n![alt text](" .
$catcourse->image . ' "' .
$record->shortname . '")';
$record->summaryformat =
FORMAT_MARKDOWN;

237
Introduction to Moodle 3.9+ Plugin Development

// Have we got an existing record.


if ($course = $DB->get_record('course',
array(
'idnumber' => $catcourse->key)
)) {
// Test if anything has changed.
$updaterequired = false;
if ($course->shortname ==
$record->shortname) {
if ($course->fullname ==
$record->fullname) {
// Hmmmm!
if ($course->summary =
$record->summary) {
if ($course->category !=
$record->category){
$updaterequired =
true;
}
}else{
$updaterequired = true;
}
}else{
$updaterequired = true;
}
}else{
$updaterequired = true;
}

if ($updaterequired) {
$record->id = $course->id;
update_course($record);
}
} else {
create_course($record);
}
}
}
}

238
Chapter 8 Developing Plugins

return true;
}

There are also some new strings in the language file:


/local/udacity_sync/lang/en/local_udacity_sync.php
$string['errorjsonparse'] = 'Could not parse response:
JSON parsing error: "{$a}"';
$string['errorapicall'] = 'Catalog API Call
failed:"{$a}"';
$string['errorservererror'] = 'Catalog API Call failed.
Server sent : "{$a}"';
$string['errorjsonparse'] = 'Could not parse response:
JSON parsing error: "{$a}"';
$string['errorapicall'] = 'Catalog API Call
failed:"{$a}"';
$string['errorservererror'] = 'Catalog API Call failed.
Server sent : "{$a}"';

Now increase the plugin’s version and upgrade. The settings form appears after the upgrade
and you can select the category we created earlier. Now run a test using
/local/udacity_sync/index.php to make sure our code is good. Note, this being the test run,
the data is coming out of the local catalog.json file we saved earlier.
We now want to integrate our task, so we create the db/tasks.php file:
/local/udacity_sync/db/tasks.php
// HEADER STUFF HERE //

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

$tasks = array(
array(
'classname' =>
'local_udacity_sync\task\udacitysync',
'blocking' => 0,
'minute' => '30',
'hour' => '01',
'day' => '*',
'month' => '*',
'dayofweek' => '*',

239
Introduction to Moodle 3.9+ Plugin Development

),
);

Increase the plugin version and upgrade. If you now navigate to the ‘Site administration-
>Server->Tasks->Scheduled tasks’ page (/admin/tool/task/scheduledtasks.php), you
should see our new task.

You might have to enable the task by editing the settings. Since the task is going to be run
by the Moodle scheduled task on the command line, it is worth testing that it will still
work. Any task, even disabled ones, can be run from the command line. In the Moodle
document root, you can run the task by running the following command, though you might
have to run it as the webserver user:
php admin/cli/scheduled_task.php --execute=\\
local_udacity_sync\\task\\udacitysync

When we do this, we get some errors. We need to require some files for the script to find
some of the core functionality, such as the cURL and course management functions. So, we
add these lines to our classes/task/udacity_sync.php file under the namespace declaration:
/local/udacity_sync/classes/task/udacitysync.php
namespace local_udacity_sync\task;

require_once($CFG->libdir . '/filelib.php');
require_once($CFG->dirroot . '/course/lib.php');

Now it should run without errors. This time, the local ‘cached’ data is not used and the data
is collected from the Udacity API location.
This is how far we are going to go with the development of this plugin. As it is, the
index.php file allows a site admin user to run the plugin for the web interface, but it should

240
Chapter 8 Developing Plugins

be should be changed.

Web and External Services for Plugins


As mentioned in the ‘Web & External Functions APIs’ section in the ‘Common APIs’
chapter (Chapter 5), we will not be covering the development of a new webservice protocol
in this book. Please read that section for a discussion of the differences between the Web
Service and External Service API, and how they relate to each other. In a nutshell, external
services use an existing webservice protocol to provide functionality for Moodle
webservice clients.

Developing an External Service Plugin


We are going to develop a REST webservice local external plugin that allows an external
application to send student grades for their practical activities, say a laboratory experiment.
It might be the application that runs on a tutor’s tablet to collect feedback and grade the
student’s efforts and which the institute wants to track in the Learning Environment.
Our plugin in the /local/practicalgrader folder will only require two files to be installed.
The version.php file:
/local/practicalgrader/version.php
// HEADER STUFF HERE //

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

$plugin->version = 2020061500;
$plugin->requires = 2020061500; // Requires M3.9
$plugin->component = 'local_practicalgrader';

and the lang/en/local_practicalgrader.php file:


/local/practicalgrader/lang/en/local_practicalgrader.php
// HEADER STUFF HERE //

$string['pluginname'] = 'Practical Activity Grade


Importer';

We can now install the plugin.


We now need to define the external service functionality and this is done in the
db/services.php file. This file must define at least one array named functions. The

241
Introduction to Moodle 3.9+ Plugin Development

functions array specifies the webservice functions available to the Web Services API.
The functions array will contain one or more function definitions. The key of each function
definition array is the name of the web service function in the format of
fullpluginname_functioname, e.g. local_myplugin_getusers, and the
value is an array with the following keys and values:
 classname: the class defined in the class file in classes/external/ and following
the Class Autoloading and Namespaces rules (see Chapter 3)
 classpath: old style – if a non-autoloading namespaced classname is defined,
this is the path to the class file and, if that is not defined, externallib.php is used
 methodname: the function to be called in the class
 description: this documentation will be displayed in the generated API
documentation
 type: ‘write’, if your function does any database changes including file records,
otherwise ‘read’
 ajax: true or false depending on whether the webservice function is callable via
ajax
 capabilities: an array of capabilities required by the function
 services: optional since Moodle 3.1 – an array of built-in services (by
shortname) where the function will be included
We will ignore the use of externallib.php files, as this format is likely to be deprecated in
the future.
Since we want our class to be autoloading and in a namespace, we will create a class called
practicalgrader in the classes/external/practicalgrader.php file. We will define a
method called local_practicalgrader_save to do the work.
So, our db/services.php file looks like this:
/local/practicalgrader/db/services.php
// HEADER STUFF HERE //

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

$functions = array(
'local_practicalgrader_save' => array(

242
Chapter 8 Developing Plugins

'classname' =>
'\local_practicalgrader\external\practicalgrader',
'methodname' => 'save_practicalgrader_grade',
'description' =>
'Saves an practical activity feedback and grade',
'type' => 'write',
),
);

The other array named services is optional, as it can be defined in the user interface.
The services array pre-builds the services, so the Moodle administrator does not need
to do it, but it also prevents the administrator from removing the services. Each service
definition has the service name as the key and the definitions as an array, with the
following keys and values:
 enabled: if enabled, the service is enabled by default
 functions: an array of the defined functions attached to this service
 requiredcapability: if set, the webservice user needs this capability to
access
 restrictedusers: if enabled, the Moodle administrator must link some user to
this service
 shortname: optional, but needed if restrictedusers is set to allow logins
 downloadfiles: true/false – allow file downloads
 uploadfiles: true/false – allow file uploads
We will want the grader to be identified, i.e. logged in, to grade the student and this
requires our service to have a short name. Since, currently, the short name cannot be
defined in the user interface, we have a very good reason to define a service. We define a
service, which will expose our local_practicalgrader_save webservice method.
So, we add the services array to db/services.php:
/local/practicalgrader/db/services.php
$services = array(
'Practical_Grader' => array(
'functions' => array(
'local_practicalgrader_save'
),

243
Introduction to Moodle 3.9+ Plugin Development

'restrictedusers' => 0,
'requiredcapability' =>
'local/practicalgrader:grade',
'enabled' => 1, // Default enabled.
'shortname' => 'Practical_Grades' // For tokens.
)
);

Users and Capabilities


The restrictedusers flag underlies the two ways in which access is managed. If the
flag is set, then each user will need to be identified and will require the correct capabilities,
moodle/webservice:createtoken and moodle/<webservice>:use, where
<webservice> is either rest, soap or xmlrpc. The user will need to be added to the
service’s ‘authorised users’ in the user interface – ‘Site administration->Plugins->Web
services->External services->Authorised users’ (/admin/webservice/service_users.php?
id=?) – to be able to access the functionality. However, there is a big gotcha with this.
Even once the user has been added as a restricted user of the service, the two required
capabilities are not assigned automatically.
The process for the client application servicing restricted users is that it will first need to
log the user in at /login/token.php with a username, password, and shortname, for which
two tokens, namely token and privatetoken, are returned to be used for all
subsequent webservice requests. The service's short name must, therefore, be set in this
case.
If the flag is not set, in theory, all users can access the service as long as they have the
moodle/<webservice>:use capability. Generally, in practice, this is only used to
allow an external service to use the functionality. It involves creating a special user,
assigning them to a new role that has the required capabilities, and generating a one-off
token that is provided to the client. The client just needs to present the token with every
request without having to authenticate.
If there is a need to further restrict access to the web service, additional system context
capabilities can be defined and then specified in the requiredcapability item in the
$services array. The requiredcapability item is supposed to be an array of
capabilities, but, at least currently in Moodle 3.9 and 3.10, it causes an error if defined as
an array and, when defined as a scalar, it does not appear to be checked for. These
capabilities can then be assigned to the relevant roles.
Since we have specified a capability ‘local/practicalgrader:grade’ for the

244
Chapter 8 Developing Plugins

service, we have to define it in the db/access.php file:


/local/practicalgrader/db/access.php
// HEADER STUFF HERE //

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

$capabilities = array(
// Allow graders to access the service.
'local/practicalgrader:grade' => array(
'riskbitmask' => RISK_XSS,
'captype' => 'write',
'contextlevel' => CONTEXT_SYSTEM
),
);

We also have to define a string for the capability:


/local/practicalgrader/lang/en/local_practicalgrader.php
$string['practicalgrader:grade'] = 'Access to Practical
Activities Grading';

Now increase the plugin’s version and upgrade it to incorporate our new capability and
strings.
In practice, for both cases, a new role with all the required capabilities needs to be created
and users assigned to them. This could actually be a bit of an administrative burden. For
example, you cannot assign the rights to teachers, as the teachers' role is defined at the
course level and the webservice capabilities are defined at the system context level.
Even though we have not stipulated that the service has restricted users in the $services
array, we will need to identify our graders and so we need to create a ‘Practical Grader’
role. For more information about managing roles, please see the Moodle documentation’s
‘Roles and Permissions’ page (https://docs.moodle.org/39/en/Roles_and_permissions). In a
nutshell:
 Navigate to ‘Site administration->Users->Permissions->Define roles’
(/admin/roles/manage.php)
 Click ‘Add a new role’ button
 Accept defaults and click the ‘Continue’ button

245
Introduction to Moodle 3.9+ Plugin Development

 Enter names and description details


 Select ‘System’ context
 Allow the following capabilities:
◦ local/practicalgrader:grade
◦ moodle/webservice:createtoken
◦ webservice/rest:use
Now we can assign users to the role. Assign the teacher-user to this role on the ‘Assign
System Roles’ page – ‘Site administration->Users->Permissions->Assign system roles’
(/admin/roles/assign.php?contextid=1).

External Functions API


So far, what has been covered is regarded as part of the Web Services API. Following the
External Functions API, we will now create our class file
classes/external/practicalgrader.php and define our class, our method, and any other
required methods.
Each class should reside in a separate file and be appropriately namespaced as per the
‘Class Autoloading and Namespaces’ section in the ‘Plugin Development Background’
chapter (Chapter 4) and follow the Frankenstyle naming conventions (that is, class names
should be in fullpluginname_classname format). Generally, it is expected that
each service will reside in its class file, but that is not strictly necessary; you could have
one class file and have each separate service as a function with its supporting functions.
For each function in your class, two other functions must be defined:
 FUNCTIONNAME_parameters() which defines the parameters of the functions
 FUNCTIONNAME_returns() which describes the return value(s)
The FUNCTIONNAME() must validate the passed-in parameters before processing the
input and this is validated against the description in the
FUNCTIONNAME_parameters() function.
Some external description classes are defined in /lib/externallib.php to help with the
parameters and return definitions, namely:
 external_value: scalar value
 external_single_structure: associative arrays

246
Chapter 8 Developing Plugins

 external_multiple_structure: bulk arrays


 external_function_parameters: function parameters
So, our save_practicalgrader_grade method also needs the
save_practicalgrader_grade_parameters() and the
save_practicalgrader_grade_returns() methods defined. All the functions
should be static public functions. If most or all your functions have the same parameters
and/or returns, you could define a private or protected function that can be called from the
FUNCTIONNAME_parameters() and FUNCTIONNAME_returns() for the relevant
function.
So, our classes/external/practicalgrader.php file looks like this:
/local/practicalgrader/classes/external/practicalgrader.php
// HEADER STUFF HERE //

namespace local_practicalgrader\external;

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

require_once($CFG->libdir . "/externallib.php");

class practicalgrader extends \external_api {

public static function


save_practicalgrader_grade() {
return 'OK';
}

public static function


save_practicalgrader_grade_parameters() {
return new \external_function_parameters(
array()
);
}

public static function


save_practicalgrader_grade_returns() {
return new \external_value(PARAM_TEXT,
'result of operation');

247
Introduction to Moodle 3.9+ Plugin Development

}
}

With the extra bits in the save_practicalgrader_grade() and


save_practicalgrader_grade_returns() functions, you can increase the
plugin’s version, upgrade and test it.
The quickest way to test it is on the command line, using the curl command. The plugin
requires two steps:
1 Login for token;
curl --data
"username=<username>&password=<pw>&service=Practical_Gra
des" <moodleurl>/login/token.php

2 Make request with token from Step 1.


curl --data
"wstoken=<token>&moodlewsrestformat=json&wsfunction=loca
l_practicalgrader_save"
<moodleurl>/webservice/rest/server.php

You should get an OK response.


So now we are ready to get to the meat of the web service. We will return either an OK or
an error message in response to any grading request. To grade the activity, we need to
know the activity, the grade, and the student user, making sure that the webservice user can
grade the student. To identify the activity, all external practical activities in any courses
will need to specify an id number. Using the id number means any type of activity can be
graded. If we had a custom ‘practical’ activity, we could find another way of identifying
the activity being graded. When adding or editing a course activity, the idnumber field
can be found in the Common module settings section.

248
Chapter 8 Developing Plugins

Our service will require that the activity’s idnumber (which has to be unique) is
stipulated in the request.
To identify the user, it is unlikely that both the external system and Moodle will have the
same user names. We agree that we will use the student’s email address as the identifier.
The other parameter we will require is the grade. As grades are not always numbers or
percentages, we need to be careful how we define this parameter.
We could use settings to give possible options to the administrator, but we have covered
settings elsewhere and we will go with our defined parameters.
So, let’s define our parameters in the
save_practicalgrader_grade_parameters() function:

/local/practicalgrader/classes/external/practicalgrader.php
public static function
save_practicalgrader_grade_parameters() {
return new \external_function_parameters(
array(
'activityidnumber' =>
new \external_value(PARAM_ALPHANUMEXT,
'activity idnumber'),
'studentemail' =>
new \external_value(PARAM_EMAIL,
'student email address'),
'activitygrade' =>

249
Introduction to Moodle 3.9+ Plugin Development

new \external_value(PARAM_RAW,
'activity grade'),
)
);
}

We can test our parameters using curl again:


curl --data
"wstoken=<token>&moodlewsrestformat=json&wsfunction=loca
l_practicalgrader_save&activityidnumber=AA01_10&studente
mail=<email>&activitygrade=<grade>"
<moodleurl>/webservice/rest/server.php

Now, we can concentrate on the bulk of our service’s work.


Our steps will be as follows:
 Validate the input
 Identify the activity
 Determine if the activity supports grading
 Check the user has grading capabilities for the activity – as some activities do not
specify a mod/<module>:grade capability, we fall back on checking for the
moodle/course:markcomplete capability that managers and teachers will
have by default
 Identify the student user and determine if the student is a course participant
 Create/update the grade via the module’s <module>_grade_item_update()
function. There is a good reason for this. Each plugin determines how its grades are
stored and retrieved. Working directly with the Gradebook API
(https://docs.moodle.org/dev/Gradebook_API), which is not covered in this book,
would complicate this.
So, our final function looks like this:
/local/practicalgrader/classes/external/practicalgrader.php
public static function
save_practicalgrader_grade($activityidnumber,
$studentemail, $activitygrade) {
global $DB, $USER;

250
Chapter 8 Developing Plugins

// Validate the inputs.


$params = self::validate_parameters(
self::save_practicalgrader_grade_parameters(),
array(
'activityidnumber' => $activityidnumber,
'studentemail' => $studentemail,
'activitygrade' => $activitygrade
)
);

// Identify the activity.


$activitymodule = $DB->get_record('course_modules',
array('idnumber' =>
$params['activityidnumber']),
'id, idnumber');
if ($activitymodule) {
list ($course, $cm) =
get_course_and_cm_from_cmid(
$activitymodule->id);
$module = $cm->modname;

if (plugin_supports('mod', $module,
FEATURE_GRADE_HAS_GRADE)) {

// Check the user has grading capabilities.


$context = \context_module::instance(
$cm->id);
$capability = 'mod/' . $module . ':grade';
// Some activities do not have grade caps.
if (get_capability_info($capability)) {
if (!has_capability(
$capability, $context)) {
print_error('errornocapability',
'local_practicalgrader', null,
(object) array(
'username' =>
$USER->username,
'capability' => $capability)
);

251
Introduction to Moodle 3.9+ Plugin Development

}
} else {
// Check for a cap that tutors have.
if (!has_capability(
'moodle/course:markcomplete',
\context_course::instance(
$course->id))) {
print_error('errornocapability',
'local_practicalgrader',
null,
(object) array(
'username' =>
$USER->username,
'capability' =>
$capability)
);
}
}

$activity = $DB->get_record($module,
array('id' => $cm->instance));
// Add the required additional field.
$activity->cmidnumber =
$activitymodule->idnumber;

// Identify the student user and ...


// ... determine if enrolled on course.
if ($students = search_users($course->id, 0,
$params['studentemail'])) {
if (count($students) > 1) {
print_error('errortoomanyusers',
'local_practicalgrader');
}
$student = reset($students);
$studentid = $student->id;
} else {
print_error('errornocourseuser',
'local_practicalgrader',
$params['studentemail']);
}

252
Chapter 8 Developing Plugins

// That should have loaded the module.


$function = $module . '_grade_item_update';

$grades = array(
'userid' => $studentid,
'rawgrade' => $params['activitygrade'],
'usermodified' => $USER->id,
'datesubmitted' => '',
'dategraded' => time()
);

// Add the grade to the grade book.

// There appears to be a problem...


// ... sometimes with some output from ...
// ... the gradebook uses mtrace to ...
// ... output text – new grades?
ob_start();
$result = $function($activity, $grades);
ob_end_clean();

switch ($result) {
case GRADE_UPDATE_FAILED :
return get_string(
'errorgradeupdate',
'local_practicalgrader');
break;
case GRADE_UPDATE_ITEM_LOCKED :
return get_string(
'errorgradelocked',
'local_practicalgrader');
break;
case GRADE_UPDATE_MULTIPLE :
return get_string(
'errorgrademultiple',
'local_practicalgrader');
break;
}

253
Introduction to Moodle 3.9+ Plugin Development

} else {
print_error('errornomodulegrades',
'local_practicalgrader');
}
} else {
print_error('errornoactivity',
'local_practicalgrader');
}

return 'OK';
}

Our language file also needs updating for all the strings defined in the function:
/local/practicalgrader/lang/en/local_practicalgrader.php
$string['errornocapability'] = 'User "{$a->username}"
does not have capability "{$a->capability}" to grade
this item.';
$string['errortoomanyusers'] = 'Too many users returned
for email address';
$string['errornocourseuser'] = 'Student with email
"{$a}" does not appear to be a course participant';
$string['errornomodulefile'] = 'Activity module does not
exist';
$string['errornomodulegrades'] = 'Activity does not
support grading';
$string['errorgradeupdate'] = 'Error: The grade could
not be updated';
$string['errorgradelocked'] = 'Error: The grade has been
locked';
$string['errorgrademultiple'] = 'Error: Too many grades
returned';
$string['errornoactivity'] = 'Error: No such grading
activity';

Now increase the plugin’s version and upgrade to test it. You can test the functionality by
creating or editing an activity in the test course with the idnumber field filled in and
ensuring that the teacher user has the relevant role in the course and is also assigned the
‘Practical Grader’ role. All the test student users will be participants in the course, so
choose one of those as the test subject. Then use curl to enter a grade into the gradebook.

254
Chapter 8 Developing Plugins

This is where we are going to leave this plugin.


If your plugin needs to deal with files, either uploading or downloading, please see the
External Functions Uploads and Downloads section in the Web & External Functions APIs
chapter.

Consuming Web Services


Sometimes we want to consume web services rather than providing them. Both the
repository plugin and the Udacity local plugin we have developed in this chapter have
examples of consuming a web service.

255
Chapter 9
Other Plugin Development Issues

Custom Scripts – When Plugins Won’t Do


It is regarded as bad practice to 'core hack' the Moodle core code. There are times though
that it is impossible not to do this. Before you do, however, see if creating custom scripts
may help. I've only recently found out about custom scripts, but apparently the feature has
existed since Moodle 1.6!! We are not going to cover the subject in this book, so please see
the Moodle ‘Custom Scripts’ page (https://docs.moodle.org/dev/customscripts) for more
information.

Backup
Though strictly not a development subject, it is worth remembering backups, especially in
cases where you need to revert your development environment to a previous point. You
could use a whole device backup to back up the environment, but that is likely to be
impractical for reverting to a particular snapshot. At the very least, you need to back up the
development database as recommended by the database documentation, e.g. mysqldump
for MySql/mariaDB databases and your /config.php file. Moodle’s source code is always
available, so you can always retrieve that if necessary. If you maintain your plugin’s source
code via some kind of version control system, such as git, and have a ‘remote’ copy in both
senses of the word, such as on Github or GitLab, then you can always restore – at least to
the last commit (please see the upcoming Using Git section for more). Otherwise, you will
need to back up your code. This may sound like teaching grandmother to suck eggs, but
you will be surprised how many times it slips our minds.
Chapter 9 Other Plugin Development Issues

I use backup database snapshots mainly when the plugin development generates lots of test
data. For more information on backing up the whole Moodle instance, please see the ‘Site
Backup’ Moodle documentation page (https://docs.moodle.org/39/en/Site_backup).
Moodle provides functionality to back up and restore courses with all the contents and, if
required, user data. This is very useful to obtain test data, maybe from a live system or to
snapshot test data that is time-consuming to generate. In the case of live data, always
remember to obfuscate all the user data, particularly the email addresses, as you would
neither want to generate emails from your development environment nor fall foul of data
protection regulations. There are a couple of development configurations for emails that
would ensure the control of emails as previously touched upon in the ‘Site Development
Configuration’ section of the ‘Plugin Development Essentials’ chapter (Chapter 6). This
functionality allows you to maintain development test data by allowing you to define
courses to be restored into new projects without having to regenerate the data every time.
Modules (aka course activities and resources plugins) should, in most cases, use the
module backup API to hook into course backups as covered in the ‘Developing Modules’
section in the ‘Developing Plugins’ chapter (Chapter 8).
The major thing to remember when using backups in different projects is that the data is
rarely backwards-compatible, which means you should create your test data in a fairly early
version of your database and/or Moodle, and the data should be upgraded when installing
your new development environment.

Using Git
Somewhere between Version 1.9 and 2.0, Moodle moved to the git version control system.
Git (https://git-scm.com/) now seems to overshadow many of the former version control
heavyweights such as Subversion and CVS. Having used both before, I prefer git by a wide
margin.
The Moodle documentation’s page ‘Git for Administrators’
(https://docs.moodle.org/39/en/Git_for_Administrators) describes the process of managing
Moodle instances using git. It may be aimed at administrators, but it is still also a useful
page for developers.
My way of managing my development environment, using git is not the only way to do so
and there have been several discussions on the Moodle forums about the different ways to
use git, e.g. the ‘Best way to use Git’ discussion at
https://moodle.org/mod/forum/discuss.php?d=393756.
In a lot of cases, when I am approached to develop custom plugins, the client has plans to

257
Introduction to Moodle 3.9+ Plugin Development

upgrade their current version sometime in the near future and, in many cases, there will be
two or more version jumps, e.g. from one LTS (Long Term Support) version to the next,
and the expectation is that the plugin will work in both versions. To this end, I maintain a
single Moodle repository in my development environment. I ensure that I remove the
origin's push URL, so that I never attempt to push any changes back to Moodle.
For my web development needs, I clone single branches, e.g. MOODLE_35_STABLE,
MOODLE_39_STABLE into web server virtual servers document roots using:
git clone -b branch_name --single-branch project_url
local_folder_to_clone_in

I can then have different versions of Moodle available. For this book, I also used soft links
to the example plugins folders in Chapter 8 in the Moodle 3.10 version to the Moodle 3.9
directories. That meant I could check the code in both versions.
The use of third-party plugins, both from the plugins directory and for custom-developed
plugins, adds a layer of complications to managing Moodle with git. My answer is to use
git submodules.

Using Git Sub-Modules for Plugins


If you have not used sub-modules before, please read the relevant section on the ‘Git for
Administrators’ page for more information
(https://docs.moodle.org/39/en/Git_for_Administrators#Installing_and_maintaining_contri
buted_extensions_using_Git_submodules).
If I am developing on multiple versions of Moodle, both instances will have the plugin
configured as a sub-module. This usually entails creating a basic remote repository, maybe
only containing a README file for the plugin.
I usually develop on the client’s current version of Moodle. Depending on how complex
the project is, I will test the plugin against the future version at every milestone or at the
end of development. The process is simple: commit the development to the remote and
then pull this into the later version of Moodle. Test. If something has to be fixed for the
later version, do the fixes and push back. Pull back into the current version and ensure that
it all still works. Thankfully, Moodle is good at maintaining some backwards compatibility
– you are more likely to get deprecation notices, if at all, with this method.
As I said, this is only an example of how to use git and git sub-modules to manage
development. I am mostly the sole developer on many of my projects, so this method may
not work for a team working together.

258
Chapter 9 Other Plugin Development Issues

Testing
For core developers, there is a requirement to produce tests for core components. There
does not seem to be a requirement for third-party plugin developers to develop tests, but it
may be good practice to do so. Providing automated tests is not a requirement for plugins
submitted to the directory – (Please see the Plugin contribution checklist page at
https://docs.moodle.org/dev/Plugin_contribution_checklist). However, I suspect more
clients will add it to their requirements in the future. The question is whether the clients
will be in a position to run such tests themselves or whether they will just want proof that
the tests were produced and the results provided. While this book does not cover testing in
any detail, it could be the subject of another book or, at least, some website articles.
If you would like to try out the testing, I suggest you install the tools and test them against
core components. The installations, unfortunately, are a little fiddly and confusing.

Unit Testing
Moodle developers use the PHPUnit (https://phpunit.de/) framework for unit testing. Unit
testing is a software testing method whereby individual units of source code are tested to
determine whether they are fit for use. One complication of unit testing is determining what
a unit means – is it a whole class or individual methods in a class?
If this is new to you, there is a lot of material about unit testing, and particularity PHP unit
testing, to learn from on the Internet.
For Moodle set up and use, have a look at Moodle documentation's ‘PHPUnit’ page
(https://docs.moodle.org/dev/PHPUnit). After some fiddling about, I did get it to work on
my development device.

Acceptance Testing
In software testing, one definition of acceptance testing, also known as user acceptance
testing (UAT) and end-user testing, is as follows:
‘Formal testing with respect to user needs, requirements, and business processes
conducted to determine whether or not a system satisfies the acceptance criteria and to
enable the user, customers or other authorized entity to determine whether or not to accept
the system.’
However, Moodle uses the automated acceptance testing, using the Behat
(https://docs.behat.org/) framework for regression testing, i.e. running functional and non-
functional tests to ensure that previously developed and tested software still performs after
a change. You will notice this if you look at the feature files delivered as part of the core.

259
Introduction to Moodle 3.9+ Plugin Development

I am inclined to believe that as new functionality as well as plugins are developed, the
Behat framework in place will lead to more behaviour-driven development (BDD) and test-
driven development (TDD) strategies for development, but we shall see. It is maybe more
likely that it will be used by plugin clients, at least for acceptance testing.
Setting up the Behat framework is just as fiddly as PHPUnit in Moodle. The easiest
instructions to follow are on the ‘Running Acceptance Test’ page
(https://docs.moodle.org/dev/Running_acceptance_test). This short version at the top of the
page only sets up to test with the Chrome browser. If you come across issues, it can be
intimidating, as can be seen on the ‘Working_combinations_of_OS/Browser/selenium’
page
(https://docs.moodle.org/dev/Acceptance_testing/Browsers/Working_combinations_of_OS
%2BBrowser%2Bselenium). I did get it to work at least for non-JavaScript (non-Selenium)
tests. Selenium (https://www.selenium.dev/) is only used for the testing where JavaScript is
required to run the test.

Publishing Your Plugin


In my case, most of the plugins I have developed have been to solve particular issues for
my clients, and so they are not usually released into the wild; there have been a couple of
exceptions, though. Some organisations, such as the UK's Open University and a few
Moodle partner organisations, not only contribute to the Moodle core code, but also release
plugins for the wider community. These plugins, along with hundreds developed by smaller
organisations and private individuals, are found in the plugins directory.
There is nothing to stop any plugin developer from releasing plugins from their site and a
small number of non-Moodle Partner organisations do, maybe because they do not meet the
requirements to be a Moodle Partner (see the ‘Partner’ pages at
https://moodle.com/become-moodle-partner/).
In theory, all plugin code is open source as per the Moodle licence, which is version 3 of
the GNU General Public License. However, the developer is not compelled to release
publicly, though if he does, it must be under a GPLv3 compatible licence.
Having said that, you might want to or be required to publish/release your plugin. There are
several good reasons itemised on the ‘Plugin Contribution’ page
(https://docs.moodle.org/dev/Plugin_contribution), including easier plugin installation for
administrators and automatic registration with the AMOS tool (the Automated
Manipulation Of Strings) (https://docs.moodle.org/dev/AMOS_manual), based on the
English language version of your plugin.
The approval process for plugins is not fast and does seem to depend on the complexity and

260
Chapter 9 Other Plugin Development Issues

whether it is new or an upgrade. You can see the 'Approval Queue Stats’ page
(https://moodle.org/plugins/queue.php) for the current status. However, before even
considering submitting the plugin, it is best to check that your plugin meets the standard set
out in the ‘Plugin Contribution Checklist’ page
(https://docs.moodle.org/dev/Plugin_contribution_checklist).
Releasing a plugin means responsibility. One of the conditions is that you have to have a
system where users can report bugs and other issues. For this reason, many developers use
GitHub as their public repository, as they can also track issues on the repository. Plugin
developers are expected to keep their plugin up-to-date and there is even an ‘early bird’
award for developers who release updated plugins soon after the release of a new major
version of Moodle. If you find you do not have the time to maintain your plugin, there is an
adopter programme (https://docs.moodle.org/dev/Plugins_adoption_programme), where
you can ask for another developer to adopt your plugin.
If you do decide to release your plugin, you will be contributing to the community that
provides the leading open-source learning environment.

261
Final Words
Well, this is it for the introduction to plugin development for Moodle. I hope you found it
useful and would appreciate your views as mentioned in the opening chapter.
Now, you are in a position to extend Moodle in so many ways and there always appears to
be someone wanting to do something different with Moodle or requiring updates to
existing, particularly, old plugins. A good place to keep an eye out for opportunities is in
the Moodle Jobs forum at https://moodle.org/mod/data/view.php?d=54. For help and to
give help, I would recommend subscribing to the General Developer forum at
https://moodle.org/mod/forum/view.php?id=55; all the top Moodle developers assist each
other and other developers in the forum.
I understand that some people find it difficult to learn from books so, as at the time of the
first print of this book, we are developing an online course as well as planning a YouTube
channel to share additional snippets and other goodies that will make you more valuable to
your employer or clients and the Moodle community at large. Keep an eye on the website
at http://mukudu.net/ or sign up for occasional notifications at
http://mukudu.net/publishing/notifications.php.
For now, good luck and hope to meet you online sometime soon. Have fun!
Index

A cache. See Cache API.


calendar. See Calendar API.
Access API, 17, 19, 47, 80–82, 113, 205 charts. See Charts API.
checking user’s capabilities, 82–83 check. See Check API.
description and purpose, 17, 19, 80 comment. See Comment API.
determining permissions, 83 competency. See Competency API.
Acceptance Test API, 22, 258 custom fields. See Custom fields API.
accessibility, 12 data definition. See Data definition API.
activity modules plugins, 22 data manipulation. See Data manipulation API.
Activity completion API, 19 editor. See Editor API.
Activity module APIs, 19 enrolment. See Enrolment API.
Admin settings API, 19, 75–78 events. See Events API.
description and purpose, 19, 75 experience. See Experience API.
Form API and, 75, 77 external functions. See External functions API.
admin tools plugins, 23 favourites. See Favourites API.
Advanced grading API, 19 file. See File API.
AJAX, form. See Form API.
assignment feedback plugin, 186, 190–193 gradebook. See Gradebook API.
cohort element, 56 groups. See Groups API.
core library, 30 lock. See Lock API.
core/ajax module, 30 logging. See Logging API.
course element, 56 media. See Media API.
Form API, 49 message. See Message API.
options array variable, 137 moodlelib. See Moodlelib API.
Rating API, 22 my profile. See My profile API.
Amazon S3, 133 navigation. See Navigation API.
AMD. See Javascript. oauth 2. See OAuth 2 API.
AMOS (Automated Manipulation Of Strings) output. See Output API.
tool, 35, 107, 259 page. See Page API.
Analytics API, 19 payment. See Payment API.
API plagiarism. See Plagiarism API.
key, 133, 137 portfolio. See Portfolio API.
access. See Access API. preference. See Preference API.
activity completion. See Activity completion privacy. See Privacy API.
API. question. See Question API.
activity modules. See Activity module APIs. rating. See Rating API.
admin settings. See Admin settings API. rss. See RSS API.
advanced grading. See Advanced grading API. search. See Search API.
analytics. See Analytics API. slideshare. See Slideshare API.
availability. See Availability API. string. See String API.
backup. See Backup API. tag. See Tag API.
badges. See Badges API. task. See Task API.

i
testing. See Testing API. required files, 111–112
time. See Time API. restrictions, 114–115
udacity. See Udacity API. types of blocks, 111
upgrade. See Upgrade API. Boost (core default theme), 12–13, 14
user-relateds. See User-related APIs.
web services. See Web services API. C
assignment feedback plugin, 23, 181–202
AJAX, 181, 186, 190–193 Cache API, 20, 162, 163
get recording script, 190–192 Calendar API, 20
installing plugin, 182 calendar types plugin, 23
JQuery, 181, 186 callbacks,
listening to audio feedback, 197–202 description and purpose, 31
recording feedback, 185–190 Events API, 88
required files, 181–182 File API, 47
settings, 184 legacy issues, 11–12
assignment submission plugin, 23 in lib.php file, 31, 107
Assistant Teacher, 101 Navigation API, 31, 86–87, 224
Asynchronous Module Definition (AMD) API. capabilities, 16–17
See Javascript. Access API, 19, 80–84
authentication, array keys, 81–82
Active Directory, 8 block plugin, 113–114
description and purpose, 8 contexts, 82–83
‘guest’ user option, 8 description and purpose, 8
requirement, 8 enrolment plugin, 204–209, 215, 217
Single Sign-On technology, 8 external service plugin, 241–244, 249–251
authentication plugins, 23, 217–223 module example, 154–155
description and purpose, 217–217 permissions and, 16–17
example, 217–223 repository plugin, 135External functions API, 20
installing plugin, 218 Charts API, 20, 226
privacy considerations, 222–223 Check API, 20
required files, 217–218 class autoloading, 11, 26
autoloading. See class autoloading, callback, 88
Availability API, 19 external service plugin, 241, 245
local plugin, 232
B locallib.php versus, 163
Task API, 90
Backup API, 20, 256 code skeleton templates, 102–104
backups, 255–256 cohorts, 8
Badges API, 20 Comment API, 20
base layout, 13 community development tools, 102
blocks plugins, 23, 111–119 Competency API, 20
content member variables, 115–116 consuming web services, 254
dashboard availability, 119 contexts, 16–17
defining language string, 116–117 block, 17
description and purpose, 111 course category, 16
installing plugin, 113 module, 16
new activity example, 111–119 system, 16
permissions and capabilities, 113–114 user, 16

ii
conventions used in the book, 3 editors plugin, 23
core hacking, 15, 255 embedded layout, 14
course activities. See modules, Enrolment API, 15, 20, 82, 83
Course Creator, 16, 101, 133 description and purpose, 20, 83
course formats plugin, 23, 119–133 plugin-specific functions, 83–84
bannertopics example, 119–133 user-specific functions, 84
description and purpose, 23, 119–119 enrolment plugin, 23, 92, 202–217
formats, 7, 23, 119 capabilities, 204–209
installing plugin, 121 catalogue example, 203–217
required files, 119–121 required files, 203–204
course layout, 13 roles, 211, 215–217
coursecategory layout, 13 enrolments,
courses, APIs and, 11
dashboard, 6 cohorts and, 8
dashboard (user home page), 6, 8–9 description and purpose, 7, 202
formats, 7 manual, 202
modules and blocks, 6 roles separate from, 202
single activity, 7 Events API, 20, 21, 88–90, 224
site home page, 6, 8–9 description and purpose, 20, 88
social format, 7 logging, 20, 89–90
topics format, 7 Experience API, 20
weekly format, 7 exporter
Cross Reference and Version Differences Site, class, 92, 95
104–104 extensions, 11
Custom Fields API, 20 External functions API, 20, 91–95
custom scripts, 255 external services plugin. See web and external
services for plugins,
D
F
Data definition API, 20, 72–75
database manager class, 72–74 Favourites API, 20
description and purpose, 20, 72–74 File API, 20, 41, 43, 55, 45–48, 61, 62, 63, 64
XMLDB developer tool, 74–75 description and purpose, 20, 45
Data manipulation API, 20, 64–72 file area, 46
cross-db compatibility, 69–70 file delivery, 47
database-related functions, 71–72 in form element, 60–64
development environment setup, 97 file masthead, 105–106
development test data, 100–101 GPL copyright statement, 105
IDEs, 101 PHP doc tags, 105–106
site development configuration, 98–99 Flickr, 9, 133
SSL, 97–98 Form API, 21, 43, 46, 48–64, 76, 78, 117, 125–
development test data, 100–101 127, 131, 138
Dropbox, 9, 133 advanced file elements, 60–64
description and purpose, 20, 48
E form elements, 51–60
frametop layout, 13
Editing Teacher, 101, 208 Frankenstyle, 2, 15, 26, 35, 46, 63, 74, 79, 106,
Editor API, 20 107, 130, 204, 245

iii
frontpage layout, 13 AMD (Asynchronous Module Definition), 29–
30, 31, 185–187
G assignment feedback plugin, 186–187, 190
framework, 29–31
GDPR, 17, 21, 91 Grunt tool, 27, 185
Git, 256–257 JQuery, 29–31, 181, 186
Git for Administrators documentation, 256 legacy issues, 11
Git sub-modules for plugins, 257 MediaDevices interface, 181, 186–187, 190
GitHub, minimised, 30, 99, 185
for backups, 255 Output API and, 38, 39
as public repository, 255 Page API, 21, 37
GitHub repositories, JSON, 30, 89, 92, 93, 190, 192–193, 234–235,
block plugin templates, 102 238, 247, 249
Certificate Authority scripts, 97
Moodle PHPdoc Checker, 25, 102 L
Mustache, 40
plugin codes, 3–4 languages and internationalisation, 14–15
templates, 14 Australian English (default), 14–15
third-party library, 160 Strings API and, 14
Google Drive, 9, 133 layouts,
Gradebook API, 21, 249 defined by theme, 13–14
gradebook export plugin, 23 list of, 13–14
gradebook import plugin, 23 LDAP, 105, 233
gradebook reports plugin, 23 legacy issues, 11–12
groups, callbacks and, 11–12
cohorts, 8 Events API and, 88
groupings of, 8 Form API and, 11, 48
Groups API, 21 lib.php, 107–108
Grunt tool, 27, 185 assignment feedback plugin, 182, 190, 192–194,
197
H course format plugin, 119–143
enrolment plugin, 203–204, 208–211, 217
HTML5, 12 externallib.php, 92, 241, 245–246
locallib.php, 107, 163, 182–183, 195
I modules, 148, 149–150, 157–160, 163–169,
172–173
IDEs, 101
reporting plugin, 224
incourse layout, 13
local plugin, 23, 229–240
index.php, 108
adding global settings, 77
block plugin, 114
callbacks, 86
enrolment plugin, 209–212
description and purpose, 23, 92, 229
local plugin, 231–234, 238, 240
example, 229–240
reporting plugin, 224–226, 228
required files, 230
installing plugins, 108–109
settings name, 231
sub-plugins supported by, 181
J Udacity, 254
JavaScript, 29–31 Lock API, 21
accessibility and, 12 logging, 89–90

iv
Logging API, 21, 88 naming conventions, 15
login layout, 13 navigation and blocks, 8–9
LTI services & sources plugin, 23 Navigation API, 21, 31, 78, 84–87, 223
callbacks, 31, 86–87
M description and purpose, 21, 84
working with navigation trees, 84–85
maintenance layout, 14 node types, 85
Manager, short name, 21
assigning roles, 101 tree structure of nodes, 84
permissions, 16–17 navigation conventions, 3
unenrolment, 205–206 null provider, 91, 222
Media API, 21
media players plugin, 24 O
Message API, 21
messaging consumers plugin, 24 OAuth 2 API, 21
Microsoft OneDrive, 133 open source,
modules, history of Moodle, 5
autoloading versus localib.php, 163 Moodle Workspace, 6
backup and restore, 171 plugin code, 259
caching, 161 Output API, 21, 38–45, 120
description and purpose, 9, 145
example, 145–181 P
third-party libraries, 160
viewing, 169 Page API, 21, 37–38, 209
Moodle, Payment API, 21
history and purpose, 5–6 PEAR,
open source, 5–6 check-in, 28
user and site statistics, 6 description and purpose, 28
Moodle 3.9, 1, 12, 97, 106, 257 GNU GPLv3 compatibility, 29
Moodle 4.0, 12, 13 modifications, 29
Moodle caching, 109 permissions, 16–17
Moodle development guidelines, allow, 16
code checker plugin, 25 capabilities and, 16–17
Moodle PHPdoc checker, 25 contexts and, 16–17
Moodle Workspace, 6 not set, 16
Moodlelib API, 21, 33–35, 36, 52, 78, 82 prevent, 16
Moosh, 104 prohibit, 16
My profile API, 21 roles and, 17
mydashboard layout, 13 PHPUnit, 22, 258, 259
mypublic layout, 13 Plagiarism API, 21
plagiarism plugin, 24
N Plugin Skeleton Generator, 102–104
plugins
namespaces, 26 activity modules. See activity modules plugins
External Functions API, 245–246 admin tools. See admin tools plugins
local plugins, 232, 240 assignment feedback. See assignment feedback
reporting plugins, 224–225 plugins
See also class autoloading,

v
assignment submission. See assignment renderers, 14
submission plugins auto-loading, 40
authentication. See authentication plugins description and purpose, 14, 40
blocks. See blocks plugins in developing course format, 119–129
calendar types. See calendar types plugins Output API, 39
course formats. See course formats plugins renderer.php files, 40
editors. See editors plugins required, 119
enrolment. See enrolment plugins templates and, 40
gradebook export. See gradebook export plugins report layout, 14
gradebook import. See gradebook import reports plugin, 24, 223–229
plugins description and purpose, 24, 223
gradebook reports. See gradebook reports graphical report example, 223–229
plugins installing plugin, 224
local. See local plugins logging, 224, 228
LTI services & sources. See LTI services & required files, 223
sources plugins repositories,
media players. See media players plugins Amazon S3, 133
messaging consumers. See messaging Dropbox, 9, 133
consumers plugins Flickr, 9, 133
plagiarism. See plagiarism plugins Google Drive, 9, 133
portfolio. See portfolio plugins Microsoft OneDrive, 133
question types. See question types plugins Office365, 9
reports. See reports plugins repository plugin, 24, 133–145
repository. See repository plugins course-specific instances, 137–138
SCORM reports. See SCORM reports plugins description and purpose, 24, 133
popup layout, 13 example, 133–145
Portfolio API, 21 file link types, 145
portfolio plugin, 24 installing plugin, 134
Preference API, 21 required files, 133–134
print layout, 14 restriction of file types, 136–137
Privacy API user instances, 137–138
description and purpose, 17, 21, 91 required files, 106–108
GDPR and, 17, 21, 91 db/install.xml, 108
publishing plugins, 259–260 index.php, 108
approval process, 259 language file, 107
responsibilities of, 259–260 lib.php, 107–108
readme file, 108
Q version.php, 106–107
REST, 92, 158, 229, 234, 240, 243
Question API, 22 roles, 8, 16
question types plugin, 24 RSS API, 22
quiz module, 233
S
R
SCORM module, 9, 24, 95, 223
Rating API, 22 SCORM reports plugin, 24
readme file, 108 Search API, 22
redirect layout, 14 secure layout, 14

vi
Selenium, 259 Unit Test API, 22
Shibboleth, 3, 105, 233 Unit testing, 258
Slideshare API, 140, 141, 144 Upgrade API, 22, 78–79
site administrators, 17, 23 user interface, 12–13
admin tools plugins. See Admin tools plugins development and debugging mode, 98
get_admin function, 71 File API and, 48
role and permission, 17 Navigation API and, 84
Task API and, 90 navigation conventions, 3
user hierarchy, 8 Page UPI and, 37
web and external services, 94 String API and, 22
site development configuration, 98–99 See also Yahoo! User Interface,
skeleton templates. See code skeleton templates, User-related APIs, 22
SOAP, 92, 243 users, 7–8
SSL, 97–98 authenticate requirement, 8
standard layout, 13 description and purpose, 7–8
String API, 22, 34, 35–37, 51, 52, 112 hierarchy, 8
sub-plugins, 181–181 site administrators as, 8, 17
in assignment activity module, 181 user-specific tools, 8
plugin types that support, 181–181
V
T
version.php, 106–107
Tag API, 22 assignment feedback plugin, 181
Task API, 22, 90, 231 authentication plugin, 217
description and purpose, 22, 90 block plugin, 111–112, 119
used in local plugin, 90, 231 course format plugin, 119–120, 126
Testing API, 22 enrolment plugin, 203
testing, 258–259 external service plugin, 240
acceptance testing, 22, 258–259 local plugin, 230
Behat framework, 258–259 modules, 145
core component testing, 258 reporting plugin, 223
unit testing, 22, 258 repository plugin, 134
themes, Upgrade API, 78
Boost (core default theme), 12–13, 14
layouts, 13–14 W
third-party developers, 1, 11
third-party library, 26–27 web and external services for plugins, 240–253
definition, 26 class file, 245
exclusion update, 27 external service functionality, 240–241
GNU GPLv3 compatibility, 26 installing plugin, 240
thirdpartylibs.xml file, 27 practical grader REST example, 240–253
Time API, 22 required files, 240
Totara Learn, 5–6 users and capabilities, 243–245
Web services API, 22, 91–95, 240, 245
U administration, 94–95
description and purpose, 22, 93–94
Udacity API, 239 file downloads, 93–94
uninstalling plugins, 108 file uploads, 93

vii
protocols resources, 91–92 Y
X Yahoo! User Interface (YUI), 11, 27, 29, 39, 43
XMLDB tool, 72, 74–75, 145–146, 182 Z
XML-RPC, 92, 243
Zend framework, 27–28

viii

You might also like