Moodle 39+ Plugin Development-Podia
Moodle 39+ Plugin Development-Podia
Benjamin C Ellis
Introduction to Moodle 3.9+ Plugin
Development
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.
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
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.
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.
6
Chapter 1 Introduction to Moodle and Learning Environments
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.
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.
8
Chapter 1 Introduction to Moodle and Learning Environments
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.
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
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.
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).
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
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
Plugin Types
This is a list of the most commonly used plugin types supported by Moodle 3.9:
22
Chapter 3 Moodle APIs and Plugin Types
23
Introduction to Moodle 3.9+ Plugin Development
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.
26
Chapter 4 Plugin Development Background
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
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
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
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
39
Introduction to Moodle 3.9+ Plugin Development
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).
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
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:
42
Chapter 5: Common APIs
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
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
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
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
require_once($CFG->libdir . '/formslib.php');
function definition() {
$mform = $this->_form; // MoodleQuickForm obj.
$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
54
Chapter 5: Common APIs
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
55
Introduction to Moodle 3.9+ Plugin Development
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_time_selector Class for a group of elements used to input a date and time
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
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
html Add arbitrary HTML – usually to add the start and end of a HTML
div
Parameters – $elementName, $value
57
Introduction to Moodle 3.9+ Plugin Development
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
58
Chapter 5: Common APIs
$attributes
selectwithlink Specialised custom class for a ‘select’ type element with options
containing links
Parameters – $elementName, $elementLabel,
$options, $attributes, $linkdata
tags Specialised custom class for editing tags, either standard or not
59
Introduction to Moodle 3.9+ Plugin Development
$options, $attributes
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
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);
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);
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( ... )
63
Introduction to Moodle 3.9+ Plugin Development
$filearea, $itemid);
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
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
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
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 '%'.
71
Introduction to Moodle 3.9+ Plugin Development
72
Chapter 5: Common APIs
// Define table.
$table = new xmldb_table('mynewtool');
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).
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
$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
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) {
$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
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
);
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
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
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
85
Introduction to Moodle 3.9+ Plugin Development
86
Chapter 5: Common APIs
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).
88
Chapter 5: Common APIs
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).
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)
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
96
Chapter 6
Plugin Development Essentials
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
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
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
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
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.
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.
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).
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
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.
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
For more information on these and other variables, please see the Moodle documentation's
‘version.php’ page (https://docs.moodle.org/dev/version.php).
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.
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.
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 //
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 //
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'
),
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
/blocks/new_activity/block_new_activity.php
public function applicable_formats() {
return array(
'my' => true,
);
}
116
Chapter 8 Developing Plugins
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();
For a full explanation of the block functions, see the ‘Blocks – Appendix A’ page
117
Introduction to Moodle 3.9+ Plugin Development
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 //
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
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
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.
120
Chapter 8 Developing Plugins
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2020061500;
$plugin->requires = 2020061500; // Requires M3.9.
$plugin->component = 'format_bannertopics';
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');
}
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();
$renderer = $PAGE->get_renderer('format_bannertopics');
$renderer->print_multiple_section_page($course,
null, null, null, null);
defined('MOODLE_INTERNAL') || die();
123
Introduction to Moodle 3.9+ Plugin Development
class format_bannertopics_renderer
extends format_section_renderer_base {
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();
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;
}
/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);
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
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;
}
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()) {
131
Introduction to Moodle 3.9+ Plugin Development
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);
file_prepare_draft_area(
$draftitemid,
$ctxid,
'format_bannertopics',
'banners',
$itemid,
array('subdirs' => 0)
);
$savedoptions['sectionbanner'] =
$draftitemid;
}
return $savedoptions;
}
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).
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2020061500;
$plugin->requires = 2020061500; // Requires M3.9.
$plugin->component = 'repository_slideshare';
/repository/slideshare/lang/en/repository_slideshare.php
// HEADER STUFF HERE //
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 //
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
)
)
);
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
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';
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);
}
At the moment our repository is a global one. To enable ‘instance’ creating and
140
Chapter 8 Developing Plugins
$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);
}
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);
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) {
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.
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';
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
149
Introduction to Moodle 3.9+ Plugin Development
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;
152
Chapter 8 Developing Plugins
//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
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(
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);
}
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.
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
)
);
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 {
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;
}
$id = $DB->insert_record('wikipediasnippet',
$wikipediasnippet);
166
Chapter 8 Developing Plugins
"{$a}"';
$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
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');
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();
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.
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;
}
}
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 {
174
Chapter 8 Developing Plugins
'includecitations',
'timecreated',
'timemodified'
)
);
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
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>
<p dir="ltr" style="text-align: left;">
Some description.
<br></p>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 {
$paths = array();
$paths[] = new restore_path_element(
'wikipediasnippet',
'/activity/wikipediasnippet');
179
Introduction to Moodle 3.9+ Plugin Development
$data = (object)$data;
// Get the new course id.
$data->course = $this->get_courseid();
/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() {
/**
* Define (add) steps this activity can have.
*/
protected function define_my_steps() {
$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();
}
}
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)
);
/mod/assign/feedback/audio/lang/en/assignfeedback_audio.php
// HEADER STUFF HERE //
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
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();
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');
}
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;
$PAGE->requires->js_call_amd(
'assignfeedback_audio/record-me', 'init',
$params);
// Title.
$mform->addElement('header', 'general',
get_string('pluginname',
'assignfeedback_audio'));
$mform->addElement('html',
\html_writer::div('',
189
Introduction to Moodle 3.9+ Plugin Development
$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);
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');
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');
}
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);
}
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()) {
195
Introduction to Moodle 3.9+ Plugin Development
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));
}
$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()
);
197
Introduction to Moodle 3.9+ Plugin Development
$draftfile);
}
return true;
}
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);
}
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()
);
if ($title) {
$content = "<div><h5>$title</h5></div>\n".
$content;
}
}
}
return $content;
}
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;
$PAGE->requires->js_call_amd(
'assignfeedback_audio/record-me', 'init',
$params);
// Title.
$mform->addElement('header', 'general',
get_string('pluginname',
'assignfeedback_audio'));
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'))
);
$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
return true;
}
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).
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2020061500;
$plugin->requires = 2020061500; // Requires M3.9
$plugin->component = 'enrol_catalogue';
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();
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;
}
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,
)
),
207
Introduction to Moodle 3.9+ Plugin Development
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
/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');
}
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;
}
/enrol/catalogue/lib.php
public function can_add_instance($courseid) {
$context = context_course::instance($courseid,
MUST_EXIST);
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);
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');
$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);
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') .')') ;
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))
));
}
}
}
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
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).
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 //
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');
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
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
);
}
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();
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).
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2020061500;
$plugin->requires = 2020061500; // Requires M3.9
$plugin->component = 'report_plugins';
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
defined('MOODLE_INTERNAL') || die();
// CLASS HEADER //
class report_viewed extends \core\event\base {
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();
$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();
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);
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).
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).
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';
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 ){
231
Introduction to Moodle 3.9+ Plugin Development
0, ' / ');
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;
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');
233
Introduction to Moodle 3.9+ Plugin Development
echo $OUTPUT->header();
echo $OUTPUT->heading(get_string('pluginname',
'local_udacity_sync'));
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';
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']));
}
}
}
237
Introduction to Moodle 3.9+ Plugin Development
if ($updaterequired) {
$record->id = $course->id;
update_course($record);
}
} else {
create_course($record);
}
}
}
}
238
Chapter 8 Developing Plugins
return true;
}
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.
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2020061500;
$plugin->requires = 2020061500; // Requires M3.9
$plugin->component = 'local_practicalgrader';
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.
)
);
244
Chapter 8 Developing Plugins
defined('MOODLE_INTERNAL') || die();
$capabilities = array(
// Allow graders to access the service.
'local/practicalgrader:grade' => array(
'riskbitmask' => RISK_XSS,
'captype' => 'write',
'contextlevel' => CONTEXT_SYSTEM
),
);
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
246
Chapter 8 Developing Plugins
namespace local_practicalgrader\external;
defined('MOODLE_INTERNAL') || die();
require_once($CFG->libdir . "/externallib.php");
247
Introduction to Moodle 3.9+ Plugin Development
}
}
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'),
)
);
}
250
Chapter 8 Developing Plugins
if (plugin_supports('mod', $module,
FEATURE_GRADE_HAS_GRADE)) {
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;
252
Chapter 8 Developing Plugins
$grades = array(
'userid' => $studentid,
'rawgrade' => $params['activitygrade'],
'usermodified' => $USER->id,
'datesubmitted' => '',
'dategraded' => time()
);
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
255
Chapter 9
Other Plugin Development Issues
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.
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.
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
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