<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://docs.moodle.org/dev/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Davidbalch</id>
	<title>MoodleDocs - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://docs.moodle.org/dev/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Davidbalch"/>
	<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/Special:Contributions/Davidbalch"/>
	<updated>2026-08-02T01:00:52Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.43.5</generator>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Time_API&amp;diff=57525</id>
		<title>Time API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Time_API&amp;diff=57525"/>
		<updated>2020-05-21T12:58:41Z</updated>

		<summary type="html">&lt;p&gt;Davidbalch: /* Examples */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
&lt;br /&gt;
Internally Moodle always stores all times in unixtime format (number of seconds since epoch) which is independent of timezones.&lt;br /&gt;
&lt;br /&gt;
The Time API is used to display proper date-time depending on user or site timezones.&lt;br /&gt;
&lt;br /&gt;
==Functions==&lt;br /&gt;
There is a class in Moodle to handle most needs of working with times. There are 2 cases to consider when working with time:&lt;br /&gt;
&lt;br /&gt;
System Time&lt;br /&gt;
&lt;br /&gt;
This is when you are dealing with dates on the server e.g. executing scheduled tasks, performing background tasks - anything which does not depend on the timezone of any specific user.&lt;br /&gt;
&lt;br /&gt;
User Time&lt;br /&gt;
&lt;br /&gt;
This is when you are manipulating dates and times and you need to display them to the user in their current timezone (which may be different for each user).&lt;br /&gt;
&lt;br /&gt;
The main API for time is in the class &amp;quot;core_date&amp;quot; which will give you php DateTimeZone objects for either user time or server time as needed. You can then use the php datetime classes to manipulate the time. When finished manipulating the time, get a timestamp with DateTime::getTimestamp().&lt;br /&gt;
&lt;br /&gt;
Example: Get the current server time + 1 day.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
$tomorrow = new DateTime(&amp;quot;1 day&amp;quot;, core_date::get_server_timezone_object());&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Get a timestamp for storing in the database:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
$tomorrowint = $tomorrow-&amp;gt;getTimestamp();&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Get a timestamp for 3pm tomorrow in the current users timezone.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
$time = new DateTime(&amp;quot;now&amp;quot;, core_date::get_user_timezone_object());&lt;br /&gt;
$time-&amp;gt;add(new DateInterval(&amp;quot;P1D&amp;quot;));&lt;br /&gt;
$time-&amp;gt;setTime(15, 0, 0);&lt;br /&gt;
&lt;br /&gt;
$timestamp = $time-&amp;gt;getTimestamp();&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Remember! NEVER NEVER NEVER add or subtract timestamps for any reason - you will get it wrong (DST is a killer)!&lt;br /&gt;
&lt;br /&gt;
Other functions related to time api can be found in lib/moodlelib.php. &lt;br /&gt;
# Time API&#039;s for current user&lt;br /&gt;
#* &#039;&#039;&#039;make_timestamp&#039;&#039;&#039; - Given date-time, it produces a GMT timestamp for current user.&lt;br /&gt;
#* &#039;&#039;&#039;userdate&#039;&#039;&#039; - Gets formatted string that represents a date in user time (note that the format required by this function is the [https://www.php.net/manual/en/function.strftime.php strftime()] format, not the more common format used by date())&lt;br /&gt;
#* &#039;&#039;&#039;usertime&#039;&#039;&#039; - Given a GMT timestamp (seconds since epoch), offsets it by the timezone.  eg 3pm in India is 3pm GMT - 5.5 * 3600 seconds&lt;br /&gt;
#* &#039;&#039;&#039;usergetdate&#039;&#039;&#039; - Given a timestamp in GMT, returns an array that represents the date-time in user time&lt;br /&gt;
#* &#039;&#039;&#039;usergetmidnight&#039;&#039;&#039; - Given a date, return the GMT timestamp of the most recent midnight for the current user.&lt;br /&gt;
#* &#039;&#039;&#039;usertimezone&#039;&#039;&#039; - Returns current user&#039;s timezone&lt;br /&gt;
&lt;br /&gt;
# System Time API&lt;br /&gt;
#* &#039;&#039;&#039;format_time&#039;&#039;&#039; - Format a date/time (seconds) as weeks, days, hours etc as needed&lt;br /&gt;
#* &#039;&#039;&#039;dst_offset_on&#039;&#039;&#039; - Calculates the Daylight Saving Offset for a given date/time (timestamp)&lt;br /&gt;
#* &#039;&#039;&#039;find_day_in_month&#039;&#039;&#039; - Calculates when the day appears in specific month&lt;br /&gt;
#* &#039;&#039;&#039;days_in_month&#039;&#039;&#039; - Calculate number of days in a given month&lt;br /&gt;
#* &#039;&#039;&#039;dayofweek&#039;&#039;&#039; - Calculate the position in the week of a specific calendar day&lt;br /&gt;
&lt;br /&gt;
# Older legacy date/time functions. Do not use in new code.&lt;br /&gt;
#* &#039;&#039;&#039;usertime&#039;&#039;&#039; - Appends the users timezone offset to an integer timestamp&lt;br /&gt;
#* &#039;&#039;&#039;get_timezone_offset&#039;&#039;&#039; - Systems&#039;s timezone difference from GMT in seconds&lt;br /&gt;
#* &#039;&#039;&#039;get_user_timezone_offset&#039;&#039;&#039; - Returns user&#039;s timezone difference from GMT in hours&lt;br /&gt;
#* &#039;&#039;&#039;dst_changes_for_year&#039;&#039;&#039; -  Calculates the required DST change and returns a Timestamp Array&lt;br /&gt;
&lt;br /&gt;
==Glossary==&lt;br /&gt;
===Timezone===&lt;br /&gt;
Moodle supports following timezone formats:&lt;br /&gt;
# UTC (specifically UTC−11 to UTC+11)&lt;br /&gt;
# Time offsets from UTC (int +-(0-13) or float +-(0.5-12.5))&lt;br /&gt;
# World timezones (Australia/Perth)&lt;br /&gt;
&lt;br /&gt;
===Location===&lt;br /&gt;
Timezone depends on [[:en:Location]] of the user and can be forced upon by administrator.&lt;br /&gt;
===DST===&lt;br /&gt;
DST is abbreviation of &#039;&#039;&#039;Daylight Saving Time&#039;&#039;&#039; (also known as &amp;quot;Day light saving&amp;quot; and &amp;quot;Summer Time&amp;quot;). Many countries, and sometimes just certain regions of countries, adopt daylight saving time during part of the year. Moodle automatically calculates DST for current user, depending on user location.&lt;br /&gt;
&lt;br /&gt;
==Examples==&lt;br /&gt;
===Create DateTime with date/time from a unixtime (number of seconds)===&lt;br /&gt;
&lt;br /&gt;
$date = new DateTime();&lt;br /&gt;
$date-&amp;gt;setTimestamp(intval($unixtime);&lt;br /&gt;
echo userdate($date-&amp;gt;getTimestamp());&lt;br /&gt;
&lt;br /&gt;
===Time API&#039;s for current user===&lt;br /&gt;
Prints a formatted date in user time&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
// Get current day, month and year for current user.&lt;br /&gt;
$date = new DateTime(&amp;quot;now&amp;quot;, core_date::get_user_timezone_object());&lt;br /&gt;
$date-&amp;gt;setTime(0, 0, 0);&lt;br /&gt;
// Print formatted date in user time.&lt;br /&gt;
echo userdate($date-&amp;gt;getTimestamp(), &amp;quot;Current user time&amp;quot;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===System Time API===&lt;br /&gt;
Find the day of the week for the first day in this month.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$now = new DateTime(&amp;quot;now&amp;quot;, core_date::get_server_timezone_object());&lt;br /&gt;
&lt;br /&gt;
$year = $now-&amp;gt;format(&#039;Y&#039;);&lt;br /&gt;
$month = $now-&amp;gt;format(&#039;m&#039;);&lt;br /&gt;
&lt;br /&gt;
$now-&amp;gt;setDate($year, $month, 1);&lt;br /&gt;
$dayofweek = $now-&amp;gt;format(&#039;N&#039;);&lt;br /&gt;
echo $dayofweek;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
[[http://php.net/manual/en/class.datetime.php Php DateTime class]]&lt;br /&gt;
* [[Core APIs]]&lt;br /&gt;
&lt;br /&gt;
[[Category:API]]&lt;/div&gt;</summary>
		<author><name>Davidbalch</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Time_API&amp;diff=57524</id>
		<title>Time API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Time_API&amp;diff=57524"/>
		<updated>2020-05-21T11:52:32Z</updated>

		<summary type="html">&lt;p&gt;Davidbalch: /* DST */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
&lt;br /&gt;
Internally Moodle always stores all times in unixtime format (number of seconds since epoch) which is independent of timezones.&lt;br /&gt;
&lt;br /&gt;
The Time API is used to display proper date-time depending on user or site timezones.&lt;br /&gt;
&lt;br /&gt;
==Functions==&lt;br /&gt;
There is a class in Moodle to handle most needs of working with times. There are 2 cases to consider when working with time:&lt;br /&gt;
&lt;br /&gt;
System Time&lt;br /&gt;
&lt;br /&gt;
This is when you are dealing with dates on the server e.g. executing scheduled tasks, performing background tasks - anything which does not depend on the timezone of any specific user.&lt;br /&gt;
&lt;br /&gt;
User Time&lt;br /&gt;
&lt;br /&gt;
This is when you are manipulating dates and times and you need to display them to the user in their current timezone (which may be different for each user).&lt;br /&gt;
&lt;br /&gt;
The main API for time is in the class &amp;quot;core_date&amp;quot; which will give you php DateTimeZone objects for either user time or server time as needed. You can then use the php datetime classes to manipulate the time. When finished manipulating the time, get a timestamp with DateTime::getTimestamp().&lt;br /&gt;
&lt;br /&gt;
Example: Get the current server time + 1 day.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
$tomorrow = new DateTime(&amp;quot;1 day&amp;quot;, core_date::get_server_timezone_object());&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Get a timestamp for storing in the database:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
$tomorrowint = $tomorrow-&amp;gt;getTimestamp();&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Get a timestamp for 3pm tomorrow in the current users timezone.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
$time = new DateTime(&amp;quot;now&amp;quot;, core_date::get_user_timezone_object());&lt;br /&gt;
$time-&amp;gt;add(new DateInterval(&amp;quot;P1D&amp;quot;));&lt;br /&gt;
$time-&amp;gt;setTime(15, 0, 0);&lt;br /&gt;
&lt;br /&gt;
$timestamp = $time-&amp;gt;getTimestamp();&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Remember! NEVER NEVER NEVER add or subtract timestamps for any reason - you will get it wrong (DST is a killer)!&lt;br /&gt;
&lt;br /&gt;
Other functions related to time api can be found in lib/moodlelib.php. &lt;br /&gt;
# Time API&#039;s for current user&lt;br /&gt;
#* &#039;&#039;&#039;make_timestamp&#039;&#039;&#039; - Given date-time, it produces a GMT timestamp for current user.&lt;br /&gt;
#* &#039;&#039;&#039;userdate&#039;&#039;&#039; - Gets formatted string that represents a date in user time (note that the format required by this function is the [https://www.php.net/manual/en/function.strftime.php strftime()] format, not the more common format used by date())&lt;br /&gt;
#* &#039;&#039;&#039;usertime&#039;&#039;&#039; - Given a GMT timestamp (seconds since epoch), offsets it by the timezone.  eg 3pm in India is 3pm GMT - 5.5 * 3600 seconds&lt;br /&gt;
#* &#039;&#039;&#039;usergetdate&#039;&#039;&#039; - Given a timestamp in GMT, returns an array that represents the date-time in user time&lt;br /&gt;
#* &#039;&#039;&#039;usergetmidnight&#039;&#039;&#039; - Given a date, return the GMT timestamp of the most recent midnight for the current user.&lt;br /&gt;
#* &#039;&#039;&#039;usertimezone&#039;&#039;&#039; - Returns current user&#039;s timezone&lt;br /&gt;
&lt;br /&gt;
# System Time API&lt;br /&gt;
#* &#039;&#039;&#039;format_time&#039;&#039;&#039; - Format a date/time (seconds) as weeks, days, hours etc as needed&lt;br /&gt;
#* &#039;&#039;&#039;dst_offset_on&#039;&#039;&#039; - Calculates the Daylight Saving Offset for a given date/time (timestamp)&lt;br /&gt;
#* &#039;&#039;&#039;find_day_in_month&#039;&#039;&#039; - Calculates when the day appears in specific month&lt;br /&gt;
#* &#039;&#039;&#039;days_in_month&#039;&#039;&#039; - Calculate number of days in a given month&lt;br /&gt;
#* &#039;&#039;&#039;dayofweek&#039;&#039;&#039; - Calculate the position in the week of a specific calendar day&lt;br /&gt;
&lt;br /&gt;
# Older legacy date/time functions. Do not use in new code.&lt;br /&gt;
#* &#039;&#039;&#039;usertime&#039;&#039;&#039; - Appends the users timezone offset to an integer timestamp&lt;br /&gt;
#* &#039;&#039;&#039;get_timezone_offset&#039;&#039;&#039; - Systems&#039;s timezone difference from GMT in seconds&lt;br /&gt;
#* &#039;&#039;&#039;get_user_timezone_offset&#039;&#039;&#039; - Returns user&#039;s timezone difference from GMT in hours&lt;br /&gt;
#* &#039;&#039;&#039;dst_changes_for_year&#039;&#039;&#039; -  Calculates the required DST change and returns a Timestamp Array&lt;br /&gt;
&lt;br /&gt;
==Glossary==&lt;br /&gt;
===Timezone===&lt;br /&gt;
Moodle supports following timezone formats:&lt;br /&gt;
# UTC (specifically UTC−11 to UTC+11)&lt;br /&gt;
# Time offsets from UTC (int +-(0-13) or float +-(0.5-12.5))&lt;br /&gt;
# World timezones (Australia/Perth)&lt;br /&gt;
&lt;br /&gt;
===Location===&lt;br /&gt;
Timezone depends on [[:en:Location]] of the user and can be forced upon by administrator.&lt;br /&gt;
===DST===&lt;br /&gt;
DST is abbreviation of &#039;&#039;&#039;Daylight Saving Time&#039;&#039;&#039; (also known as &amp;quot;Day light saving&amp;quot; and &amp;quot;Summer Time&amp;quot;). Many countries, and sometimes just certain regions of countries, adopt daylight saving time during part of the year. Moodle automatically calculates DST for current user, depending on user location.&lt;br /&gt;
&lt;br /&gt;
==Examples==&lt;br /&gt;
===Time API&#039;s for current user===&lt;br /&gt;
Prints a formatted date in user time&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
// Get current day, month and year for current user.&lt;br /&gt;
$date = new DateTime(&amp;quot;now&amp;quot;, core_date::get_user_timezone_object());&lt;br /&gt;
$date-&amp;gt;setTime(0, 0, 0);&lt;br /&gt;
// Print formatted date in user time.&lt;br /&gt;
echo userdate($date-&amp;gt;getTimestamp(), &amp;quot;Current user time&amp;quot;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===System Time API===&lt;br /&gt;
Find the day of the week for the first day in this month.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$now = new DateTime(&amp;quot;now&amp;quot;, core_date::get_server_timezone_object());&lt;br /&gt;
&lt;br /&gt;
$year = $now-&amp;gt;format(&#039;Y&#039;);&lt;br /&gt;
$month = $now-&amp;gt;format(&#039;m&#039;);&lt;br /&gt;
&lt;br /&gt;
$now-&amp;gt;setDate($year, $month, 1);&lt;br /&gt;
$dayofweek = $now-&amp;gt;format(&#039;N&#039;);&lt;br /&gt;
echo $dayofweek;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
[[http://php.net/manual/en/class.datetime.php Php DateTime class]]&lt;br /&gt;
* [[Core APIs]]&lt;br /&gt;
&lt;br /&gt;
[[Category:API]]&lt;/div&gt;</summary>
		<author><name>Davidbalch</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Time_API&amp;diff=57523</id>
		<title>Time API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Time_API&amp;diff=57523"/>
		<updated>2020-05-21T11:52:16Z</updated>

		<summary type="html">&lt;p&gt;Davidbalch: /* DST */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
&lt;br /&gt;
Internally Moodle always stores all times in unixtime format (number of seconds since epoch) which is independent of timezones.&lt;br /&gt;
&lt;br /&gt;
The Time API is used to display proper date-time depending on user or site timezones.&lt;br /&gt;
&lt;br /&gt;
==Functions==&lt;br /&gt;
There is a class in Moodle to handle most needs of working with times. There are 2 cases to consider when working with time:&lt;br /&gt;
&lt;br /&gt;
System Time&lt;br /&gt;
&lt;br /&gt;
This is when you are dealing with dates on the server e.g. executing scheduled tasks, performing background tasks - anything which does not depend on the timezone of any specific user.&lt;br /&gt;
&lt;br /&gt;
User Time&lt;br /&gt;
&lt;br /&gt;
This is when you are manipulating dates and times and you need to display them to the user in their current timezone (which may be different for each user).&lt;br /&gt;
&lt;br /&gt;
The main API for time is in the class &amp;quot;core_date&amp;quot; which will give you php DateTimeZone objects for either user time or server time as needed. You can then use the php datetime classes to manipulate the time. When finished manipulating the time, get a timestamp with DateTime::getTimestamp().&lt;br /&gt;
&lt;br /&gt;
Example: Get the current server time + 1 day.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
$tomorrow = new DateTime(&amp;quot;1 day&amp;quot;, core_date::get_server_timezone_object());&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Get a timestamp for storing in the database:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
$tomorrowint = $tomorrow-&amp;gt;getTimestamp();&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Get a timestamp for 3pm tomorrow in the current users timezone.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
$time = new DateTime(&amp;quot;now&amp;quot;, core_date::get_user_timezone_object());&lt;br /&gt;
$time-&amp;gt;add(new DateInterval(&amp;quot;P1D&amp;quot;));&lt;br /&gt;
$time-&amp;gt;setTime(15, 0, 0);&lt;br /&gt;
&lt;br /&gt;
$timestamp = $time-&amp;gt;getTimestamp();&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Remember! NEVER NEVER NEVER add or subtract timestamps for any reason - you will get it wrong (DST is a killer)!&lt;br /&gt;
&lt;br /&gt;
Other functions related to time api can be found in lib/moodlelib.php. &lt;br /&gt;
# Time API&#039;s for current user&lt;br /&gt;
#* &#039;&#039;&#039;make_timestamp&#039;&#039;&#039; - Given date-time, it produces a GMT timestamp for current user.&lt;br /&gt;
#* &#039;&#039;&#039;userdate&#039;&#039;&#039; - Gets formatted string that represents a date in user time (note that the format required by this function is the [https://www.php.net/manual/en/function.strftime.php strftime()] format, not the more common format used by date())&lt;br /&gt;
#* &#039;&#039;&#039;usertime&#039;&#039;&#039; - Given a GMT timestamp (seconds since epoch), offsets it by the timezone.  eg 3pm in India is 3pm GMT - 5.5 * 3600 seconds&lt;br /&gt;
#* &#039;&#039;&#039;usergetdate&#039;&#039;&#039; - Given a timestamp in GMT, returns an array that represents the date-time in user time&lt;br /&gt;
#* &#039;&#039;&#039;usergetmidnight&#039;&#039;&#039; - Given a date, return the GMT timestamp of the most recent midnight for the current user.&lt;br /&gt;
#* &#039;&#039;&#039;usertimezone&#039;&#039;&#039; - Returns current user&#039;s timezone&lt;br /&gt;
&lt;br /&gt;
# System Time API&lt;br /&gt;
#* &#039;&#039;&#039;format_time&#039;&#039;&#039; - Format a date/time (seconds) as weeks, days, hours etc as needed&lt;br /&gt;
#* &#039;&#039;&#039;dst_offset_on&#039;&#039;&#039; - Calculates the Daylight Saving Offset for a given date/time (timestamp)&lt;br /&gt;
#* &#039;&#039;&#039;find_day_in_month&#039;&#039;&#039; - Calculates when the day appears in specific month&lt;br /&gt;
#* &#039;&#039;&#039;days_in_month&#039;&#039;&#039; - Calculate number of days in a given month&lt;br /&gt;
#* &#039;&#039;&#039;dayofweek&#039;&#039;&#039; - Calculate the position in the week of a specific calendar day&lt;br /&gt;
&lt;br /&gt;
# Older legacy date/time functions. Do not use in new code.&lt;br /&gt;
#* &#039;&#039;&#039;usertime&#039;&#039;&#039; - Appends the users timezone offset to an integer timestamp&lt;br /&gt;
#* &#039;&#039;&#039;get_timezone_offset&#039;&#039;&#039; - Systems&#039;s timezone difference from GMT in seconds&lt;br /&gt;
#* &#039;&#039;&#039;get_user_timezone_offset&#039;&#039;&#039; - Returns user&#039;s timezone difference from GMT in hours&lt;br /&gt;
#* &#039;&#039;&#039;dst_changes_for_year&#039;&#039;&#039; -  Calculates the required DST change and returns a Timestamp Array&lt;br /&gt;
&lt;br /&gt;
==Glossary==&lt;br /&gt;
===Timezone===&lt;br /&gt;
Moodle supports following timezone formats:&lt;br /&gt;
# UTC (specifically UTC−11 to UTC+11)&lt;br /&gt;
# Time offsets from UTC (int +-(0-13) or float +-(0.5-12.5))&lt;br /&gt;
# World timezones (Australia/Perth)&lt;br /&gt;
&lt;br /&gt;
===Location===&lt;br /&gt;
Timezone depends on [[:en:Location]] of the user and can be forced upon by administrator.&lt;br /&gt;
===DST===&lt;br /&gt;
DST is abbreviation of &#039;&#039;&#039;Daylight Saving Time&#039;&#039;&#039; (also known as &#039;&#039;&#039;Day light saving&#039;&#039;&#039; and &amp;quot;Summer Time&amp;quot;). Many countries, and sometimes just certain regions of countries, adopt daylight saving time during part of the year. Moodle automatically calculates DST for current user, depending on user location.&lt;br /&gt;
&lt;br /&gt;
==Examples==&lt;br /&gt;
===Time API&#039;s for current user===&lt;br /&gt;
Prints a formatted date in user time&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
// Get current day, month and year for current user.&lt;br /&gt;
$date = new DateTime(&amp;quot;now&amp;quot;, core_date::get_user_timezone_object());&lt;br /&gt;
$date-&amp;gt;setTime(0, 0, 0);&lt;br /&gt;
// Print formatted date in user time.&lt;br /&gt;
echo userdate($date-&amp;gt;getTimestamp(), &amp;quot;Current user time&amp;quot;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===System Time API===&lt;br /&gt;
Find the day of the week for the first day in this month.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$now = new DateTime(&amp;quot;now&amp;quot;, core_date::get_server_timezone_object());&lt;br /&gt;
&lt;br /&gt;
$year = $now-&amp;gt;format(&#039;Y&#039;);&lt;br /&gt;
$month = $now-&amp;gt;format(&#039;m&#039;);&lt;br /&gt;
&lt;br /&gt;
$now-&amp;gt;setDate($year, $month, 1);&lt;br /&gt;
$dayofweek = $now-&amp;gt;format(&#039;N&#039;);&lt;br /&gt;
echo $dayofweek;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
[[http://php.net/manual/en/class.datetime.php Php DateTime class]]&lt;br /&gt;
* [[Core APIs]]&lt;br /&gt;
&lt;br /&gt;
[[Category:API]]&lt;/div&gt;</summary>
		<author><name>Davidbalch</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=PHPUnit&amp;diff=56599</id>
		<title>PHPUnit</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=PHPUnit&amp;diff=56599"/>
		<updated>2019-11-06T09:26:47Z</updated>

		<summary type="html">&lt;p&gt;Davidbalch: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.3}}&lt;br /&gt;
&lt;br /&gt;
=What is PHPUnit=&lt;br /&gt;
PHPUnit by Sebastian Bergmann is an advanced unit testing framework for PHP. It is installed as Composer dependency and is not part of Moodle installation. To run PHPUnit tests, you have to manually install it on your development computer or test server.&lt;br /&gt;
&lt;br /&gt;
Read the excellent guide at&lt;br /&gt;
* [https://phpunit.de/documentation.html PHPUnit Manual]&lt;br /&gt;
&lt;br /&gt;
=Installation of PHPUnit via Composer=&lt;br /&gt;
&lt;br /&gt;
* Install Composer&lt;br /&gt;
&lt;br /&gt;
Instructions for installing composer on all platforms are here: https://getcomposer.org/download/&lt;br /&gt;
&lt;br /&gt;
Install the composer.phar file to your moodle folder.&lt;br /&gt;
&lt;br /&gt;
* Execute Composer installer&lt;br /&gt;
&lt;br /&gt;
 cd /your/moodle/dirroot&lt;br /&gt;
&lt;br /&gt;
 php composer.phar install&lt;br /&gt;
&lt;br /&gt;
(If that gives you connection problems try...)&lt;br /&gt;
&lt;br /&gt;
 php composer.phar install --prefer-source&lt;br /&gt;
&lt;br /&gt;
Troubleshooting:&lt;br /&gt;
* On Windows if you are behind a proxy you will need to setup an environment variable called HTTP_PROXY with a value detailing your HTTP Proxy address and port before composer will correctly download files.&lt;br /&gt;
* You may be prompted for github credentials when installing composer dependencies.&lt;br /&gt;
** This is used to generate an personal access token to avoid being rate limited by github.&lt;br /&gt;
** If you have Two Factor Authentication enabled on your github account, or do not wish to supply your own credentials you will need to generate a token manually:&lt;br /&gt;
*** Visit https://github.com/settings/applications and request personal access token&lt;br /&gt;
*** Copy this token and add it to your [https://gist.github.com/andrewnicols/c5377ed25a9df1006ce1 ~/.composer/config.json]&lt;br /&gt;
** ( See [https://github.com/composer/composer/issues/2280 composer issue #2280] for further details of this bug.)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Detailed instructions:&lt;br /&gt;
* [http://getcomposer.org/doc/00-intro.md Composer documentation]&lt;br /&gt;
&lt;br /&gt;
Detailed instructions:&lt;br /&gt;
* [[PHPUnit installation in Windows]]&lt;br /&gt;
* [[PHPUnit installation in OS X]]&lt;br /&gt;
&lt;br /&gt;
== Uninstalling previous PEAR based version ==&lt;br /&gt;
&lt;br /&gt;
Before using composer, this page used to suggest to install phpunit via PEAR. If you did so, you may wish to uninstall that package now. This should work:&lt;br /&gt;
&lt;br /&gt;
   $ pear uninstall phpunit/DbUnit&lt;br /&gt;
   $ pear uninstall phpunit/PHPUnit&lt;br /&gt;
&lt;br /&gt;
=Initialisation of test environment=&lt;br /&gt;
&lt;br /&gt;
Our PHPUnit integration requires a dedicated database and dataroot.  First, add a new dataroot directory and prefix into your config.php, you can find examples in config-dist.php (scroll down to &#039;Section 9&#039;).&lt;br /&gt;
&lt;br /&gt;
 $CFG-&amp;gt;phpunit_prefix = &#039;phpu_&#039;;&lt;br /&gt;
 $CFG-&amp;gt;phpunit_dataroot = &#039;/home/example/phpu_moodledata&#039;;&lt;br /&gt;
&lt;br /&gt;
Some PHPUnit tests require a live internet connection. If your system does not have a direct connection to the Internet, you also need to specify your proxy in config.php - even though you normally specify it by using the admin settings user interface. (If you do not use a proxy, you can skip this step.) Check the settings on the relevant admin settings page, or from the mdl_config table in your database, if you are unsure of the correct values.&lt;br /&gt;
 &lt;br /&gt;
 // Normal proxy settings&lt;br /&gt;
 $CFG-&amp;gt;proxyhost = &#039;wwwcache.example.org&#039;;&lt;br /&gt;
 $CFG-&amp;gt;proxyport = 80;&lt;br /&gt;
 $CFG-&amp;gt;proxytype = &#039;HTTP&#039;;&lt;br /&gt;
 $CFG-&amp;gt;proxybypass = &#039;localhost, 127.0.0.1, .example.org&#039;;&lt;br /&gt;
 // Omit the next lines if your proxy doesn&#039;t need a username/password:&lt;br /&gt;
 $CFG-&amp;gt;proxyuser = &#039;systemusername&#039;;&lt;br /&gt;
 $CFG-&amp;gt;proxypassword = &#039;systempassword&#039;;&lt;br /&gt;
&lt;br /&gt;
From Moodle 2.8.5 onwards, you can also provide specific database settings for unit testing (if these are not provided, the standard database settings will be used):&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;$CFG-&amp;gt;phpunit_dbtype    = &#039;pgsql&#039;;      // &#039;pgsql&#039;, &#039;mariadb&#039;, &#039;mysqli&#039;, &#039;mssql&#039;, &#039;sqlsrv&#039; or &#039;oci&#039;&lt;br /&gt;
$CFG-&amp;gt;phpunit_dblibrary = &#039;native&#039;;     // &#039;native&#039; only at the moment&lt;br /&gt;
$CFG-&amp;gt;phpunit_dbhost    = &#039;127.0.0.1&#039;;  // eg &#039;localhost&#039; or &#039;db.isp.com&#039; or IP&lt;br /&gt;
$CFG-&amp;gt;phpunit_dbname    = &#039;mytestdb&#039;;     // database name, eg moodle&lt;br /&gt;
$CFG-&amp;gt;phpunit_dbuser    = &#039;postgres&#039;;   // your database username&lt;br /&gt;
$CFG-&amp;gt;phpunit_dbpass    = &#039;some_password&#039;;   // your database password&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then you need to initialise the test environment using following command.&lt;br /&gt;
&lt;br /&gt;
 cd /home/example/moodle&lt;br /&gt;
 php admin/tool/phpunit/cli/init.php&lt;br /&gt;
&lt;br /&gt;
This command has to be repeated after any upgrade, plugin (un)installation or if you have added tests to a plugin you are developing:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTE:&#039;&#039;&#039; make sure that your php cli executable (or the one you want to use) is correctly on your path as the individual init scripts will call it repeatedly. Also, ensure en_AU locale is installed on your server.&lt;br /&gt;
&lt;br /&gt;
== LDAP ==&lt;br /&gt;
If you want to run LDAP unit tests you must have a working, configured LDAP environment on your test server. There must be a basic LDAP tree structure in place or tests will fail with &amp;quot;Search: No such object&amp;quot;. Build an LDAP tree structure in a new shell prompt:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$ ldapadd -H ldap://127.0.0.1 -D &amp;quot;cn=admin,dc=yourcomputer,dc=local&amp;quot; -W&lt;br /&gt;
dn:dc=yourcomputer,dc=local&lt;br /&gt;
objectClass:dcObject&lt;br /&gt;
objectClass:organizationalUnit&lt;br /&gt;
dc:yourcomputer&lt;br /&gt;
ou:YOURCOMPUTER&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In config.php tell Moodle where to look for your test LDAP environment:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
// Constants for auth/ldap tests.&lt;br /&gt;
define(&#039;TEST_AUTH_LDAP_HOST_URL&#039;, &#039;ldap://127.0.0.1&#039;);&lt;br /&gt;
define(&#039;TEST_AUTH_LDAP_BIND_DN&#039;, &#039;cn=admin,dc=yourcomputer,dc=local&#039;);&lt;br /&gt;
define(&#039;TEST_AUTH_LDAP_BIND_PW&#039;, &#039;*&#039;);&lt;br /&gt;
define(&#039;TEST_AUTH_LDAP_DOMAIN&#039;, &#039;dc=yourcomputer,dc=local&#039;);&lt;br /&gt;
&lt;br /&gt;
// Constants for enrol/ldap tests.&lt;br /&gt;
define(&#039;TEST_ENROL_LDAP_HOST_URL&#039;, &#039;ldap://127.0.0.1&#039;);&lt;br /&gt;
define(&#039;TEST_ENROL_LDAP_BIND_DN&#039;, &#039;cn=admin,dc=yourcomputer,dc=local&#039;);&lt;br /&gt;
define(&#039;TEST_ENROL_LDAP_BIND_PW&#039;, &#039;*&#039;);&lt;br /&gt;
define(&#039;TEST_ENROL_LDAP_DOMAIN&#039;, &#039;dc=yourcomputer,dc=local&#039;);&lt;br /&gt;
&lt;br /&gt;
// Constants for lib/ldap tests.&lt;br /&gt;
define(&#039;TEST_LDAPLIB_HOST_URL&#039;, &#039;ldap://127.0.0.1&#039;);&lt;br /&gt;
define(&#039;TEST_LDAPLIB_BIND_DN&#039;, &#039;cn=admin,dc=yourcomputer,dc=local&#039;);&lt;br /&gt;
define(&#039;TEST_LDAPLIB_BIND_PW&#039;, &#039;*&#039;);&lt;br /&gt;
define(&#039;TEST_LDAPLIB_DOMAIN&#039;, &#039;dc=yourcomputer,dc=local&#039;);&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Test execution=&lt;br /&gt;
&lt;br /&gt;
To execute all test suites from main configuration file execute the &amp;lt;code&amp;gt;vendor/bin/phpunit&amp;lt;/code&amp;gt; script from your &amp;lt;code&amp;gt;$CFG-&amp;gt;dirroot&amp;lt;/code&amp;gt; directory.&lt;br /&gt;
&lt;br /&gt;
 cd /home/example/moodle&lt;br /&gt;
 vendor/bin/phpunit&lt;br /&gt;
&lt;br /&gt;
The rest of examples uses &amp;lt;code&amp;gt;phpunit&amp;lt;/code&amp;gt;, please substitute it with &amp;lt;code&amp;gt;vendor/bin/phpunit&amp;lt;/code&amp;gt; or create a shortcut in your dirroot.&lt;br /&gt;
&lt;br /&gt;
In IDEs, you may need to specify the path to the PHPUnit configuration file. Use the absolute path to &amp;lt;code&amp;gt;phpunit.xml&amp;lt;/code&amp;gt; from your &amp;lt;code&amp;gt;$CFG-&amp;gt;dirroot&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
There is an alternative script for running of tests via web interface: &amp;lt;code&amp;gt;admin/tool/phpunit/webrunner.php&amp;lt;/code&amp;gt;. Use this as the last resort only when you cannot use the command-line interface on your test server. It will most probably break due to permissions problems if you try to execute it both from command-line and from webrunner. This feature is not officially supported.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===How to run only some tests===&lt;br /&gt;
&lt;br /&gt;
==== Running a single test quickly ====&lt;br /&gt;
&lt;br /&gt;
The fastest way to run a single test is:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;code php&amp;gt;&lt;br /&gt;
vendor/bin/phpunit my/tests/filename.php&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
so, run this command in the CLI to see a real test in action:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
vendor/bin/phpunit cohort/tests/cohortlib_test.php&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
You can also run a single test method inside a class:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
vendor/bin/phpunit --filter test_function_name path/to/file.php&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note: You should be careful because it may be possible to run tests this way which are not included in the normal run if, for example, the file is not placed in the correct location. If you use this method, do at least one full test run (or --group run, as below) to ensure the test can be found.&lt;br /&gt;
&lt;br /&gt;
Filters can also be applied to capture a group of similar tests across all testsuites:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
vendor/bin/phpunit --filter test_flag_user&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
It is also possible to run all tests in a component by using the testsuite option:&lt;br /&gt;
&amp;lt;code bash&amp;gt;&lt;br /&gt;
vendor/bin/phpunit --testsuite core_privacy_testsuite&lt;br /&gt;
vendor/bin/phpunit --testsuite core_privacy_testsuite --filter test_component_is_compliant&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Using the @group annotation ====&lt;br /&gt;
&lt;br /&gt;
If you add annotations like&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Unit tests for {@link stack_cas_keyval}.&lt;br /&gt;
 * @group qtype_stack&lt;br /&gt;
 */&lt;br /&gt;
class qtype_stack_cas_keyval_exception_testcase extends basic_testcase {&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
to all the classes in your plugin, then you can run just the tests for your plugin by doing&lt;br /&gt;
&lt;br /&gt;
 phpunit --group qtype_stack&lt;br /&gt;
&lt;br /&gt;
Therefore, it is suggested that you annotate all your tests with the Frankenstyle name of your plugin.&lt;br /&gt;
&lt;br /&gt;
==== Using multiple phpunit.xml files ====&lt;br /&gt;
&lt;br /&gt;
It&#039;s easy to create alternative phpunit.xml files defining which tests must be run together. For reference, take a look to the default /phpunit.xml available in your base directory once the testing environment has been initialised. After creating the custom file you will be able to run those tests with&lt;br /&gt;
&lt;br /&gt;
 vendor/bin/phpunit -c path/to/your/alternative/phpunit/file.xml&lt;br /&gt;
&lt;br /&gt;
Also, for commodity, you can use this command:&lt;br /&gt;
&lt;br /&gt;
 php admin/tool/phpunit/cli/util.php --buildcomponentconfigs&lt;br /&gt;
&lt;br /&gt;
It will, automatically, create one valid phpunit.xml file within each component (plugin or subsystem) and other important directories, so later you will be able to execute tests like&lt;br /&gt;
&lt;br /&gt;
 vendor/bin/phpunit -c mod/forum[/phpunit.xml]  // Note that it&#039;s not needed to specify the name of the file (if it is &#039;phpunit.xml&#039;).&lt;br /&gt;
 vendor/bin/phpunit -c question&lt;br /&gt;
 vendor/bin/phpunit -c question/type/calculated&lt;br /&gt;
 vendor/bin/phpunit -c backup&lt;br /&gt;
 vendor/bin/phpunit -c lib/dml&lt;br /&gt;
 ...&lt;br /&gt;
&lt;br /&gt;
or, also&lt;br /&gt;
&lt;br /&gt;
 cd directory/with/phpunit.xml&lt;br /&gt;
 phpunit&lt;br /&gt;
&lt;br /&gt;
=External test resources=&lt;br /&gt;
{{Moodle 2.6}}&lt;br /&gt;
By default Moodle phpunit tests contact http://download.moodle.org server when testing curl related functionality. Optionally you may checkout a local copy of the test scripts and access it instead:&lt;br /&gt;
&lt;br /&gt;
# clone https://github.com/moodlehq/moodle-exttests to web directory&lt;br /&gt;
# add to your config.php or modify phpunit.xml file &amp;lt;code php&amp;gt;define(&#039;TEST_EXTERNAL_FILES_HTTP_URL&#039;, &#039;http://localhost/moodle-exttests&#039;);&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Writing new tests=&lt;br /&gt;
* read [http://www.phpunit.de/manual/current/en/ official PHPUnit online documentation]&lt;br /&gt;
* see [[Writing PHPUnit tests]]&lt;br /&gt;
&lt;br /&gt;
=Conversion of existing SimpleTests=&lt;br /&gt;
* see [[SimpleTest conversion]]&lt;br /&gt;
&lt;br /&gt;
=PHPUnit support in IDEs=&lt;br /&gt;
&lt;br /&gt;
* [[Setting up Eclipse]]&lt;br /&gt;
* [[Setting up Netbeans]]&lt;br /&gt;
* [[Setting up PhpStorm]]&lt;br /&gt;
&lt;br /&gt;
=Common Unit Test Problems=&lt;br /&gt;
[[Common unit test problems]]&lt;br /&gt;
&lt;br /&gt;
=Performance=&lt;br /&gt;
&lt;br /&gt;
A typical run of full PHPUnit tests for Moodle 2.7 takes 10-20 minutes depending on the machine. If tests run slowly for you:&lt;br /&gt;
&lt;br /&gt;
* Ensure you are using a database and filesystem running on the same machine that is running the tests (not on a remote server).&lt;br /&gt;
* Apply developer-only performance settings to your database: [[Postgres Tuning For Developers]]&lt;br /&gt;
&lt;br /&gt;
=Command line tips=&lt;br /&gt;
&lt;br /&gt;
* Run all tests for a plugin (mod_mymodule in this example): vendor/bin/phpunit --testsuite=mod_mymodule_testsuite &lt;br /&gt;
* Run only named test: vendor/bin/phpunit --filter=test_my_test_function_name&lt;br /&gt;
* Display each test name before running it (useful e.g. if it crashes before the end and you want to know which test it was running at that point) vendor/bin/phpunit --debug (you will probably want to redirect this to a file as it gets very long).&lt;br /&gt;
&lt;br /&gt;
[[Category:Unit testing]]&lt;/div&gt;</summary>
		<author><name>Davidbalch</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Form_API&amp;diff=56337</id>
		<title>Form API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Form_API&amp;diff=56337"/>
		<updated>2019-08-23T10:47:49Z</updated>

		<summary type="html">&lt;p&gt;Davidbalch: /* Highlights */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
Web Forms in moodle are created using forms API. Form API supports all html elements (checkbox, radio, textbox etc), with improved accessibility and security.&lt;br /&gt;
==Highlights==&lt;br /&gt;
# Tested and optimised for use on major screen-readers Dragon and JAWS. &lt;br /&gt;
# Tableless layout.&lt;br /&gt;
# Process form data securely, with required_param, optional_param and session key.&lt;br /&gt;
# Supports client-side validation&lt;br /&gt;
# Facility to add Moodle help buttons to forms. &lt;br /&gt;
# Support for file repository using [[File_API]]&lt;br /&gt;
# Support for many custom moodle specific and non-specific form elements.&lt;br /&gt;
# Addition for [https://docs.moodle.org/dev/lib/formslib.php_repeat_elements repeated elements].&lt;br /&gt;
# Addition for form elements in advance group&lt;br /&gt;
&lt;br /&gt;
==Usage==&lt;br /&gt;
For creating a form in moodle, you have to create class extending moodleform class and override [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#definition.28.29 definition] for including form elements.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
//moodleform is defined in formslib.php&lt;br /&gt;
require_once(&amp;quot;$CFG-&amp;gt;libdir/formslib.php&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
class simplehtml_form extends moodleform {&lt;br /&gt;
    //Add elements to form&lt;br /&gt;
    public function definition() {&lt;br /&gt;
        global $CFG;&lt;br /&gt;
       &lt;br /&gt;
        $mform = $this-&amp;gt;_form; // Don&#039;t forget the underscore! &lt;br /&gt;
&lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;text&#039;, &#039;email&#039;, get_string(&#039;email&#039;)); // Add elements to your form&lt;br /&gt;
        $mform-&amp;gt;setType(&#039;email&#039;, PARAM_NOTAGS);                   //Set type of element&lt;br /&gt;
        $mform-&amp;gt;setDefault(&#039;email&#039;, &#039;Please enter email&#039;);        //Default value&lt;br /&gt;
            ...&lt;br /&gt;
    }&lt;br /&gt;
    //Custom validation should be added here&lt;br /&gt;
    function validation($data, $files) {&lt;br /&gt;
        return array();&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Then instantiate form (in this case simplehtml_form) on your page.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
//include simplehtml_form.php&lt;br /&gt;
require_once(&#039;PATH_TO/simplehtml_form.php&#039;);&lt;br /&gt;
&lt;br /&gt;
//Instantiate simplehtml_form &lt;br /&gt;
$mform = new simplehtml_form();&lt;br /&gt;
&lt;br /&gt;
//Form processing and displaying is done here&lt;br /&gt;
if ($mform-&amp;gt;is_cancelled()) {&lt;br /&gt;
    //Handle form cancel operation, if cancel button is present on form&lt;br /&gt;
} else if ($fromform = $mform-&amp;gt;get_data()) {&lt;br /&gt;
  //In this case you process validated data. $mform-&amp;gt;get_data() returns data posted in form.&lt;br /&gt;
} else {&lt;br /&gt;
  // this branch is executed if the form is submitted but the data doesn&#039;t validate and the form should be redisplayed&lt;br /&gt;
  // or on the first display of the form.&lt;br /&gt;
&lt;br /&gt;
  //Set default data (if any)&lt;br /&gt;
  $mform-&amp;gt;set_data($toform);&lt;br /&gt;
  //displays the form&lt;br /&gt;
  $mform-&amp;gt;display();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you wish to use the form within a block then you should consider using the render method, as demonstrated below:&lt;br /&gt;
&lt;br /&gt;
Note that the render method does the same as the display method, except returning the HTML rather than outputting it to the browser, as with above make sure you&#039;ve included the file which contains the class for your Moodle form.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
class block_yourblock extends block_base{&lt;br /&gt;
	public function init(){&lt;br /&gt;
		$this-&amp;gt;title = &#039;Your Block&#039;;&lt;br /&gt;
	}&lt;br /&gt;
	public function get_content(){&lt;br /&gt;
&lt;br /&gt;
		$this-&amp;gt;content = new stdClass();&lt;br /&gt;
		$this-&amp;gt;content-&amp;gt;text = &#039;&#039;;&lt;br /&gt;
&lt;br /&gt;
		$mform = new simplehtml_form();&lt;br /&gt;
&lt;br /&gt;
		//Form processing and displaying is done here&lt;br /&gt;
		if ($mform-&amp;gt;is_cancelled()) {&lt;br /&gt;
			//Handle form cancel operation, if cancel button is present on form&lt;br /&gt;
		} else if ($fromform = $mform-&amp;gt;get_data()) {&lt;br /&gt;
			//In this case you process validated data. $mform-&amp;gt;get_data() returns data posted in form.&lt;br /&gt;
		} else {&lt;br /&gt;
			// this branch is executed if the form is submitted but the data doesn&#039;t validate and the form should be redisplayed&lt;br /&gt;
			// or on the first display of the form.&lt;br /&gt;
&lt;br /&gt;
			//Set default data (if any)&lt;br /&gt;
			$mform-&amp;gt;set_data($toform);&lt;br /&gt;
&lt;br /&gt;
			//displays the form&lt;br /&gt;
			$this-&amp;gt;content-&amp;gt;text = $mform-&amp;gt;render();&lt;br /&gt;
		}&lt;br /&gt;
&lt;br /&gt;
		return $this-&amp;gt;content;&lt;br /&gt;
&lt;br /&gt;
	}&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Form elements==&lt;br /&gt;
===Basic form elements===&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#button button]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#checkbox checkbox]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#radio radio]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#select select]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#multi-select multi-select]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#password password]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#hidden hidden]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#html html] - div element&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#static static] - Display a static text.&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#text text]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#textarea textarea]&lt;br /&gt;
&lt;br /&gt;
===Custom form elements===&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#autocomplete Autocomplete] - A select box that allows you to start typing to narrow the list of options, or search for results.&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#advcheckbox advcheckbox] - Advance checkbox&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#float float]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#passwordunmask passwordunmask] - A password element with option to show the password in plaintext.&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#recaptcha recaptcha]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#selectyesno selectyesno]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#selectwithlink selectwithlink]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#date_selector date_selector]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#date_time_selector date_time_selector]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#duration duration]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#editor editor]&lt;br /&gt;
# [https://docs.moodle.org/dev/Using_the_File_API_in_Moodle_forms#filepicker filepicker] - upload single file&lt;br /&gt;
# [https://docs.moodle.org/dev/Using_the_File_API_in_Moodle_forms#filemanager filemanager] - upload multiple files&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#tags tags]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#addGroup addGroup]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#modgrade modgrade]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#modvisible modvisible]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#choosecoursefile choosecoursefile]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#grading grading]&lt;br /&gt;
# [https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#questioncategory questioncategory]&lt;br /&gt;
&lt;br /&gt;
==Commonly used functions==&lt;br /&gt;
&lt;br /&gt;
====add_action_buttons($cancel = true, $submitlabel=null);====&lt;br /&gt;
&lt;br /&gt;
You will normally use this helper function which is a method of moodleform to add all the &#039;action&#039; buttons to the end of your form. A boolean parameter allow you to specify whether to include a cancel button and specify the label for your submit button (pass the result of get_string). Default for the submit button label is get_string(&#039;savechanges&#039;). Note the &#039;&#039;&#039;$this&#039;&#039;&#039; not &#039;&#039;&#039;$mform&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
   $this-&amp;gt;add_action_buttons();&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====[[lib/formslib.php_Form_Definition#setDefault_2|setDefault()]]====&lt;br /&gt;
To set the default value for an element.&lt;br /&gt;
&lt;br /&gt;
====[[lib/formslib.php_Form_Definition#disabledIf|disabledIf()]]====&lt;br /&gt;
For any element or groups of element in a form you can conditionally disable the group or individual element depending on conditions.&lt;br /&gt;
&lt;br /&gt;
====[[lib/formslib.php_Form_Definition#hideIf|hideif()]]====&lt;br /&gt;
For any element or groups of element in a form you can conditionally hide the group or individual element depending on conditions.&lt;br /&gt;
Same syntax as disabledIf. Can do a simple search and replace on disabledIf.  Added in Moodle 3.4.&lt;br /&gt;
&lt;br /&gt;
====[[lib/formslib.php_Form_Definition#addRule|addRule()]]====&lt;br /&gt;
Add rule for server/client side validation. Like text field is required element and is of type email.&lt;br /&gt;
&lt;br /&gt;
====[[lib/formslib.php_Form_Definition#setHelpButton|setHelpButton()]]====&lt;br /&gt;
Sets pop-up help button to a form element.&lt;br /&gt;
&lt;br /&gt;
====[[lib/formslib.php_Form_Definition#addHelpButton|addHelpButton()]]====&lt;br /&gt;
Adds pop-up help button to a form element&lt;br /&gt;
&lt;br /&gt;
====[[lib/formslib.php_Form_Definition#setType|setType()]]====&lt;br /&gt;
PARAM_* types are used to specify how a submitted variable should be cleaned.&lt;br /&gt;
&lt;br /&gt;
====[[lib/formslib.php_Form_Definition#disable_form_change_checker|disable_form_change_checker()]]====&lt;br /&gt;
By default, any Moodle form will pop-up an &amp;quot;Are you sure?&amp;quot; alert if you make some changes and then try to leave the page without saving. Occasionally, that is undesirable, in which case you can call &amp;lt;tt&amp;gt;$mform-&amp;gt;disable_form_change_checker()&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==FAQ==&lt;br /&gt;
[https://docs.moodle.org/dev/lib/formslib.php_Form_Definition#Use_Fieldsets_to_group_Form_Elements How to group elements]&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
* [[Core_APIs]]&lt;br /&gt;
* [[lib/formslib.php_Usage]] &lt;br /&gt;
* [[lib/formslib.php_Form_Definition]]&lt;br /&gt;
* [[Designing usable forms]]&lt;br /&gt;
* [[Fragment]]&lt;br /&gt;
* [[MForm_Modal]]&lt;br /&gt;
&lt;br /&gt;
[[Category:API]]&lt;/div&gt;</summary>
		<author><name>Davidbalch</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Javascript_Modules&amp;diff=52790</id>
		<title>Javascript Modules</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Javascript_Modules&amp;diff=52790"/>
		<updated>2017-08-01T16:12:24Z</updated>

		<summary type="html">&lt;p&gt;Davidbalch: /* Including an external javascript/jquery library */  Tweak example numbering.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.9}}&lt;br /&gt;
&lt;br /&gt;
= Javascript Modules =&lt;br /&gt;
&lt;br /&gt;
== What is a Javascript module and why do I care? ==&lt;br /&gt;
&lt;br /&gt;
A Javascript module is nothing more than a collection of Javascript code that can be used (reliably) from other pieces of Javascript. &lt;br /&gt;
&lt;br /&gt;
== Why should I package my code as a module? ==&lt;br /&gt;
&lt;br /&gt;
By packaging your code as a module you break your code up into smaller reusable pieces. This is good because:&lt;br /&gt;
&lt;br /&gt;
a) Each smaller piece is simpler to understand / debug&lt;br /&gt;
&lt;br /&gt;
b) Each smaller piece is simpler to test&lt;br /&gt;
&lt;br /&gt;
c) You can re-use common code instead of duplicating it&lt;br /&gt;
&lt;br /&gt;
= How do I write a Javascript module in Moodle? =&lt;br /&gt;
&lt;br /&gt;
Since version 2.9, Moodle supports Javascript modules written using the Asynchronous Module Definition ([https://github.com/amdjs/amdjs-api/wiki/AMD AMD]) API. This is a standard API for creating Javascript modules and you will find many useful third party libraries that are already using this format. &lt;br /&gt;
&lt;br /&gt;
To edit or create an AMD module in Moodle you need to do a couple of things. &lt;br /&gt;
&lt;br /&gt;
== Install grunt ==&lt;br /&gt;
&lt;br /&gt;
The AMD modules in Moodle must be processed by some build tools before they will be visible to your web browser. We use &amp;quot;[http://gruntjs.com/ grunt]&amp;quot; as a build tool to wrap our different processes. Grunt is a build tool written in Javascript that runs in the &amp;quot;[http://nodejs.org/ nodejs]&amp;quot; environment.&lt;br /&gt;
&lt;br /&gt;
This means you first have to &#039;&#039;&#039;install nodejs&#039;&#039;&#039; - and its package manager [https://www.npmjs.com/ npm]. The details of how to install those packages will vary by operating system, but on Linux it&#039;s probably similar to &amp;quot;sudo apt-get install nodejs npm&amp;quot;. There are downloadable packages for other operating systems here: http://nodejs.org/download/. Moodle currently requires node &amp;quot;v4&amp;quot; and does not work with the latest node &amp;quot;v6&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Once this is done, you can &#039;&#039;&#039;run the command&#039;&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
 npm install&lt;br /&gt;
 npm install -g grunt-cli&lt;br /&gt;
&lt;br /&gt;
from the top of the Moodle directory to install all of the required tools. (You may need extra permissions to use the -g option.)&lt;br /&gt;
&lt;br /&gt;
== Development mode ==&lt;br /&gt;
&lt;br /&gt;
To avoid having to constantly run grunt, make sure you set the following in your config.php&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// Prevent JS caching&lt;br /&gt;
$CFG-&amp;gt;cachejs = false;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Moodle will now run your module from the amd/src module. Don&#039;t forget to switch this off and run &#039;grunt&#039; before deploying the new version!&lt;br /&gt;
&lt;br /&gt;
In this mode - if you get a strange message in your javascript console like &amp;quot;No define call for core/first&amp;quot; it means you have a syntax error in the javascript you are developing.&lt;br /&gt;
&lt;br /&gt;
== Running grunt ==&lt;br /&gt;
&lt;br /&gt;
You can run grunt in your plugin&#039;s &#039;amd&#039; directory and it will only operate on your modules. If you&#039;re having problems or just want to check your work it is worth running for the &#039;lint&#039; feature. This can find basic problems. This sub-directory support wont work on Windows unfortunately but there is an alternative: Run grunt from the top directory with the --root=path/to/dir to limit execution to a sub-directory.&lt;br /&gt;
&lt;br /&gt;
See [[Grunt#Running_grunt]] for more details of specific grunt commands which can be used.&lt;br /&gt;
&lt;br /&gt;
If you get the error message&lt;br /&gt;
&lt;br /&gt;
 /usr/bin/env: node: No such file or directory&lt;br /&gt;
&lt;br /&gt;
Then see the thread https://github.com/nodejs/node-v0.x-archive/issues/3911&lt;br /&gt;
&lt;br /&gt;
On Ubuntu 14.04 this fixed it for me:&lt;br /&gt;
&lt;br /&gt;
 sudo ln -fs /usr/bin/nodejs /usr/local/bin/node&lt;br /&gt;
&lt;br /&gt;
== Minimum (getting started) module for plugins ==&lt;br /&gt;
&lt;br /&gt;
This shows the absolute minimum module you need to get started adding modules to your plugins. It&#039;s actually quite simple...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// Put this file in path/to/plugin/amd/src&lt;br /&gt;
// You can call it anything you like&lt;br /&gt;
&lt;br /&gt;
define([&#039;jquery&#039;], function($) {&lt;br /&gt;
&lt;br /&gt;
    return {&lt;br /&gt;
        init: function() {&lt;br /&gt;
&lt;br /&gt;
            // Put whatever you like here. $ is available&lt;br /&gt;
            // to you as normal.&lt;br /&gt;
            $(&amp;quot;.someclass&amp;quot;).change(function() {&lt;br /&gt;
                alert(&amp;quot;It changed!!&amp;quot;);&lt;br /&gt;
            });&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The idea here is that we will run the &#039;init&#039; function from our (PHP) code to set things up. This is called from PHP like this...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    $PAGE-&amp;gt;requires-&amp;gt;js_call_amd(&#039;frankenstyle_path/your_js_filename&#039;, &#039;init&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Don&#039;t forget to supply the complete &#039;frankenstyle&#039; path. The .js is not needed. &lt;br /&gt;
&lt;br /&gt;
js_call_amd takes a third parameter which is an &#039;&#039;array&#039;&#039; of parameters. These will translate to individual parameters in the &#039;init&#039; function call. For example...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    $PAGE-&amp;gt;requires-&amp;gt;js_call_amd(&#039;block_iomad_company_admin/department_select&#039;, &#039;init&#039;, array($first, $last));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
...calls&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
    return {&lt;br /&gt;
        init: function(first, last) {&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A more comprehensive explanation follows...&lt;br /&gt;
&lt;br /&gt;
== &amp;quot;Hello World&amp;quot; I am a Javascript Module ==&lt;br /&gt;
Lets now create a simple Javascript module so we can see how to lay things out. &lt;br /&gt;
&lt;br /&gt;
Each Javascript module is contained in a single source file in the &amp;lt;componentdir&amp;gt;/amd/src folder. The final name of the module is taken from the file name and the component name. E.g. block_overview/amd/src/helloworld.js would be a module named &amp;quot;block_overview/helloworld&amp;quot;. the name of the module is important when you want to call it from somewhere else in the code. &lt;br /&gt;
&lt;br /&gt;
After running grunt - the minified Javascript files are stored in the &amp;lt;componentdir&amp;gt;/amd/build folder. The javascript files are renamed to show that they are minified (helloworld.js becomes helloworld.min.js). &lt;br /&gt;
&lt;br /&gt;
Don&#039;t forget to add the built files (the ones in amd/build) to your git commits, or in production no-one will see your changes. &lt;br /&gt;
&lt;br /&gt;
Lets create a simple module now:&lt;br /&gt;
&lt;br /&gt;
blocks/overview/amd/src/helloworld.js&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// Standard license block omitted.&lt;br /&gt;
/*&lt;br /&gt;
 * @package    block_overview&lt;br /&gt;
 * @copyright  2015 Someone cool&lt;br /&gt;
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later&lt;br /&gt;
 */&lt;br /&gt;
&lt;br /&gt;
 /**&lt;br /&gt;
  * @module block_overview/helloworld&lt;br /&gt;
  */&lt;br /&gt;
define([&#039;jquery&#039;], function($) {&lt;br /&gt;
&lt;br /&gt;
     /** &lt;br /&gt;
      * Give me blue.&lt;br /&gt;
      * @access private&lt;br /&gt;
      * @return {string}&lt;br /&gt;
      */&lt;br /&gt;
     var makeItBlue = function() {&lt;br /&gt;
          // We can use our jquery dependency here.&lt;br /&gt;
          return $(&#039;.blue&#039;).show();&lt;br /&gt;
     };&lt;br /&gt;
      &lt;br /&gt;
    /**&lt;br /&gt;
     * @constructor&lt;br /&gt;
     * @alias module:block_overview/helloworld&lt;br /&gt;
     */&lt;br /&gt;
    var greeting = function() {&lt;br /&gt;
        /** @access private */&lt;br /&gt;
        var privateThoughts = &#039;I like the colour blue&#039;;&lt;br /&gt;
        &lt;br /&gt;
        /** @access public */&lt;br /&gt;
        this.publicThoughts = &#039;I like the colour orange&#039;;&lt;br /&gt;
&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * A formal greeting.&lt;br /&gt;
     * @access public&lt;br /&gt;
     * @return {string}&lt;br /&gt;
     */&lt;br /&gt;
    greeting.prototype.formal = function() {&lt;br /&gt;
        return &#039;How do you do?&#039;;&lt;br /&gt;
    };&lt;br /&gt;
&lt;br /&gt;
    /**&lt;br /&gt;
     * An informal greeting.&lt;br /&gt;
     * @access public&lt;br /&gt;
     * @return {string}&lt;br /&gt;
     */&lt;br /&gt;
    greeting.prototype.informal = function() {&lt;br /&gt;
        return &#039;Wassup!&#039;;&lt;br /&gt;
    };&lt;br /&gt;
    return greeting;&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The most interesting line above is:&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
define([&#039;jquery&#039;], function($) {&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All AMD modules must call &amp;quot;define()&amp;quot; as the first and only global scoped piece of code. This ensures the javascript code contains no global variables and will not conflict with any other loaded module. The name of the module does not need to be specified because it is determined from the filename and component (but it can be listed in a comment for JSDoc as shown here). &lt;br /&gt;
&lt;br /&gt;
The first argument to &amp;quot;define&amp;quot; is the list of dependencies for the module. This argument must be passed as an array, even if there is only one. In this example &amp;quot;jquery&amp;quot; is a dependency. &amp;quot;jquery&amp;quot; is shipped as a core module is available to all AMD modules. &lt;br /&gt;
&lt;br /&gt;
The second argument to &amp;quot;define&amp;quot; is the function that defines the module. This function will receive as arguments, each of the requested dependencies in the same order they were requested. In this example we receive JQuery as an argument and we name the variable &amp;quot;$&amp;quot; (it&#039;s a JQuery thing). We can then access JQuery normally through the $ variable which is in scope for any code in our module. &lt;br /&gt;
&lt;br /&gt;
The rest of the code in this example is a standard way to define a Javascript module with public/private variables and methods. There are many ways to do this, this is only one.&lt;br /&gt;
&lt;br /&gt;
It is important that we are returning &#039;greeting&#039;. If there is no return then your module will be declared as undefined.&lt;br /&gt;
&lt;br /&gt;
== Loading modules dynamically ==&lt;br /&gt;
What do you do if you don&#039;t know in advance which modules will be required? Stuffing all possible required modules in the define call is one solution, but it&#039;s ugly and it only works for code that is in an AMD module (what about inline code in the page?). AMD lets you load a dependency any time you like. &lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
&lt;br /&gt;
// Load a new dependency.&lt;br /&gt;
require([&#039;mod_wiki/timer&#039;], function(timer) {&lt;br /&gt;
   // timer is available to do my bidding.&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Including an external javascript/jquery library ==&lt;br /&gt;
If you want to include a javascript / jquery library downloaded from the internet you can do so as follows:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Warning: if the library you download, supports AMD but is already &amp;quot;named&amp;quot; you will not be able to include it directly&#039;&#039;&#039;&lt;br /&gt;
e.g. &lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
        define(&amp;quot;typeahead.js&amp;quot;, *[ &amp;quot;jquery&amp;quot; ], function(a0) {&lt;br /&gt;
            return factory(a0);&lt;br /&gt;
        });&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
will not work, as moodle injects it&#039;s own define name when loading the library.&lt;br /&gt;
&lt;br /&gt;
If the library is in AMD format and has a define:&lt;br /&gt;
e.g. i want to include the jquery final countdown timer on my page ( hilios.github.io/jQuery.countdown/ )&lt;br /&gt;
* download the module in both normal and minified versions&lt;br /&gt;
* place the modules in your moodle install e.g. your custom theme dir, or plugin dir&lt;br /&gt;
* /theme/mytheme/amd/src/jquery.countdown.js&lt;br /&gt;
&lt;br /&gt;
you can now include the module and initialise it (there are multiple ways to do this)&lt;br /&gt;
php:&lt;br /&gt;
&lt;br /&gt;
1. Create your own AMD module and initialise it:&lt;br /&gt;
&lt;br /&gt;
In your PHP file:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$this-&amp;gt;page-&amp;gt;requires-&amp;gt;js_call_amd(&#039;theme_mytheme/countdowntimer&#039;, &#039;initialise&#039;, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Javascript module:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// /theme/mytheme/amd/src/countdowntimer.js&lt;br /&gt;
define([&#039;jquery&#039;, &#039;theme_mytheme/jquery.countdown&#039;], function($, c) {&lt;br /&gt;
    return {&lt;br /&gt;
        initialise: function ($params) {&lt;br /&gt;
           $(&#039;#clock&#039;).countdown(&#039;2020/10/10&#039;, function(event) {&lt;br /&gt;
             $(this).html(event.strftime(&#039;%D days %H:%M:%S&#039;));&lt;br /&gt;
           });&lt;br /&gt;
        }&lt;br /&gt;
    };&lt;br /&gt;
});&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
2. Put the javascript into a mustache template:&lt;br /&gt;
&amp;lt;code javascript&amp;gt;&lt;br /&gt;
// /theme/mytheme/templates/countdowntimer.mustache&lt;br /&gt;
&amp;lt;span id=&amp;quot;clock&amp;quot;&amp;gt;&amp;lt;/span&amp;gt;&lt;br /&gt;
{{#js}}&lt;br /&gt;
require([&#039;jquery&#039;, &#039;theme_mytheme/jquery.countdown&#039;], function($) {&lt;br /&gt;
           $(&#039;#clock&#039;).countdown(&#039;2020/10/10&#039;, function(event) {&lt;br /&gt;
             $(this).html(event.strftime(&#039;%D days %H:%M:%S&#039;));&lt;br /&gt;
           });&lt;br /&gt;
});&lt;br /&gt;
{{/js}}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
3. Call the javascript directly from php (although who would want to put javascript into php? ergh):&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$PAGE-&amp;gt;requires-&amp;gt;js_amd_inline(&#039;&lt;br /&gt;
require([&#039;theme_mytheme/jquery.countdown&#039;], function(min) {&lt;br /&gt;
           $(&#039;#clock&#039;).countdown(&#039;2020/10/10&#039;, function(event) {&lt;br /&gt;
             $(this).html(event.strftime(&#039;%D days %H:%M:%S&#039;));&lt;br /&gt;
           });&lt;br /&gt;
});&lt;br /&gt;
&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Embedding AMD code in a page ==&lt;br /&gt;
So you have created lots of cool Javascript modules. Great. How do we actually call them? Any javascript code that calls an AMD module must execute AFTER the requirejs module loader has finished loading. We have provided a function &amp;quot;js_call_amd&amp;quot; that will call a single function from an AMD module with parameters.  &lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$PAGE-&amp;gt;requires-&amp;gt;js_call_amd($modulename, $functionname, $params);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
that will &amp;quot;do the right thing&amp;quot; with your block of AMD code and execute it at the end of the page, after our AMD module loader has loaded. &lt;br /&gt;
Notes:&lt;br /&gt;
* the $modulename is the &#039;componentname/modulename&#039; discussed above&lt;br /&gt;
* the $functionname is the name of a public function exposed by the amd module. &lt;br /&gt;
* the $params is an array of params passed as arguments to the function. These should be simple types that can be handled by json_encode (no recursive arrays, or complex classes please). &lt;br /&gt;
* if the size of the params array is too large (&amp;gt; 1Kb), this will produce a developer warning. Do not attempt to pass large amounts of data through this function, it will pollute the page size. A preferred approach is to pass css selectors for DOM elements that contain data-attributes for any required data, or fetch data via ajax in the background.&lt;br /&gt;
&lt;br /&gt;
AMD / JS code can also be embedded on a page via mustache templates&lt;br /&gt;
see here: https://docs.moodle.org/dev/Templates#What_if_a_template_contains_javascript.3F&lt;br /&gt;
&lt;br /&gt;
== But I have a mega JS file I don&#039;t want loaded on every page? ==&lt;br /&gt;
Loading all JS files at once and stuffing them in the browser cache is the right choice for MOST js files, there are probably some exceptions. For these files, you can rename the javascript file to end with the suffix &amp;quot;-lazy.js&amp;quot; which indicates that the module will not be loaded by default, it will be requested the first time it is used. There is no difference in usage for lazy loaded modules, the require() call looks exactly the same, it&#039;s just that the module name will also have the &amp;quot;-lazy&amp;quot; suffix.&lt;br /&gt;
&lt;br /&gt;
[[Category:AJAX]]&lt;br /&gt;
[[Category:Javascript]]&lt;/div&gt;</summary>
		<author><name>Davidbalch</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=ctags&amp;diff=52369</id>
		<title>ctags</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=ctags&amp;diff=52369"/>
		<updated>2017-05-12T10:29:28Z</updated>

		<summary type="html">&lt;p&gt;Davidbalch: /* Moodle 1.9.6 and newer */  Update contents of tags.txt&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Moodle 1.9.6 and newer ==&lt;br /&gt;
As of Moodle 1.9.6 the tags file is no longer included in the Moodle distribution. Instead a file tags.txt is included providing instructions on creating the tag file yourself. The text of this file is as follows:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Generating a tags file&lt;br /&gt;
======================&lt;br /&gt;
&lt;br /&gt;
If you need a tags file so that you can jump around Moodle code&lt;br /&gt;
easily in your editor (eg vim or emacs), you can generate one:&lt;br /&gt;
&lt;br /&gt;
Exuberant ctags (default on Linux, can be compiled on other platforms):&lt;br /&gt;
----------------------------------------------------------------------&lt;br /&gt;
&lt;br /&gt;
ctags -R --languages=php --php-kinds=f \&lt;br /&gt;
--regex-PHP=&#039;/abstract +class +([^ ]*)/\1/c/&#039; \&lt;br /&gt;
--regex-PHP=&#039;/interface +([^ ]*)/\1/c/&#039; \&lt;br /&gt;
--regex-PHP=&#039;/(public |static |abstract |protected |private )+ *function +([^ (]*)/\2/f/&#039;&lt;br /&gt;
&lt;br /&gt;
BSD ctags (Default on Mac OS X):&lt;br /&gt;
-------------------------------&lt;br /&gt;
&lt;br /&gt;
 (TODO)&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Using tags with Vim ==&lt;br /&gt;
&lt;br /&gt;
In order to use the tags file with the Vim editor (on a Unix style system), you should first add the following line to the file &#039;&#039;&#039;.vimrc&#039;&#039;&#039; in your home directory:&lt;br /&gt;
&lt;br /&gt;
    set tags=tags;/&lt;br /&gt;
&lt;br /&gt;
If .vimrc does not exist you should create it. This directive will force Vim to search for the tags file in the directory tree in which you are working (it searches up from where you are) so it will work for multiple versions of the code automatically.&lt;br /&gt;
&lt;br /&gt;
=== How to jump to a tag with Vim ===&lt;br /&gt;
&lt;br /&gt;
There are a number of things you can do:&lt;br /&gt;
&lt;br /&gt;
* Place the cursor over the tag (e.g., function name) and press &#039;&#039;&#039;CTRL+]&#039;&#039;&#039;.&lt;br /&gt;
* Type the command &#039;&#039;&#039;:tag &amp;lt;tagname&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
* To open in a new editor window type the command &#039;&#039;&#039;:stag &amp;lt;tagname&amp;gt;&#039;&#039;&#039;&lt;br /&gt;
* To return to your previous location type &#039;&#039;&#039;CTRL+t&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Using tags with Emacs ==&lt;br /&gt;
To create the TAGS file add a -e to the command of tags.txt, like this:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
ctags -e -R --languages=php --exclude=&amp;quot;CVS&amp;quot; --php-kinds=f \&lt;br /&gt;
--regex-PHP=&#039;/abstract class ([^ ]*)/\1/c/&#039; \&lt;br /&gt;
--regex-PHP=&#039;/interface ([^ ]*)/\1/c/&#039; \&lt;br /&gt;
--regex-PHP=&#039;/(public |static |abstract |protected |private )+function ([^ (]*)/\2/f/&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
For instructions about how to use it look to the emacs wiki page [http://www.emacswiki.org/emacs/EmacsTags EmacsTags]&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
* [http://www.vim.org/tips/tip.php?tip_id=94 Vim tags usage tip]&lt;br /&gt;
* [http://vimdoc.sourceforge.net/htmldoc/usr_29.html#29.1 Vim documentation: Using tags]&lt;br /&gt;
* [http://vimdoc.sourceforge.net/htmldoc/tagsrch.html Vim documentation: Tags and special searches]&lt;br /&gt;
&lt;br /&gt;
[[Category:Developer tools]]&lt;/div&gt;</summary>
		<author><name>Davidbalch</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Plugin_types&amp;diff=50642</id>
		<title>Plugin types</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Plugin_types&amp;diff=50642"/>
		<updated>2016-07-20T11:03:37Z</updated>

		<summary type="html">&lt;p&gt;Davidbalch: Remove duplicate &amp;quot;Antivirus plugins&amp;quot; row.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Plugins development}}&lt;br /&gt;
&lt;br /&gt;
The M in Moodle stands for modular.  The easiest and most maintainable way to add new functionality to Moodle is by writing one of these types of plugin. &lt;br /&gt;
If none of the standardized types fits your needs, you can use the &amp;quot;local&amp;quot; type.&lt;br /&gt;
&lt;br /&gt;
== List of Moodle plugin types ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Plugin type&lt;br /&gt;
! Component name ([[Frankenstyle]])&lt;br /&gt;
! Moodle path&lt;br /&gt;
! Description&lt;br /&gt;
! Moodle versions&lt;br /&gt;
|-&lt;br /&gt;
| [[Activity modules]]&lt;br /&gt;
| mod&lt;br /&gt;
| /mod&lt;br /&gt;
| Activity modules are essential types of plugins in Moodle as they provide activities in courses. For example: Forum, Quiz and Assignment.&lt;br /&gt;
| 1.0+&lt;br /&gt;
|-&lt;br /&gt;
| [[Antivirus plugins]]&lt;br /&gt;
| antivirus&lt;br /&gt;
| /lib/antivirus&lt;br /&gt;
| Antivirus scanner plugins provide functionality for virus scanning user uploaded files using third-party virus scanning tools in Moodle. For example: ClamAV.&lt;br /&gt;
| 3.1+&lt;br /&gt;
|-&lt;br /&gt;
| [[Assign_submission_plugins|Assignment submission plugins]]&lt;br /&gt;
| assignsubmission&lt;br /&gt;
| /mod/assign/submission&lt;br /&gt;
| Different forms of assignment submissions&lt;br /&gt;
| 2.3+&lt;br /&gt;
|-&lt;br /&gt;
| [[Assign_feedback_plugins|Assignment feedback plugins]]&lt;br /&gt;
| assignfeedback&lt;br /&gt;
| /mod/assign/feedback&lt;br /&gt;
| Different forms of assignment feedbacks&lt;br /&gt;
| 2.3+&lt;br /&gt;
|-&lt;br /&gt;
| [[Book tools]]&lt;br /&gt;
| booktool&lt;br /&gt;
| /mod/book/tool&lt;br /&gt;
| Small information-displays or tools that can be moved around pages&lt;br /&gt;
| 2.1+&lt;br /&gt;
|-&lt;br /&gt;
| [[Database fields]]&lt;br /&gt;
| datafield&lt;br /&gt;
| /mod/data/field&lt;br /&gt;
| Different types of data that may be added to the Database activity module&lt;br /&gt;
| 1.6+&lt;br /&gt;
|-&lt;br /&gt;
| [[Database presets]]&lt;br /&gt;
| datapreset&lt;br /&gt;
| /mod/data/preset&lt;br /&gt;
| Pre-defined templates for the Database activity module&lt;br /&gt;
| 1.6+&lt;br /&gt;
|-&lt;br /&gt;
| [[External tool source|LTI sources]]&lt;br /&gt;
| ltisource&lt;br /&gt;
| /mod/lti/source&lt;br /&gt;
| LTI providers can be added to external tools easily through the external tools interface see [https://docs.moodle.org/en/External_tool Documentation on External Tools]. This type of plugin is specific to LTI providers that need a plugin that can register custom handlers to process LTI messages&lt;br /&gt;
| 2.7+&lt;br /&gt;
|-&lt;br /&gt;
| [[LTI services]]&lt;br /&gt;
| ltiservice&lt;br /&gt;
| /mod/lti/service&lt;br /&gt;
| Allows the implementation of LTI services as described by the IMS LTI specification&lt;br /&gt;
| 2.8+&lt;br /&gt;
|-&lt;br /&gt;
| [[Quiz reports]]&lt;br /&gt;
| quiz&lt;br /&gt;
| /mod/quiz/report&lt;br /&gt;
| Display and analyse the results of quizzes, or just plug miscellaneous behaviour into the quiz module&lt;br /&gt;
| 1.1+&lt;br /&gt;
|-&lt;br /&gt;
| [[Quiz access rules]]&lt;br /&gt;
| quizaccess&lt;br /&gt;
| /mod/quiz/accessrule&lt;br /&gt;
| Add conditions to when or where quizzes can be attempted, for example only from some IP addresses, or student must enter a password first&lt;br /&gt;
| 2.2+&lt;br /&gt;
|-&lt;br /&gt;
| [[SCORM reports]]&lt;br /&gt;
| scormreport&lt;br /&gt;
| /mod/scorm/report&lt;br /&gt;
| Analysis of SCORM attempts&lt;br /&gt;
| 2.2+&lt;br /&gt;
|-&lt;br /&gt;
| [[Workshop grading strategies]]&lt;br /&gt;
| workshopform&lt;br /&gt;
| /mod/workshop/form&lt;br /&gt;
| Define the type of the grading form and implement the calculation of the grade for submission in the [[Workshop]] module&lt;br /&gt;
| 2.0+&lt;br /&gt;
|-&lt;br /&gt;
| [[Workshop allocation methods]]&lt;br /&gt;
| workshopallocation&lt;br /&gt;
| /mod/workshop/allocation&lt;br /&gt;
| Define ways how submissions are assigned for assessment in the [[Workshop]] module&lt;br /&gt;
| 2.0+&lt;br /&gt;
|-&lt;br /&gt;
| [[Workshop evaluation methods]]&lt;br /&gt;
| workshopeval&lt;br /&gt;
| /mod/workshop/eval&lt;br /&gt;
| Implement the calculation of the grade for assessment (grading grade) in the [[Workshop]] module&lt;br /&gt;
| 2.0+&lt;br /&gt;
|-&lt;br /&gt;
| [[Blocks]]&lt;br /&gt;
| block&lt;br /&gt;
| /blocks&lt;br /&gt;
| Small information-displays or tools that can be moved around pages&lt;br /&gt;
| 2.0+&lt;br /&gt;
|-&lt;br /&gt;
| [[Question types]]&lt;br /&gt;
| qtype&lt;br /&gt;
| /question/type&lt;br /&gt;
| Different types of question (e.g. multiple-choice, drag-and-drop) that can be used in quizzes and other activities&lt;br /&gt;
| 1.6+&lt;br /&gt;
|-&lt;br /&gt;
| [[Question behaviours]]&lt;br /&gt;
| qbehaviour&lt;br /&gt;
| /question/behaviour&lt;br /&gt;
| Control how student interact with questions during an attempt&lt;br /&gt;
| 2.1+&lt;br /&gt;
|-&lt;br /&gt;
| [[Question formats|Question import/export formats]]&lt;br /&gt;
| qformat&lt;br /&gt;
| /question/format&lt;br /&gt;
| Import and export question definitions to/from the question bank&lt;br /&gt;
| 1.6+&lt;br /&gt;
|-&lt;br /&gt;
| [[Filters|Text filters]]&lt;br /&gt;
| filter&lt;br /&gt;
| /filter&lt;br /&gt;
| Automatically convert, highlight, and transmogrify text posted into Moodle.&lt;br /&gt;
| 1.4+&lt;br /&gt;
|-&lt;br /&gt;
| [[Editors]]&lt;br /&gt;
| editor&lt;br /&gt;
| /lib/editor&lt;br /&gt;
| Alternative text editors for editing content&lt;br /&gt;
| 2.0+&lt;br /&gt;
|-&lt;br /&gt;
| [[Atto|Atto editor plugins]]&lt;br /&gt;
| atto&lt;br /&gt;
| /lib/editor/atto/plugins&lt;br /&gt;
| Extra functionality for the Atto text editor&lt;br /&gt;
| 2.7+&lt;br /&gt;
|-&lt;br /&gt;
| [[TinyMCE editor plugins]]&lt;br /&gt;
| tinymce&lt;br /&gt;
| /lib/editor/tinymce/plugins&lt;br /&gt;
| Extra functionality for the TinyMCE text editor.&lt;br /&gt;
| 2.4+&lt;br /&gt;
|-&lt;br /&gt;
| [[Enrolment plugins]]&lt;br /&gt;
| enrol&lt;br /&gt;
| /enrol&lt;br /&gt;
| Ways to control who is enrolled in courses&lt;br /&gt;
| 2.0+&lt;br /&gt;
|-&lt;br /&gt;
| [[Authentication plugins]]&lt;br /&gt;
| auth&lt;br /&gt;
| /auth&lt;br /&gt;
| Allows connection to external sources of authentication&lt;br /&gt;
| 2.0+&lt;br /&gt;
|-&lt;br /&gt;
| [[Admin tools]]&lt;br /&gt;
| tool&lt;br /&gt;
| /admin/tool&lt;br /&gt;
| Provides utility scripts useful for various site administration and maintenance tasks&lt;br /&gt;
| 2.2+&lt;br /&gt;
|-&lt;br /&gt;
| [[Log stores]]&lt;br /&gt;
| logstore&lt;br /&gt;
| /admin/tool/log/store&lt;br /&gt;
| Event logs storage back-ends&lt;br /&gt;
| 2.7+&lt;br /&gt;
|-&lt;br /&gt;
| [[Availability conditions]]&lt;br /&gt;
| availability&lt;br /&gt;
| /availability/condition&lt;br /&gt;
| Conditions to restrict user access to activities and sections.&lt;br /&gt;
| 2.7+&lt;br /&gt;
|-&lt;br /&gt;
| [[Calendar types]]&lt;br /&gt;
| calendartype&lt;br /&gt;
| /calendar/type&lt;br /&gt;
| Defines how dates are displayed throughout Moodle&lt;br /&gt;
| 2.6+&lt;br /&gt;
|-&lt;br /&gt;
| [[Messaging consumers]]&lt;br /&gt;
| message&lt;br /&gt;
| /message/output&lt;br /&gt;
| Represent various targets where messages and notifications can be sent to (email, sms, jabber, ...)&lt;br /&gt;
| 2.0+&lt;br /&gt;
|-&lt;br /&gt;
| [[Course formats]]&lt;br /&gt;
| format&lt;br /&gt;
| /course/format&lt;br /&gt;
| Different ways of laying out the activities and blocks in a course&lt;br /&gt;
| 1.3+&lt;br /&gt;
|-&lt;br /&gt;
| [[Data formats]]&lt;br /&gt;
| dataformat&lt;br /&gt;
| /dataformat&lt;br /&gt;
| Formats for data exporting and downloading&lt;br /&gt;
| 3.1+&lt;br /&gt;
|-&lt;br /&gt;
| [[User profile fields]]&lt;br /&gt;
| profilefield&lt;br /&gt;
| /user/profile/field&lt;br /&gt;
| Add new types of data to user profiles&lt;br /&gt;
| 1.9+&lt;br /&gt;
|-&lt;br /&gt;
| [[Reports]]&lt;br /&gt;
| report&lt;br /&gt;
| /report&lt;br /&gt;
| Provides useful views of data in a Moodle site for admins and teachers&lt;br /&gt;
| 2.2+&lt;br /&gt;
|-&lt;br /&gt;
| [[Course reports]]&lt;br /&gt;
| coursereport&lt;br /&gt;
| /course/report&lt;br /&gt;
| Reports of activity within the course&lt;br /&gt;
| Up to 2.1 (for 2.2+ see [[Reports]])&lt;br /&gt;
|-&lt;br /&gt;
| [[Gradebook export]]&lt;br /&gt;
| gradeexport&lt;br /&gt;
| /grade/export&lt;br /&gt;
| Export grades in various formats&lt;br /&gt;
| 1.9+&lt;br /&gt;
|-&lt;br /&gt;
| [[Gradebook import]]&lt;br /&gt;
| gradeimport&lt;br /&gt;
| /grade/import&lt;br /&gt;
| Import grades in various formats &lt;br /&gt;
| 1.9+&lt;br /&gt;
|-&lt;br /&gt;
| [[Gradebook reports]]&lt;br /&gt;
| gradereport&lt;br /&gt;
| /grade/report&lt;br /&gt;
| Display/edit grades in various layouts and reports&lt;br /&gt;
| 1.9+&lt;br /&gt;
|-&lt;br /&gt;
| [[Grading methods|Advanced grading methods]]&lt;br /&gt;
| gradingform&lt;br /&gt;
| /grade/grading/form&lt;br /&gt;
| Interfaces for actually performing grading in activity modules (eg Rubrics)&lt;br /&gt;
| 2.2+&lt;br /&gt;
|-&lt;br /&gt;
| [[MNet services]]&lt;br /&gt;
| mnetservice&lt;br /&gt;
| /mnet/service&lt;br /&gt;
| Allows to implement remote services for the [[MNet]] environment (deprecated, use web services instead)&lt;br /&gt;
| 2.0+&lt;br /&gt;
|-&lt;br /&gt;
| [[Webservice protocols]]&lt;br /&gt;
| webservice&lt;br /&gt;
| /webservice&lt;br /&gt;
| Define new protocols for web service communication (such as SOAP, XML-RPC, JSON, REST ...)&lt;br /&gt;
| 2.0+&lt;br /&gt;
|-&lt;br /&gt;
| [[Repository plugins]]&lt;br /&gt;
| repository&lt;br /&gt;
| /repository&lt;br /&gt;
| Connect to external sources of files to use in Moodle&lt;br /&gt;
| 2.0+&lt;br /&gt;
|-&lt;br /&gt;
| [[Portfolio plugins]]&lt;br /&gt;
| portfolio&lt;br /&gt;
| /portfolio&lt;br /&gt;
| Connect external portfolio services as destinations for users to store Moodle content&lt;br /&gt;
| 1.9+&lt;br /&gt;
|-&lt;br /&gt;
| [[Search engines]]&lt;br /&gt;
| search&lt;br /&gt;
| /search/engine&lt;br /&gt;
| Search engine backends to index Moodle&#039;s contents.&lt;br /&gt;
| 3.1+&lt;br /&gt;
|-&lt;br /&gt;
| [[Plagiarism plugins]]&lt;br /&gt;
| plagiarism&lt;br /&gt;
| /plagiarism&lt;br /&gt;
| Define external services to process submitted files and content&lt;br /&gt;
| 2.0+&lt;br /&gt;
|-&lt;br /&gt;
| [[Cache store]]&lt;br /&gt;
| cachestore&lt;br /&gt;
| /cache/stores&lt;br /&gt;
| Cache storage back-ends.&lt;br /&gt;
| 2.4+&lt;br /&gt;
|-&lt;br /&gt;
| [[Cache locks]]&lt;br /&gt;
| cachelock&lt;br /&gt;
| /cache/locks&lt;br /&gt;
| Cache lock implementations.&lt;br /&gt;
| 2.4+&lt;br /&gt;
|-&lt;br /&gt;
| [[Themes]]&lt;br /&gt;
| theme&lt;br /&gt;
| /theme&lt;br /&gt;
| Change the look of Moodle by changing the the HTML and the CSS. &lt;br /&gt;
| 2.0+&lt;br /&gt;
|-&lt;br /&gt;
| [[Local plugins]]&lt;br /&gt;
| local&lt;br /&gt;
| /local&lt;br /&gt;
| Generic plugins for local customisations&lt;br /&gt;
| 2.0+&lt;br /&gt;
|-&lt;br /&gt;
| [[Assignment types|Legacy assignment types]]&lt;br /&gt;
| assignment&lt;br /&gt;
| /mod/assignment/type&lt;br /&gt;
| Different forms of assignments to be graded by teachers&lt;br /&gt;
| 1.x - 2.2&lt;br /&gt;
|-&lt;br /&gt;
| [[Admin reports|Legacy admin reports]]&lt;br /&gt;
| report&lt;br /&gt;
| /admin/report&lt;br /&gt;
| Provides useful views of data in a Moodle site, for admins only.&lt;br /&gt;
| Up to 2.1 (for 2.2+ see [[Reports]])&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Obtaining the list of plugin types known to your Moodle ==&lt;br /&gt;
&lt;br /&gt;
To get the most exact list of types in your version of Moodle, use the following script. Put it to a file in the root directory of your Moodle installation and execute it via command line.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
define(&#039;CLI_SCRIPT&#039;, true);&lt;br /&gt;
require(&#039;config.php&#039;);&lt;br /&gt;
&lt;br /&gt;
$pluginman = core_plugin_manager::instance();&lt;br /&gt;
&lt;br /&gt;
foreach ($pluginman-&amp;gt;get_plugin_types() as $type =&amp;gt; $dir) {&lt;br /&gt;
    $dir = substr($dir, strlen($CFG-&amp;gt;dirroot));&lt;br /&gt;
    printf(&amp;quot;%-20s %-50s %s&amp;quot;.PHP_EOL, $type, $pluginman-&amp;gt;plugintype_name_plural($type), $dir);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Things you can find in all plugins==&lt;br /&gt;
&lt;br /&gt;
Although there are many different types of plugin, there are some things that work the same way in all plugin types, and we have [[Things that work the same in all plugin types|a page that describes them]].&lt;br /&gt;
&lt;br /&gt;
Additionally you probably want to look at the page [[Plugin files]].&lt;br /&gt;
&lt;br /&gt;
== Naming conventions ==&lt;br /&gt;
&lt;br /&gt;
Warning if you have to choose a plugin (directory) name. The name is validated by the method &amp;lt;tt&amp;gt;lib/classes/component.php::is_valid_plugin_name()&amp;lt;/tt&amp;gt; with a regexp: &amp;lt;tt&amp;gt;/^[a-z](?:[a-z0-9_](?!__))*[a-z0-9]+$/&amp;lt;/tt&amp;gt;. In particular, the minus (-) character is not considered as valid, and the plugin will be silently ignored if the name is not valid.&lt;br /&gt;
&lt;br /&gt;
There is an exception for [[Activity modules|activity modules]] that can not have the underscore in their name for legacy reasons.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[Guidelines_for_contributed_code|Guidelines for contributing code]]&lt;br /&gt;
* [[Core APIs]]&lt;br /&gt;
* [[Frankenstyle]]&lt;br /&gt;
* [http://moodle.org/plugins Moodle Plugins directory] &lt;br /&gt;
* [[Tutorial]] to help you learn how to write plugins for Moodle from start to finish, while showing you how to navigate the most important developer documentation along the way. &lt;br /&gt;
&lt;br /&gt;
[[Category:Coding guidelines|Plugins]]&lt;br /&gt;
[[Category:Plugins]]&lt;/div&gt;</summary>
		<author><name>Davidbalch</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Navigation_API&amp;diff=50426</id>
		<title>Navigation API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Navigation_API&amp;diff=50426"/>
		<updated>2016-06-16T08:54:17Z</updated>

		<summary type="html">&lt;p&gt;Davidbalch: /* Code extension */ I wasted hours trying to get callbacks in lib.php working before realising that I needed to use settings.php. Hopfully this will help save others the same experience.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Overview==&lt;br /&gt;
The Navigation API allows for the manipulation of the navigation system used in Moodle.&lt;br /&gt;
&lt;br /&gt;
==What the navigation is==&lt;br /&gt;
It&#039;s very important to understand what the navigation is exactly within Moodle. One of the goals for Moodle 2.0 was to standardise navigation throughout Moodle and try to bring order to the structure of a Moodle site.  Navigation is available through the page object &#039;&#039;&#039;$PAGE&#039;&#039;&#039;, against which you set the heading for the page, the title, any JavaScript requirements, etc.  The navigation structure uses the information $PAGE contains to generate a navigation structure for the site.  The navigation or settings [[blocks]] are interpretations of the navigation structure Moodle creates.&lt;br /&gt;
&lt;br /&gt;
This navigation structure is available through three variables:&lt;br /&gt;
&lt;br /&gt;
; $PAGE-&amp;gt;navigation : This is the main navigation structure, it will contain items that will allow the user to browse to the other available pages.&lt;br /&gt;
; $PAGE-&amp;gt;settingsnav : This is the settings navigation structure contains items that will allow the user to edit settings.&lt;br /&gt;
; $PAGE-&amp;gt;navbar : The navbar is a special structure for page breadcrumbs.&lt;br /&gt;
&lt;br /&gt;
==What the navigation is not==&lt;br /&gt;
The navigation is &#039;&#039;&#039;NOT&#039;&#039;&#039; the navigation block or the settings block!  These two blocks were created to display the navigation structure. The navigation block looks at &#039;&#039;$PAGE-&amp;gt;navigation&#039;&#039;, and the settings block looks at &#039;&#039;$PAGE-&amp;gt;settingsnav&#039;&#039;.  Both blocks interpret their data into an HTML structure and render it.&lt;br /&gt;
&lt;br /&gt;
# The navigation is a back-end structure that is built behind the scenes and has no immediate method of display.&lt;br /&gt;
# The navigation and settings blocks display the back-end navigation structure but add nothing to it at all.&lt;br /&gt;
::In the [[model-view-controller pattern]], $PAGE-&amp;gt;navigation, $PAGE-&amp;gt;settingsnav, and $PAGE-&amp;gt;navbar are the models, and the blocks are views.&lt;br /&gt;
&lt;br /&gt;
The navbar is just the path to the active navigation or settings item. The navbar is not displayed by a block; instead it is added into the theme&#039;s layout files and displayed by the core renderer. &lt;br /&gt;
&lt;br /&gt;
==How the navigation works==&lt;br /&gt;
&lt;br /&gt;
The main navigation structure can be accessed through &#039;&#039;&#039;$PAGE-&amp;gt;navigation&#039;&#039;&#039;.  The navigation and settings are contextual in that they will relate to the page that the user is viewing. This is determined by other $PAGE object properties:&lt;br /&gt;
* &#039;&#039;&#039;$PAGE-&amp;gt;context&#039;&#039;&#039; is a Moodle context that immediately outlines the nature of the page the user is viewing.&lt;br /&gt;
* &#039;&#039;&#039;$PAGE-&amp;gt;course&#039;&#039;&#039; is the course the user is viewing.  This is essential if the context is CONTEXT_COURSE or greater.  However, it is also useful in other contexts such as CONTEXT_USER.&lt;br /&gt;
* &#039;&#039;&#039;$PAGE-&amp;gt;cm&#039;&#039;&#039; is the course module instance.  This is essential if the context is CONTEXT_MODULE or greater.&lt;br /&gt;
* &#039;&#039;&#039;$PAGE-&amp;gt;url&#039;&#039;&#039; is used to match the active navigation item. &lt;br /&gt;
&lt;br /&gt;
Nearly every page sets $PAGE-&amp;gt;url through a call to &#039;&#039;&#039;$PAGE-&amp;gt;set_url&#039;&#039;&#039; however not many explicitly set the context, course, or cm. When you call &#039;&#039;&#039;require_login&#039;&#039;&#039; with a course or cm it automatically calls the following:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if ($cm) {&lt;br /&gt;
    $PAGE-&amp;gt;set_cm($cm, $course); // sets up global $COURSE&lt;br /&gt;
} else {&lt;br /&gt;
    $PAGE-&amp;gt;set_course($course);// sets up global $COURSE&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A page will only be required to explicitly set a context, course, or cm under one of these conditions:&lt;br /&gt;
# &#039;&#039;&#039;require_login&#039;&#039;&#039; is NOT being called correctly&lt;br /&gt;
# The page is using CONTEXT_SYSTEM, CONTEXT_COURSECAT, or CONTEXT_USER (call &#039;&#039;&#039;$PAGE-&amp;gt;set_context&#039;&#039;&#039; ).&lt;br /&gt;
# The page is using a course or cm but it is also using one of the above contexts (call &#039;&#039;&#039;$PAGE-&amp;gt;set_course&#039;&#039;&#039; or &#039;&#039;&#039;$PAGE-&amp;gt;set_cm&#039;&#039;&#039; ).&lt;br /&gt;
&lt;br /&gt;
The navigation structure cannot be generated before the $PAGE object is configured. It is only generated when it is first used, either when something tries to access the structure or when code tries to add to it.  The navigation is initialised in a specific order:&lt;br /&gt;
# Main navigation structure&lt;br /&gt;
# Settings navigation&lt;br /&gt;
# Navbar (does not need to be generated because of its simple contents and rendering)&lt;br /&gt;
&lt;br /&gt;
==Extending the navigation==&lt;br /&gt;
&lt;br /&gt;
===Code extension===&lt;br /&gt;
This method of extending is when the code arbitrarily extends the navigation during its execution. Extending the navigation through this means allows you to extend the navigation anywhere easily, however it will only be shown on pages where your extending code gets called (you should probably put it in a function within lib.php).&lt;br /&gt;
&lt;br /&gt;
Navigation extensions that apply all the time (even when not on pages including your code) can be made by putting them in your plugin&#039;s settings.php file.&lt;br /&gt;
&lt;br /&gt;
These examples are taken from the [http://moodle.org/mod/forum/discuss.php?d=152391 General Developer Forum: Moodle 2 - how to set up breadcrumbs for a module page]. It has further information that is well worth reading.&lt;br /&gt;
====Navigation====&lt;br /&gt;
This is extending the main navigation structure.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$previewnode = $PAGE-&amp;gt;navigation-&amp;gt;add(get_string(&#039;preview&#039;), new moodle_url(&#039;/a/link/if/you/want/one.php&#039;), navigation_node::TYPE_CONTAINER);&lt;br /&gt;
$thingnode = $previewnode-&amp;gt;add(get_string(&#039;name of thing&#039;), new moodle_url(&#039;/a/link/if/you/want/one.php&#039;));&lt;br /&gt;
$thingnode-&amp;gt;make_active();&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The above lines of code adds a preview node to the bottom of the navigation and then adds a thingnode to the previewnode (adding a leaf to our tree).&lt;br /&gt;
The final line of code makes the thingnode active so that the navbar finds it however if the URL you give it is the same as the url you set for the page it will automatically be marked active and you won&#039;t need this call.&lt;br /&gt;
&lt;br /&gt;
Next extending the navigation for the course.&lt;br /&gt;
For this you will need to know the course id and have called require_login($courseorid); so that the navigation is loaded for the course.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$coursenode = $PAGE-&amp;gt;navigation-&amp;gt;find($courseid, navigation_node::TYPE_COURSE);&lt;br /&gt;
$thingnode = $coursenode-&amp;gt;add(get_string(&#039;Name of thing&#039;), new moodle_url(&#039;/a/link/if/you/want/one.php&#039;));&lt;br /&gt;
$thingnode-&amp;gt;make_active();&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The new bit of code here really is the first line which simply finds the course node, to do this we give it the course id and the node type in this case TYPE_COURSE.&lt;br /&gt;
What we are doing here is relying on the navigation to generate the navigation up to the course and then just adding to the course.&lt;br /&gt;
&lt;br /&gt;
Note that Moodle loads plugins in alphabetical order. Therefore plugin_b can find a node added by plugin_a but not the other way around.&lt;br /&gt;
====Settings navigation====&lt;br /&gt;
Adding to the settings navigation is very similar to navigation&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$settingnode = $PAGE-&amp;gt;settingsnav-&amp;gt;add(get_string(&#039;setting&#039;), new moodle_url(&#039;/a/link/if/you/want/one.php&#039;), navigation_node::TYPE_CONTAINER);&lt;br /&gt;
$thingnode = $settingnode-&amp;gt;add(get_string(&#039;Name of thing&#039;), new moodle_url(&#039;/a/link/if/you/want/one.php&#039;));&lt;br /&gt;
$thingnode-&amp;gt;make_active();&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Add Settings folder to navigation====&lt;br /&gt;
&lt;br /&gt;
An example of a settings folder in a module can be see by navigating to Site administration / Plugins / Activity modules / Assignment. &lt;br /&gt;
&lt;br /&gt;
[[File:assignmentmenu.png]]&lt;br /&gt;
&lt;br /&gt;
An example of adding a navigation folder to a settings.php for a block with a link to the settings page and a external page is bellow.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&amp;lt;?php&lt;br /&gt;
&lt;br /&gt;
defined(&#039;MOODLE_INTERNAL&#039;) || die;&lt;br /&gt;
&lt;br /&gt;
// Create folder / submenu in block menu, modsettings for activity modules, localplugins for Local plugins. &lt;br /&gt;
// The default folders are defined in admin/settings/plugins.php.&lt;br /&gt;
$ADMIN-&amp;gt;add(&#039;blocksettings&#039;, new admin_category(&#039;blocksamplefolder&#039;,&lt;br /&gt;
        get_string(&#039;pluginname&#039;, &#039;mod_sample&#039;)));&lt;br /&gt;
&lt;br /&gt;
// Create settings block.&lt;br /&gt;
$settings = new admin_settingpage($section, get_string(&#039;settings&#039;, &#039;block_sample&#039;));&lt;br /&gt;
if ($ADMIN-&amp;gt;fulltree) {&lt;br /&gt;
    $settings-&amp;gt;add(new admin_setting_configcheckbox(&#039;block_sample_checkbox&#039;, get_string(&#039;checkbox&#039;, &#039;block_sample&#039;),&lt;br /&gt;
        get_string(&#039;checkboxdescription&#039;, &#039;block_kronoshtml&#039;), 0));&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// This adds the settings link to the folder/submenu.&lt;br /&gt;
$ADMIN-&amp;gt;add(&#039;blocksamplefolder&#039;, $settings);&lt;br /&gt;
// This adds a link to an external page.&lt;br /&gt;
$ADMIN-&amp;gt;add(&#039;blocksamplefolder&#039;, new admin_externalpage(&#039;block_sample_page&#039;, get_string(&#039;externalpage&#039;, &#039;block_sample&#039;),&lt;br /&gt;
        $CFG-&amp;gt;wwwroot.&#039;/blocks/sample/sample.php&#039;));&lt;br /&gt;
// Prevent Moodle from adding settings block in standard location.&lt;br /&gt;
$settings = null;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Navbar====&lt;br /&gt;
This is extending the settings navigation.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$PAGE-&amp;gt;navbar-&amp;gt;ignore_active();&lt;br /&gt;
$PAGE-&amp;gt;navbar-&amp;gt;add(get_string(&#039;preview&#039;), new moodle_url(&#039;/a/link/if/you/want/one.php&#039;));&lt;br /&gt;
$PAGE-&amp;gt;navbar-&amp;gt;add(get_string(&#039;name of thing&#039;), new moodle_url(&#039;/a/link/if/you/want/one.php&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The above code tells the navbar to ignore what ever the active page was and just use what you add, at which point we add two items as shown.&lt;br /&gt;
&lt;br /&gt;
===Plugin Callbacks===&lt;br /&gt;
These are specific functions that the navigation looks for and calls if they exist for the plugin, presently only three plugin types can extend the navigation through these call-backs.&lt;br /&gt;
&lt;br /&gt;
Ideally all entries in &amp;quot;Administration / Site administration&amp;quot; tree should be done via settings.php files but sometimes it may be easier to directly modify the navigation structure created from the admin settings tree (such as when adding links to external pages).&lt;br /&gt;
&lt;br /&gt;
====Module====&lt;br /&gt;
Modules have two call-back methods, first to extend the navigation, and second to extend the settings. These call-backs get called when ever the user is viewing a page within the module and should only extend the navigation for the module.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
function {modulename}_extend_navigation(${modulename}node, $course, $module, $cm)&lt;br /&gt;
function {modulename}_extend_settings_navigation($settings, ${modulename}node)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Course Formats====&lt;br /&gt;
Course formats are able to completely redefine the way in which navigation is generated for a course, as well as this they also have several methods to ensure the navigation is generated correctly.&lt;br /&gt;
&lt;br /&gt;
====Course Reports====&lt;br /&gt;
By default reports don&#039;t add themselves or anything else to the navigation however there is a call-back that can be implemented to allow them to do so.&lt;br /&gt;
&lt;br /&gt;
====Local Plugins====&lt;br /&gt;
{{Moodle 2.9}}Local plugins have two call-back methods, first to extend the navigation, and second to extend the settings.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
function local_{pluginname}_extend_navigation(global_navigation $nav)&lt;br /&gt;
function local_{pluginname}_extend_settings_navigation(settings_navigation $nav, context $context)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In versions before Moodle 2.9, the supported callbacks have &amp;lt;tt&amp;gt;_extends_&amp;lt;/tt&amp;gt; (not imperative mood) in their names. This was a consistency bug fixed in MDL-49643.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
function local_{pluginname}_extends_navigation(global_navigation $nav)&lt;br /&gt;
function local_{pluginname}_extends_settings_navigation(settings_navigation $nav, context $context)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Course settings====&lt;br /&gt;
{{Moodle 3.0}}&lt;br /&gt;
Any plugin implementing the following callback in lib.php can extend the course settings navigation. Prior to 3.0 only &#039;&#039;reports&#039;&#039; and &#039;&#039;admin tools&#039;&#039; could extend the course settings navigation.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
function &amp;lt;component&amp;gt;_extend_navigation_course(navigation_node $parentnode, stdClass $course, context_course $context);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====User settings====&lt;br /&gt;
{{Moodle 3.0}}&lt;br /&gt;
Any plugin implementing the following callback in lib.php can extend the user settings navigation. Prior to 3.0 only &#039;&#039;admin tools&#039;&#039; could extend the user settings navigation.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
function &amp;lt;component&amp;gt;_extend_navigation_user_settings(navigation_node $parentnode, stdClass $user, context_user $context, stdClass $course, context_course $coursecontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Category settings====&lt;br /&gt;
{{Moodle 3.0}}&lt;br /&gt;
Any plugin implementing the following callback in lib.php can extend the category settings navigation.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
function &amp;lt;component&amp;gt;_extend_navigation_category_settings(navigation_node $parentnode, context_coursecat $context);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Frontpage settings====&lt;br /&gt;
{{Moodle 3.0}}&lt;br /&gt;
Any plugin implementing the following callback in lib.php can extend the frontpage settings navigation. Prior to 3.0 only &#039;&#039;admin tools&#039;&#039; could extend the frontpage settings navigation.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
function &amp;lt;component&amp;gt;_extend_navigation_frontpage(navigation_node $parentnode, stdClass $course, context_course $context);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====User profile====&lt;br /&gt;
{{Moodle 3.1}}&lt;br /&gt;
Any plugin implementing the following callback in lib.php can extend the user profile navigation.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
function &amp;lt;component&amp;gt;_extend_navigation_user(navigation_node $parentnode, stdClass $user, context_user $context, stdClass $course, context_course $coursecontext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==FAQ&#039;s and troubleshooting==&lt;br /&gt;
&#039;&#039;&#039;Q.&#039;&#039;&#039; My page is on the navigation but it doesn&#039;t find it?&lt;br /&gt;
&lt;br /&gt;
The first thing to do here is check the URL you are setting for the page. It should match the URL your page has within the navigation. If it doesn&#039;t you have two options, first change your &#039;&#039;&#039;$PAGE-&amp;gt;set_url&#039;&#039;&#039; call, or second override the URL the navigation is using to find the active node as shown below:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
navigation_node::override_active_url(new moodle_url(&#039;/your/url/here.php&#039;, array(&#039;param&#039;=&amp;gt;&#039;value&#039;)));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
* [[Core APIs]]&lt;br /&gt;
* [https://moodle.org/mod/forum/discuss.php?d=170325&amp;amp;parent=753095 Forum discussion - adding navigation to local plugins]&lt;br /&gt;
&lt;br /&gt;
[[Category:API]]&lt;/div&gt;</summary>
		<author><name>Davidbalch</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Course_completion&amp;diff=49152</id>
		<title>Course completion</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Course_completion&amp;diff=49152"/>
		<updated>2015-12-15T10:04:15Z</updated>

		<summary type="html">&lt;p&gt;Davidbalch: Remove obsolete &amp;quot;Notifications&amp;quot; reference. Ref https://docs.moodle.org/dev/index.php?title=Course_completion&amp;amp;type=revision&amp;amp;diff=34956&amp;amp;oldid=8813&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;p class=&amp;quot;note&amp;quot;&amp;gt;&#039;&#039;&#039;Note&#039;&#039;&#039;: up to date information for users is available  in the user documentation on [https://docs.moodle.org/en/Course_completion Course completion].&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Overview=&lt;br /&gt;
We need to flag when the &amp;quot;student&amp;quot; has finished a course. :-)&lt;br /&gt;
&lt;br /&gt;
We need to allow several types of criteria to determine how a &amp;quot;student&amp;quot; can complete a course:&lt;br /&gt;
* &#039;&#039;&#039;Manual self completion&#039;&#039;&#039;: A &amp;quot;student&amp;quot; can mark her/himself complete a course.&lt;br /&gt;
* &#039;&#039;&#039;Manual completion by&#039;&#039;&#039;: An &amp;quot;admin&amp;quot;, &amp;quot;teacher&amp;quot;, &amp;quot;tutor&amp;quot;, etc can mark a student complete for a course.&lt;br /&gt;
* &#039;&#039;&#039;Activity completion&#039;&#039;&#039;: A &amp;quot;student&amp;quot; completes an activity, building on [https://docs.moodle.org/en/Development:Conditional_activities#Completion_tracking_2 Activity Completion] developed for 2.0 by Sam Marshall from OU.&lt;br /&gt;
* &#039;&#039;&#039;Course grade&#039;&#039;&#039;: A &amp;quot;student&amp;quot; meets or exceeds a passing grade in the course.&lt;br /&gt;
* &#039;&#039;&#039;Date&#039;&#039;&#039;: After a specified date all &amp;quot;students&amp;quot; are marked as completed in a course.&lt;br /&gt;
* &#039;&#039;&#039;Duration after enrolment&#039;&#039;&#039;: After a specified amount of time after enrolment (e.g., 12 weeks), a &amp;quot;student&amp;quot; is automatically marked as complete in a course.&lt;br /&gt;
* &#039;&#039;&#039;Unenrol&#039;&#039;&#039;: After being unenroled, a &amp;quot;student&amp;quot; is marked complete for the course. Useful for external enrolment plugins.&lt;br /&gt;
&lt;br /&gt;
We need to record when and how a &amp;quot;student&amp;quot; meets each criteria in a course.&lt;br /&gt;
&lt;br /&gt;
We need to regularly aggregate the results for the criteria, measure progress, and check to see if a &amp;quot;student&amp;quot; has completed a course.&lt;br /&gt;
&lt;br /&gt;
We need to show:&lt;br /&gt;
* A &amp;quot;student&amp;quot; the necessary criteria to complete a course&lt;br /&gt;
* A &amp;quot;student&amp;quot; how s/he is progressing in a course along the necessary criteria&lt;br /&gt;
* A &amp;quot;teacher&amp;quot; how his/her students are progressing in a course and which students have completed it&lt;br /&gt;
&lt;br /&gt;
=Settings=&lt;br /&gt;
==Site settings==&lt;br /&gt;
[https://docs.moodle.org/en/Development:Conditional_activities#Completion_tracking_2 Activity Completion] provides an &amp;quot;Enable completion tracking&amp;quot; setting (found in the Site Administration block&#039;s Advanced features / optional subsystems settings page) for activity completion. We need to change the setting&#039;s name and add another setting for course completion.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Proposed changes:&#039;&#039;&#039;&lt;br /&gt;
* Rename the setting &amp;quot;Enable activity completion tracking&amp;quot; (the setting will continue to allow activity completion to be enabled in all courses)&lt;br /&gt;
* Create a new setting named &amp;quot;Enable course completion tracking&amp;quot; (the setting will allow course completion to be enabled in all courses)&lt;br /&gt;
&lt;br /&gt;
==Course settings==&lt;br /&gt;
[https://docs.moodle.org/en/Development:Conditional_activities#Completion_tracking_2 Activity Completion] provides a fieldset (named &amp;quot;Student progress&amp;quot;) which contains a setting (named &amp;quot;Completion tracking&amp;quot;) which enables/disables activity completion in a course. We need to rename the setting to describe activity completion and add a setting to enable course completion.&lt;br /&gt;
&lt;br /&gt;
&amp;quot;Enable completion tracking&amp;quot; setting (found in the Site Administration block&#039;s Advanced features / optional subsystems settings page) for activity completion. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Proposed changes:&#039;&#039;&#039;&lt;br /&gt;
* Rename the setting &amp;quot;Completion tracking&amp;quot; to &amp;quot;Activity completion tracking&amp;quot; (the setting will continue to make activity completion features available in the courses)&lt;br /&gt;
* Create a new setting named &amp;quot;Course completion tracking&amp;quot; (the setting will make course completion features available in the course)&lt;br /&gt;
&lt;br /&gt;
==Course completion settings==&lt;br /&gt;
When course completion is enabled in a course (see above), a menu item labeled &amp;quot;Completion&amp;quot; will appear in the course Administration block on the main course page. When a user clicks on the &amp;quot;Completion&amp;quot; link, the course completion admin page will display the criteria described below and in the mockup also below:&lt;br /&gt;
&lt;br /&gt;
[[Image:Edit_course_completion_settings.png|thumb|600px|Edit course completion settings]]&lt;br /&gt;
&lt;br /&gt;
===Criteria enabling===&lt;br /&gt;
We need to allow criteria to be enabled.&lt;br /&gt;
&lt;br /&gt;
We need some rules about enabled criteria in a course:&lt;br /&gt;
* Each criteria (e.g., &amp;quot;Manual self completion&amp;quot;, &amp;quot;Manual completion by&amp;quot;, etc) can be enabled&lt;br /&gt;
* By default, all criteria are not enabled&lt;br /&gt;
&lt;br /&gt;
===Criteria aggregation===&lt;br /&gt;
We need to define overall aggregation methods for the criteria type. We also need to a consistent set of aggregation methods for criteria types which have more than one possible option to determine an individual user&#039;s course completion:&lt;br /&gt;
* &amp;quot;Manual completion by&amp;quot; // more than one role can be enabled to determine if a student has completed the course&lt;br /&gt;
* &amp;quot;Activity completed&amp;quot; // more than one activity completion can be selected to determine if a student has completed the course&lt;br /&gt;
&lt;br /&gt;
Aggregation methods:&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;2&amp;quot; cellspacing=&amp;quot;0&amp;quot;&lt;br /&gt;
|&#039;&#039;&#039;Method&#039;&#039;&#039; &lt;br /&gt;
|&#039;&#039;&#039;Required to complete the criteria type&#039;&#039;&#039; &lt;br /&gt;
|&#039;&#039;&#039;Example&#039;&#039;&#039; &lt;br /&gt;
|&#039;&#039;&#039;Info&#039;&#039;&#039; &lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;All&#039;&#039;&#039;&lt;br /&gt;
|All of the enabled child criteria must be completed&lt;br /&gt;
|Not applicable&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;Any&#039;&#039;&#039;&lt;br /&gt;
|Any one (1) of the enabled child criteria must be completed&lt;br /&gt;
|Not applicable&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Criteria types===&lt;br /&gt;
====Manual self-completion====&lt;br /&gt;
&#039;&#039;&#039;Action:&#039;&#039;&#039;&lt;br /&gt;
* A &amp;quot;student&amp;quot; can manually indicate that s/he has completed a course via the course&#039;s [https://docs.moodle.org/en/Development:Course_completion#Block Completion progress block].&lt;br /&gt;
&#039;&#039;&#039;Tracked:&#039;&#039;&#039;&lt;br /&gt;
* The day/time when the &amp;quot;student&amp;quot; marked him/herself complete.&lt;br /&gt;
&lt;br /&gt;
====Manual completion by====&lt;br /&gt;
&#039;&#039;&#039;Action:&#039;&#039;&#039; &lt;br /&gt;
* A user assigned to the role in the course will see a checkbox in the [https://docs.moodle.org/en/Development:Course_completion#Report Completion progress report], which will allow him/her to manually &amp;quot;Mark complete&amp;quot; each applicable student enrolled in the course.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Tracked:&#039;&#039;&#039;&lt;br /&gt;
* The roleid of the user who marked the &amp;quot;student&amp;quot; complete.&lt;br /&gt;
* The userid of the user who marked the &amp;quot;student&amp;quot; complete.&lt;br /&gt;
* The day/time when the user marked the &amp;quot;student&amp;quot; as complete in the course.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Notes:&#039;&#039;&#039;&lt;br /&gt;
# The Completion progress report will respect:&lt;br /&gt;
#* &#039;&#039;&#039;Separate groups:&#039;&#039;&#039; A &amp;quot;teacher&amp;quot; will only be able to see and mark complete students in her/his group(s) if separate groups is enabled.&lt;br /&gt;
#* &#039;&#039;&#039;Role assignments on the USER-level context:&#039;&#039;&#039; A &amp;quot;Tutor&amp;quot; or &amp;quot;Mentor&amp;quot; will only be able to see and mark complete users assigned to him/her.&lt;br /&gt;
# The criteria can be met only once by the role:&lt;br /&gt;
#* If more than one user is assigned to the role and has the capability of marking the user complete&lt;br /&gt;
#* After a &amp;quot;student&amp;quot; has been marked complete one time by a user in the role, the [https://docs.moodle.org/en/Development:Course_completion#Report Completion progress report] will not provide a checkbox in the &amp;quot;Mark complete&amp;quot; column when viewed by any user in the role in the course.&lt;br /&gt;
# We really be relying on role capabilities and permissions for this.&lt;br /&gt;
&lt;br /&gt;
====Activities completed====&lt;br /&gt;
&#039;&#039;&#039;Action:&#039;&#039;&#039; &lt;br /&gt;
* A &amp;quot;student&amp;quot; will meet the criteria when completing the specified activity, per the activity&#039;s &#039;Activity Completion&#039; settings.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Tracked:&#039;&#039;&#039; &lt;br /&gt;
* The cmid, module type, and activity/resource name for each activity.&lt;br /&gt;
* The day/time when the criteria was detected as complete.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039;&lt;br /&gt;
# The list of activities/resources will contain:&lt;br /&gt;
#* Each activity/resource in the course with &amp;quot;Completion tracking&amp;quot; enabled within the activity/resource.&lt;br /&gt;
#* The type of each activity followed by the activity name (i.e., &amp;quot;[mod name] - [activity/resource name]&amp;quot;). See the mockup on this page (or for a basic example: &amp;quot;Quiz - Derivatives&amp;quot;).&lt;br /&gt;
# If &amp;quot;Activity completion&amp;quot; is not enabled for the site, the &amp;quot;Activities completed&amp;quot; field set will not appear on the page.&lt;br /&gt;
# If &amp;quot;Activity completion&amp;quot; is enabled for the site and not enabled for the course, the &amp;quot;Activities completed&amp;quot; field set will appear and it will only contain this message:&lt;br /&gt;
#:&#039;&#039;&amp;quot;Activity completion must be enabled for this course.&amp;quot;&#039;&#039;&lt;br /&gt;
# If &amp;quot;Activity completion&amp;quot; is enabled for the site and course, and there are no activities in the course, the &amp;quot;Activities completed&amp;quot; field set will appear and it will only contain this message:&lt;br /&gt;
#:&#039;&#039;&amp;quot;Currently, there are no activities in this course&amp;quot;&#039;&#039;&lt;br /&gt;
# If &amp;quot;Activity completion&amp;quot; is enabled for the site and course, and there are activities but none have activity completion enabled, the &amp;quot;Activities completed&amp;quot; field set will appear and it will only contain this message:&lt;br /&gt;
#:&#039;&#039;&amp;quot;Currently, none of the activities in this course have defined activity completion settings.&amp;quot;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
====Course grade====&lt;br /&gt;
&#039;&#039;&#039;Action:&#039;&#039;&#039; &lt;br /&gt;
* A &amp;quot;student&amp;quot; will meet the criteria when earning a total course grade above the passing grade.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Tracked:&#039;&#039;&#039; &lt;br /&gt;
* The day/time when the user&#039;s course grade was evaluated as having exceeded the passing grade.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039;&lt;br /&gt;
* The passing grade can also be updated via the course&#039;s &amp;quot;Grade to pass&amp;quot; setting found in the Grader report&#039;s edit page for the top level category for the course.&lt;br /&gt;
* Once a student has completed the course, both the &amp;quot;Passing grade&amp;quot; setting on the course page and the &amp;quot;Grade to pass&amp;quot; setting in the top level category for the course will be locked. A message about locking will appear on the settings page for the top level category for the course.&lt;br /&gt;
&lt;br /&gt;
====Date====&lt;br /&gt;
&#039;&#039;&#039;Action:&#039;&#039;&#039; &lt;br /&gt;
* A &amp;quot;student&amp;quot; enroled in the course will automatically meet the criteria after a specified date.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Tracked:&#039;&#039;&#039; &lt;br /&gt;
* The day/time when specified day was evaluated as having passed.&lt;br /&gt;
&lt;br /&gt;
====Duration after enrolment====&lt;br /&gt;
&#039;&#039;&#039;Action:&#039;&#039;&#039; &lt;br /&gt;
* A &amp;quot;student&amp;quot; will meet the criteria after the specified number of days after his/her enrolment.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Tracked:&#039;&#039;&#039; &lt;br /&gt;
* The day/time when the &amp;quot;student&amp;quot; marked him/herself complete.&lt;br /&gt;
&lt;br /&gt;
====Unenrolment====&lt;br /&gt;
&#039;&#039;&#039;Action:&#039;&#039;&#039; &lt;br /&gt;
* A &amp;quot;student&amp;quot; will meet the criteria when being unenroled from the course.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Tracked:&#039;&#039;&#039;&lt;br /&gt;
* The day/time when the &amp;quot;student&amp;quot; was unenroled from the course.&lt;br /&gt;
&lt;br /&gt;
===Locking===&lt;br /&gt;
When the first student has completed the course, the course completion criteria settings will be locked. The course settings can be unlocked (via an &amp;quot;Unlock course completion options&amp;quot; button), however a warning message will be displayed stating that unlocking the settings requires deleting all prior course completion data for the course. Once the completion data is deleted, the settings become unlocked (i.e., un-greyed out).&lt;br /&gt;
&lt;br /&gt;
This is consistent with activity completion in 2.0, in which activity completion settings are locked when a user first completes an activity. Activity completion settings can be unlocked after all completion activity is deleted.&lt;br /&gt;
&lt;br /&gt;
==Activity settings==&lt;br /&gt;
If activity completion is enabled and activity completion is a criteria for course completion, then all activity completion settings will be locked once a student has completed the course. In this case, unlocking an activity&#039;s completion settings will require deleting both the activity&#039;s completion data and the course&#039;s completion data.&lt;br /&gt;
&lt;br /&gt;
If an activity is being tracked for course completion, we should add a warning/confirmation screen when an &amp;quot;admin&amp;quot; attempts to delete it. The message should say, &amp;quot;[Activity name] is currently a criteria for course completion. Are you sure you want to delete it?&amp;quot; [Delete anyway] / [Cancel]. If the &amp;quot;admin&amp;quot; confirms, the activity will be deleted however completion data for users who completed it will still remain tracked.&lt;br /&gt;
&lt;br /&gt;
Should the activity completion data remain?&lt;br /&gt;
Should the course completion data be re-aggregated?&lt;br /&gt;
&lt;br /&gt;
=Data structures=&lt;br /&gt;
[[Image:Course_completion_data_model.png|thumb|400px|Course completion data model]]&lt;br /&gt;
&lt;br /&gt;
===course===&lt;br /&gt;
The course table will require the following new fields. &#039;enablecompletion&#039; will now be used for activity and course completion:&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;2&amp;quot; cellspacing=&amp;quot;0&amp;quot;&lt;br /&gt;
!Field&lt;br /&gt;
!Type&lt;br /&gt;
!Default &lt;br /&gt;
!Info&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;completionnotify&#039;&#039;&#039;&lt;br /&gt;
|int(1)&lt;br /&gt;
|&lt;br /&gt;
|Notify users when they complete this course&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===course_completion_aggr_meth===&lt;br /&gt;
This new table will be populated with the aggregation method for &amp;quot;Overall criteria&amp;quot;, &amp;quot;Manual completion by&amp;quot;, and &amp;quot;Activities completed&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;2&amp;quot; cellspacing=&amp;quot;0&amp;quot;&lt;br /&gt;
!Field&lt;br /&gt;
!Type&lt;br /&gt;
!Default &lt;br /&gt;
!Info&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;id&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|Auto incrementing &lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;course&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|The id of the course that the course completion aggregation relates to&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;criteriatype&#039;&#039;&#039;&lt;br /&gt;
|int(20)&lt;br /&gt;
|null&lt;br /&gt;
|The criteria type&#039;s integer constant (&#039;role&#039;, &#039;activity&#039;) or null if &#039;overall&#039; course aggregation. &lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;method&#039;&#039;&#039;&lt;br /&gt;
|int(1)&lt;br /&gt;
|1&lt;br /&gt;
|&#039;1&#039;=&#039;all&#039;, &#039;2&#039;=&#039;any&#039;, &#039;3&#039;=&#039;fraction&#039;, &#039;4&#039;=&#039;unit&#039;.&lt;br /&gt;
|-&lt;br /&gt;
&lt;br /&gt;
|&#039;&#039;&#039;value&#039;&#039;&#039;&lt;br /&gt;
|number(10)&lt;br /&gt;
|null&lt;br /&gt;
|null for &#039;all&#039; and &#039;any&#039;, 0..1 for &#039;fraction&#039;, int &amp;gt; 0 for &#039;unit&#039;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===course_completion_criteria===&lt;br /&gt;
This new table will be populated with each criteria in each course for course completion:&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;2&amp;quot; cellspacing=&amp;quot;0&amp;quot;&lt;br /&gt;
!Field&lt;br /&gt;
!Type&lt;br /&gt;
!Default &lt;br /&gt;
!Info&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;id&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|Auto incrementing &lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;course&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|The id of the course that the course completion criteria relates to&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;itemname&#039;&#039;&#039; (not implemented)&lt;br /&gt;
|varchar(100)&lt;br /&gt;
|null&lt;br /&gt;
|The name of the item (e.g. &amp;quot;Mark yourself complete&amp;quot;, &amp;quot;Teacher marks you complete&amp;quot;, &amp;quot;[Activity name]&amp;quot;, &amp;quot;[postenrol] days after enrolment&amp;quot;, &amp;quot;Official Date: [date]&amp;quot;. Used for all item types except unenrol.&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;criteriatype&#039;&#039;&#039;&lt;br /&gt;
|int(20)&lt;br /&gt;
|&lt;br /&gt;
|The criteria type&#039;s integer constant (&#039;self&#039;, &#039;role&#039;, &#039;mod&#039;, &#039;grade&#039;, &#039;enrolperiod&#039;, &#039;date&#039;, &#039;postenrol&#039;).&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;module&#039;&#039;&#039;&lt;br /&gt;
|varchar(100)&lt;br /&gt;
|null&lt;br /&gt;
|The name of the module (e.g., &#039;scorm&#039;, &#039;quiz&#039;, &#039;feedback&#039;). Used only if criteriatype=&#039;mod&#039;.&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;moduleinstance&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|null&lt;br /&gt;
|The id of the activity/resource module or role. Used only if criteriatype=&#039;mod&#039;.&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;enrolperiod&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|null&lt;br /&gt;
|The number of seconds after enrolment. Used only if criteriatype=&#039;enrolperiod&#039;.&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;date&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|null&lt;br /&gt;
|The timestamp of the date for course completion. Used only if criteriatype=&#039;date&#039;.&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;gradepass&#039;&#039;&#039;&lt;br /&gt;
|number(10)&lt;br /&gt;
|null&lt;br /&gt;
|The course grade required to complete this criteria. Used only if criteriatype=&#039;grade&#039;.&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;role&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|null&lt;br /&gt;
|The role id that can mark &#039;student&#039;s as complete in the course. Used only if criteriatype=&#039;role&#039;.&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;lock&#039;&#039;&#039; (currently unused)&lt;br /&gt;
|int(10)&lt;br /&gt;
|null&lt;br /&gt;
|Date this criteria was locked&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===course_completion_crit_compl===&lt;br /&gt;
This new table will be populated with records for each user as they complete each course completion criteria.&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;2&amp;quot; cellspacing=&amp;quot;0&amp;quot;&lt;br /&gt;
!Field&lt;br /&gt;
!Type&lt;br /&gt;
!Default &lt;br /&gt;
!Info&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;id&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|Auto incrementing &lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;userid&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|The id of the user who has completed the course&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;course&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|The id of the completed course&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;criteriaid&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|The id of the completion criteria this references&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;gradefinal&#039;&#039;&#039;&lt;br /&gt;
|number(10)&lt;br /&gt;
|null&lt;br /&gt;
|The final grade for the course (if referenced criteria type=&#039;grade&#039;).&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;unenroled&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|null&lt;br /&gt;
|Timestamp when the user was unenroled (if referenced criteria type=&#039;unenrol&#039;).&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;deleted&#039;&#039;&#039; (currently unused)&lt;br /&gt;
|int(1)&lt;br /&gt;
|null&lt;br /&gt;
|The course was deleted&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;timecompleted&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|null (should be notnull)&lt;br /&gt;
|Timestamp when the user completed the criteria&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===course_completions===&lt;br /&gt;
This table records a user&#039;s course completion state in a course (but not for individual criteria).&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;2&amp;quot; cellspacing=&amp;quot;0&amp;quot;&lt;br /&gt;
!Field&lt;br /&gt;
!Type&lt;br /&gt;
!Default &lt;br /&gt;
!Info&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;id&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|Auto incrementing &lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;userid&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|The id of the user who has completed the course&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;course&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|The id of the completed course&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;timeenrolled&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|Timestamp when the user was enrolled in the course. In the case of multiple enrollments, the earliest timestamp for a current enrollment is used. If this is reported as 0, the current time is used instead.&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;timestarted&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|Timestamp when the user first made progress in the course&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;timecompleted&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|null&lt;br /&gt;
|Timestamp when the user completed the course&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=Evaluating completion=&lt;br /&gt;
Since the gradebook aggregation is performed via cron, it makes sense that course completion is also evaluated by cron.&lt;br /&gt;
&lt;br /&gt;
This creates an issue in which the progress block will not instantly not show course completion after the necessary criteria has been met by a &amp;quot;student&amp;quot;. However, this can be solved by adding some instructional text to say &amp;quot;may take a few minutes to update&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
=Role capabilities=&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;2&amp;quot; cellspacing=&amp;quot;0&amp;quot;&lt;br /&gt;
|&#039;&#039;&#039;Feature&#039;&#039;&#039;&lt;br /&gt;
|&#039;&#039;&#039;Description&#039;&#039;&#039;&lt;br /&gt;
|&#039;&#039;&#039;Capability&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|Progress block&lt;br /&gt;
|Mark self-complete in progress block&lt;br /&gt;
|block/progress:markselfcomplete&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|Progress report&lt;br /&gt;
|View course progress report&lt;br /&gt;
|coursereport/progress:view&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|Progress report&lt;br /&gt;
|Mark users complete in course progress report&lt;br /&gt;
|coursereport/progress:markcomplete&lt;br /&gt;
&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=Course reporting=&lt;br /&gt;
==Completion progress report==&lt;br /&gt;
[[Image:Completion_report.png|thumb|600px|Course completion progress report]]&lt;br /&gt;
&lt;br /&gt;
A course completion report is available when course completion is enabled.The report can be found via on &amp;quot;Reports&amp;quot; menu item link in the course Administration block and when &amp;quot;Completion tracking&amp;quot; is enabled for the site and course.&lt;br /&gt;
&lt;br /&gt;
The report shows:&lt;br /&gt;
* A column for each of the activities/resources in the course with &amp;quot;Completion tracking&amp;quot; enabled&amp;quot;&lt;br /&gt;
* A row for each user enroled in the course&lt;br /&gt;
* The completion status for each activity&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Proposed changes:&#039;&#039;&#039;&lt;br /&gt;
* Include in the report all enabled criteria for course completion (e.g., &amp;quot;Manual self completion&amp;quot;, &amp;quot;Manual completion by&amp;quot;, as well as the activities etc)&lt;br /&gt;
* Each user&#039;s status for completion of each criteria, indicated by a status icon&lt;br /&gt;
* Allow the completion admin page to determine which activities are shown in the report&lt;br /&gt;
* Allow the list of users to be sortable, searchable, and filterable (by group, groupings, and/or user profile fields).&lt;br /&gt;
* Allow user profile fields (standard and custom) can be added/removed as columns in the list.&lt;br /&gt;
* When rolling over the each icon, additional details (including the completion date) will display.&lt;br /&gt;
&lt;br /&gt;
See the mockup to the right with all course completion criteria appearing (i.e., all criteria types have been enabled in a course).&lt;br /&gt;
&lt;br /&gt;
As described further above, display rules for separate groups will apply. If separate groups is enabled for the course, a &amp;quot;teacher&amp;quot; will only see students and completion data for users in the group(s) that she/he is assigned to.&lt;br /&gt;
&lt;br /&gt;
Options for exporting completion data to CSV, ODF or Excel will be available. Completion dates will be included in the export.&lt;br /&gt;
&lt;br /&gt;
A column labeled &amp;quot;Mark complete&amp;quot; will appear containing a checkbox for each student if the user viewing the report is assigned to a role that is allowed to manually indicate that the students have completed the course.&lt;br /&gt;
&lt;br /&gt;
Not shown in the mockup, however we should display the specified values under the applicable heading in the report for each of the following feilds:&lt;br /&gt;
* Period after enrolment&lt;br /&gt;
* Passing grade&lt;br /&gt;
* Date (for completion)&lt;br /&gt;
&lt;br /&gt;
==Gradebook==&lt;br /&gt;
# If &amp;quot;Passing grade&amp;quot; is enabled as a criteria for course completion, a grader should have the option of seeing completion status (i.e., &amp;quot;complete&amp;quot; / &amp;quot;not complete&amp;quot;) as a column in the grader report.&lt;br /&gt;
# If a user assigned to a role that is enabled for &amp;quot;Manual completion by&amp;quot;, the user should have the option of manually indicating which students have completed the course, a &amp;quot;Mark complete&amp;quot; column will appear to the right of the &amp;quot;Course total&amp;quot; column with a checkbox for each student who has not yet completed the course.&lt;br /&gt;
&lt;br /&gt;
We should also provide an option to show course completion status in gradebook exports. Field options will include:&lt;br /&gt;
* Completion status (e.g., &amp;quot;Complete&amp;quot;, &amp;quot;Not complete&amp;quot;)&lt;br /&gt;
* Completion date&lt;br /&gt;
&lt;br /&gt;
==Completion progress block==&lt;br /&gt;
[[Image:Course_completion_progress_block.png|thumb|190px|Course completion progress block]]&lt;br /&gt;
&lt;br /&gt;
If completion is enabled for the site and a course, a completion block will be available to be added to the course.&lt;br /&gt;
&lt;br /&gt;
The block will allow a &amp;quot;student&amp;quot; to see:&lt;br /&gt;
* The completion criteria for completing the course&lt;br /&gt;
* His/her status for completion of each criteria (similar to the Completion progress report)&lt;br /&gt;
* His/her overall completion status for the course (complete / not complete)&lt;br /&gt;
&lt;br /&gt;
If all of the enabled criteria are required, the introduction text should say:&lt;br /&gt;
:&#039;&#039;&amp;quot;All of the criteria below needed&amp;quot;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If any of the enabled criteria are required, the introduction text should say:&lt;br /&gt;
:&#039;&#039;&amp;quot;Any of the criteria below needed&amp;quot;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
If the current user can self complete the course, the block will provide an &amp;quot;I&#039;ve completed this course&amp;quot; button (if the user has not already marked him/herself complete).&lt;br /&gt;
&lt;br /&gt;
Activity completion criteria/status (if enabled for course completion) should be included in the block. See [http://tracker.moodle.org/browse/MDL-16466 MDL-16466] (&amp;quot;Activities on the course page do not convey to student that completion is expected or available for the activity&amp;quot;).&lt;br /&gt;
&lt;br /&gt;
==User (course) profile==&lt;br /&gt;
Activity and course completion criteria/status data should appear in a user&#039;s &amp;quot;Outline report&amp;quot; and &amp;quot;Complete report&amp;quot; in the user profile area, which is being reworked for 2.0 separately from this spec.&lt;br /&gt;
&lt;br /&gt;
=Site reporting=&lt;br /&gt;
&lt;br /&gt;
==User (site) profile==&lt;br /&gt;
[[Image:Course_completion_user_profile_activity.png|thumb|400px|Mockup of the user profile page with Course completion report]]&lt;br /&gt;
&lt;br /&gt;
Activity and course completion criteria/status data should appear in a user&#039;s profile area.&lt;br /&gt;
&lt;br /&gt;
It would make sense for courses to be grouped by:&lt;br /&gt;
* Courses in progress&lt;br /&gt;
* Courses completed&lt;br /&gt;
* Courses not yet started&lt;br /&gt;
&lt;br /&gt;
For each course, the required criteria and status should appear.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=Cron=&lt;br /&gt;
A cron function will check the criteria and update the course_completion_completions table as necessary. Here&#039;s a breakdown of the tasks for each criteria, and will need to be optimised for performance and scalability across all courses:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Manual self completion:&#039;&#039;&#039; Cron not needed, performed directly from the Completion progress block.&lt;br /&gt;
* &#039;&#039;&#039;Manual completion by:&#039;&#039;&#039; Cron not needed, performed directly via the Completion progress report.&lt;br /&gt;
* &#039;&#039;&#039;Course grade:&#039;&#039;&#039; Compare the course grade to the passing grade.&lt;br /&gt;
* &#039;&#039;&#039;Unenrol:&#039;&#039;&#039; Compare course_completion-&amp;gt;unenroled to role_assignments-&amp;gt;timeend for the &amp;quot;student&amp;quot; role in the course.&lt;br /&gt;
* &#039;&#039;&#039;Date:&#039;&#039;&#039; Compare the current date to the specified date.&lt;br /&gt;
* &#039;&#039;&#039;Duration after enrolment:&#039;&#039;&#039; Compare the current date to the role_assignments-&amp;gt;timestart + the specified period after enrolment.&lt;br /&gt;
* &#039;&#039;&#039;Activity completion:&#039;&#039;&#039; Evaluate the necessary records in the course_modules_completion table.&lt;br /&gt;
* &#039;&#039;&#039;Course completion:&#039;&#039;&#039; Aggregate the necessary fields in course_completion table, updating the timecompleted field with the current timestamp if the user has met the necessary criteria.&lt;br /&gt;
&lt;br /&gt;
=Backup and restore=&lt;br /&gt;
* The new completion fields in the course table will be included in the backup.&lt;br /&gt;
* Entries in the course_completion_items, course_completion_aggregation_methods, and course_completion_notify tables for the course should also be included in the backup.&lt;br /&gt;
* When user data is included in a course backup, the backup will also include records in the course_completion_completions table for the course.&lt;br /&gt;
&lt;br /&gt;
=Events=&lt;br /&gt;
The following course completion related events have been added to core:&lt;br /&gt;
* course_completed&lt;br /&gt;
** Triggered when a user completes a course. Event data is the full course_completions database record.&lt;br /&gt;
** Added in version 2.3.2&lt;br /&gt;
&lt;br /&gt;
[[Category:Completion]]&lt;/div&gt;</summary>
		<author><name>Davidbalch</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Customising_the_theme_user_menu&amp;diff=48976</id>
		<title>Customising the theme user menu</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Customising_the_theme_user_menu&amp;diff=48976"/>
		<updated>2015-11-12T14:54:14Z</updated>

		<summary type="html">&lt;p&gt;Davidbalch: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:Themes}}{{Moodle 3.0}}&lt;br /&gt;
&lt;br /&gt;
As of 3.0, the [https://docs.moodle.org/29/en/Theme_settings#User_menu_items user menu] can be customised via an array of options, when it is called via &amp;lt;code&amp;gt;user_get_user_navigation_info()&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Initially, only the size of the user&#039;s avatar picture can be changed (from the default of 35px square), using the &amp;lt;code&amp;gt;avatarsize&amp;lt;/code&amp;gt; key with a new size.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
user_get_user_navigation_info($user, $this-&amp;gt;page, array(&#039;avatarsize&#039; =&amp;gt; 85));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Unfortunately, to  change the one line that calls &amp;lt;code&amp;gt;user_get_user_navigation_info()&amp;lt;/code&amp;gt;, the whole of &amp;lt;code&amp;gt;core_renderer::user_menu()&amp;lt;/code&amp;gt; must be overridden:&lt;br /&gt;
&lt;br /&gt;
In &#039;&#039;&#039;/themes/&amp;lt;yourtheme&amp;gt;/renderers.lib&#039;&#039;&#039;...&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function user_menu($user = null, $withlinks = null, $loginlogo = null) {&lt;br /&gt;
&lt;br /&gt;
    // Lots of code copied from lib/outputrenderers.php...&lt;br /&gt;
&lt;br /&gt;
    // Get some navigation opts.&lt;br /&gt;
    $opts = user_get_user_navigation_info($user, $this-&amp;gt;page, array(&#039;avatarsize&#039; =&amp;gt; 85));&lt;br /&gt;
&lt;br /&gt;
    $avatarclasses = &amp;quot;avatars&amp;quot;;&lt;br /&gt;
    $avatarcontents = html_writer::span($opts-&amp;gt;metadata[&#039;useravatar&#039;], &#039;avatar current&#039;);&lt;br /&gt;
    $usertextcontents = $opts-&amp;gt;metadata[&#039;userfullname&#039;];&lt;br /&gt;
&lt;br /&gt;
    // Lots more code copied from lib/outputrenderers.php...&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
NB: The image size in the browser may actually be set via CSS, as the pixel size of the image is rounded up to one of a few hard-coded sizes - see MDL-50420.&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
* MDL-50839 - Allow themes to set User menu avatar size&lt;br /&gt;
* [[Overriding a renderer]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Themes]]&lt;br /&gt;
[[es:Personalizando el menú de usuario del tema]]&lt;/div&gt;</summary>
		<author><name>Davidbalch</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_3.0_release_notes&amp;diff=48881</id>
		<title>Moodle 3.0 release notes</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_3.0_release_notes&amp;diff=48881"/>
		<updated>2015-10-30T10:27:18Z</updated>

		<summary type="html">&lt;p&gt;Davidbalch: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Work in progress}}&lt;br /&gt;
&lt;br /&gt;
[[Releases]] &amp;gt; {{FULLPAGENAME}}&lt;br /&gt;
&lt;br /&gt;
Release date: Monday, 9th November 2015&lt;br /&gt;
 &lt;br /&gt;
Here is [https://tracker.moodle.org/secure/IssueNavigator!executeAdvanced.jspa?jqlQuery=project+%3D+mdl+AND+resolution+%3D+fixed+AND+fixVersion+in+%28%223.0%22%29+ORDER+BY+priority+DESC&amp;amp;runQuery=true&amp;amp;clear=true the full list of fixed issues in 3.0].&lt;br /&gt;
&lt;br /&gt;
See our [https://docs.moodle.org/30/en/New_features New Features page] for a more user-friendly introduction to Moodle 3.0 with screenshots.&lt;br /&gt;
&lt;br /&gt;
If you are upgrading from previous version, make sure to read the [https://docs.moodle.org/30/en/Upgrading Upgrading] documentation. &lt;br /&gt;
&lt;br /&gt;
==Server requirements==&lt;br /&gt;
&lt;br /&gt;
These are just the minimum supported versions. We recommend keeping all of your software up-to-date.&lt;br /&gt;
&lt;br /&gt;
* Moodle upgrade:  Moodle 2.2 or later (if upgrading from earlier versions, you must upgrade to 2.2.11 as a first step)&lt;br /&gt;
* PHP version: minimum PHP 5.4.4 (always use latest PHP 5.4.x or 5.5.x on Windows - http://windows.php.net/download/), PHP 7 is NOT supported&lt;br /&gt;
* Ghostscript should be installed for pdf annotation.&lt;br /&gt;
&lt;br /&gt;
=== Database requirements ===&lt;br /&gt;
&lt;br /&gt;
Moodle supports the following database servers. Again, version numbers are just the minimum supported version. We recommend running the latest stable version of any software.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Database&lt;br /&gt;
! Minimum version&lt;br /&gt;
! Recommended&lt;br /&gt;
|-&lt;br /&gt;
| [http://www.postgresql.org/ PostgreSQL]&lt;br /&gt;
| 9.1&lt;br /&gt;
| Latest&lt;br /&gt;
|-&lt;br /&gt;
| [http://www.mysql.com/ MySQL]&lt;br /&gt;
| 5.5.31&lt;br /&gt;
| Latest&lt;br /&gt;
|-&lt;br /&gt;
| [https://mariadb.org/ MariaDB]&lt;br /&gt;
| 5.5.31&lt;br /&gt;
| Latest&lt;br /&gt;
|-&lt;br /&gt;
| [http://www.microsoft.com/en-us/server-cloud/products/sql-server/ Microsoft SQL Server]&lt;br /&gt;
| 2008&lt;br /&gt;
| Latest&lt;br /&gt;
|-&lt;br /&gt;
| [http://www.oracle.com/us/products/database/overview/index.html Oracle Database]&lt;br /&gt;
| 10.2&lt;br /&gt;
| Latest&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Client requirements==&lt;br /&gt;
&lt;br /&gt;
=== Browser support ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Browser&lt;br /&gt;
! Minimum version&lt;br /&gt;
! Recommended version&lt;br /&gt;
! Notes&lt;br /&gt;
|-&lt;br /&gt;
| [https://www.google.com/intl/en_au/chrome/browser/ Google Chrome]&lt;br /&gt;
| 30.0&lt;br /&gt;
| Latest&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| [http://www.mozilla.org/en-US/ Mozilla Firefox]&lt;br /&gt;
| 25.0&lt;br /&gt;
| Latest&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| [http://www.apple.com/safari/ Apple Safari]&lt;br /&gt;
| 6&lt;br /&gt;
| Latest&lt;br /&gt;
|&lt;br /&gt;
|-&lt;br /&gt;
| [http://windows.microsoft.com/en-AU/internet-explorer/download-ie Microsoft Internet Explorer]&lt;br /&gt;
| 9&lt;br /&gt;
| Latest&lt;br /&gt;
| Version 10 is required for drag-and-drop upload of content from outside the browser into Moodle&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Major features==&lt;br /&gt;
&lt;br /&gt;
===Highlights===&lt;br /&gt;
&lt;br /&gt;
* MDL-29801 - Allow users to delete personal messages&lt;br /&gt;
* MDL-27177 - Allow students to see each other&#039;s contact details in full profile without global permission if they are able to see each other&#039;s course profile&lt;br /&gt;
* MDL-46455 - Re-introduce backup/restore functionality with new logging system&lt;br /&gt;
* MDL-46878 - Reset Dashboard for all users after changing the default dashboard&lt;br /&gt;
&lt;br /&gt;
===Quiz===&lt;br /&gt;
&lt;br /&gt;
* MDL-47494 - New question types from the OU - [https://docs.moodle.org/30/en/Select_missing_words_question_type Select missing words], [https://docs.moodle.org/30/en/Drag_and_drop_into_text_question_type Drag and drop into text], [https://docs.moodle.org/30/en/Drag_and_drop_onto_image_question_type Drag and drop onto image] and [https://docs.moodle.org/30/en/Drag_and_drop_markers_question_type Drag and drop markers]&lt;br /&gt;
* MDL-38214 - Add MCS, MCVS, MCHS Cloze subquestion types&lt;br /&gt;
* MDL-50127 - By default the list of question types should be sorted with common ones at the top&lt;br /&gt;
* MDL-29771 - Interactive behaviour should show number of tries left in the Try again state&lt;br /&gt;
&lt;br /&gt;
===Forum===&lt;br /&gt;
&lt;br /&gt;
* MDL-49682 - Make forum email template editable&lt;br /&gt;
* MDL-46321 - Uninformative error when moving forum without first selecting destination&lt;br /&gt;
* MDL-50993 - Timed discussions are now displayed to students in a logical order&lt;br /&gt;
* MDL-50430 - Add number of subscribers to forum subscriber list&lt;br /&gt;
&lt;br /&gt;
===Assignment===&lt;br /&gt;
&lt;br /&gt;
* MDL-49176 - Assigment marking guide &#039;flattens&#039; instructions for markers and students&lt;br /&gt;
* MDL-49515 - Upgrade FPDI library in assignfeedback_editpdf to 1.5.4 to fix problems with PDF annotator on some files&lt;br /&gt;
* MDL-50283 - Improve Rubric interface to include the ability to duplicate rows&lt;br /&gt;
&lt;br /&gt;
===Other activity modules===&lt;br /&gt;
&lt;br /&gt;
* MDL-49028 - Wiki: Option to delete pages during course reset&lt;br /&gt;
* MDL-40836 - File resource: New [https://docs.moodle.org/30/en/File_resource_settings file resource setting] option to display upload/modified date&lt;br /&gt;
* MDL-26501 - Glossary: Do not allow to browse by author if author is never displayed&lt;br /&gt;
* MDL-50673 - Workshop: display all participants on &amp;quot;Submisstion phase&amp;quot; page in a table&lt;br /&gt;
* MDL-50664 - Database: add setting to disallow managing of own entries after approval&lt;br /&gt;
* MDL-50658 - LTI: Add support for LTI Memberships service&lt;br /&gt;
* MDL-49581 - Lesson: Remove high scores list feature&lt;br /&gt;
* MDL-49882 - Lesson: Essay questions are not imported into the lesson Module&lt;br /&gt;
* MDL-50720 - Database: Highlight database entries that are not yet approved.&lt;br /&gt;
&lt;br /&gt;
===User interface and usability improvements===&lt;br /&gt;
&lt;br /&gt;
* MDL-51051 - Rename &#039;Categories and items&#039; to &#039;Gradebook setup&#039; and add link&lt;br /&gt;
* MDL-51250 - Show default section name when editing section details with default checkbox being checked&lt;br /&gt;
* MDL-49984 - Add visual blocks outlines to My profile page to help separate information&lt;br /&gt;
* MDL-48947 - Collect all course section editing buttons under one &amp;quot;Edit&amp;quot; dropdown&lt;br /&gt;
* MDL-51087 - Use client-side validation in the signup form&lt;br /&gt;
* MDL-50113 - Improve display of long user and course names in Messaging&lt;br /&gt;
* MDL-50919 - Simplify the Manage tags page, allow to quickly change name, flag and official status of the tags&lt;br /&gt;
* MDL-51013 - Navbar button should appear for smaller screens only when the custom menu or language menu is not empty&lt;br /&gt;
* MDL-51260 - Use the new autocomplete form field for tags&lt;br /&gt;
* MDL-51296 - Add title to page when adding blog post&lt;br /&gt;
* MDL-38763 - Permission override UI should use JS confirmation&lt;br /&gt;
* MDL-29763 - Add description to Portfolio settings page&lt;br /&gt;
&lt;br /&gt;
===Atto editor===&lt;br /&gt;
&lt;br /&gt;
* MDL-45515 - New table editing features in Atto editor&lt;br /&gt;
* MDL-49732 - Keyboard interaction for hyperlink in Atto (Ctrl+K)&lt;br /&gt;
* MDL-50936 - More Atto Math Equation buttons (sum, sqrt, int, etc.)&lt;br /&gt;
* MDL-50142 - Text editor preferences help pop-up&lt;br /&gt;
&lt;br /&gt;
===Enrolments===&lt;br /&gt;
&lt;br /&gt;
* MDL-30674 - Set guest access key from enrolment methods page&lt;br /&gt;
* MDL-30157 - Allow users to start manual enrolments right now&lt;br /&gt;
* MDL-49746 - Allow to sort enrolled users page by last course access&lt;br /&gt;
* MDL-48074 - Group filter in enrolments list should have option &amp;quot;not in any group&amp;quot;&lt;br /&gt;
&lt;br /&gt;
===Administration===&lt;br /&gt;
&lt;br /&gt;
* MDL-49329 - Multiple improvements in the plugins installation/update system including ability to install several plugins at the same time&lt;br /&gt;
* MDL-49280 - New configuration setting to allow duplicate email addresses&lt;br /&gt;
* MDL-51330 - Show scheduled tasks component in the cron log&lt;br /&gt;
* MDL-51261 - Upgrade key - mechanism to protect anonymous web access to upgrade screens&lt;br /&gt;
* MDL-50602 - New settings in [https://docs.moodle.org/30/en/Automated_course_backup Automated backup setup] for deleting older backups and keeping a minimum number of backups&lt;br /&gt;
* MDL-48438 - Add real name to email about login failures&lt;br /&gt;
* MDL-30960 - New option in email settings to specify SSL or TLS (SMTPSecure property of PHPMailer)&lt;br /&gt;
* MDL-46623, MDL-51824 - CAS and LDAP: Replace CLI script to synchronise users with a scheduled tasks&lt;br /&gt;
* MDL-39319 - Allow administrator to uninstall several languages in one single action&lt;br /&gt;
* MDL-50155 - Move and rename &amp;quot;Common activities settings&amp;quot; link to be under &amp;quot;Manage activities&amp;quot; for consistency&lt;br /&gt;
* MDL-50631 - Display Moodle ASCII logo in CLI installer&lt;br /&gt;
* MDL-46167 - New option for CLI installation: skip database&lt;br /&gt;
* MDL-50572 - Disable Youtube repository by default since it requires setting up&lt;br /&gt;
* MDL-51739 - Lock theme selector UI when $CFG-&amp;gt;theme is hardcoded in config.php&lt;br /&gt;
* MDL-51478 - Enable Mobile services by default&lt;br /&gt;
* MDL-19748 - Do not allow to edit tags in the default authenticated user role&lt;br /&gt;
* MDL-46398 - Make HTML5 video the default player for capable videos&lt;br /&gt;
&lt;br /&gt;
===Other improvements===&lt;br /&gt;
&lt;br /&gt;
* MDL-51132 - Introduce course tagging as a replacement for user-course-tagging in the &amp;quot;Tags&amp;quot; block. See [https://docs.moodle.org/30/en/Tags_block#Course_tagging_changes_in_Moodle_3.0 upgrade documentation]&lt;br /&gt;
* MDL-41042 - Course contacts shown in course listings no longer lag by an hour&lt;br /&gt;
* MDL-44273 - Back-off strategy for RSS feeds &lt;br /&gt;
* MDL-45981 - CAS Auth Config needs way to specify that curl should use SSLv3.&lt;br /&gt;
* MDL-49891 - Add description meta to frontpage&lt;br /&gt;
* MDL-25451 - Go straight to &amp;quot;Permissions&amp;quot; from block context menu instead of &amp;quot;Assign roles&amp;quot; if theya are not available&lt;br /&gt;
* MDL-50647 - Add &#039;not in group&#039; section to group overview page&lt;br /&gt;
* MDL-50956 - Allow main menu block to be displayed &amp;quot;throughout the entire site&amp;quot;&lt;br /&gt;
* MDL-28954 - Allow images and embedded files in the cohort descriptions&lt;br /&gt;
* MDL-50371 - Use $CFG-&amp;gt;gradepointdefault for new manual gradeitems and grade categories&lt;br /&gt;
&lt;br /&gt;
==Security issues==&lt;br /&gt;
&lt;br /&gt;
A number of security related issues were resolved. Details of these issues will be released after a period of approximately one week to allow system administrators to safely update to the latest version.&lt;br /&gt;
&lt;br /&gt;
==For developers==&lt;br /&gt;
&lt;br /&gt;
* MDL-46455 - Events must define fields mappings in order to be correctly restored ([https://docs.moodle.org/dev/Event_2#Backup.2Frestore documentation])&lt;br /&gt;
* MDL-50125 - Allow all plugins to inject links in the preferences page&lt;br /&gt;
* MDL-51247 - Revive / refresh / rebuild the autocomplete mform element.&lt;br /&gt;
* MDL-50839 - Allow themes to set User menu avatar size ([https://docs.moodle.org/dev/Customising_the_theme_user_menu documentation])&lt;br /&gt;
* MDL-48494 - Make $plugin-&amp;gt;component required for all plugins&lt;br /&gt;
* MDL-43896 - Drop support for $module in version.php files for Moodle 3.0&lt;br /&gt;
* MDL-50645 - Cache the list of available callbacks per plugin&lt;br /&gt;
* MDL-33564 - rss_error() should return a proper HTTP response code&lt;br /&gt;
* MDL-37864 - New method to add help icons to the sortable table headers ([https://docs.moodle.org/dev/lib/tablelib.php documentation])&lt;br /&gt;
* MDL-51737 - Add ability to detect MS Edge in our browser sniffing code&lt;br /&gt;
* MDL-51213 - external_format_text should be safe to call from web or webservice ([https://docs.moodle.org/dev/How_to_contribute_a_web_service_function_to_core documentation])&lt;br /&gt;
* MDL-51413 - Add an additional return field in get_forums_by_courses in order to specify if the current user can create discussions&lt;br /&gt;
* MDL-51217 - Using recaptcha is not possible outside auth_email plugin.&lt;br /&gt;
* MDL-51107 - Add a callback to inject nodes in the category settings navigation&lt;br /&gt;
* MDL-50891 - is_web_crawler should be moved to useragent class&lt;br /&gt;
* MDL-50453 - Replace reserved word usage from \core\progress\null (PHP7)&lt;br /&gt;
* MDL-50009 - Prevent scheduled tasks from leaving unfinished db transactions&lt;br /&gt;
* MDL-49821 - Some Web Services miss checks for guest and deleted users&lt;br /&gt;
* MDL-50926 - Upgrade to phpunit 4.x&lt;br /&gt;
* MDL-50491 - New format_text option to exclude particular filters&lt;br /&gt;
* MDL-50783 - Allow some ajax external functions to be called without a session&lt;br /&gt;
* MDL-50150 - Add &amp;quot;Blocks&amp;quot; feature to JS and PHP mustache engines ([https://docs.moodle.org/dev/Templates#Blocks_.28Moodle_3.0_onwards.29 documentation])&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Release notes]]&lt;br /&gt;
[[Category:Moodle 3.0]]&lt;br /&gt;
&lt;br /&gt;
[[fr:Notes de mise à jour de Moodle 3.0]]&lt;br /&gt;
[[es:Notas de Moodle 3.0]]&lt;/div&gt;</summary>
		<author><name>Davidbalch</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Customising_the_theme_user_menu&amp;diff=48880</id>
		<title>Customising the theme user menu</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Customising_the_theme_user_menu&amp;diff=48880"/>
		<updated>2015-10-30T10:24:47Z</updated>

		<summary type="html">&lt;p&gt;Davidbalch: Add page for MDL-50839&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Template:Themes}}{{Moodle 3.0}}&lt;br /&gt;
&lt;br /&gt;
As of 3.0, the [https://docs.moodle.org/29/en/Theme_settings#User_menu_items user menu] can be customised via an array of options, when it is called via &amp;lt;code&amp;gt;user_get_user_navigation_info()&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Initially, only the size of the user&#039;s avatar picture can be changed (from the default of 35px square), using the &amp;lt;code&amp;gt;avatarsize&amp;lt;/code&amp;gt; key with a new size.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
user_get_user_navigation_info($user, $this-&amp;gt;page, array(&#039;avatarsize&#039; =&amp;gt; 85));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Unfortunately, to  change the one line that calls &amp;lt;code&amp;gt;user_get_user_navigation_info()&amp;lt;/code&amp;gt;, the whole of &amp;lt;code&amp;gt;core_renderer::user_menu()&amp;lt;/code&amp;gt; must be overridden:&lt;br /&gt;
&lt;br /&gt;
In &#039;&#039;&#039;/themes/&amp;lt;yourtheme&amp;gt;/renderers.lib&#039;&#039;&#039;...&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function user_menu($user = null, $withlinks = null, $loginlogo = null) {&lt;br /&gt;
&lt;br /&gt;
    // Lots of code copied from lib/outputrenderers.php...&lt;br /&gt;
&lt;br /&gt;
    // Get some navigation opts.&lt;br /&gt;
    $opts = user_get_user_navigation_info($user, $this-&amp;gt;page, array(&#039;avatarsize&#039; =&amp;gt; 85));&lt;br /&gt;
&lt;br /&gt;
    $avatarclasses = &amp;quot;avatars&amp;quot;;&lt;br /&gt;
    $avatarcontents = html_writer::span($opts-&amp;gt;metadata[&#039;useravatar&#039;], &#039;avatar current&#039;);&lt;br /&gt;
    $usertextcontents = $opts-&amp;gt;metadata[&#039;userfullname&#039;];&lt;br /&gt;
&lt;br /&gt;
    // Lots more code copied from lib/outputrenderers.php...&lt;br /&gt;
&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
NB: The image size in the browser may be enforced via CSS, with the pixel size of the image rounded up to one of a few hard-coded sizes - see MDL-50420.&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
* MDL-50839 - Allow themes to set User menu avatar size&lt;br /&gt;
* [[Overriding a renderer]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Themes]]&lt;/div&gt;</summary>
		<author><name>Davidbalch</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=HTML5_player&amp;diff=47489</id>
		<title>HTML5 player</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=HTML5_player&amp;diff=47489"/>
		<updated>2015-03-10T11:41:35Z</updated>

		<summary type="html">&lt;p&gt;Davidbalch: Fix misaligned columns in license row&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Work in progress}}&lt;br /&gt;
{{Infobox Project&lt;br /&gt;
|name = HTML5 Player with flash fallback&lt;br /&gt;
|state = In Development&lt;br /&gt;
|tracker = https://tracker.moodle.org/browse/MDL-38158&lt;br /&gt;
|discussion = https://moodle.org/mod/forum/discuss.php?d=222753#p1110024&lt;br /&gt;
|assignee = [https://tracker.moodle.org/secure/ViewProfile.jspa?name=kabalin Ruslan Kabalin]&lt;br /&gt;
}}&lt;br /&gt;
{{Moodle 2.x}}&lt;br /&gt;
&lt;br /&gt;
== Project goals ==&lt;br /&gt;
&lt;br /&gt;
The purpose of this document is to choose HTML5 player with flash fallback for integration in Moodle and plan its implementation.&lt;br /&gt;
&lt;br /&gt;
The aim of this change is to reach a consistent look of the player among different platforms and browsers, as well as making sure that player starts using flash if html5 is not supported by browser for the given format. Another aim is to improve accessibility and usability, enhance customisation.&lt;br /&gt;
&lt;br /&gt;
== Considerations ==&lt;br /&gt;
&lt;br /&gt;
* Usability of the player - is it easy to use it and intuitively understand how to use controls?&lt;br /&gt;
* Accessibility of the player - possibility of keyboard navigation, subtitles, support?&lt;br /&gt;
* Appearance of the player - can its interface be modified, themes support, RTL?&lt;br /&gt;
* Consistency among browsers - does player look the same?&lt;br /&gt;
* Audio support - can player handle HTML5 &amp;lt;audio&amp;gt; tag?&lt;br /&gt;
* Mobile platforms support - does player also work on iOS and Android?&lt;br /&gt;
* Streaming media support (including adaptive-bitrate delivery) - can player handle RTMP, Apple HLS, Adobe HDS, Microsoft HSS?&lt;br /&gt;
* Plugins support - can we use or develop community plugins easily?&lt;br /&gt;
* Embedding support - so users could post media content outside Moodle, if permitted.&lt;br /&gt;
* Maintainability of the links between the player and Moodle - how much work will we need to do every time we upgrade?&lt;br /&gt;
&lt;br /&gt;
== Candidates ==&lt;br /&gt;
&lt;br /&gt;
* [http://flowplayer.org/ Flowplayer HTML5]&lt;br /&gt;
* [http://www.videojs.com/ Video.JS]&lt;br /&gt;
* [http://www.projekktor.com/ Projekktor]&lt;br /&gt;
* [http://mediaelementjs.com/ MediaElement.js]&lt;br /&gt;
* ?Are there other alternatives?&lt;br /&gt;
&lt;br /&gt;
== Comparison Matrix ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
! [[#Flowplayer_HTML5|Flowplayer HTML5]]&lt;br /&gt;
! [[#Video.JS|Video.JS]]&lt;br /&gt;
! [[#Projekktor|Projekktor]]&lt;br /&gt;
! [[#MediaElement.js|MediaElement.js]]&lt;br /&gt;
! No Player&lt;br /&gt;
|-&lt;br /&gt;
! Accessible Player:&lt;br /&gt;
| colspan=&amp;quot;4&amp;quot;|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Keyboard control&lt;br /&gt;
| Yes&lt;br /&gt;
| No&lt;br /&gt;
| Yes (configurable)&lt;br /&gt;
| Yes&lt;br /&gt;
| unknown&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Subtitles support&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes (via plugin)&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes (see http://caniuse.com/webvtt)&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Touch interface&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
! Appearance:&lt;br /&gt;
| colspan=&amp;quot;4&amp;quot;|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Skins&lt;br /&gt;
| Yes (via CSS)&lt;br /&gt;
| Yes (via CSS)&lt;br /&gt;
| Yes (via CSS)&lt;br /&gt;
| Yes (via CSS)&lt;br /&gt;
| No&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* CSS customisation&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| unknown&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* RTL support&lt;br /&gt;
| Yes&lt;br /&gt;
| No&lt;br /&gt;
| No&lt;br /&gt;
| Unknown (CSS, so I guess so...)&lt;br /&gt;
| Unknown&lt;br /&gt;
|-&lt;br /&gt;
! Mobile platforms:&lt;br /&gt;
| colspan=&amp;quot;4&amp;quot;|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* iOS&lt;br /&gt;
| Yes&lt;br /&gt;
| No&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Android&lt;br /&gt;
| Yes&lt;br /&gt;
| No&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
! Streaming protocols:&lt;br /&gt;
| colspan=&amp;quot;4&amp;quot;|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* RTMP&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes (via playlist)&lt;br /&gt;
| Yes&lt;br /&gt;
| No&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Adaptive HTTP&lt;br /&gt;
| HLS&lt;br /&gt;
| HLS (via plugin)&lt;br /&gt;
| HLS (via playlist)&lt;br /&gt;
| HLS&lt;br /&gt;
| Only in Chrome so far (Media Source Extension)&lt;br /&gt;
|-&lt;br /&gt;
! Audio player&lt;br /&gt;
| No&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
! style=&amp;quot;border-bottom: 2px solid;&amp;quot; | Embedding support&lt;br /&gt;
| style=&amp;quot;border-bottom: 2px solid;&amp;quot; | Yes (configurable)&lt;br /&gt;
| style=&amp;quot;border-bottom: 2px solid;&amp;quot; | Yes (via addThis plugin)&lt;br /&gt;
| style=&amp;quot;border-bottom: 2px solid;&amp;quot; | Yes (via plugin)&lt;br /&gt;
| style=&amp;quot;border-bottom: 2px solid;&amp;quot; | Yes&lt;br /&gt;
| style=&amp;quot;border-bottom: 2px solid;&amp;quot; | No&lt;br /&gt;
|-&lt;br /&gt;
! License&lt;br /&gt;
| GPL v3 (with additional terms)&lt;br /&gt;
| Apache 2&lt;br /&gt;
| GPL v3&lt;br /&gt;
| MIT&lt;br /&gt;
| no extra code needed&lt;br /&gt;
|-&lt;br /&gt;
! JS library&lt;br /&gt;
| jQuery&lt;br /&gt;
| -&lt;br /&gt;
| jQuery&lt;br /&gt;
| jQuery&lt;br /&gt;
| none required&lt;br /&gt;
|-&lt;br /&gt;
! Plugins infrastructure&lt;br /&gt;
| No&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| No&lt;br /&gt;
|-&lt;br /&gt;
! Flash fallback player&lt;br /&gt;
| Flowplayer Flash&lt;br /&gt;
| video-js flash&lt;br /&gt;
| StobeMediaPlayback (but can be jarisplayer or some other).&lt;br /&gt;
| Flash and Silverlight (for .wmv support) fallback, used to provide the HTML5 video/audio elements.&lt;br /&gt;
| The Video tag can contain fallback content (e.g. Flash object) for browsers that don&#039;t support it at all (http://camendesign.com/code/video_for_everybody), but not based on codec support.&lt;br /&gt;
|-&lt;br /&gt;
! API&lt;br /&gt;
| Yes (plus window.flowplayer object)&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Flowplayer HTML5 ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Pros:&#039;&#039;&#039;&lt;br /&gt;
* Good looking player, resembles existing flash flowplayer in Moodle (with &amp;quot;functional&amp;quot; skin)&lt;br /&gt;
* Extensive customisation (via CSS3 skins and configuration object)&lt;br /&gt;
* Streaming support (RTMP, HLS)&lt;br /&gt;
* Good accessibility features&lt;br /&gt;
* Configurable embedding functionality&lt;br /&gt;
* Existing flash flowplayer can be configured for fallback (the appearance of player will not be affected).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Cons:&#039;&#039;&#039;&lt;br /&gt;
* Require jQuery for functioning&lt;br /&gt;
* Flowplayer logo remains on screen&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Technical notes (lessons learned from the prototypes)&#039;&#039;&#039;&lt;br /&gt;
* Player initialisation is done using wrapper around &amp;lt;video&amp;gt;.&lt;br /&gt;
* width and height attributes in video tag are ignored, player size is configured via wrapper size only.&lt;br /&gt;
* RTMP server connection is configured via data-rtmp attribute in wrapper or player init config parameter, actual path the file specified in src attribute of source item.&lt;br /&gt;
* &amp;lt;source type=&amp;quot;video/flash&amp;quot;... dummy MIME type to force using plash player for whatever content.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Missing features&#039;&#039;&#039;&lt;br /&gt;
* Plugins infrastructure&lt;br /&gt;
* No audio support&lt;br /&gt;
&lt;br /&gt;
=== Video.JS ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Pros:&#039;&#039;&#039;&lt;br /&gt;
* No logo on screen.&lt;br /&gt;
* Extensive API.&lt;br /&gt;
* No extra JS library needed.&lt;br /&gt;
* Many plugins.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Cons:&#039;&#039;&#039;&lt;br /&gt;
* Poor documentation&lt;br /&gt;
* RTMP requires non-standard MIME.&lt;br /&gt;
* Android and iOS override Video.JS with own player, when not overridden, content is not playing.&lt;br /&gt;
* No RTL.&lt;br /&gt;
* HLS support via plugin (many files to include, external contributor see https://github.com/videojs/videojs-contrib-hls).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Technical notes (lessons learned from the prototypes)&#039;&#039;&#039;&lt;br /&gt;
* Player initialisation is done using &amp;lt;video&amp;gt; tag directly (no wrapper needed).&lt;br /&gt;
* RTMP server connection and path specified as single URI in src attribute of source item, &#039;&amp;amp;&#039; character is used as server connection and path divider.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Missing features&#039;&#039;&#039;&lt;br /&gt;
* Embedded HLS support.&lt;br /&gt;
&lt;br /&gt;
=== MediaElement.js ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Pros:&#039;&#039;&#039;&lt;br /&gt;
* Flash fall-forward - uses Flash/Silverlight to provide the HTML5 MediaElement API.&lt;br /&gt;
* Consistent HTML/CSS Player&lt;br /&gt;
* No logo on screen.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Cons:&#039;&#039;&#039;&lt;br /&gt;
* ...&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Technical notes (lessons learned from the prototypes)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* ...&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Missing features&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* ...&lt;br /&gt;
&lt;br /&gt;
=== Projekktor ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Pros:&#039;&#039;&#039;&lt;br /&gt;
* No logo on screen.&lt;br /&gt;
* Well documented API.&lt;br /&gt;
* Media RSS support.&lt;br /&gt;
* Possibility to choose flash player for fallback (included by default are StrobeMediaPlayback and Jarisplayer, both Open Source).&lt;br /&gt;
* Platform preference order selection (e.g. browser, ios, android, flash, native).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Cons:&#039;&#039;&#039;&lt;br /&gt;
* Uses flash where it is not supposed to (e.g. mp4 in Chrome).&lt;br /&gt;
* Issues on ios and android (suggests to download file when play button is pressed).&lt;br /&gt;
* RTMP only work if defined via playlist rather than in &amp;lt;source&amp;gt; tags.&lt;br /&gt;
* Subtitles plugin is not free.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Technical notes (lessons learned from the prototypes)&#039;&#039;&#039;&lt;br /&gt;
* Player initialisation is done using &amp;lt;video&amp;gt; tag directly (no wrapper needed).&lt;br /&gt;
* Make sure &amp;lt;video&amp;gt; tag has class attribute with &#039;projekktor&#039; value (even if you initialise it by ID of the element), player will not work properly otherwise.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Missing features&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== HTML5 browser native ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Pros:&#039;&#039;&#039;&lt;br /&gt;
* Simple&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Cons:&#039;&#039;&#039;&lt;br /&gt;
* IE8 has no support&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Technical notes&#039;&#039;&#039;&lt;br /&gt;
* &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Missing features&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Cross-browser support ==&lt;br /&gt;
&lt;br /&gt;
=== Method ===&lt;br /&gt;
Compatibility checks were done manually by accessing the sample page using different browsers and platforms. Sample page contained the list of identical player instances, initialised for each &amp;lt;video&amp;gt; definition with a &amp;lt;source&amp;gt; of particular format: mp4, webm, ogg, flv. Streaming support was checked by setting up a &amp;lt;video&amp;gt; container with two sources of the same video, one was HLS (Apple HTTP Live Streaming) stream for browsers and platforms that support html5 video streaming, another one was RTMP which is played using flash engine and supposed to be used in case if HLS is not supported.&lt;br /&gt;
&lt;br /&gt;
=== Sample Pages ===&lt;br /&gt;
&lt;br /&gt;
* [https://dev.mle.lancs.ac.uk/videotest/players.php?player=flowplayer Flowplayer HTML5] version 5.4.6.&lt;br /&gt;
* [https://dev.mle.lancs.ac.uk/videotest/players.php?player=videojs Video.JS] version 4.4.1.&lt;br /&gt;
* [https://dev.mle.lancs.ac.uk/videotest/players.php?player=projekktor Projekktor] version 1.3.09.&lt;br /&gt;
* [https://dev.mle.lancs.ac.uk/videotest/players.php?player=mediaelementjs MediaElement.js] version 2.6.14&lt;br /&gt;
* [https://dev.mle.lancs.ac.uk/videotest/players.php?player=native Native browser support].&lt;br /&gt;
&lt;br /&gt;
=== Source ===&lt;br /&gt;
&lt;br /&gt;
Source of testing page is hosted on [https://github.com/kabalin/test-html5-players/blob/master/players.php github]. The actual source of the html page can be viewed in browser.&lt;br /&gt;
&lt;br /&gt;
=== Discussion ===&lt;br /&gt;
&lt;br /&gt;
The players comparison that has been carried out demonstrated that &amp;quot;least hassle&amp;quot; player is Flowplayer. Despite some drawbacks listed in [[#Flowplayer_HTML5|corresponding section above]], it is clearly a winner as it works as expected and according to documentation for all browsers and platforms I had chance to test it. This is the only player that supports RTL layout, display platform incompatibility message prior to attempt to play, and was simple to configure (no undocumented tricks that I had to figure out to make it work). Both VideoJS and Projekktor do now perform properly in all cases. Among those two, I personally prefer Projekktor more for its extensive API, plugins infrastructure and configuration flexibility. However problems on mobile platforms and flash fallback inconsistency make them unreliable at this stage.&lt;br /&gt;
&lt;br /&gt;
=== Compatibility results ===&lt;br /&gt;
&lt;br /&gt;
The table was populated using Flowplayer as others do not perform good enough with some formats and platforms, e.g. VideoJS allow mobile platform player to override itself, Projekktor does not always use HTML5 where browser clearly can handle it (supported in native mode and Flowplayer).&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Browser&lt;br /&gt;
! mp4&lt;br /&gt;
! webm&lt;br /&gt;
! ogg&lt;br /&gt;
! flv&lt;br /&gt;
! RTMP/HLS*&lt;br /&gt;
|-&lt;br /&gt;
! Windows 7&lt;br /&gt;
| colspan=&amp;quot;5&amp;quot;|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Chrome 33.0.1750.117&lt;br /&gt;
| html5&lt;br /&gt;
| html5&lt;br /&gt;
| html5&lt;br /&gt;
| flash&lt;br /&gt;
| flash&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Firefox 27.0.1&lt;br /&gt;
| html5&lt;br /&gt;
| html5&lt;br /&gt;
| html5&lt;br /&gt;
| flash&lt;br /&gt;
| flash&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Opera 12.16&lt;br /&gt;
| flash&lt;br /&gt;
| html5&lt;br /&gt;
| -&lt;br /&gt;
| flash&lt;br /&gt;
| flash&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Safari 5.1.7&lt;br /&gt;
| flash&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| flash&lt;br /&gt;
| flash&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Internet Explorer 11.0.9600.x&lt;br /&gt;
| html5&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| flash&lt;br /&gt;
| flash&lt;br /&gt;
|-&lt;br /&gt;
! Linux (Debian Wheezy)&lt;br /&gt;
| colspan=&amp;quot;5&amp;quot;|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Chrome 33.0.1750.115&lt;br /&gt;
| html5&lt;br /&gt;
| html5&lt;br /&gt;
| html5&lt;br /&gt;
| flash&lt;br /&gt;
| flash&lt;br /&gt;
|-&lt;br /&gt;
! OSX 10.9&lt;br /&gt;
| colspan=&amp;quot;5&amp;quot;|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Safari 7.0 (9537.71)&lt;br /&gt;
| html5&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| flash&lt;br /&gt;
| html5&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Chrome 33.0.1750.117&lt;br /&gt;
| html5&lt;br /&gt;
| html5&lt;br /&gt;
| html5&lt;br /&gt;
| flash&lt;br /&gt;
| flash&lt;br /&gt;
|-&lt;br /&gt;
! Android 4.2.2 phone (CyanogenMod, no flash)&lt;br /&gt;
| colspan=&amp;quot;5&amp;quot;|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Chrome 32.0.1700.99&lt;br /&gt;
| html5&lt;br /&gt;
| html5&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Browser 4.2.2 (com.android.browser)&lt;br /&gt;
| html5&lt;br /&gt;
| html5&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| html5&lt;br /&gt;
|-&lt;br /&gt;
! Android 4.2.2 tablet (GalaxyTab2, flash app 11.1.115.36)&lt;br /&gt;
| colspan=&amp;quot;5&amp;quot;|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Chrome 32.0.1700.99&lt;br /&gt;
| html5&lt;br /&gt;
| html5&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Browser 4.2.2 (com.android.browser)&lt;br /&gt;
| html5&lt;br /&gt;
| html5&lt;br /&gt;
| -&lt;br /&gt;
| flash&lt;br /&gt;
| html5&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Opera 19.0.1340.x&lt;br /&gt;
| html5&lt;br /&gt;
| html5&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
|-&lt;br /&gt;
! iPhone 4S (6.1.3)&lt;br /&gt;
| colspan=&amp;quot;5&amp;quot;|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Safari&lt;br /&gt;
| html5&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| html5&lt;br /&gt;
|-&lt;br /&gt;
! iPad 4 (7.0.6)&lt;br /&gt;
| colspan=&amp;quot;5&amp;quot;|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Safari&lt;br /&gt;
| html5&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| html5&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;small&amp;gt;* For HLS/RTMP, html5 in the table means HLS has been used, flash means player fell back and used RTMP.&amp;lt;/small&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Sub Tasks ==&lt;br /&gt;
&lt;br /&gt;
In initial stage FlowPlayer HTML5 (or any other) will handle all existing HTML5 Video extensions as per core_media_player_html5video class, plus m3u8 (HLS). It will also be possible optionally to make flash files handled by HTML5 Video player as well (possibility of using existing flash FlowPlayer for flv files will remain).&lt;br /&gt;
&lt;br /&gt;
== Mock-Ups/Prototypes ==&lt;br /&gt;
&lt;br /&gt;
See [[#Sample_Pages|Sample pages]] section.&lt;br /&gt;
&lt;br /&gt;
== Workaround ==&lt;br /&gt;
&lt;br /&gt;
As intermediate solution, if you require player consistency throughout all browsers and platforms, you may consider using JW Player filter: https://github.com/lucisgit/moodle-filter_jwplayer Notice, that JW Player is not free for commercial use.&lt;br /&gt;
&lt;br /&gt;
== Related Tracker Issues ==&lt;br /&gt;
* MDL-38158&lt;br /&gt;
&lt;br /&gt;
== Useful links ==&lt;br /&gt;
* http://praegnanz.de/html5video/&lt;br /&gt;
* http://html5video.org/wiki/HTML5_Video_Player_Comparison&lt;br /&gt;
* http://www.streamingmedia.com/Articles/Editorial/Featured-Articles/Choosing-a-Video-Player-Features-and-Specs-for-the-Top-Five-94188.aspx&lt;br /&gt;
&lt;br /&gt;
[[Category:Project]]&lt;/div&gt;</summary>
		<author><name>Davidbalch</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=HTML5_player&amp;diff=47472</id>
		<title>HTML5 player</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=HTML5_player&amp;diff=47472"/>
		<updated>2015-03-09T17:17:41Z</updated>

		<summary type="html">&lt;p&gt;Davidbalch: Add MediaElement.js&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Work in progress}}&lt;br /&gt;
{{Infobox Project&lt;br /&gt;
|name = HTML5 Player with flash fallback&lt;br /&gt;
|state = In Development&lt;br /&gt;
|tracker = https://tracker.moodle.org/browse/MDL-38158&lt;br /&gt;
|discussion = https://moodle.org/mod/forum/discuss.php?d=222753#p1110024&lt;br /&gt;
|assignee = [https://tracker.moodle.org/secure/ViewProfile.jspa?name=kabalin Ruslan Kabalin]&lt;br /&gt;
}}&lt;br /&gt;
{{Moodle 2.x}}&lt;br /&gt;
&lt;br /&gt;
== Project goals ==&lt;br /&gt;
&lt;br /&gt;
The purpose of this document is to choose HTML5 player with flash fallback for integration in Moodle and plan its implementation.&lt;br /&gt;
&lt;br /&gt;
The aim of this change is to reach a consistent look of the player among different platforms and browsers, as well as making sure that player starts using flash if html5 is not supported by browser for the given format. Another aim is to improve accessibility and usability, enhance customisation.&lt;br /&gt;
&lt;br /&gt;
== Considerations ==&lt;br /&gt;
&lt;br /&gt;
* Usability of the player - is it easy to use it and intuitively understand how to use controls?&lt;br /&gt;
* Accessibility of the player - possibility of keyboard navigation, subtitles, support?&lt;br /&gt;
* Appearance of the player - can its interface be modified, themes support, RTL?&lt;br /&gt;
* Consistency among browsers - does player look the same?&lt;br /&gt;
* Audio support - can player handle HTML5 &amp;lt;audio&amp;gt; tag?&lt;br /&gt;
* Mobile platforms support - does player also work on iOS and Android?&lt;br /&gt;
* Streaming media support (including adaptive-bitrate delivery) - can player handle RTMP, Apple HLS, Adobe HDS, Microsoft HSS?&lt;br /&gt;
* Plugins support - can we use or develop community plugins easily?&lt;br /&gt;
* Embedding support - so users could post media content outside Moodle, if permitted.&lt;br /&gt;
* Maintainability of the links between the player and Moodle - how much work will we need to do every time we upgrade?&lt;br /&gt;
&lt;br /&gt;
== Candidates ==&lt;br /&gt;
&lt;br /&gt;
* [http://flowplayer.org/ Flowplayer HTML5]&lt;br /&gt;
* [http://www.videojs.com/ Video.JS]&lt;br /&gt;
* [http://www.projekktor.com/ Projekktor]&lt;br /&gt;
* [http://mediaelementjs.com/ MediaElement.js]&lt;br /&gt;
* ?Are there other alternatives?&lt;br /&gt;
&lt;br /&gt;
== Comparison Matrix ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
! [[#Flowplayer_HTML5|Flowplayer HTML5]]&lt;br /&gt;
! [[#Video.JS|Video.JS]]&lt;br /&gt;
! [[#Projekktor|Projekktor]]&lt;br /&gt;
! [[#MediaElement.js|MediaElement.js]]&lt;br /&gt;
! No Player&lt;br /&gt;
|-&lt;br /&gt;
! Accessible Player:&lt;br /&gt;
| colspan=&amp;quot;4&amp;quot;|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Keyboard control&lt;br /&gt;
| Yes&lt;br /&gt;
| No&lt;br /&gt;
| Yes (configurable)&lt;br /&gt;
| Yes&lt;br /&gt;
| unknown&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Subtitles support&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes (via plugin)&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes (see http://caniuse.com/webvtt)&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Touch interface&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
! Appearance:&lt;br /&gt;
| colspan=&amp;quot;4&amp;quot;|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Skins&lt;br /&gt;
| Yes (via CSS)&lt;br /&gt;
| Yes (via CSS)&lt;br /&gt;
| Yes (via CSS)&lt;br /&gt;
| Yes (via CSS)&lt;br /&gt;
| No&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* CSS customisation&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| unknown&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* RTL support&lt;br /&gt;
| Yes&lt;br /&gt;
| No&lt;br /&gt;
| No&lt;br /&gt;
| Unknown (CSS, so I guess so...)&lt;br /&gt;
| Unknown&lt;br /&gt;
|-&lt;br /&gt;
! Mobile platforms:&lt;br /&gt;
| colspan=&amp;quot;4&amp;quot;|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* iOS&lt;br /&gt;
| Yes&lt;br /&gt;
| No&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Android&lt;br /&gt;
| Yes&lt;br /&gt;
| No&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
! Streaming protocols:&lt;br /&gt;
| colspan=&amp;quot;4&amp;quot;|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* RTMP&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes (via playlist)&lt;br /&gt;
| Yes&lt;br /&gt;
| No&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Adaptive HTTP&lt;br /&gt;
| HLS&lt;br /&gt;
| HLS (via plugin)&lt;br /&gt;
| HLS (via playlist)&lt;br /&gt;
| HLS&lt;br /&gt;
| Only in Chrome so far (Media Source Extension)&lt;br /&gt;
|-&lt;br /&gt;
! Audio player&lt;br /&gt;
| No&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
! style=&amp;quot;border-bottom: 2px solid;&amp;quot; | Embedding support&lt;br /&gt;
| style=&amp;quot;border-bottom: 2px solid;&amp;quot; | Yes (configurable)&lt;br /&gt;
| style=&amp;quot;border-bottom: 2px solid;&amp;quot; | Yes (via addThis plugin)&lt;br /&gt;
| style=&amp;quot;border-bottom: 2px solid;&amp;quot; | Yes (via plugin)&lt;br /&gt;
| style=&amp;quot;border-bottom: 2px solid;&amp;quot; | Yes&lt;br /&gt;
| style=&amp;quot;border-bottom: 2px solid;&amp;quot; | No&lt;br /&gt;
|-&lt;br /&gt;
! License&lt;br /&gt;
| GPL v3 (with additional terms)&lt;br /&gt;
| Apache 2&lt;br /&gt;
| GPL v3&lt;br /&gt;
| no extra code needed&lt;br /&gt;
| MIT&lt;br /&gt;
|-&lt;br /&gt;
! JS library&lt;br /&gt;
| jQuery&lt;br /&gt;
| -&lt;br /&gt;
| jQuery&lt;br /&gt;
| jQuery&lt;br /&gt;
| none required&lt;br /&gt;
|-&lt;br /&gt;
! Plugins infrastructure&lt;br /&gt;
| No&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| No&lt;br /&gt;
|-&lt;br /&gt;
! Flash fallback player&lt;br /&gt;
| Flowplayer Flash&lt;br /&gt;
| video-js flash&lt;br /&gt;
| StobeMediaPlayback (but can be jarisplayer or some other).&lt;br /&gt;
| Flash and Silverlight (for .wmv support) fallback, used to provide the HTML5 video/audio elements.&lt;br /&gt;
| The Video tag can contain fallback content (e.g. Flash object) for browsers that don&#039;t support it at all (http://camendesign.com/code/video_for_everybody), but not based on codec support.&lt;br /&gt;
|-&lt;br /&gt;
! API&lt;br /&gt;
| Yes (plus window.flowplayer object)&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
| Yes&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Flowplayer HTML5 ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Pros:&#039;&#039;&#039;&lt;br /&gt;
* Good looking player, resembles existing flash flowplayer in Moodle (with &amp;quot;functional&amp;quot; skin)&lt;br /&gt;
* Extensive customisation (via CSS3 skins and configuration object)&lt;br /&gt;
* Streaming support (RTMP, HLS)&lt;br /&gt;
* Good accessibility features&lt;br /&gt;
* Configurable embedding functionality&lt;br /&gt;
* Existing flash flowplayer can be configured for fallback (the appearance of player will not be affected).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Cons:&#039;&#039;&#039;&lt;br /&gt;
* Require jQuery for functioning&lt;br /&gt;
* Flowplayer logo remains on screen&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Technical notes (lessons learned from the prototypes)&#039;&#039;&#039;&lt;br /&gt;
* Player initialisation is done using wrapper around &amp;lt;video&amp;gt;.&lt;br /&gt;
* width and height attributes in video tag are ignored, player size is configured via wrapper size only.&lt;br /&gt;
* RTMP server connection is configured via data-rtmp attribute in wrapper or player init config parameter, actual path the file specified in src attribute of source item.&lt;br /&gt;
* &amp;lt;source type=&amp;quot;video/flash&amp;quot;... dummy MIME type to force using plash player for whatever content.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Missing features&#039;&#039;&#039;&lt;br /&gt;
* Plugins infrastructure&lt;br /&gt;
* No audio support&lt;br /&gt;
&lt;br /&gt;
=== Video.JS ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Pros:&#039;&#039;&#039;&lt;br /&gt;
* No logo on screen.&lt;br /&gt;
* Extensive API.&lt;br /&gt;
* No extra JS library needed.&lt;br /&gt;
* Many plugins.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Cons:&#039;&#039;&#039;&lt;br /&gt;
* Poor documentation&lt;br /&gt;
* RTMP requires non-standard MIME.&lt;br /&gt;
* Android and iOS override Video.JS with own player, when not overridden, content is not playing.&lt;br /&gt;
* No RTL.&lt;br /&gt;
* HLS support via plugin (many files to include, external contributor see https://github.com/videojs/videojs-contrib-hls).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Technical notes (lessons learned from the prototypes)&#039;&#039;&#039;&lt;br /&gt;
* Player initialisation is done using &amp;lt;video&amp;gt; tag directly (no wrapper needed).&lt;br /&gt;
* RTMP server connection and path specified as single URI in src attribute of source item, &#039;&amp;amp;&#039; character is used as server connection and path divider.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Missing features&#039;&#039;&#039;&lt;br /&gt;
* Embedded HLS support.&lt;br /&gt;
&lt;br /&gt;
=== MediaElement.js ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Pros:&#039;&#039;&#039;&lt;br /&gt;
* Flash fall-forward - uses Flash/Silverlight to provide the HTML5 MediaElement API.&lt;br /&gt;
* Consistent HTML/CSS Player&lt;br /&gt;
* No logo on screen.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Cons:&#039;&#039;&#039;&lt;br /&gt;
* ...&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Technical notes (lessons learned from the prototypes)&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* ...&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Missing features&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* ...&lt;br /&gt;
&lt;br /&gt;
=== Projekktor ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Pros:&#039;&#039;&#039;&lt;br /&gt;
* No logo on screen.&lt;br /&gt;
* Well documented API.&lt;br /&gt;
* Media RSS support.&lt;br /&gt;
* Possibility to choose flash player for fallback (included by default are StrobeMediaPlayback and Jarisplayer, both Open Source).&lt;br /&gt;
* Platform preference order selection (e.g. browser, ios, android, flash, native).&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Cons:&#039;&#039;&#039;&lt;br /&gt;
* Uses flash where it is not supposed to (e.g. mp4 in Chrome).&lt;br /&gt;
* Issues on ios and android (suggests to download file when play button is pressed).&lt;br /&gt;
* RTMP only work if defined via playlist rather than in &amp;lt;source&amp;gt; tags.&lt;br /&gt;
* Subtitles plugin is not free.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Technical notes (lessons learned from the prototypes)&#039;&#039;&#039;&lt;br /&gt;
* Player initialisation is done using &amp;lt;video&amp;gt; tag directly (no wrapper needed).&lt;br /&gt;
* Make sure &amp;lt;video&amp;gt; tag has class attribute with &#039;projekktor&#039; value (even if you initialise it by ID of the element), player will not work properly otherwise.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Missing features&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== HTML5 browser native ===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Pros:&#039;&#039;&#039;&lt;br /&gt;
* Simple&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Cons:&#039;&#039;&#039;&lt;br /&gt;
* IE8 has no support&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Technical notes&#039;&#039;&#039;&lt;br /&gt;
* &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Missing features&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
== Cross-browser support ==&lt;br /&gt;
&lt;br /&gt;
=== Method ===&lt;br /&gt;
Compatibility checks were done manually by accessing the sample page using different browsers and platforms. Sample page contained the list of identical player instances, initialised for each &amp;lt;video&amp;gt; definition with a &amp;lt;source&amp;gt; of particular format: mp4, webm, ogg, flv. Streaming support was checked by setting up a &amp;lt;video&amp;gt; container with two sources of the same video, one was HLS (Apple HTTP Live Streaming) stream for browsers and platforms that support html5 video streaming, another one was RTMP which is played using flash engine and supposed to be used in case if HLS is not supported.&lt;br /&gt;
&lt;br /&gt;
=== Sample Pages ===&lt;br /&gt;
&lt;br /&gt;
* [https://dev.mle.lancs.ac.uk/videotest/players.php?player=flowplayer Flowplayer HTML5] version 5.4.6.&lt;br /&gt;
* [https://dev.mle.lancs.ac.uk/videotest/players.php?player=videojs Video.JS] version 4.4.1.&lt;br /&gt;
* [https://dev.mle.lancs.ac.uk/videotest/players.php?player=projekktor Projekktor] version 1.3.09.&lt;br /&gt;
* [https://dev.mle.lancs.ac.uk/videotest/players.php?player=native Native browser support].&lt;br /&gt;
&lt;br /&gt;
=== Source ===&lt;br /&gt;
&lt;br /&gt;
Source of testing page is hosted on [https://github.com/kabalin/test-html5-players/blob/master/players.php github]. The actual source of the html page can be viewed in browser.&lt;br /&gt;
&lt;br /&gt;
=== Discussion ===&lt;br /&gt;
&lt;br /&gt;
The players comparison that has been carried out demonstrated that &amp;quot;least hassle&amp;quot; player is Flowplayer. Despite some drawbacks listed in [[#Flowplayer_HTML5|corresponding section above]], it is clearly a winner as it works as expected and according to documentation for all browsers and platforms I had chance to test it. This is the only player that supports RTL layout, display platform incompatibility message prior to attempt to play, and was simple to configure (no undocumented tricks that I had to figure out to make it work). Both VideoJS and Projekktor do now perform properly in all cases. Among those two, I personally prefer Projekktor more for its extensive API, plugins infrastructure and configuration flexibility. However problems on mobile platforms and flash fallback inconsistency make them unreliable at this stage.&lt;br /&gt;
&lt;br /&gt;
=== Compatibility results ===&lt;br /&gt;
&lt;br /&gt;
The table was populated using Flowplayer as others do not perform good enough with some formats and platforms, e.g. VideoJS allow mobile platform player to override itself, Projekktor does not always use HTML5 where browser clearly can handle it (supported in native mode and Flowplayer).&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Browser&lt;br /&gt;
! mp4&lt;br /&gt;
! webm&lt;br /&gt;
! ogg&lt;br /&gt;
! flv&lt;br /&gt;
! RTMP/HLS*&lt;br /&gt;
|-&lt;br /&gt;
! Windows 7&lt;br /&gt;
| colspan=&amp;quot;5&amp;quot;|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Chrome 33.0.1750.117&lt;br /&gt;
| html5&lt;br /&gt;
| html5&lt;br /&gt;
| html5&lt;br /&gt;
| flash&lt;br /&gt;
| flash&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Firefox 27.0.1&lt;br /&gt;
| html5&lt;br /&gt;
| html5&lt;br /&gt;
| html5&lt;br /&gt;
| flash&lt;br /&gt;
| flash&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Opera 12.16&lt;br /&gt;
| flash&lt;br /&gt;
| html5&lt;br /&gt;
| -&lt;br /&gt;
| flash&lt;br /&gt;
| flash&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Safari 5.1.7&lt;br /&gt;
| flash&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| flash&lt;br /&gt;
| flash&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Internet Explorer 11.0.9600.x&lt;br /&gt;
| html5&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| flash&lt;br /&gt;
| flash&lt;br /&gt;
|-&lt;br /&gt;
! Linux (Debian Wheezy)&lt;br /&gt;
| colspan=&amp;quot;5&amp;quot;|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Chrome 33.0.1750.115&lt;br /&gt;
| html5&lt;br /&gt;
| html5&lt;br /&gt;
| html5&lt;br /&gt;
| flash&lt;br /&gt;
| flash&lt;br /&gt;
|-&lt;br /&gt;
! OSX 10.9&lt;br /&gt;
| colspan=&amp;quot;5&amp;quot;|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Safari 7.0 (9537.71)&lt;br /&gt;
| html5&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| flash&lt;br /&gt;
| html5&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Chrome 33.0.1750.117&lt;br /&gt;
| html5&lt;br /&gt;
| html5&lt;br /&gt;
| html5&lt;br /&gt;
| flash&lt;br /&gt;
| flash&lt;br /&gt;
|-&lt;br /&gt;
! Android 4.2.2 phone (CyanogenMod, no flash)&lt;br /&gt;
| colspan=&amp;quot;5&amp;quot;|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Chrome 32.0.1700.99&lt;br /&gt;
| html5&lt;br /&gt;
| html5&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Browser 4.2.2 (com.android.browser)&lt;br /&gt;
| html5&lt;br /&gt;
| html5&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| html5&lt;br /&gt;
|-&lt;br /&gt;
! Android 4.2.2 tablet (GalaxyTab2, flash app 11.1.115.36)&lt;br /&gt;
| colspan=&amp;quot;5&amp;quot;|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Chrome 32.0.1700.99&lt;br /&gt;
| html5&lt;br /&gt;
| html5&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Browser 4.2.2 (com.android.browser)&lt;br /&gt;
| html5&lt;br /&gt;
| html5&lt;br /&gt;
| -&lt;br /&gt;
| flash&lt;br /&gt;
| html5&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Opera 19.0.1340.x&lt;br /&gt;
| html5&lt;br /&gt;
| html5&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
|-&lt;br /&gt;
! iPhone 4S (6.1.3)&lt;br /&gt;
| colspan=&amp;quot;5&amp;quot;|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Safari&lt;br /&gt;
| html5&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| html5&lt;br /&gt;
|-&lt;br /&gt;
! iPad 4 (7.0.6)&lt;br /&gt;
| colspan=&amp;quot;5&amp;quot;|&lt;br /&gt;
|-&lt;br /&gt;
!&lt;br /&gt;
* Safari&lt;br /&gt;
| html5&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| -&lt;br /&gt;
| html5&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&amp;lt;small&amp;gt;* For HLS/RTMP, html5 in the table means HLS has been used, flash means player fell back and used RTMP.&amp;lt;/small&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Sub Tasks ==&lt;br /&gt;
&lt;br /&gt;
In initial stage FlowPlayer HTML5 (or any other) will handle all existing HTML5 Video extensions as per core_media_player_html5video class, plus m3u8 (HLS). It will also be possible optionally to make flash files handled by HTML5 Video player as well (possibility of using existing flash FlowPlayer for flv files will remain).&lt;br /&gt;
&lt;br /&gt;
== Mock-Ups/Prototypes ==&lt;br /&gt;
&lt;br /&gt;
See [[#Sample_Pages|Sample pages]] section.&lt;br /&gt;
&lt;br /&gt;
== Workaround ==&lt;br /&gt;
&lt;br /&gt;
As intermediate solution, if you require player consistency throughout all browsers and platforms, you may consider using JW Player filter: https://github.com/lucisgit/moodle-filter_jwplayer Notice, that JW Player is not free for commercial use.&lt;br /&gt;
&lt;br /&gt;
== Related Tracker Issues ==&lt;br /&gt;
* MDL-38158&lt;br /&gt;
&lt;br /&gt;
== Useful links ==&lt;br /&gt;
* http://praegnanz.de/html5video/&lt;br /&gt;
* http://html5video.org/wiki/HTML5_Video_Player_Comparison&lt;br /&gt;
* http://www.streamingmedia.com/Articles/Editorial/Featured-Articles/Choosing-a-Video-Player-Features-and-Specs-for-the-Top-Five-94188.aspx&lt;br /&gt;
&lt;br /&gt;
[[Category:Project]]&lt;/div&gt;</summary>
		<author><name>Davidbalch</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Map_API&amp;diff=45588</id>
		<title>Map API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Map_API&amp;diff=45588"/>
		<updated>2014-06-27T08:52:40Z</updated>

		<summary type="html">&lt;p&gt;Davidbalch: /* Plugins */ Link to approved plugins.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Infobox Project&lt;br /&gt;
|name = New Map API&lt;br /&gt;
|state = Proposal/Development in progress&lt;br /&gt;
|tracker = MDL-42522&lt;br /&gt;
|discussion = https://moodle.org/mod/forum/discuss.php?d=245569&lt;br /&gt;
}}&lt;br /&gt;
{{Moodle 2.7}}This page is for collecting feature requests for a possible new API to enable easy use of geographical maps.&lt;br /&gt;
&lt;br /&gt;
== Current status ==&lt;br /&gt;
Moodle + contrib has three separate map implementations:&lt;br /&gt;
# The built-in map in the IP address lookup (under /iplookup)&lt;br /&gt;
# [https://moodle.org/plugins/view.php?plugin=block_online_users_map block_online_users_map] is a contrib module that shows recent users plotted on a world map.&lt;br /&gt;
# [https://moodle.org/plugins/view.php?plugin=block_simple_map block_simple_map] is a contrib module that allows users to search for places specified by a CSV upload&lt;br /&gt;
&lt;br /&gt;
* Additionally, the Database activity has a latlong field, which outputs as links to external mapping services.&lt;br /&gt;
&lt;br /&gt;
== High-level proposal == &lt;br /&gt;
&lt;br /&gt;
A core module to provide an easy way to add maps and mapping features:&lt;br /&gt;
# Display a map with specified size, location, zoom, and tileset.&lt;br /&gt;
# Display one or more markers on the map.&lt;br /&gt;
# Display popups (or other display methods) with additional information when a user clicks on markers. &lt;br /&gt;
# Geocoding (i.e. find lat/long from location name - should adopt existing iplookup solution).  &lt;br /&gt;
# Input of locations by clicking on the map.&lt;br /&gt;
# Reverse geocoding (i.e. find location name from lat/long).&lt;br /&gt;
&lt;br /&gt;
== Existing work (2013-12-10) ==&lt;br /&gt;
&lt;br /&gt;
An alpha level implementation is done, including all proposed features except integration of the existing geocoding system.&lt;br /&gt;
&lt;br /&gt;
=== Map API (local module) ===&lt;br /&gt;
* The [http://leafletjs.com/ Leaflet] javascript map library as a YUI module.†&lt;br /&gt;
* A PHP class to easily: Add maps, display markers and popups, input locations, perform reverse geocoding.&lt;br /&gt;
&lt;br /&gt;
Demonstration at: http://dbtest.conted.ox.ac.uk/moodlemaps/local/map&lt;br /&gt;
&lt;br /&gt;
† There are other map libs, e.g.:&lt;br /&gt;
* OpenLayers - older, larger, with more features than I think we need here.&lt;br /&gt;
* Modest Maps - fairly minimal, with fewer&lt;br /&gt;
* Polymap - looks interesting for data mapping, but not sure how useful that would be in Moodle.&lt;br /&gt;
&lt;br /&gt;
...however, I&#039;m most familiar with Leaflet and feel it hits the right features/weight balance (e.g. includes geoJSON, but avoids complications with projections), so think it&#039;s the right place to start.&lt;br /&gt;
&lt;br /&gt;
=== Database activity submodules ===&lt;br /&gt;
# A modification/fork of the latlong field to enable maps for display and input&lt;br /&gt;
# A new preset (named &amp;quot;Infomap&amp;quot;) to utilise the modified latlong field.&lt;br /&gt;
&lt;br /&gt;
Demonstration at: http://dbtest.conted.ox.ac.uk/moodlemaps/course/view.php?id=2&lt;br /&gt;
&lt;br /&gt;
=== Plugins ===&lt;br /&gt;
&lt;br /&gt;
The local and database modules have been formatted into three modules: &lt;br /&gt;
* https://moodle.org/plugins/view.php?plugin=local_map - the API.&lt;br /&gt;
* https://moodle.org/plugins/view.php?plugin=datafield_latlongmap - a modified latlong field.&lt;br /&gt;
* https://moodle.org/plugins/view.php?plugin=datapreset_infomap_latlongmap - a preset using datafield_latlongmap.&lt;br /&gt;
&lt;br /&gt;
Feedback on the plugins (or the Git branch linked in MDL-42522) would be appreciated.&lt;br /&gt;
&lt;br /&gt;
== Detailed propsal ==&lt;br /&gt;
TBC&lt;/div&gt;</summary>
		<author><name>Davidbalch</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Events_API&amp;diff=44916</id>
		<title>Events API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Events_API&amp;diff=44916"/>
		<updated>2014-05-22T09:33:46Z</updated>

		<summary type="html">&lt;p&gt;Davidbalch: /* Backwards compatibility and migration */ Add link to &amp;quot;Migrating logging calls in plugins&amp;quot; docs&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Infobox Project&lt;br /&gt;
|name = Events 2&lt;br /&gt;
|state = Completed&lt;br /&gt;
|tracker = MDL-39797 , MDL-39952, MDL-39846&lt;br /&gt;
|discussion = https://moodle.org/mod/forum/discuss.php?d=229425&lt;br /&gt;
|assignee = Backend Team&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
= What are events? =&lt;br /&gt;
&lt;br /&gt;
Events are atomic pieces of information describing something that happened in Moodle. Events are primarily the result of user actions, but could also be the result of the [[:en:Cron|cron]] process or administration actions [[:en:Administration via command line|undertaken via the command line]].&lt;br /&gt;
&lt;br /&gt;
When an action takes place, an event is created by a [[Core APIs|core API]] or [[Plugins|plugin]]. The Events system then disseminates this event information to observers registered for this event. In this way, the events system acts as a communication backbone throughout the Moodle system. Event observers can not modify event data or interrupt the dispatching of events, it is a one way communication channel.&lt;br /&gt;
&lt;br /&gt;
= Why was a new events system needed? =&lt;br /&gt;
&lt;br /&gt;
The need to improve the Events system was prompted by a need for a richer and more efficient logging system, however the benefits of this improvement are useful to other parts of Moodle that observe event information.&lt;br /&gt;
&lt;br /&gt;
* The events need to be more strictly defined for logging and other advanced use cases. They need to contain more information, organised in a standardised way (in addition to the fields from the legacy log table and log_actions table).&lt;br /&gt;
* Complex data types were allowed in old events, which was causing major problems when serialising/storing/unserialising the data.&lt;br /&gt;
* The logging tables and events contain similar information and were triggered at the same places; utilising events for logging would remove this code duplication. All events should be loggable and all current log entries should be triggered as events.&lt;br /&gt;
* The logging system is an event observer, listening to all events and directing them to logging storage plugins in a controllable way.&lt;br /&gt;
* It is possible to subscribe to &#039;*&#039; event, which would allow a system to potentially observe, and selectively deal with, all events. Previously, handlers did not receive event names which made this problematic.&lt;br /&gt;
* Previously, event handlers could trigger exceptions during site upgrades, which would lead to fatal upgrade problems. The new design eliminates this.&lt;br /&gt;
* Failure in previous handlers blocked dispatching of subsequent events. Problems in new observers would only be logged and execution would continue normally.&lt;br /&gt;
* Previously, execution of external handlers during DB transactions blocked other handlers. This would be eliminated by in-memory buffer for external events.&lt;br /&gt;
* Previously there was no observer priority.&lt;br /&gt;
* Previously, nested events were not dispatched sequentially, which would change the order of events were received by lower priority handlers.&lt;br /&gt;
&lt;br /&gt;
= Performance =&lt;br /&gt;
Some basic profiling was conducted.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;p class=&amp;quot;note&amp;quot;&amp;gt;This next two paragraphs need to be replaced with results.&amp;lt;/p&amp;gt;&lt;br /&gt;
There is a general plan to complete pre- and post-implementation testing as development happens. The new system will be implemented in parallel with the old one, which should help with comparison of new and old logging and event triggering performance on each page.&lt;br /&gt;
&lt;br /&gt;
Our aim is to capture more information about actions in Moodle by triggering more events and logging more information about each event. This is going to impact negatively on performance. We hope to offset that impact by improving log storage, simplifying event dispatching and adding other core performance improvements. The proposed class structure of the base event should allow some new advanced techniques, which may also improve performance in some scenarios.&lt;br /&gt;
&lt;br /&gt;
More details will be added to this section soon.&lt;br /&gt;
&lt;br /&gt;
= Events API =&lt;br /&gt;
&lt;br /&gt;
Each plugin defines the events that it can report (trigger) by extending an abstract base class, once for each possible event. This approach has several benefits.&lt;br /&gt;
; Events are active objects&lt;br /&gt;
: When they are triggered and possibly after they are reinstantiated (say, when they are retrieved from a log), an event object is able to provide callback functions for various purposes (such as capability checks).&lt;br /&gt;
; Automatic inclusion&lt;br /&gt;
: Event class definitions is automatically included when needed, without having to maintain lists of known event types. New event definitions can be added without the need to upgrade, only purging of the MUC cache is required after adding a new observer.&lt;br /&gt;
; Maintainability&lt;br /&gt;
: It is relatively simple to add new events and migrate existing events. Code review is simplified because there is less duplication of code when triggering same events and all event related information/code is concentrated in one file in fixed locations.&lt;br /&gt;
; Self documenting&lt;br /&gt;
: The behaviour of events is combined with the definition of events in one place (file). It is easy for event observer writers to know what events a plugin can trigger. This includes support for autocompletion and code inspection in modern IDEs. A list of all events is now available as a report to administrators and researchers.&lt;br /&gt;
; Quick, self-validating data structure&lt;br /&gt;
: As events are instantiated objects, the PHP processor will validate the structure and type of event classes. This does not ensure data value validity, but does give some assurance of consistency and it also detects unintentional typos.&lt;br /&gt;
&lt;br /&gt;
== Backwards compatibility and migration ==&lt;br /&gt;
&lt;br /&gt;
Events:&lt;br /&gt;
* All legacy events in the standard distribution have been replaced with the new events API and events_trigger() has been deprecated.&lt;br /&gt;
* For events that already exist in Moodle 2.5, the additional legacy information has been added to the event data (in properties &#039;legacyeventname&#039; and &#039;legacyeventdata&#039;)&lt;br /&gt;
* The legacy events handling code is maintained  separately and will continue being supported in Moodle 2.7.&lt;br /&gt;
* All legacy events-handlers have been migrated to new observers in standard distribution.&lt;br /&gt;
* In future, more subsystems may be migrated to events-observers, ex.: gradebook history, completion.&lt;br /&gt;
&lt;br /&gt;
Logging:&lt;br /&gt;
* The function add_to_log() has been deprecated with a notice as of Moodle 2.7.&lt;br /&gt;
* Existing add_to_log() parameters has been migrated inside new events method, in get_legacy_log_data(). Calls to core_event_base::trigger() will lead to entries being added to the legacy log table if the legacy log plugin is enabled.&lt;br /&gt;
&lt;br /&gt;
See https://docs.moodle.org/dev/Migrating_logging_calls_in_plugins&lt;br /&gt;
&lt;br /&gt;
== Event dispatching and observers ==&lt;br /&gt;
&lt;br /&gt;
The new event dispatching system is completely separate from the old events code. Original event handlers are now called observers with the description stored in the same db/events.php file, but as a new array with a different format.&lt;br /&gt;
&lt;br /&gt;
=== Event observers ===&lt;br /&gt;
&lt;br /&gt;
Observers are described in db/events.php in the array $observers, the array is not indexed and contains a list of observers defined as an array with the following properties;&lt;br /&gt;
* eventname – fully qualified event class name or &amp;quot;*&amp;quot; indicating all events, ex.: &#039;&#039;\plugintype_pluginname\event\something_happened&#039;&#039;.&lt;br /&gt;
* callback - PHP callable type.&lt;br /&gt;
* includefile - optional. File to be included before calling the observer. Path relative to dirroot.&lt;br /&gt;
* priority - optional. Defaults to 0. Observers with higher priority are notified first.&lt;br /&gt;
* internal - optional. Defaults to true. Non-internal observers are not called during database transactions, but instead after a successful commit of the transaction.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
&lt;br /&gt;
$observers = array(&lt;br /&gt;
&lt;br /&gt;
    array(&lt;br /&gt;
        &#039;eventname&#039;   =&amp;gt; &#039;\core\event\sample_executed&#039;,&lt;br /&gt;
        &#039;callback&#039;    =&amp;gt; &#039;core_event_sample_observer::observe_one&#039;,&lt;br /&gt;
    ),&lt;br /&gt;
&lt;br /&gt;
    array(&lt;br /&gt;
        &#039;eventname&#039;   =&amp;gt; &#039;\core\event\sample_executed&#039;,&lt;br /&gt;
        &#039;callback&#039;    =&amp;gt; &#039;core_event_sample_observer::external_observer&#039;,&lt;br /&gt;
        &#039;priority&#039;    =&amp;gt; 200,&lt;br /&gt;
        &#039;internal&#039;    =&amp;gt; false,&lt;br /&gt;
    ),&lt;br /&gt;
&lt;br /&gt;
    array(&lt;br /&gt;
        &#039;eventname&#039;   =&amp;gt; &#039;*&#039;,&lt;br /&gt;
        &#039;callback&#039;    =&amp;gt; &#039;core_event_sample_observer::observe_all&#039;,&lt;br /&gt;
        &#039;includefile&#039; =&amp;gt; null,&lt;br /&gt;
        &#039;internal&#039;    =&amp;gt; true,&lt;br /&gt;
        &#039;priority&#039;    =&amp;gt; 9999,&lt;br /&gt;
    ),&lt;br /&gt;
&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Event dispatching ===&lt;br /&gt;
&lt;br /&gt;
A list of available observers is constructed on the fly directly from all available in &#039;events.php&#039; files. Previously handlers were installed only during installation and upgrade. This has little risk of performance regression because the list is already cached in MUC. Observers get events before installation or any upgrade, however observers are not notified during the initial installation of Moodle core tables.&lt;br /&gt;
&lt;br /&gt;
Developers of observers must make sure that execution does not end with a fatal error under any condition (before install, before upgrade or normal operation). Exceptions are automatically captured, logged in the PHP error log, and notification of other observers continues. Current handlers must not throw any exceptions at any time.&lt;br /&gt;
&lt;br /&gt;
Observers are notified sequentially in the same order in which events were triggered. This means that events triggered in observers are queued in FIFO buffer and are processed after all observers are notified.&lt;br /&gt;
&lt;br /&gt;
=== Differences from old event handling ===&lt;br /&gt;
&lt;br /&gt;
# New events contain a lot more structured information.&lt;br /&gt;
# There is separate context cache that may be used when deleting data or for observer performance improvements.&lt;br /&gt;
# No database access is allowed in new event dispatching code.&lt;br /&gt;
# There is no support for cron execution. This eliminates performance problems, simplifies events dispatching and prevents abuse of cron events.&lt;br /&gt;
# Events triggered in observers are processed in a different order.&lt;br /&gt;
# External events are buffered when a transaction is in progress, instead of being sent to the cron queue.&lt;br /&gt;
# It is possible to define multiple observers for one event in one events.php file.&lt;br /&gt;
# It is possible to subscribe an observer to all events.&lt;br /&gt;
# The new event manager is using frankenstyle autoloading, which leads to a smaller memory footprint when events are not used on the current page.&lt;br /&gt;
&lt;br /&gt;
== Triggering events ==&lt;br /&gt;
&lt;br /&gt;
* All event definitions are classes extending the \core\event\base class.&lt;br /&gt;
* Events are triggered by creating a new instance of the class event and executing $event-&amp;gt;trigger().&lt;br /&gt;
* Each event class names are a unique identifier of the event.&lt;br /&gt;
* Class names and namespace follow the identifier scheme \&#039;&#039;&#039;frankenstyle_component&#039;&#039;&#039;\event\&#039;&#039;&#039;some_object_action&#039;&#039;&#039;. Core events have prefix &#039;core_&#039;.&lt;br /&gt;
* Each event class is defined in a separate file. File name and location must match the class name for the use of auto loading, for example: &#039;&#039;&#039;plugindir&#039;&#039;&#039;/classes/event/&#039;&#039;&#039;something_happened&#039;&#039;&#039;.php&lt;br /&gt;
* The event identifier suffix has the form &#039;&#039;some_object_action&#039;&#039;  (&#039;&#039;&#039;something_happened&#039;&#039;&#039; in the example above) and should follow the standard naming convention. The last word after the underscore is automatically parsed as its action, the rest of word is its object.&lt;br /&gt;
&lt;br /&gt;
Decision:[[#Verb_list| Recommended verb list]]&lt;br /&gt;
&lt;br /&gt;
Examples: \core\event\course_completed, \mod_assign\event\submission_commented, \mod_forum\event\post_shared, \mod_forum\event\post_responded, etc.&lt;br /&gt;
&lt;br /&gt;
* Ideally, it should be possible to trigger an event without gathering additional information for the event. To reduce the cost of data gathering, specifically the cost of database reads, at least the minimal values needed to trigger an event should be already available in variables.&lt;br /&gt;
&lt;br /&gt;
An example of triggering an event:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$event = \mod_myplugin\event\something_happened::create(array(&#039;context&#039; =&amp;gt; $context, &#039;objectid&#039; =&amp;gt; YYY, &#039;other&#039; =&amp;gt; ZZZ));&lt;br /&gt;
// ... code that may add some record snapshots&lt;br /&gt;
$event-&amp;gt;trigger();&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Use of PHP autoloading ===&lt;br /&gt;
&lt;br /&gt;
All new OOP APIs in Moodle 2.6 onwards are going to use class auto loading - see [[Automatic class loading]]. New events use PHP within strictly defined namespaces, which concentrate all event classes in the classes/event/ subdirectory.&lt;br /&gt;
&lt;br /&gt;
=== Why separate classes? ===&lt;br /&gt;
Pros&lt;br /&gt;
* Maintainability - It is much easier to review, debug and integrate.&lt;br /&gt;
* Self documenting, behaviour is combined with definition.&lt;br /&gt;
* It is extremely flexible for plugin developers and core devs too.&lt;br /&gt;
* Automatic lists events can be generated without being installed - PHPDocs as events documentation.&lt;br /&gt;
* It is included only when needed using autoloading.&lt;br /&gt;
* Self-validating data structure (by PHP).&lt;br /&gt;
* Some developers will find it easier to copy whole class files as templates.&lt;br /&gt;
Cons&lt;br /&gt;
* Big learning curve for developers without OOP skills (all other new subsystems in Moodle already use OOP, you can not code without these skills any more).&lt;br /&gt;
* Some developers may find it harder to copy-and-paste examples because they will need to create new class first and use it afterwards (this can be viewed as a benefit because it forces developers to think more about events).&lt;br /&gt;
* This has increased the number lines of code in Moodle for events and logging.&lt;br /&gt;
&lt;br /&gt;
== Information contained in events ==&lt;br /&gt;
&lt;br /&gt;
Events have to contain as much information as they can, but this should not affect the performance. That&#039;s why part of the information is available in properties, and the rest via methods. This allows for delayed computation of data until the time it is really needed, if it ever is.&lt;br /&gt;
&lt;br /&gt;
=== Properties ===&lt;br /&gt;
&lt;br /&gt;
The following is a list of properties that the developer has to pass to the event upon creation, or ones that can be automatically generated where possible and cost free. Some of these properties are not mandatory.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Property name&lt;br /&gt;
! Title&lt;br /&gt;
! Type&lt;br /&gt;
! Required&lt;br /&gt;
! Comment&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;eventname&#039;&#039;&lt;br /&gt;
| Event name&lt;br /&gt;
| &#039;&#039;static, automatic from class name&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| Automatically computed by copying class name&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;component&#039;&#039;&lt;br /&gt;
| Component&lt;br /&gt;
| &#039;&#039;static, automatic from top namespace&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| Component declaring the event, automatically computed from class name.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;action&#039;&#039;&lt;br /&gt;
| Action&lt;br /&gt;
| &#039;&#039;static, automatic from last word in class name&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| Can be automatically computed from class name.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;target&#039;&#039;&lt;br /&gt;
| target of action&lt;br /&gt;
| &#039;&#039;static, automatic from class name&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| Target on which the action is taken, can be automatically computed from class name.&lt;br /&gt;
|-&lt;br /&gt;
| objecttable&lt;br /&gt;
| Database table name&lt;br /&gt;
| static&lt;br /&gt;
| optional (Must be set if objetid present)&lt;br /&gt;
| Database table name which represents the event object to the best. Never use a relationship table here.&lt;br /&gt;
|-&lt;br /&gt;
| objectid&lt;br /&gt;
| Object ID&lt;br /&gt;
| variable&lt;br /&gt;
| Optional (Must be set if objettable present)&lt;br /&gt;
| Id of the object record from objecttable.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;&#039;&#039;crud&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
| Transaction type&lt;br /&gt;
| &#039;&#039;static&#039;&#039;&lt;br /&gt;
| optional&lt;br /&gt;
| One of [crud] letters. Statically declared in the event class method init().&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;&#039;&#039;level&#039;&#039;&#039;&#039;&#039; / &#039;&#039;&#039;&#039;&#039;edulevel&#039;&#039;&#039;&#039;&#039;&lt;br /&gt;
| Level&lt;br /&gt;
| &#039;&#039;static&#039;&#039;&lt;br /&gt;
| optional&lt;br /&gt;
| Level of educational value of the event. Statically declared in the event class method init(). Changed from &#039;&#039;&#039;&#039;&#039;level&#039;&#039;&#039;&#039;&#039; to &#039;&#039;&#039;&#039;&#039;edulevel&#039;&#039;&#039;&#039;&#039; in Moodle 2.7 (See below for more details)&lt;br /&gt;
|-&lt;br /&gt;
| contextid&lt;br /&gt;
| Context ID&lt;br /&gt;
| mandatory&lt;br /&gt;
| -&lt;br /&gt;
|-&lt;br /&gt;
| contextlevel&lt;br /&gt;
| Context level&lt;br /&gt;
| automatic from context&lt;br /&gt;
| -&lt;br /&gt;
| This tells you if it was a course, activity, course category, etc.&lt;br /&gt;
|-&lt;br /&gt;
| contextinstanceid&lt;br /&gt;
| Context instanceid&lt;br /&gt;
| automatic from context&lt;br /&gt;
| -&lt;br /&gt;
| Based on context level this may be course id , course module id, course category, etc.&lt;br /&gt;
|-&lt;br /&gt;
| userid&lt;br /&gt;
| User ID&lt;br /&gt;
| defaults to current user&lt;br /&gt;
| -&lt;br /&gt;
| User ID, or 0 when not logged in, or -1 when other (System, CLI, Cron, ...)&lt;br /&gt;
|-&lt;br /&gt;
| courseid&lt;br /&gt;
| Affected course&lt;br /&gt;
| defaults to course context from context&lt;br /&gt;
| -&lt;br /&gt;
| This is used only for contexts at and bellow course level - this can be used to filter events by course (includes all course activities)&lt;br /&gt;
|-&lt;br /&gt;
| relateduserid&lt;br /&gt;
| Affected user&lt;br /&gt;
| variable&lt;br /&gt;
| optional&lt;br /&gt;
| Is this action related to some user? This could be used for some personal timeline view.&lt;br /&gt;
|-&lt;br /&gt;
| anonymous&lt;br /&gt;
| Anonymous action&lt;br /&gt;
| variable&lt;br /&gt;
| optional (Defaults to 0)&lt;br /&gt;
| Is this action anonymous? Reports should ignore events with anonymous set to 1.&lt;br /&gt;
|-&lt;br /&gt;
| other&lt;br /&gt;
| All other data&lt;br /&gt;
| variable array&lt;br /&gt;
| optional&lt;br /&gt;
| Any other fields needed for event description - scalars or arrays, must be serialisable using json_encode()&lt;br /&gt;
|-&lt;br /&gt;
| timecreated&lt;br /&gt;
| Time of the event&lt;br /&gt;
| automatic&lt;br /&gt;
| -&lt;br /&gt;
| Time when the event was triggered.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
* static: same for all event instances of this class&lt;br /&gt;
* variable: might not be same for all instances of this event class&lt;br /&gt;
* mandatory: required in order to trigger the event&lt;br /&gt;
* optional: not necessarily required to trigger the event&lt;br /&gt;
&lt;br /&gt;
==== Level property ====&lt;br /&gt;
&lt;br /&gt;
The edulevel property helps define the educational value of the event. It is intentional that the list is limited to only 3 different constants as having too many options would make it harder for developers to pick the right one(s). We also have to keep in mind that this is not supposed to answer all the questions about a particular event, other event properties like the courseid, the context, the component name can be used with the level to get more granular reports.&lt;br /&gt;
&lt;br /&gt;
Remember that this is event based. If the user that has triggered the event is not really &amp;quot;participating&amp;quot; because he is an admin, or a manager, then it is the job of the report to filter those. The event itself has a static educational level.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Teaching&#039;&#039;&#039; (LEVEL_TEACHING: 1)&lt;br /&gt;
&lt;br /&gt;
Any event/action that is performed by someone (typically a teacher) and has a teaching value (anything that is effecting the learning experience/environment of the students). This should not be combined with &amp;quot;Participating&amp;quot; events.&lt;br /&gt;
&lt;br /&gt;
Valid events:&lt;br /&gt;
&lt;br /&gt;
* A teacher grading a student&lt;br /&gt;
* A teacher modifying the course settings&lt;br /&gt;
* A teacher adding a new section to the course page&lt;br /&gt;
* A teacher modifying a module settings&lt;br /&gt;
* A teacher adding a page to course&lt;br /&gt;
* A teacher leaving a feedback&lt;br /&gt;
&lt;br /&gt;
INVALID events:&lt;br /&gt;
&lt;br /&gt;
* A teacher posting in a forum (it might affect the learning experience, but not necessarily, so the teacher is just participating)&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Participating&#039;&#039;&#039; (LEVEL_PARTICIPATING: 2)&lt;br /&gt;
&lt;br /&gt;
Any event/action that is performed by a user, that is related (or could be related) to his learning experience.&lt;br /&gt;
&lt;br /&gt;
Valid events:&lt;br /&gt;
&lt;br /&gt;
* A user posting to a forum&lt;br /&gt;
* A user submitting an assignment&lt;br /&gt;
* A user blogging&lt;br /&gt;
* A user reading someone&#039;s blog&lt;br /&gt;
* A user posting a comment&lt;br /&gt;
* A user chatting on a chat activity&lt;br /&gt;
* A user viewing the course page&lt;br /&gt;
* A user deletes a blog post&lt;br /&gt;
&lt;br /&gt;
INVALID events:&lt;br /&gt;
&lt;br /&gt;
* A user updating his profile&lt;br /&gt;
* A user visiting someone&#039;s profile&lt;br /&gt;
* A user viewing his /my/ page&lt;br /&gt;
* A user sending a message to another one&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Other&#039;&#039;&#039; (LEVEL_OTHER: 0)&lt;br /&gt;
&lt;br /&gt;
Any other action, whether they are related to the site administration, or are specific to user. They do not have any educational value.&lt;br /&gt;
&lt;br /&gt;
=== Methods ===&lt;br /&gt;
&lt;br /&gt;
The computation of this data is not required by default, but can be accessed by any event observer if need be.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Method&lt;br /&gt;
! Comment&lt;br /&gt;
|-&lt;br /&gt;
| get_name()&lt;br /&gt;
| Returns localised name of the event, it is the same for all instances.&lt;br /&gt;
|-&lt;br /&gt;
| get_description()&lt;br /&gt;
| Returns non-localised description of one particular event. There are plans to make this localised in future.&lt;br /&gt;
&lt;br /&gt;
It is recommended that the string begins with identifying the user who triggered the event by providing the user id in quotations. This applies to other variables used in the description as well. If an activity is also mentioned then the course module id should be used, not the id from the activity table. For example &amp;quot;The user with the id &#039;$this-&amp;gt;userid&#039; updated the activity &#039;youractivity&#039; with the course module id &#039;$this-&amp;gt;contextinstanceid&#039;.&amp;quot;.&lt;br /&gt;
|-&lt;br /&gt;
| can_view($user)&lt;br /&gt;
| This method is deprecated, please do not use this.&lt;br /&gt;
|-&lt;br /&gt;
| get_url()&lt;br /&gt;
| Returns Moodle URL where the event can be observed afterwards. Can be null, if no valid location is present.&lt;br /&gt;
|-&lt;br /&gt;
| get_legacy_eventname()&lt;br /&gt;
| Information necessary for event backward compatibility.&lt;br /&gt;
|-&lt;br /&gt;
| get_legacy_eventdata()&lt;br /&gt;
| Information necessary for event backward compatibility.&lt;br /&gt;
|-&lt;br /&gt;
| get_legacy_logdata()&lt;br /&gt;
| Information necessary for logging backward compatibility.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===Record caching===&lt;br /&gt;
&lt;br /&gt;
The standard event data may not contain all the information observers need. The built-in record snapshot support in events allows developers to attach more auxiliary information when triggering events, it may be for example course record, some record that was just deleted, etc. &lt;br /&gt;
* Snapshots are expected to be an exact snapshot of the database table at the instance when the event was triggered.&lt;br /&gt;
* Snapshot can be set and requested for any table. If not cached, it defaults to direct $DB-&amp;gt;get_record calls.&lt;br /&gt;
* Please be aware that the snapshots are not stored in the event, and cannot be restored.&lt;br /&gt;
* Snapshot are supposed to be used only by observers. They should never be used by reports or by the event itself.&lt;br /&gt;
* When using a snapshot in an observer, please make sure you are taking proper care of handling exceptions as the record you are requesting could have been deleted in the time, in between your observer is notified about the event and the trigger.&lt;br /&gt;
* We recommend all delete event must add snapshot of the deleted record.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    $event = \core\event\role_assigned::create(&lt;br /&gt;
        array(&#039;context&#039;=&amp;gt;$context, &#039;objectid&#039;=&amp;gt;$ra-&amp;gt;roleid, &#039;relateduserid&#039;=&amp;gt;$ra-&amp;gt;userid,&lt;br /&gt;
            &#039;other&#039;=&amp;gt;array(&#039;id&#039;=&amp;gt;$ra-&amp;gt;id, &#039;component&#039;=&amp;gt;$ra-&amp;gt;component, &#039;itemid&#039;=&amp;gt;$ra-&amp;gt;itemid)));&lt;br /&gt;
    $event-&amp;gt;add_record_snapshot(&#039;role_assignments&#039;, $ra);&lt;br /&gt;
    $event-&amp;gt;trigger();&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    $event = \core\event\role_unassigned::create(&lt;br /&gt;
        array(&#039;context&#039;=&amp;gt;$context, &#039;objectid&#039;=&amp;gt;$ra-&amp;gt;roleid, &#039;relateduserid&#039;=&amp;gt;$ra-&amp;gt;userid,&lt;br /&gt;
            &#039;other&#039;=&amp;gt;array(&#039;id&#039;=&amp;gt;$ra-&amp;gt;id, &#039;component&#039;=&amp;gt;$ra-&amp;gt;component, &#039;itemid&#039;=&amp;gt;$ra-&amp;gt;itemid)));&lt;br /&gt;
    $event-&amp;gt;add_record_snapshot(&#039;role_assignments&#039;, $ra);&lt;br /&gt;
    $event-&amp;gt;trigger();&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The related methods are:&lt;br /&gt;
* public function add_record_snapshot($tablename, $record)&lt;br /&gt;
* public function get_record_snapshot($tablename, $id)&lt;br /&gt;
&lt;br /&gt;
== Events naming convention ==&lt;br /&gt;
&lt;br /&gt;
Clear event names help developers when reading what events are triggered, and defining the events properties when defining the event class.&lt;br /&gt;
&lt;br /&gt;
 Decision: \&amp;lt;component&amp;gt;\event\&amp;lt;some_object&amp;gt;_&amp;lt;verb&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Existing events ===&lt;br /&gt;
&lt;br /&gt;
List of existing events in Moodle code base, along with their 2.5 couterparts.&lt;br /&gt;
&lt;br /&gt;
This list is out of date. For a full list of events check out the [https://docs.moodle.org/en/Event_list_report Event list report] which is located in &amp;quot;Site administration &amp;gt; Reports &amp;gt; Event list&amp;quot; of your Moodle installation (2.7+).&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Fully qualified event name&lt;br /&gt;
! 2.5 name&lt;br /&gt;
! Component&lt;br /&gt;
! Object&lt;br /&gt;
! Action (Verb)&lt;br /&gt;
! Comment&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;assignsubmission_comments\event\comment_created&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| assignsubmission_comments&lt;br /&gt;
| comment&lt;br /&gt;
| created&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;assignsubmission_comments\event\comment_deleted&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| assignsubmission_comments&lt;br /&gt;
| comment&lt;br /&gt;
| deleted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;assignsubmission_file\event\assessable_uploaded&#039;&#039;&#039;&lt;br /&gt;
| assessable_file_uploaded&lt;br /&gt;
| assignsubmission_file&lt;br /&gt;
| assessable&lt;br /&gt;
| uploaded&lt;br /&gt;
| To be deprecated MDL-35197&lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;assignsubmission_file\event\submission_created&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| assignsubmission_file&lt;br /&gt;
| submission&lt;br /&gt;
| created&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;assignsubmission_file\event\submission_updated&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| assignsubmission_file&lt;br /&gt;
| submission&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;assignsubmission_onlinetext\event\assessable_uploaded&#039;&#039;&#039;&lt;br /&gt;
| assessable_content_uploaded&lt;br /&gt;
| assignsubmission_onlinetext&lt;br /&gt;
| assessable&lt;br /&gt;
| uploaded&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;assignsubmission_onlinetext\event\submission_created&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| assignsubmission_onlinetext&lt;br /&gt;
| submission&lt;br /&gt;
| created&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;assignsubmission_onlinetext\event\submission_updated&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| assignsubmission_onlinetext&lt;br /&gt;
| submission&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;block_comments\event\comment_created&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| block_comments&lt;br /&gt;
| comment&lt;br /&gt;
| created&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;block_comments\event\comment_deleted&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| block_comments&lt;br /&gt;
| comment&lt;br /&gt;
| deleted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;booktool_exportimscp\event\book_exported&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| booktool_exportimscp&lt;br /&gt;
| book&lt;br /&gt;
| exported&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;booktool_print\event\book_printed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| booktool_print&lt;br /&gt;
| book&lt;br /&gt;
| printed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;booktool_print\event\chapter_printed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| booktool_print&lt;br /&gt;
| chapter&lt;br /&gt;
| printed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\assessable_submitted&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| assessable&lt;br /&gt;
| submitted&lt;br /&gt;
| Abstract class &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\assessable_uploaded&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| assessable&lt;br /&gt;
| uploaded&lt;br /&gt;
| Abstract class &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\base&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| base&lt;br /&gt;
| -&lt;br /&gt;
| Abstract class &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\blog_association_created&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| blog_association&lt;br /&gt;
| created&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\blog_comment_created&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| blog_comment&lt;br /&gt;
| created&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\blog_comment_deleted&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| blog_comment&lt;br /&gt;
| deleted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\blog_entries_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| blog_entries&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\blog_entry_created&#039;&#039;&#039;&lt;br /&gt;
| blog_entry_added&lt;br /&gt;
| core&lt;br /&gt;
| blog_entry&lt;br /&gt;
| created&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\blog_entry_deleted&#039;&#039;&#039;&lt;br /&gt;
| blog_entry_deleted&lt;br /&gt;
| core&lt;br /&gt;
| blog_entry&lt;br /&gt;
| deleted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\blog_entry_updated&#039;&#039;&#039;&lt;br /&gt;
| blog_entry_edited&lt;br /&gt;
| core&lt;br /&gt;
| blog_entry&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\cohort_created&#039;&#039;&#039;&lt;br /&gt;
| cohort_added&lt;br /&gt;
| core&lt;br /&gt;
| cohort&lt;br /&gt;
| created&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\cohort_deleted&#039;&#039;&#039;&lt;br /&gt;
| cohort_deleted&lt;br /&gt;
| core&lt;br /&gt;
| cohort&lt;br /&gt;
| deleted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\cohort_member_added&#039;&#039;&#039;&lt;br /&gt;
| cohort_member_added&lt;br /&gt;
| core&lt;br /&gt;
| cohort_member&lt;br /&gt;
| added&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\cohort_member_removed&#039;&#039;&#039;&lt;br /&gt;
| cohort_member_removed&lt;br /&gt;
| core&lt;br /&gt;
| cohort_member&lt;br /&gt;
| removed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\cohort_updated&#039;&#039;&#039;&lt;br /&gt;
| cohort_updated&lt;br /&gt;
| core&lt;br /&gt;
| cohort&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\comment_created&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| comment&lt;br /&gt;
| created&lt;br /&gt;
| Abstract class &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\comment_deleted&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| comment&lt;br /&gt;
| deleted&lt;br /&gt;
| Abstract class &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\comments_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| comments&lt;br /&gt;
| viewed&lt;br /&gt;
| Abstract class &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\content_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| content&lt;br /&gt;
| viewed&lt;br /&gt;
| Abstract class &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\course_category_created&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| course_category&lt;br /&gt;
| created&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\course_category_deleted&#039;&#039;&#039;&lt;br /&gt;
| course_category_deleted&lt;br /&gt;
| core&lt;br /&gt;
| course_category&lt;br /&gt;
| deleted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\course_category_updated&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| course_category&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\course_completed&#039;&#039;&#039;&lt;br /&gt;
| course_completed&lt;br /&gt;
| core&lt;br /&gt;
| course&lt;br /&gt;
| completed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\course_completion_updated&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| course_completion&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\course_content_deleted&#039;&#039;&#039;&lt;br /&gt;
| course_content_removed&lt;br /&gt;
| core&lt;br /&gt;
| course_content&lt;br /&gt;
| deleted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\course_created&#039;&#039;&#039;&lt;br /&gt;
| course_created&lt;br /&gt;
| core&lt;br /&gt;
| course&lt;br /&gt;
| created&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\course_deleted&#039;&#039;&#039;&lt;br /&gt;
| course_deleted&lt;br /&gt;
| core&lt;br /&gt;
| course&lt;br /&gt;
| deleted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\course_module_completion_updated&#039;&#039;&#039;&lt;br /&gt;
| activity_completion_changed&lt;br /&gt;
| core&lt;br /&gt;
| course_module_completion&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\course_module_created&#039;&#039;&#039;&lt;br /&gt;
| mod_created&lt;br /&gt;
| core&lt;br /&gt;
| course_module&lt;br /&gt;
| created&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\course_module_deleted&#039;&#039;&#039;&lt;br /&gt;
| mod_deleted&lt;br /&gt;
| core&lt;br /&gt;
| course_module&lt;br /&gt;
| deleted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\course_module_instance_list_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| course_module_instance_list&lt;br /&gt;
| viewed&lt;br /&gt;
| Abstract class &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\course_module_instances_list_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| course_module_instances_list&lt;br /&gt;
| viewed&lt;br /&gt;
| Abstract class &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\course_module_updated&#039;&#039;&#039;&lt;br /&gt;
| mod_updated&lt;br /&gt;
| core&lt;br /&gt;
| course_module&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\course_module_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| course_module&lt;br /&gt;
| viewed&lt;br /&gt;
| Abstract class &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\course_reset_ended&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| course_reset&lt;br /&gt;
| ended&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\course_reset_started&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| course_reset&lt;br /&gt;
| started&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\course_restored&#039;&#039;&#039;&lt;br /&gt;
| course_restored&lt;br /&gt;
| core&lt;br /&gt;
| course&lt;br /&gt;
| restored&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\course_section_updated&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| course_section&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\course_updated&#039;&#039;&#039;&lt;br /&gt;
| course_updated&lt;br /&gt;
| core&lt;br /&gt;
| course&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\email_failed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| email&lt;br /&gt;
| failed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\group_created&#039;&#039;&#039;&lt;br /&gt;
| groups_group_created&lt;br /&gt;
| core&lt;br /&gt;
| group&lt;br /&gt;
| created&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\group_deleted&#039;&#039;&#039;&lt;br /&gt;
| groups_group_deleted&lt;br /&gt;
| core&lt;br /&gt;
| group&lt;br /&gt;
| deleted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\group_member_added&#039;&#039;&#039;&lt;br /&gt;
| groups_member_added&lt;br /&gt;
| core&lt;br /&gt;
| group_member&lt;br /&gt;
| added&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\group_member_removed&#039;&#039;&#039;&lt;br /&gt;
| groups_member_removed&lt;br /&gt;
| core&lt;br /&gt;
| group_member&lt;br /&gt;
| removed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\group_updated&#039;&#039;&#039;&lt;br /&gt;
| groups_group_updated&lt;br /&gt;
| core&lt;br /&gt;
| group&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\grouping_created&#039;&#039;&#039;&lt;br /&gt;
| groups_grouping_created&lt;br /&gt;
| core&lt;br /&gt;
| grouping&lt;br /&gt;
| created&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\grouping_deleted&#039;&#039;&#039;&lt;br /&gt;
| groups_grouping_deleted&lt;br /&gt;
| core&lt;br /&gt;
| grouping&lt;br /&gt;
| deleted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\grouping_updated&#039;&#039;&#039;&lt;br /&gt;
| groups_grouping_updated&lt;br /&gt;
| core&lt;br /&gt;
| grouping&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\manager&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| manager&lt;br /&gt;
| anager&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\mnet_access_control_created&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| mnet_access_control&lt;br /&gt;
| created&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\mnet_access_control_updated&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| mnet_access_control&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\note_created&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| note&lt;br /&gt;
| created&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\note_deleted&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| note&lt;br /&gt;
| deleted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\note_updated&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| note&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\notes_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| notes&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\role_allow_assign_updated&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| role_allow_assign&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\role_allow_override_updated&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| role_allow_override&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\role_allow_switch_updated&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| role_allow_switch&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\role_assigned&#039;&#039;&#039;&lt;br /&gt;
| role_assigned&lt;br /&gt;
| core&lt;br /&gt;
| role&lt;br /&gt;
| assigned&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\role_capabilities_updated&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| role_capabilities&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\role_deleted&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| role&lt;br /&gt;
| deleted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\role_unassigned&#039;&#039;&#039;&lt;br /&gt;
| role_unassigned&lt;br /&gt;
| core&lt;br /&gt;
| role&lt;br /&gt;
| unassigned&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\user_created&#039;&#039;&#039;&lt;br /&gt;
| user_created&lt;br /&gt;
| core&lt;br /&gt;
| user&lt;br /&gt;
| created&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\user_deleted&#039;&#039;&#039;&lt;br /&gt;
| user_deleted&lt;br /&gt;
| core&lt;br /&gt;
| user&lt;br /&gt;
| deleted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\user_enrolment_created&#039;&#039;&#039;&lt;br /&gt;
| user_enrolled&lt;br /&gt;
| core&lt;br /&gt;
| user_enrolment&lt;br /&gt;
| created&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\user_enrolment_deleted&#039;&#039;&#039;&lt;br /&gt;
| user_unenrolled&lt;br /&gt;
| core&lt;br /&gt;
| user_enrolment&lt;br /&gt;
| deleted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\user_enrolment_updated&#039;&#039;&#039;&lt;br /&gt;
| user_enrol_modified&lt;br /&gt;
| core&lt;br /&gt;
| user_enrolment&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\user_list_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| user_list&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\user_loggedin&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| user&lt;br /&gt;
| loggedin&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\user_loggedinas&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| user&lt;br /&gt;
| loggedinas&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\user_loggedout&#039;&#039;&#039;&lt;br /&gt;
| user_logout&lt;br /&gt;
| core&lt;br /&gt;
| user&lt;br /&gt;
| loggedout&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\user_login_failed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| user_login&lt;br /&gt;
| failed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\user_profile_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| user_profile&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\user_updated&#039;&#039;&#039;&lt;br /&gt;
| user_updated&lt;br /&gt;
| core&lt;br /&gt;
| user&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\webservice_function_called&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| webservice_function&lt;br /&gt;
| called&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\webservice_login_failed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| webservice_login&lt;br /&gt;
| failed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\webservice_service_created&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| webservice_service&lt;br /&gt;
| created&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\webservice_service_deleted&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| webservice_service&lt;br /&gt;
| deleted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\webservice_service_updated&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| webservice_service&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\webservice_service_user_added&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| webservice_service_user&lt;br /&gt;
| added&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\webservice_service_user_removed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| webservice_service_user&lt;br /&gt;
| removed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\webservice_token_created&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| webservice_token&lt;br /&gt;
| created&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;core\event\webservice_token_sent&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| core&lt;br /&gt;
| webservice_token&lt;br /&gt;
| sent&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;logstore_legacy\event\legacy_logged&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| logstore_legacy&lt;br /&gt;
| legacy&lt;br /&gt;
| logged&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_assign\event\all_submissions_downloaded&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_assign&lt;br /&gt;
| all_submissions&lt;br /&gt;
| downloaded&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_assign\event\assessable_submitted&#039;&#039;&#039;&lt;br /&gt;
| assessable_submitted&lt;br /&gt;
| mod_assign&lt;br /&gt;
| assessable&lt;br /&gt;
| submitted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_assign\event\extension_granted&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_assign&lt;br /&gt;
| extension&lt;br /&gt;
| granted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_assign\event\identities_revealed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_assign&lt;br /&gt;
| identities&lt;br /&gt;
| revealed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_assign\event\marker_updated&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_assign&lt;br /&gt;
| marker&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_assign\event\statement_accepted&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_assign&lt;br /&gt;
| statement&lt;br /&gt;
| accepted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_assign\event\submission_created&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_assign&lt;br /&gt;
| submission&lt;br /&gt;
| created&lt;br /&gt;
| Abstract class &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_assign\event\submission_duplicated&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_assign&lt;br /&gt;
| submission&lt;br /&gt;
| duplicated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_assign\event\submission_graded&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_assign&lt;br /&gt;
| submission&lt;br /&gt;
| graded&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_assign\event\submission_locked&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_assign&lt;br /&gt;
| submission&lt;br /&gt;
| locked&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_assign\event\submission_status_updated&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_assign&lt;br /&gt;
| submission_status&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_assign\event\submission_unlocked&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_assign&lt;br /&gt;
| submission&lt;br /&gt;
| unlocked&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_assign\event\submission_updated&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_assign&lt;br /&gt;
| submission&lt;br /&gt;
| updated&lt;br /&gt;
| Abstract class &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_assign\event\workflow_state_updated&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_assign&lt;br /&gt;
| workflow_state&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_book\event\chapter_created&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_book&lt;br /&gt;
| chapter&lt;br /&gt;
| created&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_book\event\chapter_deleted&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_book&lt;br /&gt;
| chapter&lt;br /&gt;
| deleted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_book\event\chapter_updated&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_book&lt;br /&gt;
| chapter&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_book\event\chapter_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_book&lt;br /&gt;
| chapter&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_book\event\course_module_instance_list_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_book&lt;br /&gt;
| course_module_instance_list&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_book\event\course_module_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_book&lt;br /&gt;
| course_module&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_chat\event\course_module_instance_list_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_chat&lt;br /&gt;
| course_module_instance_list&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_chat\event\message_sent&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_chat&lt;br /&gt;
| message&lt;br /&gt;
| sent&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_chat\event\sessions_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_chat&lt;br /&gt;
| sessions&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_choice\event\answer_submitted&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_choice&lt;br /&gt;
| answer&lt;br /&gt;
| submitted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_choice\event\answer_updated&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_choice&lt;br /&gt;
| answer&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_choice\event\course_module_instance_list_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_choice&lt;br /&gt;
| course_module_instance_list&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_choice\event\course_module_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_choice&lt;br /&gt;
| course_module&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_choice\event\report_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_choice&lt;br /&gt;
| report&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_data\event\comment_created&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_data&lt;br /&gt;
| comment&lt;br /&gt;
| created&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_data\event\comment_deleted&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_data&lt;br /&gt;
| comment&lt;br /&gt;
| deleted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_data\event\course_module_instance_list_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_data&lt;br /&gt;
| course_module_instance_list&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_data\event\course_module_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_data&lt;br /&gt;
| course_module&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_data\event\field_created&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_data&lt;br /&gt;
| field&lt;br /&gt;
| created&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_data\event\field_deleted&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_data&lt;br /&gt;
| field&lt;br /&gt;
| deleted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_data\event\field_updated&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_data&lt;br /&gt;
| field&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_data\event\record_created&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_data&lt;br /&gt;
| record&lt;br /&gt;
| created&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_data\event\record_deleted&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_data&lt;br /&gt;
| record&lt;br /&gt;
| deleted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_data\event\record_updated&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_data&lt;br /&gt;
| record&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_data\event\template_updated&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_data&lt;br /&gt;
| template&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_data\event\template_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_data&lt;br /&gt;
| template&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_feedback\event\course_module_instance_list_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_feedback&lt;br /&gt;
| course_module_instance_list&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_feedback\event\course_module_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_feedback&lt;br /&gt;
| course_module&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_feedback\event\response_deleted&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_feedback&lt;br /&gt;
| response&lt;br /&gt;
| deleted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_feedback\event\response_submitted&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_feedback&lt;br /&gt;
| response&lt;br /&gt;
| submitted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_folder\event\course_module_instance_list_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_folder&lt;br /&gt;
| course_module_instance_list&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_folder\event\course_module_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_folder&lt;br /&gt;
| course_module&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_folder\event\folder_updated&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_folder&lt;br /&gt;
| folder&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_forum\event\assessable_uploaded&#039;&#039;&#039;&lt;br /&gt;
| assessable_content_uploaded&lt;br /&gt;
| mod_forum&lt;br /&gt;
| assessable&lt;br /&gt;
| uploaded&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_forum\event\course_module_instance_list_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_forum&lt;br /&gt;
| course_module_instance_list&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_forum\event\course_searched&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_forum&lt;br /&gt;
| course&lt;br /&gt;
| searched&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_forum\event\discussion_created&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_forum&lt;br /&gt;
| discussion&lt;br /&gt;
| created&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_forum\event\discussion_deleted&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_forum&lt;br /&gt;
| discussion&lt;br /&gt;
| deleted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_forum\event\discussion_moved&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_forum&lt;br /&gt;
| discussion&lt;br /&gt;
| moved&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_forum\event\discussion_updated&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_forum&lt;br /&gt;
| discussion&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_forum\event\discussion_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_forum&lt;br /&gt;
| discussion&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_forum\event\forum_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_forum&lt;br /&gt;
| forum&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_forum\event\post_created&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_forum&lt;br /&gt;
| post&lt;br /&gt;
| created&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_forum\event\post_deleted&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_forum&lt;br /&gt;
| post&lt;br /&gt;
| deleted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_forum\event\post_updated&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_forum&lt;br /&gt;
| post&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_forum\event\readtracking_disabled&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_forum&lt;br /&gt;
| readtracking&lt;br /&gt;
| disabled&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_forum\event\readtracking_enabled&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_forum&lt;br /&gt;
| readtracking&lt;br /&gt;
| enabled&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_forum\event\subscribers_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_forum&lt;br /&gt;
| subscribers&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_forum\event\subscription_created&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_forum&lt;br /&gt;
| subscription&lt;br /&gt;
| created&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_forum\event\subscription_deleted&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_forum&lt;br /&gt;
| subscription&lt;br /&gt;
| deleted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_forum\event\userreport_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_forum&lt;br /&gt;
| userreport&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_glossary\event\comment_created&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_glossary&lt;br /&gt;
| comment&lt;br /&gt;
| created&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_glossary\event\comment_deleted&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_glossary&lt;br /&gt;
| comment&lt;br /&gt;
| deleted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_lesson\event\course_module_instance_list_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_lesson&lt;br /&gt;
| course_module_instance_list&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_lesson\event\course_module_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_lesson&lt;br /&gt;
| course_module&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_lesson\event\essay_assessed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_lesson&lt;br /&gt;
| essay&lt;br /&gt;
| assessed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_lesson\event\essay_attempt_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_lesson&lt;br /&gt;
| essay_attempt&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_lesson\event\highscore_added&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_lesson&lt;br /&gt;
| highscore&lt;br /&gt;
| added&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_lesson\event\highscores_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_lesson&lt;br /&gt;
| highscores&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_lesson\event\lesson_ended&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_lesson&lt;br /&gt;
| lesson&lt;br /&gt;
| ended&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_lesson\event\lesson_started&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_lesson&lt;br /&gt;
| lesson&lt;br /&gt;
| started&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_lti\event\course_module_instance_list_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_lti&lt;br /&gt;
| course_module_instance_list&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_lti\event\course_module_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_lti&lt;br /&gt;
| course_module&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_lti\event\unknown_service_api_called&#039;&#039;&#039;&lt;br /&gt;
| lti_unknown_service_api_call&lt;br /&gt;
| mod_lti&lt;br /&gt;
| unknown_service_api&lt;br /&gt;
| called&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_page\event\course_module_instance_list_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_page&lt;br /&gt;
| course_module_instance_list&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_page\event\course_module_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_page&lt;br /&gt;
| course_module&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_quiz\event\attempt_abandoned&#039;&#039;&#039;&lt;br /&gt;
| quiz_attempt_abandoned&lt;br /&gt;
| mod_quiz&lt;br /&gt;
| attempt&lt;br /&gt;
| abandoned&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_quiz\event\attempt_becameoverdue&#039;&#039;&#039;&lt;br /&gt;
| quiz_attempt_overdue&lt;br /&gt;
| mod_quiz&lt;br /&gt;
| attempt&lt;br /&gt;
| becameoverdue&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_quiz\event\attempt_started&#039;&#039;&#039;&lt;br /&gt;
| quiz_attempt_started&lt;br /&gt;
| mod_quiz&lt;br /&gt;
| attempt&lt;br /&gt;
| started&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_quiz\event\attempt_submitted&#039;&#039;&#039;&lt;br /&gt;
| quiz_attempt_submitted&lt;br /&gt;
| mod_quiz&lt;br /&gt;
| attempt&lt;br /&gt;
| submitted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_resource\event\course_module_instance_list_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_resource&lt;br /&gt;
| course_module_instance_list&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_resource\event\course_module_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_resource&lt;br /&gt;
| course_module&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_scorm\event\attempt_deleted&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_scorm&lt;br /&gt;
| attempt&lt;br /&gt;
| deleted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_scorm\event\course_module_instance_list_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_scorm&lt;br /&gt;
| course_module_instance_list&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_scorm\event\course_module_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_scorm&lt;br /&gt;
| course_module&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_scorm\event\interactions_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_scorm&lt;br /&gt;
| interactions&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_scorm\event\report_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_scorm&lt;br /&gt;
| report&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_scorm\event\sco_launched&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_scorm&lt;br /&gt;
| sco&lt;br /&gt;
| launched&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_scorm\event\tracks_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_scorm&lt;br /&gt;
| tracks&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_scorm\event\user_report_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_scorm&lt;br /&gt;
| user_report&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_url\event\course_module_instance_list_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_url&lt;br /&gt;
| course_module_instance_list&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_url\event\course_module_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_url&lt;br /&gt;
| course_module&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_wiki\event\comment_created&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_wiki&lt;br /&gt;
| comment&lt;br /&gt;
| created&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_wiki\event\comment_deleted&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_wiki&lt;br /&gt;
| comment&lt;br /&gt;
| deleted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_wiki\event\comments_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_wiki&lt;br /&gt;
| comments&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_wiki\event\course_module_instance_list_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_wiki&lt;br /&gt;
| course_module_instance_list&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_wiki\event\course_module_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_wiki&lt;br /&gt;
| course_module&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_wiki\event\page_created&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_wiki&lt;br /&gt;
| page&lt;br /&gt;
| created&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_wiki\event\page_deleted&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_wiki&lt;br /&gt;
| page&lt;br /&gt;
| deleted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_wiki\event\page_diff_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_wiki&lt;br /&gt;
| page_diff&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_wiki\event\page_history_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_wiki&lt;br /&gt;
| page_history&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_wiki\event\page_locks_deleted&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_wiki&lt;br /&gt;
| page_locks&lt;br /&gt;
| deleted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_wiki\event\page_map_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_wiki&lt;br /&gt;
| page_map&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_wiki\event\page_updated&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_wiki&lt;br /&gt;
| page&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_wiki\event\page_version_deleted&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_wiki&lt;br /&gt;
| page_version&lt;br /&gt;
| deleted&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_wiki\event\page_version_restored&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_wiki&lt;br /&gt;
| page_version&lt;br /&gt;
| restored&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_wiki\event\page_version_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_wiki&lt;br /&gt;
| page_version&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_wiki\event\page_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_wiki&lt;br /&gt;
| page&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_workshop\event\assessable_uploaded&#039;&#039;&#039;&lt;br /&gt;
| assessable_content_uploaded&lt;br /&gt;
| mod_workshop&lt;br /&gt;
| assessable&lt;br /&gt;
| uploaded&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_workshop\event\assessment_evaluated&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_workshop&lt;br /&gt;
| assessment&lt;br /&gt;
| evaluated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_workshop\event\assessment_evaluations_reset&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_workshop&lt;br /&gt;
| assessment_evaluations&lt;br /&gt;
| reset&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_workshop\event\assessment_reevaluated&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_workshop&lt;br /&gt;
| assessment&lt;br /&gt;
| reevaluated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_workshop\event\course_module_viewed&#039;&#039;&#039;&lt;br /&gt;
| workshop_viewed&lt;br /&gt;
| mod_workshop&lt;br /&gt;
| course_module&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_workshop\event\instances_list_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_workshop&lt;br /&gt;
| instances_list&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_workshop\event\phase_switched&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_workshop&lt;br /&gt;
| phase&lt;br /&gt;
| switched&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_workshop\event\submission_assessed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_workshop&lt;br /&gt;
| submission&lt;br /&gt;
| assessed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_workshop\event\submission_created&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_workshop&lt;br /&gt;
| submission&lt;br /&gt;
| created&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_workshop\event\submission_reassessed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_workshop&lt;br /&gt;
| submission&lt;br /&gt;
| reassessed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_workshop\event\submission_updated&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_workshop&lt;br /&gt;
| submission&lt;br /&gt;
| updated&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;mod_workshop\event\submission_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| mod_workshop&lt;br /&gt;
| submission&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;report_log\event\content_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| report_log&lt;br /&gt;
| content&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;report_loglive\event\content_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| report_loglive&lt;br /&gt;
| content&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;report_outline\event\content_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| report_outline&lt;br /&gt;
| content&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;report_participation\event\content_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| report_participation&lt;br /&gt;
| content&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
| &#039;&#039;&#039;report_stats\event\content_viewed&#039;&#039;&#039;&lt;br /&gt;
| -&lt;br /&gt;
| report_stats&lt;br /&gt;
| content&lt;br /&gt;
| viewed&lt;br /&gt;
| &lt;br /&gt;
|- &lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Verb list ===&lt;br /&gt;
All events must use a verb from this list. New verbs can be added to this list if required, but additions should only be made if there is no valid alternative (we want to keep this list as small as possible).&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
!verb&lt;br /&gt;
!Explanation&lt;br /&gt;
!Source&lt;br /&gt;
|-&lt;br /&gt;
|abandoned&lt;br /&gt;
|When a attempt is abandoned by user (Quiz attempt)&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|accepted&lt;br /&gt;
|Example: Accepting a statement when submitting an assignment.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|added&lt;br /&gt;
|	Used to represent &amp;quot;something that already exists is now part of/bound to another entity&amp;quot;. Examples: &amp;quot;Admin added role to user X&amp;quot;, &amp;quot;Admin added user X to group A&amp;quot;. Wrong example: &amp;quot;User added course in category&amp;quot; because it is a &#039;move&#039; action, except if a course can be part of multiple categories. The good examples work because: A user can have multiple roles, a user can be in multiple groups.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|answered&lt;br /&gt;
| Indicates the actor responded to a Question&lt;br /&gt;
|Tincan&lt;br /&gt;
|-&lt;br /&gt;
|assessed&lt;br /&gt;
| Some submitted material has been assessed&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|assigned&lt;br /&gt;
| Assign some privilege or role to user.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|attempted&lt;br /&gt;
| Trying to do an activity. Example: attempting a Math class.&lt;br /&gt;
|Tincan&lt;br /&gt;
|-&lt;br /&gt;
|awarded&lt;br /&gt;
| ex:-teacher awarded student a badge.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|backedup&lt;br /&gt;
| When a backup has been performed.&lt;br /&gt;
|Moodle	&lt;br /&gt;
|-&lt;br /&gt;
|becomeoverdue&lt;br /&gt;
| When an activity is overdue Example: Quiz attempt is overdue&lt;br /&gt;
|Moodle	&lt;br /&gt;
|-&lt;br /&gt;
|called&lt;br /&gt;
| When a call to something is made like an API @see unknown_service_api_called.php&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|commented&lt;br /&gt;
|Offered an opinion or written experience of the activity. Can be used with the learner as the actor or a system as an actor. Comments can be sent from either party with the idea that the other will read and react to the content.&lt;br /&gt;
|Tincan&lt;br /&gt;
|-&lt;br /&gt;
|completed&lt;br /&gt;
|To experience the activity in its entirety. Used to affirm the completion of content. This can be simply experiencing all the content, be tied to objectives or interactions, or determined in any other way. Any content that has been initialized, but not yet completed, should be considered incomplete. There is no verb to &#039;incomplete&#039; an activity, one would void the statement which completes the activity.&amp;quot;&lt;br /&gt;
|Tincan&lt;br /&gt;
|-&lt;br /&gt;
|created&lt;br /&gt;
|Used to represent &amp;quot;something new has been created&amp;quot;.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|deleted&lt;br /&gt;
|Used to indicate the object in context was deleted.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|disabled&lt;br /&gt;
|When an activity is disabled. Example: forum read tracking disabled.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|downloaded&lt;br /&gt;
|When a user download file from user. Example submission/report downloaded.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|duplicated&lt;br /&gt;
|For something that has been copied.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|enabled&lt;br /&gt;
|When some setting is enabled. Example: forum read tracking enabled.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|ended&lt;br /&gt;
|When a process ends. Example: Lesson ended or course reset ended.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|evaluated&lt;br /&gt;
|Material has been evaluated.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|exported&lt;br /&gt;
|When a report is exported in certain format.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|failed&lt;br /&gt;
|Learner did not perform the activity to a level of pre-determined satisfaction. Used to affirm the lack of success a learner experienced within the learning content in relation to a threshold. If the user performed below the minimum to the level of this threshold, the content is &#039;failed&#039;. The opposite of &#039;passed&#039;. This is also used in case when message sending is failed.&lt;br /&gt;
|Tincan&lt;br /&gt;
|-&lt;br /&gt;
|graded&lt;br /&gt;
|Used to represent an activity was graded.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|granted&lt;br /&gt;
|User is granted some extension or capability. Example: extension granted for submission.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|imported&lt;br /&gt;
|The act of moving an object into another location or system.&lt;br /&gt;
|Tincan&lt;br /&gt;
|-&lt;br /&gt;
|launched&lt;br /&gt;
|When an external object is launched. Try consider started if there is related stopped event.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|locked&lt;br /&gt;
|When an activity is locked. Should have a related unlocked event.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|loggedin/loggedout&lt;br /&gt;
|	For login and logout.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|loggedinas&lt;br /&gt;
|	 If user is logged in as different user. This is used by only one event (user_loggedinas). Adding this verb makes event name more clear, then using loggedin verb.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|locked&lt;br /&gt;
|&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|moved&lt;br /&gt;
|	Used to indicate the object in context was moved.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|passed&lt;br /&gt;
|Used to affirm the success a learner experienced within the learning content in relation to a threshold. If the user performed at a minimum to the level of this threshold, the content is &#039;passed&#039;. The opposite of &#039;failed&#039;.&lt;br /&gt;
|Tincan&lt;br /&gt;
|-&lt;br /&gt;
|printed&lt;br /&gt;
|Something is printed.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|reassessed&lt;br /&gt;
|Submitted material has been assessed again.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|reevaluated&lt;br /&gt;
|Material has been evaluated again.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|removed&lt;br /&gt;
|By opposition to &amp;quot;Added&amp;quot;. This does not mean that the object has been deleted, but removed from the entity, or not bound to it any more.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|reset&lt;br /&gt;
|Sets one or more properties back to the default value.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|restored&lt;br /&gt;
|When restoring a backup. Rolling back to a previous state.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|revealed&lt;br /&gt;
|Some identity is revealed. Example: Identities revealed after blind marking.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|searched&lt;br /&gt;
|Something is searched. Example: searched in course.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|sent&lt;br /&gt;
|Message sent.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|started&lt;br /&gt;
|Some activity started&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|submitted&lt;br /&gt;
|This is very close to &amp;quot;Attempted&amp;quot;. Depends on context which one should be used. For example:- &amp;quot;Admin submitted a form. Student attempted a quiz.&amp;quot;  is correct, however some cases might not be as clear as the previous example. We can say both &amp;quot;Student submitted an assignment&amp;quot; or &amp;quot;student attempted an assignment&amp;quot;. We need to make the difference clear.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|suspended&lt;br /&gt;
| Suspend something. (example a user)&lt;br /&gt;
| Tincan (However the context is different)&lt;br /&gt;
|-&lt;br /&gt;
|switched&lt;br /&gt;
|Something has been switched. For example:- The workshop phase has been switched to assessment&amp;quot;&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|unassigned&lt;br /&gt;
|As opposed to assigned. When some role is unassigned.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|unlocked&lt;br /&gt;
|&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|upgraded&lt;br /&gt;
|Something was upgraded, some module probably&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|updated&lt;br /&gt;
|Used to indicate the object in context was updated. Simple example is &amp;quot;Admin updated course xyz&amp;quot;.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|uploaded&lt;br /&gt;
|When an assignment is uploaded.&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|viewed&lt;br /&gt;
|Something has been viewed. For example:- &amp;quot;Student viewed chapter 1 of book 1.&amp;quot;&lt;br /&gt;
|Moodle&lt;br /&gt;
|-&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Rules ===&lt;br /&gt;
&lt;br /&gt;
==== Use singular ====&lt;br /&gt;
&lt;br /&gt;
Plurals must be used on objects when it&#039;s a &#039;&#039;One to Many&#039;&#039; relationship. Ex: bulk import, mass deletion, ... In any other case, use the singular.&lt;br /&gt;
&lt;br /&gt;
==== Ends with a verb ====&lt;br /&gt;
&lt;br /&gt;
The last word (after the last underscore) must be a verb.&lt;br /&gt;
&lt;br /&gt;
=== Deprecated events ===&lt;br /&gt;
Following are the events that were supported in 2.5, but deprecated in 2.6 or later&lt;br /&gt;
&lt;br /&gt;
groups_groupings_deleted (see MDL-41312)&lt;br /&gt;
&lt;br /&gt;
groups_groupings_groups_removed (see MDL-41312)&lt;br /&gt;
&lt;br /&gt;
groups_groups_deleted (see MDL-41312)&lt;br /&gt;
&lt;br /&gt;
groups_members_removed (see MDL-41312)&lt;br /&gt;
&lt;br /&gt;
== Shared events ==&lt;br /&gt;
&lt;br /&gt;
 Decision: Not supported at this stage.&lt;br /&gt;
&lt;br /&gt;
In Moodle 2.5 we have a good example of a shared event: &#039;assessable_content_uploaded&#039; which is triggered in &#039;&#039;forum&#039;&#039;, &#039;assignment&#039;&#039; and &#039;&#039;workshop&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
The problem with shared events is that we cannot easily track what component triggered them. Of course we could add a new property to the event to keep track of that, but we would soon need more information and more properties. Also, in the case of a logger, the event received would be unique, where in fact it should be considered different depending on the component firing it.&lt;br /&gt;
&lt;br /&gt;
In our first implementation, we will create one specific event per module. This flexibility does not prevent any observer from capturing them, but still makes sure that the consistency and specificity of each event is maintained.&lt;br /&gt;
&lt;br /&gt;
It could happen that some events are defined in core and shared, but this should not really happen as low-level APIs should trigger the event, and a module should call that low API instead of doing the job itself.&lt;br /&gt;
&lt;br /&gt;
== One to many ==&lt;br /&gt;
&lt;br /&gt;
 Decision: Each event should have a one to one relationship. We can reconsider this at a later stage, if the performance hit is extremely high.&lt;br /&gt;
&lt;br /&gt;
In 2.5, some events are triggered when an action happens on multiple objects. We have to decide whether we want to keep supporting &#039;&#039;One to Many&#039;&#039; events or not.&lt;br /&gt;
&lt;br /&gt;
Keeping a list of all changes for multiple actions may be problematic because you would have to keep them all in memory until all things are processed. This might also result in the order of events being incorrect. The only correct solution seems to be to trigger each item individually and then many things at the end. Performance needs to be improved elsewhere...&lt;br /&gt;
&lt;br /&gt;
=== Accuracy ===&lt;br /&gt;
&lt;br /&gt;
When uploading a bunch of users using the CSV upload feature, if only one event is triggered, it means that the observers of &#039;&#039;user_created&#039;&#039; won&#039;t be triggered. And so some functionality can be lost as, as a plugin developer, I expect this &#039;&#039;user_created&#039;&#039; to be triggered regardless of the way they have been uploaded. Of course, the developer could observe the event &#039;&#039;bulk_user_imported&#039;&#039;, but that means that he could miss some relevant observers.&lt;br /&gt;
&lt;br /&gt;
This applies to existing events.&lt;br /&gt;
&lt;br /&gt;
=== Performance ===&lt;br /&gt;
&lt;br /&gt;
Triggering one event is cheaper then repeating the same events x number of times...&lt;br /&gt;
&lt;br /&gt;
=== Information tracking ===&lt;br /&gt;
&lt;br /&gt;
A &#039;&#039;bulk&#039;&#039; event, might not be verbose enough to allow for proper logging afterwards. Though this is the responsibility of the logger, we probably want to make it easy to store relevant information.&lt;br /&gt;
&lt;br /&gt;
=== Double event ===&lt;br /&gt;
&lt;br /&gt;
In the case of a bulk user import, if we were to trigger an event per user created, we probably want to trigger one event &#039;user_bulk_upload_started&#039; when the action starts.&lt;br /&gt;
&lt;br /&gt;
== Unit Testing ==&lt;br /&gt;
&lt;br /&gt;
With unit testing for this system we want to assert the following:&lt;br /&gt;
&lt;br /&gt;
* That event strict validation and custom validation works.&lt;br /&gt;
* Missing event data is auto filled with accurate data.&lt;br /&gt;
* Typos in properties passed to ::create() are captured (if we decide to validate).&lt;br /&gt;
* The legacy methods return the expected values (use assertEventLegacyData() and assertEventLegacyData())&lt;br /&gt;
* The class properties are correctly overridden (crud, level, action, object, ...).&lt;br /&gt;
* The properties automatically generated (component, name, ...) are correct.&lt;br /&gt;
* Events are dispatched to the corresponding observers.&lt;br /&gt;
* Events are dispatched to the corresponding legacy handlers.&lt;br /&gt;
* Events are dispatched to the * observers.&lt;br /&gt;
* Events perform an add_to_log() if it has legacy log data.&lt;br /&gt;
* &#039;Events restore&#039; restored the whole event data, and does not miss any information.&lt;br /&gt;
* &#039;Events restore&#039; does not generate any extra information.&lt;br /&gt;
* Event methods should check context object or avoid using it, as context might not be valid at time of event restore (use assertEventContextNotUsed())&lt;br /&gt;
&lt;br /&gt;
= PHP docs =&lt;br /&gt;
* All events php docs must include @since parameter, indicating when the event was first included in standard Moodle distribution.&lt;br /&gt;
* All events must declare the $other properties using mark down in the php docs. This later might be converted to a self documenting structure. (See MDL-45108)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * blog_association_created&lt;br /&gt;
 *&lt;br /&gt;
 * Class for event to be triggered when a new blog entry is associated with a context.&lt;br /&gt;
 *&lt;br /&gt;
 * @property-read array $other {&lt;br /&gt;
 *      Extra information about event.&lt;br /&gt;
 *&lt;br /&gt;
 *      - string associatetype: type of blog association, course/coursemodule.&lt;br /&gt;
 *      - int blogid: id of blog.&lt;br /&gt;
 *      - int associateid: id of associate.&lt;br /&gt;
 *      - string subject: blog subject.&lt;br /&gt;
 * }&lt;br /&gt;
 *&lt;br /&gt;
 * @package    core&lt;br /&gt;
 * @since      Moodle 2.7&lt;br /&gt;
 * @copyright  2013 onwards Ankit Agarwal&lt;br /&gt;
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later&lt;br /&gt;
 */&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
= Example events =&lt;br /&gt;
&lt;br /&gt;
== Assignment ==&lt;br /&gt;
&lt;br /&gt;
Assumption: Course contains groups with students in each group.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;1.&#039;&#039;&#039; Teacher creates an assignment with group mode set to &#039;Separate groups&#039; and Feedback type set to comments and files.&lt;br /&gt;
*Event: User &#039;Teacher&#039; has created assignment &#039;B&#039; in course &#039;C101&#039;.&lt;br /&gt;
&#039;&#039;&#039;2.&#039;&#039;&#039; A student views the assignment.&lt;br /&gt;
*Event: User &#039;Student&#039; has viewed assignment &#039;B&#039; in course &#039;C101&#039;.&lt;br /&gt;
&#039;&#039;&#039;3.&#039;&#039;&#039; A member from one of the groups submits an assignment&lt;br /&gt;
*Event: User &#039;Student&#039; has added a submission for assignment &#039;B&#039; for group &#039;C&#039; in course &#039;C101&#039;.&lt;br /&gt;
*Event: Email sent to the teacher and all students in that group.&lt;br /&gt;
&#039;&#039;&#039;4.&#039;&#039;&#039; User &#039;Adrian&#039; adds some changes to the assignment and updates it.&lt;br /&gt;
*Event: User &#039;Adrian&#039; has updated the submission for assignment &#039;B&#039; for group &#039;C&#039; in course &#039;C101&#039;.&lt;br /&gt;
*Event: Email sent to the teacher and all students in that group.&lt;br /&gt;
&#039;&#039;&#039;5.&#039;&#039;&#039; Teacher views the assignment.&lt;br /&gt;
*Event: User &#039;Teacher&#039; has viewed assignment &#039;B&#039; in course &#039;C101&#039;.&lt;br /&gt;
&#039;&#039;&#039;6.&#039;&#039;&#039; Teacher clicks on &#039;View/grade all submissions&#039;&lt;br /&gt;
*Event: User &#039;Teacher&#039; has viewed the assignment &#039;B&#039; grade area in course &#039;C101&#039;.&lt;br /&gt;
&#039;&#039;&#039;7.&#039;&#039;&#039; Teacher clicks to grade the student&#039;s submission.&lt;br /&gt;
*Event: User &#039;Teacher&#039; has viewed the submission for user &#039;student&#039; for assignment &#039;B&#039; in course &#039;C101&#039;.&lt;br /&gt;
&#039;&#039;&#039;8.&#039;&#039;&#039; Teacher marks the assignment with the setting &#039;Apply grades and feedback to entire group&#039; set to &#039;Yes&#039; leaving a comment and a file.&lt;br /&gt;
*Event: User &#039;Teacher&#039; has marked assignment &#039;B&#039; for group &#039;C&#039; in course &#039;C101&#039;.&lt;br /&gt;
*Event: User &#039;Teacher&#039; has left a comment for assignment &#039;B&#039; for group &#039;C&#039; in course &#039;C101&#039;.&lt;br /&gt;
*Event: User &#039;Teacher&#039; has uploaded a feedback file for assignment &#039;B&#039; for group &#039;C&#039; in course &#039;C101&#039;.&lt;br /&gt;
*Event: User &#039;Teacher&#039; has uploaded a file to the course &#039;C101&#039;.&lt;br /&gt;
*Event: Email sent to user &#039;Student&#039; notifying them their submission for assignment &#039;B&#039; has been marked. - This is done for all users in the group.&lt;br /&gt;
&#039;&#039;&#039;9.&#039;&#039;&#039; User &#039;Adrian&#039; views the feedback.&lt;br /&gt;
*Event: User &#039;Adrian&#039; has viewed assignment &#039;B&#039; in course &#039;C101&#039;.&lt;br /&gt;
&#039;&#039;&#039;10.&#039;&#039;&#039; User &#039;Adrian&#039; opens the feedback file.&lt;br /&gt;
*Event: User &#039;Adrian&#039; has viewed the file &#039;A&#039; for assignment &#039;B&#039; in course &#039;C101&#039;.&lt;br /&gt;
&#039;&#039;&#039;11.&#039;&#039;&#039; User &#039;Adrian&#039; adds some changes to the assignment insulting the teachers marking and updates it.&lt;br /&gt;
*Event: User &#039;Adrian&#039; has updated the submission for assignment &#039;B&#039; for group &#039;C&#039; in course &#039;C101&#039;.&lt;br /&gt;
*Event: Email sent to user &#039;Teacher&#039; notifying them that user &#039;Adrian&#039; has updated the submission for assignment &#039;B&#039;.&lt;br /&gt;
*Event: Email sent to user &#039;Student&#039; notifying them their submission for assignment &#039;B&#039; has been updated. - This is done for all users in the group.&lt;br /&gt;
&#039;&#039;&#039;12.&#039;&#039;&#039; The teacher clicks directly on the link in the email to be taken to the grading page.&lt;br /&gt;
*Event: User &#039;Teacher&#039; has viewed the submission for user &#039;student&#039; for assignment &#039;B&#039; in course &#039;C101&#039;.&lt;br /&gt;
&#039;&#039;&#039;13.&#039;&#039;&#039; The teacher is upset due to the harsh comments and decides to mark Adrian down, but not the rest of the group by setting &#039;Apply grades and feedback to entire group&#039; set to &#039;No&#039;.&lt;br /&gt;
*Event: User &#039;Teacher&#039; has marked assignment &#039;B&#039; for user &#039;Adrian&#039; in course &#039;C101&#039;.&lt;br /&gt;
*Event: User &#039;Teacher&#039; has left a comment for assignment &#039;B&#039; for user &#039;Adrian&#039; in course &#039;C101&#039;.&lt;br /&gt;
*Event: User &#039;Teacher&#039; has uploaded a feedback file for assignment &#039;B&#039; for user &#039;Adrian&#039; in course &#039;C101&#039;.&lt;br /&gt;
*Event: Email sent to user &#039;Adrian&#039; notifying them their submission for assignment &#039;B&#039; has been marked.&lt;br /&gt;
&lt;br /&gt;
= FAQs =&lt;br /&gt;
&lt;br /&gt;
; Why not create events in core subsystems? : Because we could not see all core events in one place, it would create problems when naming event classes and finally subsystems are incomplete, we would have to add more now because we could not move the events in the future.&lt;br /&gt;
&lt;br /&gt;
= Possible future work = &lt;br /&gt;
* MDL-45108 &amp;quot;Other&amp;quot; parameters should be defined in a method similar to webservices&lt;br /&gt;
* MDL-45217 Create traits for refactoring event triggers&lt;br /&gt;
* MDL-42897 Converting completion to use events&lt;br /&gt;
* MDL-42898 Develop a timeline page/block&lt;br /&gt;
&lt;br /&gt;
= See Also =&lt;br /&gt;
[[Logging 2]]  &lt;br /&gt;
&lt;br /&gt;
[[Tin Can]]&lt;/div&gt;</summary>
		<author><name>Davidbalch</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Old_Events_API&amp;diff=44662</id>
		<title>Old Events API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Old_Events_API&amp;diff=44662"/>
		<updated>2014-05-06T09:09:33Z</updated>

		<summary type="html">&lt;p&gt;Davidbalch: Add link to Events 2&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&#039;&#039;&#039;See [[Event 2]] for the new Events API.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
==Overview==&lt;br /&gt;
&lt;br /&gt;
The Events API is a core system in Moodle to allow communication between modules.  &lt;br /&gt;
&lt;br /&gt;
An &#039;&#039;&#039;event&#039;&#039;&#039; is when something &amp;quot;interesting&amp;quot; happens in Moodle that is worth alerting the system about.&lt;br /&gt;
&lt;br /&gt;
Any Moodle modules can &#039;&#039;&#039;trigger&#039;&#039;&#039; new events (with attached data), and other modules can elect to &#039;&#039;&#039;handle&#039;&#039;&#039; those events with custom functions that operate on the given data.&lt;br /&gt;
&lt;br /&gt;
==Example==&lt;br /&gt;
&lt;br /&gt;
Let&#039;s look at an example of how events are used to implement enrolment in Moodle 2.0. In the enrolment system, lib/enrollib.php will generate a &#039;user_enrolled&#039; event upon completion of an enrolment of a user.&lt;br /&gt;
&lt;br /&gt;
===Triggering an event===&lt;br /&gt;
&lt;br /&gt;
When a user is enrolled, a &#039;user_enrolled&#039; event is built and sent out by the enrol API. In this example let&#039;s pretend someone was just enrolled.&lt;br /&gt;
&lt;br /&gt;
The enrol lib (could even be a module thats creating this event) needs to create an object with the data that this event needs. This may vary completely for different types of events, it&#039;s just a data object.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$ue = new stdClass();&lt;br /&gt;
$ue-&amp;gt;enrolid      = $instance-&amp;gt;id;&lt;br /&gt;
$ue-&amp;gt;status       = is_null($status) ? ENROL_USER_ACTIVE : $status;&lt;br /&gt;
$ue-&amp;gt;userid       = $userid;&lt;br /&gt;
$ue-&amp;gt;timestart    = $timestart;&lt;br /&gt;
$ue-&amp;gt;timeend      = $timeend;&lt;br /&gt;
$ue-&amp;gt;modifierid   = $USER-&amp;gt;id;&lt;br /&gt;
...&lt;br /&gt;
$ue-&amp;gt;courseid  = $courseid;&lt;br /&gt;
$ue-&amp;gt;enrol     = $name;&lt;br /&gt;
&lt;br /&gt;
events_trigger(&#039;user_enrolled&#039;, $ue);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Then we post the object as an event and forget about it:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
events_trigger(&#039;user_enrolled&#039;, $eventdata);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
We have now broadcast an event that we think other parts of the system might need to know about.&lt;br /&gt;
&lt;br /&gt;
===Handling an event===&lt;br /&gt;
&lt;br /&gt;
Modules or core code can define an events.php in under its */db directory which defines events they want to be notified about, and describes which of their functions or class methods should be notified.  For this case, there is this definition of a handler in mod/forum/db/events.php.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$handlers = array (&lt;br /&gt;
    &#039;user_enrolled&#039; =&amp;gt; array (&lt;br /&gt;
        &#039;handlerfile&#039;      =&amp;gt; &#039;/mod/forum/lib.php&#039;,&lt;br /&gt;
        &#039;handlerfunction&#039;  =&amp;gt; &#039;forum_user_enrolled&#039;,&lt;br /&gt;
        &#039;schedule&#039;         =&amp;gt; &#039;instant&#039;,&lt;br /&gt;
        &#039;internal&#039;         =&amp;gt; 1,&lt;br /&gt;
    ),&lt;br /&gt;
&lt;br /&gt;
    &#039;user_unenrolled&#039; =&amp;gt; array (&lt;br /&gt;
        &#039;handlerfile&#039;      =&amp;gt; &#039;/mod/forum/lib.php&#039;,&lt;br /&gt;
        &#039;handlerfunction&#039;  =&amp;gt; &#039;forum_user_unenrolled&#039;,&lt;br /&gt;
        &#039;schedule&#039;         =&amp;gt; &#039;instant&#039;,&lt;br /&gt;
        &#039;internal&#039;         =&amp;gt; 1,&lt;br /&gt;
    ),&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
These events.php files are parsed during install / upgrade and stored in a simple database table.&lt;br /&gt;
&lt;br /&gt;
Now, when a &#039;&#039;&#039;user_enrolled&#039;&#039;&#039; event happens, all the registered handlers functions for that event will be called something like this (but with more error handling):&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
          include_once($CFG-&amp;gt;dirroot.$handlers[&#039;user_enrolled&#039;][&#039;handlerfile&#039;]);&lt;br /&gt;
          call_user_func($handlers[&#039;user_enrolled&#039;][&#039;handlerfunction&#039;], $eventdata);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Any code can hook into any events this way.&lt;br /&gt;
&lt;br /&gt;
The handler function accepts one parameter (the event data object) and should return a boolean.  Returning false indicates that there was an error and the event will be left in the event queue.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    function forum_user_unenrolled($eventdata) {&lt;br /&gt;
        // handle event &lt;br /&gt;
        // ...&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
note: a lib/db/events.php exists as legacy and no events should be added here. Core API is not supposed to define events for consumption. Only modules and core components (non-api) should define events for consumption/handling.&lt;br /&gt;
&lt;br /&gt;
==Database structure==&lt;br /&gt;
&lt;br /&gt;
There are 3 core tables for events. Note that if a handler is queued, and yet to be processed or processing failed, then all subsequent calls on that handler must be queued.&lt;br /&gt;
&lt;br /&gt;
===events_handlers===&lt;br /&gt;
&lt;br /&gt;
This table is for storing which components requests what type of event, and the location of the responsible handler functions.&lt;br /&gt;
&lt;br /&gt;
These entries are created by parsing events.php files in all the modules, and can be rebuilt any time (during an upgrade, say).&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;2&amp;quot; cellspacing=&amp;quot;0&amp;quot;&lt;br /&gt;
|&#039;&#039;&#039;Field&#039;&#039;&#039;&lt;br /&gt;
|&#039;&#039;&#039;Type&#039;&#039;&#039;&lt;br /&gt;
|&#039;&#039;&#039;Info&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
|id&lt;br /&gt;
|int(10)&lt;br /&gt;
|auto increment identifier&lt;br /&gt;
|-&lt;br /&gt;
|eventname&lt;br /&gt;
|varchar(255)&lt;br /&gt;
|name of the event, e.g. &#039;message_send&#039;&lt;br /&gt;
|-&lt;br /&gt;
|handlermodule&lt;br /&gt;
|varchar(255)&lt;br /&gt;
|e.g. moodle, mod/forum, block/rss_client&lt;br /&gt;
|-&lt;br /&gt;
|handlerfile&lt;br /&gt;
|varchar(255)&lt;br /&gt;
|path to the file of the function, eg /lib/messagelib.php&lt;br /&gt;
|-&lt;br /&gt;
|handlerfunction&lt;br /&gt;
|text&lt;br /&gt;
|serialized string or array describing function, suitable to be passed to &#039;&#039;&#039;call_user_func()&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
|schedule 	&lt;br /&gt;
|varchar(255) 	&lt;br /&gt;
|&#039;cron&#039; or &#039;instant&#039;.&lt;br /&gt;
|-&lt;br /&gt;
|status&lt;br /&gt;
|int(10)&lt;br /&gt;
|number of failed attempts to process this handler&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===events_queue===&lt;br /&gt;
&lt;br /&gt;
This table is for storing queued events. It stores only one copy of the eventdata here, and entries from this table are being references by the events_queue_handlers table.&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;2&amp;quot; cellspacing=&amp;quot;0&amp;quot;&lt;br /&gt;
|&#039;&#039;&#039;Field&#039;&#039;&#039;&lt;br /&gt;
|&#039;&#039;&#039;Type&#039;&#039;&#039;&lt;br /&gt;
|&#039;&#039;&#039;Info&#039;&#039;&#039;&lt;br /&gt;
|-&lt;br /&gt;
|id&lt;br /&gt;
|int(10)&lt;br /&gt;
|auto increment identifier&lt;br /&gt;
|-&lt;br /&gt;
|eventdata 	&lt;br /&gt;
|longtext 	&lt;br /&gt;
|serialized version of the data object passed to the event handler.&lt;br /&gt;
|-&lt;br /&gt;
|stackdump&lt;br /&gt;
|text&lt;br /&gt;
|serialized debug_backtrace showing where the event was fired from&lt;br /&gt;
|-&lt;br /&gt;
|userid&lt;br /&gt;
|int(10)&lt;br /&gt;
|$USER-&amp;gt;id when the event was fired&lt;br /&gt;
|-&lt;br /&gt;
|timecreated&lt;br /&gt;
|int(10) 	&lt;br /&gt;
|time stamp of the first time this was added&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===events_queue_handlers===&lt;br /&gt;
&lt;br /&gt;
This is the list of queued handlers for processing. The event object is retrieved from the events_queue table. When no further reference is made to the events_queue table, the corresponding entry in the events_queue table should be deleted. Entry should get deleted (?) after a successful event processing by the specified handler.  The status field keeps track of failures, after it gets to a certain number (eg 10?) it should trigger an &amp;quot;event failed&amp;quot; event (that could result in admin being emailed etc, or perhaps even the originating module taking care of it or rolling something back etc).&lt;br /&gt;
&lt;br /&gt;
{| border=&amp;quot;1&amp;quot; cellpadding=&amp;quot;2&amp;quot; cellspacing=&amp;quot;0&amp;quot;&lt;br /&gt;
|&#039;&#039;&#039;Field&#039;&#039;&#039;&lt;br /&gt;
|&#039;&#039;&#039;Type&#039;&#039;&#039;&lt;br /&gt;
|&#039;&#039;&#039;Info&#039;&#039;&#039;&lt;br /&gt;
|- &lt;br /&gt;
|id&lt;br /&gt;
|int(10)&lt;br /&gt;
|auto increment identifier&lt;br /&gt;
|-&lt;br /&gt;
|queuedeventid&lt;br /&gt;
|int(10)&lt;br /&gt;
|foreign key id corresponding to the id of the event_queues table&lt;br /&gt;
|-&lt;br /&gt;
|handlerid&lt;br /&gt;
|int(10)&lt;br /&gt;
|foreign key id corresponding to the id of the event_handlers table&lt;br /&gt;
|-&lt;br /&gt;
|status&lt;br /&gt;
|int(10)&lt;br /&gt;
|number of failed attempts to process this handler&lt;br /&gt;
|-&lt;br /&gt;
|errormessage&lt;br /&gt;
|text&lt;br /&gt;
|if an error happened last time we tried to process this event, record it here.&lt;br /&gt;
|-&lt;br /&gt;
|timemodified&lt;br /&gt;
|int(10)&lt;br /&gt;
|time stamp of the last attempt to run this from the queue&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
==Standards for naming events==&lt;br /&gt;
&lt;br /&gt;
All event names should follow a consistent naming pattern, such as componentname_noun_verb  (See [[Frankenstyle]] about the component name)&lt;br /&gt;
&lt;br /&gt;
If the event is being fired after the action has taken place (as in most cases) then use the past tense for the verb (created / deleted / updated / sent).&lt;br /&gt;
&lt;br /&gt;
If the event &#039;&#039;&#039;is&#039;&#039;&#039; the action, then use the present tense (create / delete / update / send).&lt;br /&gt;
&lt;br /&gt;
==Events which exist==&lt;br /&gt;
&lt;br /&gt;
When we add new events to core we should always add them here too.&lt;br /&gt;
&lt;br /&gt;
Under each event, list the data sent as part of the event.&lt;br /&gt;
&lt;br /&gt;
===Users===&lt;br /&gt;
* user_created&lt;br /&gt;
** full new record from &#039;user&#039; table&lt;br /&gt;
* user_deleted&lt;br /&gt;
** record from &#039;user&#039; table before marked as deleted&lt;br /&gt;
* user_updated&lt;br /&gt;
** full new record from &#039;user&#039; table&lt;br /&gt;
* user_enrolled&lt;br /&gt;
** full user enrolment record (TBC)&lt;br /&gt;
* user_logout&lt;br /&gt;
** params TBC&lt;br /&gt;
* user_unenrol_modified&lt;br /&gt;
** full user enrolment record (TBC)&lt;br /&gt;
* user_unenrolled&lt;br /&gt;
** full user enrolment record (TBC)&lt;br /&gt;
&lt;br /&gt;
===Roles===&lt;br /&gt;
* role_assigned&lt;br /&gt;
** full new record from &#039;role_assignments&#039; table&lt;br /&gt;
* role_unassigned&lt;br /&gt;
** record from &#039;role_assignments&#039;, course context only&lt;br /&gt;
&lt;br /&gt;
===Courses===&lt;br /&gt;
* course_created&lt;br /&gt;
** full course record&lt;br /&gt;
* course_updated&lt;br /&gt;
** full course record&lt;br /&gt;
* course_deleted&lt;br /&gt;
** full course record&lt;br /&gt;
* course_category_deleted&lt;br /&gt;
** full category record&lt;br /&gt;
* course_content_removed&lt;br /&gt;
** full course record&lt;br /&gt;
* course_restored (2.5)&lt;br /&gt;
&lt;br /&gt;
===Groups===&lt;br /&gt;
* groups_member_added&lt;br /&gt;
** groupid, userid&lt;br /&gt;
* groups_member_removed&lt;br /&gt;
** groupid, userid&lt;br /&gt;
* groups_group_created&lt;br /&gt;
** id, courseid, name, description, timecreated, timemodified, picture&lt;br /&gt;
* groups_group_updated&lt;br /&gt;
** id, courseid, name, description, timecreated, timemodified, picture&lt;br /&gt;
* groups_group_deleted&lt;br /&gt;
** id, courseid, name, description, timecreated, timemodified, picture&lt;br /&gt;
* groups_grouping_created&lt;br /&gt;
** id, courseid, name, timecreated, timemodified&lt;br /&gt;
* groups_grouping_updated&lt;br /&gt;
** id, courseid, name, timecreated, timemodified&lt;br /&gt;
* groups_grouping_deleted&lt;br /&gt;
** id, courseid, name, timecreated, timemodified&lt;br /&gt;
* groups_members_removed &#039;&#039;(user deleted from all groups in a course)&#039;&#039;&lt;br /&gt;
** courseid, userid&lt;br /&gt;
* groups_groupings_groups_removed &#039;&#039;(remove all groups from all groupings in a course)&#039;&#039;&lt;br /&gt;
** courseid (as plain integer, not object)&lt;br /&gt;
* groups_groups_deleted &#039;&#039;(delete all groups in a course)&#039;&#039;&lt;br /&gt;
** courseid (as plain integer, not object)&lt;br /&gt;
* groups_groupings_deleted &#039;&#039;(delete all groupings in a course)&#039;&#039;&lt;br /&gt;
** courseid (as plain integer, not object)&lt;br /&gt;
&lt;br /&gt;
===Cohorts===&lt;br /&gt;
* cohort_added&lt;br /&gt;
** full cohort record (TBC)&lt;br /&gt;
* cohort_deleted&lt;br /&gt;
** full cohort record (TBC)&lt;br /&gt;
* cohort_member_added&lt;br /&gt;
** cohort ID, user ID (TBC)&lt;br /&gt;
* cohort_member_removed&lt;br /&gt;
** cohort ID, user ID (TBC)&lt;br /&gt;
* cohort_updated&lt;br /&gt;
** full cohort record (TBC)&lt;br /&gt;
&lt;br /&gt;
===Messaging===&lt;br /&gt;
* message_send&lt;br /&gt;
** component = &#039;mod/forum&#039;: path in Moodle&lt;br /&gt;
** name = &#039;posts&#039;: type of message from that module (as module defines it)&lt;br /&gt;
** userfrom = $userfrom: a user object to send from&lt;br /&gt;
** userto = $userto: a user object to send to&lt;br /&gt;
** subject = &#039;subject line&#039;: a short text line&lt;br /&gt;
** fullmessage = &#039;full plain text&#039;: raw text as entered by user&lt;br /&gt;
** fullmessageformat = FORMAT_PLAIN|FORMAT_HTML|FORMAT_MOODLE|FORMAT_MARKDOWN: the format of this text&lt;br /&gt;
** fullmessagehtml = &#039;long html text&#039;; html rendered version (optional)&lt;br /&gt;
** smallmessage = &#039;short text&#039;: useful for plugins like sms or twitter (optional)&lt;br /&gt;
&lt;br /&gt;
===Portfolio===&lt;br /&gt;
* portfolio_send&lt;br /&gt;
** id : recordid in portfolio_tempdata table, used for itemid in file storage&lt;br /&gt;
&lt;br /&gt;
===Other===&lt;br /&gt;
* assessable_file_uploaded&lt;br /&gt;
* assessable_files_done&lt;br /&gt;
* mod_created&lt;br /&gt;
* mod_deleted&lt;br /&gt;
* mod_updated&lt;br /&gt;
* quiz_attempt_started&lt;br /&gt;
* quiz_attempt_submitted - &#039;&#039;Note: these two quiz events changed in Moodle 2.1&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
==Events wishlist==&lt;br /&gt;
&lt;br /&gt;
List of events which it would be nice to have.  Please add to this list if what you want is not shown here.&lt;br /&gt;
&lt;br /&gt;
* mform_print_form -- this for all types of form e.g. admin settings, user profile, module updating, + some sort of standard way of discriminating between them e.g. if ($form-&amp;gt;name == &#039;user_profile&#039;) {}. This would be better triggered at the end of the form generation process so that new bits can be inserted at any point, or existing bits could be removed.&lt;br /&gt;
* module_installed &#039;&#039;(= mod_created in v2?)&#039;&#039;&lt;br /&gt;
* module_removed &#039;&#039;(= mod_deleted in v2?)&#039;&#039;&lt;br /&gt;
* grade_update &#039;&#039;(= quiz_attempt_processed in v2?)&#039;&#039;&lt;br /&gt;
* assignment_submitted&lt;br /&gt;
* course - editing turned on or off&lt;br /&gt;
* course completed&lt;br /&gt;
* course module completion state changed (completed / not completed)&lt;br /&gt;
* course was reset&lt;br /&gt;
* Some will_be ... events that would allow another plugin to react before these things happen:&lt;br /&gt;
** course_will_be_restored (with info about course being restored and the target category)&lt;br /&gt;
** course_category_will_be_moved (with info about category being moved and target category)&lt;br /&gt;
** course_will_be_moved (with course info and target category info)&lt;br /&gt;
&lt;br /&gt;
Provide event trigger hooks for the modules, similar to what is done for the cron service which checks the LOCAL directory for a cron file. It is already possible to define an event trigger but the core/module code must be modified to actually make use of it.&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[Core APIs]]&lt;br /&gt;
* [http://moodle.org/mod/forum/discuss.php?d=69103 Original General Developer Forum thread discussing this proposal]. &lt;br /&gt;
* [[Messaging_2.0]]&lt;br /&gt;
* [[Event_2]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Coding guidelines|Events]]&lt;br /&gt;
[[Category:Grades]]&lt;br /&gt;
[[Category:API]]&lt;/div&gt;</summary>
		<author><name>Davidbalch</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Assign_submission_plugins&amp;diff=44645</id>
		<title>Assign submission plugins</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Assign_submission_plugins&amp;diff=44645"/>
		<updated>2014-05-02T14:34:18Z</updated>

		<summary type="html">&lt;p&gt;Davidbalch: /* locallib.php */ Add headings for assign_submission_plugin functions, to improve readability.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
This page gives an overview of assignment submission plugims within the assignment module.&lt;br /&gt;
&lt;br /&gt;
=== Overview of an assignment submission plugin ===&lt;br /&gt;
An assignment submission plugin is used to display custom form fields to a student when they are editing their assignment submission. It also has full control over the display the submitted assignment to graders and students. Plugins participate in all assignment features including backup/restore, upgrades from 2.2, offline grading, group assignments and blind marking.&lt;br /&gt;
&amp;lt;gallery&amp;gt;&lt;br /&gt;
File:assignsubmission_plugin_overview1.png|An assignment submission plugin can add settings to the module settings page.&lt;br /&gt;
File:assignsubmission_plugin_overview2.png|An assignment submission plugin can show a summary of the submission to students and graders.&lt;br /&gt;
File:assignsubmission_plugin_overview3.png|An assignment submission plugin can add form fields to the student submission page.&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== History ==&lt;br /&gt;
Assignment submission plugins were added with the assignment module rewrite for Moodle 2.3. &lt;br /&gt;
&lt;br /&gt;
== Template ==&lt;br /&gt;
A great example is the &amp;quot;onlinetext&amp;quot; submission plugin included with Moodle core. &lt;br /&gt;
&lt;br /&gt;
== File structure ==&lt;br /&gt;
The files for a custom submission plugin sit under &amp;quot;mod/assign/submission/&amp;lt;pluginname&amp;gt;&amp;quot;. A plugin should not include any custom files outside of it&#039;s own plugin folder. &lt;br /&gt;
&lt;br /&gt;
Note: The plugin name should be no longer than 11 characters - this is because the database tables for a submission plugin must be prefixed with &amp;quot;assignsubmission_&amp;quot; + pluginname (17 chars + X) and the table names can be no longer than 28 chars (thanks oracle). If a plugin requires multiple database tables, the plugin name will need to be shorter to allow different table names to fit under the 28 character limit.&lt;br /&gt;
&lt;br /&gt;
All examples in this document exclude the required copyright and license information from source files for brevity.&lt;br /&gt;
&lt;br /&gt;
=== version.php ===&lt;br /&gt;
&lt;br /&gt;
To start with we need to tell Moodle the version information for our new plugin so that it can be installed and upgraded correctly. This information is added to version.php as with any other type of Moodle plugin. The component name must begin with &amp;quot;assignsubmission_&amp;quot; to identify this as a submission plugin.&lt;br /&gt;
&lt;br /&gt;
See [[version.php]] for more information.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
defined(&#039;MOODLE_INTERNAL&#039;) || die();                                                                                                &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
$plugin-&amp;gt;version   = 2012112900;                                                                                                    &lt;br /&gt;
$plugin-&amp;gt;requires  = 2012112900;                                                                                                    &lt;br /&gt;
$plugin-&amp;gt;component = &#039;assignsubmission_file&#039;;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== settings.php ===&lt;br /&gt;
&lt;br /&gt;
The settings file allows us to add custom settings to the system wide configuration page for our plugin. &lt;br /&gt;
&lt;br /&gt;
All submission settings should be named &#039;assignsubmission_pluginname/settingname&#039; in order for the setting to be associated with the plugin.&lt;br /&gt;
&lt;br /&gt;
All submission plugins should include one setting named &#039;default&#039; to indicate if the plugin should be enabled by default when creating a new assignment. &lt;br /&gt;
&lt;br /&gt;
This example from the submission_file plugin also checks to see if there is a maxbytes setting for this moodle installation and if found, it adds a new admin setting to the settings page. The name of the setting should begin with the plugin component name (&amp;quot;assignsubmission_file&amp;quot;) in this case. The strings are specified in this plugins language file. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
// Note: This is on by default.                                                                                                     &lt;br /&gt;
$settings-&amp;gt;add(new admin_setting_configcheckbox(&#039;assignsubmission_file/default&#039;,                                                    &lt;br /&gt;
                   new lang_string(&#039;default&#039;, &#039;assignsubmission_file&#039;),                                                             &lt;br /&gt;
                   new lang_string(&#039;default_help&#039;, &#039;assignsubmission_file&#039;), 1));                                                   &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
if (isset($CFG-&amp;gt;maxbytes)) {                                                                                                        &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
    $name = new lang_string(&#039;maximumsubmissionsize&#039;, &#039;assignsubmission_file&#039;);                                                      &lt;br /&gt;
    $description = new lang_string(&#039;configmaxbytes&#039;, &#039;assignsubmission_file&#039;);                                                      &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
    $element = new admin_setting_configselect(&#039;assignsubmission_file/maxbytes&#039;,                                                     &lt;br /&gt;
                                              $name,                                                                                &lt;br /&gt;
                                              $description,                                                                         &lt;br /&gt;
                                              1048576,                                                                              &lt;br /&gt;
                                              get_max_upload_sizes($CFG-&amp;gt;maxbytes));                                                &lt;br /&gt;
    $settings-&amp;gt;add($element);                                                                                                       &lt;br /&gt;
}                                                                          &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== lang/en/submission_pluginname.php ===&lt;br /&gt;
&lt;br /&gt;
The language file for this plugin must have the same name as the component name (e.g. &amp;quot;submission_file.php&amp;quot;). It should at least define a string for &amp;quot;pluginname&amp;quot;. For example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$string[&#039;pluginname&#039;] = &#039;Awesome submissions&#039;;      &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== db/access.php ===&lt;br /&gt;
&lt;br /&gt;
This is where any additional capabilities are defined if required. This file can be omitted if there are no capabilities added by the plugin.&lt;br /&gt;
&lt;br /&gt;
See [[Activity_modules#access.php]] for more information.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
$capabilities = array(&lt;br /&gt;
    &#039;assignsubmission/dungeon:master&#039; =&amp;gt; array(&lt;br /&gt;
        &#039;riskbitmask&#039; =&amp;gt; RISK_XSS,&lt;br /&gt;
        &#039;captype&#039; =&amp;gt; &#039;write&#039;,&lt;br /&gt;
        &#039;contextlevel&#039; =&amp;gt; CONTEXT_COURSE,&lt;br /&gt;
        &#039;archetypes&#039; =&amp;gt; array(&lt;br /&gt;
            &#039;editingteacher&#039; =&amp;gt; CAP_ALLOW,&lt;br /&gt;
            &#039;manager&#039; =&amp;gt; CAP_ALLOW&lt;br /&gt;
        ),&lt;br /&gt;
        &#039;clonepermissionsfrom&#039; =&amp;gt; &#039;moodle/course:manageactivities&#039;&lt;br /&gt;
    ),&lt;br /&gt;
);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== db/upgrade.php ===&lt;br /&gt;
&lt;br /&gt;
This is where any upgrade code is defined. &lt;br /&gt;
&lt;br /&gt;
See [[Activity_modules#upgrade.php]] for more infomation.&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
function xmldb_submission_file_upgrade($oldversion) {&lt;br /&gt;
    global $CFG, $DB, $OUTPUT;&lt;br /&gt;
&lt;br /&gt;
    $dbman = $DB-&amp;gt;get_manager();&lt;br /&gt;
    if ($oldversion &amp;lt; 2012091800) {&lt;br /&gt;
        // Put upgrade code here&lt;br /&gt;
&lt;br /&gt;
        // Savepoint reached.&lt;br /&gt;
        upgrade_plugin_savepoint(true, 2012091800, &#039;assignsubmission&#039;, &#039;file&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== db/install.xml ===&lt;br /&gt;
&lt;br /&gt;
This is where any database tables required to save this plugins data are defined. File submissions define a table that links to submission and contains a column to record the number of files. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot; ?&amp;gt;                                                                                             &lt;br /&gt;
&amp;lt;XMLDB PATH=&amp;quot;mod/assign/submission/file/db&amp;quot; VERSION=&amp;quot;20120423&amp;quot; COMMENT=&amp;quot;XMLDB file for Moodle mod/assign/submission/file&amp;quot;           &lt;br /&gt;
    xmlns:xsi=&amp;quot;http://www.w3.org/2001/XMLSchema-instance&amp;quot;                                                                           &lt;br /&gt;
    xsi:noNamespaceSchemaLocation=&amp;quot;../../../../../lib/xmldb/xmldb.xsd&amp;quot;                                                              &lt;br /&gt;
&amp;gt;                                                                                                                                   &lt;br /&gt;
  &amp;lt;TABLES&amp;gt;                                                                                                                          &lt;br /&gt;
    &amp;lt;TABLE NAME=&amp;quot;assignsubmission_file&amp;quot; COMMENT=&amp;quot;Info about file submissions for assignments&amp;quot;&amp;gt;                                      &lt;br /&gt;
      &amp;lt;FIELDS&amp;gt;                                                                                                                      &lt;br /&gt;
        &amp;lt;FIELD NAME=&amp;quot;id&amp;quot; TYPE=&amp;quot;int&amp;quot; LENGTH=&amp;quot;10&amp;quot; NOTNULL=&amp;quot;true&amp;quot; SEQUENCE=&amp;quot;true&amp;quot;/&amp;gt;                                                    &lt;br /&gt;
        &amp;lt;FIELD NAME=&amp;quot;assignment&amp;quot; TYPE=&amp;quot;int&amp;quot; LENGTH=&amp;quot;10&amp;quot; NOTNULL=&amp;quot;true&amp;quot; DEFAULT=&amp;quot;0&amp;quot; SEQUENCE=&amp;quot;false&amp;quot;/&amp;gt;                               &lt;br /&gt;
        &amp;lt;FIELD NAME=&amp;quot;submission&amp;quot; TYPE=&amp;quot;int&amp;quot; LENGTH=&amp;quot;10&amp;quot; NOTNULL=&amp;quot;true&amp;quot; DEFAULT=&amp;quot;0&amp;quot; SEQUENCE=&amp;quot;false&amp;quot;/&amp;gt;                               &lt;br /&gt;
        &amp;lt;FIELD NAME=&amp;quot;numfiles&amp;quot; TYPE=&amp;quot;int&amp;quot; LENGTH=&amp;quot;10&amp;quot; NOTNULL=&amp;quot;true&amp;quot; DEFAULT=&amp;quot;0&amp;quot; SEQUENCE=&amp;quot;false&amp;quot; COMMENT=&amp;quot;The number of files the student submitted.&amp;quot;/&amp;gt;&lt;br /&gt;
      &amp;lt;/FIELDS&amp;gt;                                                                                                                     &lt;br /&gt;
      &amp;lt;KEYS&amp;gt;                                                                                                                        &lt;br /&gt;
        &amp;lt;KEY NAME=&amp;quot;primary&amp;quot; TYPE=&amp;quot;primary&amp;quot; FIELDS=&amp;quot;id&amp;quot; COMMENT=&amp;quot;The unique id for this submission info.&amp;quot;/&amp;gt;                          &lt;br /&gt;
        &amp;lt;KEY NAME=&amp;quot;assignment&amp;quot; TYPE=&amp;quot;foreign&amp;quot; FIELDS=&amp;quot;assignment&amp;quot; REFTABLE=&amp;quot;assign&amp;quot; REFFIELDS=&amp;quot;id&amp;quot; COMMENT=&amp;quot;The assignment instance this submission relates to&amp;quot;/&amp;gt;&lt;br /&gt;
        &amp;lt;KEY NAME=&amp;quot;submission&amp;quot; TYPE=&amp;quot;foreign&amp;quot; FIELDS=&amp;quot;submission&amp;quot; REFTABLE=&amp;quot;assign_submission&amp;quot; REFFIELDS=&amp;quot;id&amp;quot; COMMENT=&amp;quot;The submission this file submission relates to.&amp;quot;/&amp;gt;&lt;br /&gt;
      &amp;lt;/KEYS&amp;gt;                                                                                                                       &lt;br /&gt;
    &amp;lt;/TABLE&amp;gt;                                                                                                                        &lt;br /&gt;
  &amp;lt;/TABLES&amp;gt;                                                                                                                         &lt;br /&gt;
&amp;lt;/XMLDB&amp;gt;                           &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== db/install.php ===&lt;br /&gt;
&lt;br /&gt;
This example is from the submission_comments plugin. It shows how to run custom code on installation of the plugin. In this case it makes the comments plugin the last of the three submission plugins installed by default. &lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
/**&lt;br /&gt;
 * Code run after the plugin database tables have been created.&lt;br /&gt;
 */&lt;br /&gt;
function xmldb_submission_comments_install() {&lt;br /&gt;
    global $CFG, $DB, $OUTPUT;&lt;br /&gt;
&lt;br /&gt;
    // do the install&lt;br /&gt;
&lt;br /&gt;
    require_once($CFG-&amp;gt;dirroot . &#039;/mod/assign/locallib.php&#039;);&lt;br /&gt;
    // set the correct initial order for the plugins&lt;br /&gt;
    $assignment = new assignment();&lt;br /&gt;
    $plugin = $assignment-&amp;gt;get_submission_plugin_by_type(&#039;comments&#039;);&lt;br /&gt;
    if ($plugin) {&lt;br /&gt;
        $plugin-&amp;gt;move(&#039;down&#039;);&lt;br /&gt;
        $plugin-&amp;gt;move(&#039;down&#039;);&lt;br /&gt;
    }&lt;br /&gt;
        &lt;br /&gt;
    return true;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== locallib.php ===&lt;br /&gt;
&lt;br /&gt;
This is where all the functionality for this plugin is defined. We will step through this file and describe each part as we go.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
class assign_submission_file extends assign_submission_plugin {&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
All submission plugins MUST define a class with the component name of the plugin that extends assign_submission_plugin. &lt;br /&gt;
&lt;br /&gt;
==== &#039;&#039;&#039; get_name()&#039;&#039;&#039; ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    public function get_name() {&lt;br /&gt;
        return get_string(&#039;file&#039;, &#039;assignsubmission_file&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Get name is abstract in submission_plugin and must be defined in your new plugin. Use the language strings to make your plugin translatable. &lt;br /&gt;
&lt;br /&gt;
==== &#039;&#039;&#039; get_settings()&#039;&#039;&#039; ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    public function get_settings(MoodleQuickForm $mform) {&lt;br /&gt;
       global $CFG, $COURSE;                                                                                                       &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
        $defaultmaxfilesubmissions = $this-&amp;gt;get_config(&#039;maxfilesubmissions&#039;);                                                       &lt;br /&gt;
        $defaultmaxsubmissionsizebytes = $this-&amp;gt;get_config(&#039;maxsubmissionsizebytes&#039;);                                               &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
        $settings = array();                                                                                                        &lt;br /&gt;
        $options = array();                                                                                                         &lt;br /&gt;
        for ($i = 1; $i &amp;lt;= ASSIGNSUBMISSION_FILE_MAXFILES; $i++) {                                                                  &lt;br /&gt;
            $options[$i] = $i;                                                                                                      &lt;br /&gt;
        }                                                                                                                           &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
        $name = get_string(&#039;maxfilessubmission&#039;, &#039;assignsubmission_file&#039;);                                                          &lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;select&#039;, &#039;assignsubmission_file_maxfiles&#039;, $name, $options);                                            &lt;br /&gt;
        $mform-&amp;gt;addHelpButton(&#039;assignsubmission_file_maxfiles&#039;,                                                                     &lt;br /&gt;
                              &#039;maxfilessubmission&#039;,                                                                                 &lt;br /&gt;
                              &#039;assignsubmission_file&#039;);                                                                             &lt;br /&gt;
        $mform-&amp;gt;setDefault(&#039;assignsubmission_file_maxfiles&#039;, $defaultmaxfilesubmissions);                                           &lt;br /&gt;
        $mform-&amp;gt;disabledIf(&#039;assignsubmission_file_maxfiles&#039;, &#039;assignsubmission_file_enabled&#039;, &#039;notchecked&#039;);                        &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
        $choices = get_max_upload_sizes($CFG-&amp;gt;maxbytes,                                                                             &lt;br /&gt;
                                        $COURSE-&amp;gt;maxbytes,                                                                          &lt;br /&gt;
                                        get_config(&#039;assignsubmission_file&#039;, &#039;maxbytes&#039;));                                           &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
        $settings[] = array(&#039;type&#039; =&amp;gt; &#039;select&#039;,                                                                                     &lt;br /&gt;
                            &#039;name&#039; =&amp;gt; &#039;maxsubmissionsizebytes&#039;,                                                                     &lt;br /&gt;
                            &#039;description&#039; =&amp;gt; get_string(&#039;maximumsubmissionsize&#039;, &#039;assignsubmission_file&#039;),                          &lt;br /&gt;
                            &#039;options&#039;=&amp;gt; $choices,                                                                                   &lt;br /&gt;
                            &#039;default&#039;=&amp;gt; $defaultmaxsubmissionsizebytes);                                                            &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
        $name = get_string(&#039;maximumsubmissionsize&#039;, &#039;assignsubmission_file&#039;);                                                       &lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;select&#039;, &#039;assignsubmission_file_maxsizebytes&#039;, $name, $choices);                                        &lt;br /&gt;
        $mform-&amp;gt;addHelpButton(&#039;assignsubmission_file_maxsizebytes&#039;,                                                                 &lt;br /&gt;
                              &#039;maximumsubmissionsize&#039;,                                                                              &lt;br /&gt;
                              &#039;assignsubmission_file&#039;);                                                                             &lt;br /&gt;
        $mform-&amp;gt;setDefault(&#039;assignsubmission_file_maxsizebytes&#039;, $defaultmaxsubmissionsizebytes);                                   &lt;br /&gt;
        $mform-&amp;gt;disabledIf(&#039;assignsubmission_file_maxsizebytes&#039;,                                                                    &lt;br /&gt;
                           &#039;assignsubmission_file_enabled&#039;,                                                                         &lt;br /&gt;
                           &#039;notchecked&#039;);                             &lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;get_settings&amp;quot; function is called when building the settings page for the assignment. It allows this plugin to add a list of settings to the form. Notice that the settings are prefixed by the plugin name which is good practice to avoid conflicts with other plugins. &lt;br /&gt;
&lt;br /&gt;
==== &#039;&#039;&#039; save_settings()&#039;&#039;&#039; ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    public function save_settings(stdClass $data) {                                                                                 &lt;br /&gt;
        $this-&amp;gt;set_config(&#039;maxfilesubmissions&#039;, $data-&amp;gt;assignsubmission_file_maxfiles);                                             &lt;br /&gt;
        $this-&amp;gt;set_config(&#039;maxsubmissionsizebytes&#039;, $data-&amp;gt;assignsubmission_file_maxsizebytes);                                     &lt;br /&gt;
        return true;                                                                                                                &lt;br /&gt;
    }   &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;save_settings&amp;quot; function is called when the assignment settings page is submitted, either for a new assignment or when editing an existing one. For settings specific to a single instance of the assignment you can use the assign_plugin::set_config function shown here to save key/value pairs against this assignment instance for this plugin. &lt;br /&gt;
&lt;br /&gt;
==== &#039;&#039;&#039; get_form_elements()&#039;&#039;&#039; ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    public function get_form_elements($submission, MoodleQuickForm $mform, stdClass $data) {                                        &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
        if ($this-&amp;gt;get_config(&#039;maxfilesubmissions&#039;) &amp;lt;= 0) {                                                                         &lt;br /&gt;
            return false;                                                                                                           &lt;br /&gt;
        }                                                                                                                           &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
        $fileoptions = $this-&amp;gt;get_file_options();                                                                                   &lt;br /&gt;
        $submissionid = $submission ? $submission-&amp;gt;id : 0;                                                                          &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
        $data = file_prepare_standard_filemanager($data,                                                                            &lt;br /&gt;
                                                  &#039;files&#039;,                                                                          &lt;br /&gt;
                                                  $fileoptions,                                                                     &lt;br /&gt;
                                                  $this-&amp;gt;assignment-&amp;gt;get_context(),                                                 &lt;br /&gt;
                                                  &#039;assignsubmission_file&#039;,                                                          &lt;br /&gt;
                                                  ASSIGNSUBMISSION_FILE_FILEAREA,                                                   &lt;br /&gt;
                                                  $submissionid);                                                                   &lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;filemanager&#039;, &#039;files_filemanager&#039;, html_writer::tag(&#039;span&#039;, $this-&amp;gt;get_name(),                          &lt;br /&gt;
            array(&#039;class&#039; =&amp;gt; &#039;accesshide&#039;)), null, $fileoptions);                                                                   &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
        return true;                                                                                                                &lt;br /&gt;
    }                                    &lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The get_form_elements function is called when building the submission form. It functions identically to the get_settings function except that the submission object is available (if there is a submission) to associate the settings with a single submission. This example also shows how to use a filemanager within a submission plugin. The function must return true if it has modified the form otherwise the assignment will not include a header for this plugin. &lt;br /&gt;
&lt;br /&gt;
==== &#039;&#039;&#039; save()&#039;&#039;&#039; ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   public function save(stdClass $submission, stdClass $data) {                                                                    &lt;br /&gt;
        global $USER, $DB;                                                                                                          &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
        $fileoptions = $this-&amp;gt;get_file_options();                                                                                   &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
        $data = file_postupdate_standard_filemanager($data,                                                                         &lt;br /&gt;
                                                     &#039;files&#039;,                                                                       &lt;br /&gt;
                                                     $fileoptions,                                                                  &lt;br /&gt;
                                                     $this-&amp;gt;assignment-&amp;gt;get_context(),                                              &lt;br /&gt;
                                                     &#039;assignsubmission_file&#039;,                                                       &lt;br /&gt;
                                                     ASSIGNSUBMISSION_FILE_FILEAREA,                                                &lt;br /&gt;
                                                     $submission-&amp;gt;id);                                                              &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
        $filesubmission = $this-&amp;gt;get_file_submission($submission-&amp;gt;id);                                                              &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
        // Plagiarism code event trigger when files are uploaded.                                                                   &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
        $fs = get_file_storage();                                                                                                   &lt;br /&gt;
        $files = $fs-&amp;gt;get_area_files($this-&amp;gt;assignment-&amp;gt;get_context()-&amp;gt;id,                                                          &lt;br /&gt;
                                     &#039;assignsubmission_file&#039;,                                                                       &lt;br /&gt;
                                     ASSIGNSUBMISSION_FILE_FILEAREA,                                                                &lt;br /&gt;
                                     $submission-&amp;gt;id,                                                                               &lt;br /&gt;
                                     &#039;id&#039;,                                                                                          &lt;br /&gt;
                                     false);                                                                                        &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
        $count = $this-&amp;gt;count_files($submission-&amp;gt;id, ASSIGNSUBMISSION_FILE_FILEAREA);                                               &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
        // Send files to event system.                                                                                              &lt;br /&gt;
        // This lets Moodle know that an assessable file was uploaded (eg for plagiarism detection).                                &lt;br /&gt;
        $eventdata = new stdClass();                                                                                                &lt;br /&gt;
        $eventdata-&amp;gt;modulename = &#039;assign&#039;;                                                                                          &lt;br /&gt;
        $eventdata-&amp;gt;cmid = $this-&amp;gt;assignment-&amp;gt;get_course_module()-&amp;gt;id;                                                              &lt;br /&gt;
        $eventdata-&amp;gt;itemid = $submission-&amp;gt;id;                                                                                       &lt;br /&gt;
        $eventdata-&amp;gt;courseid = $this-&amp;gt;assignment-&amp;gt;get_course()-&amp;gt;id;                                                                 &lt;br /&gt;
        $eventdata-&amp;gt;userid = $USER-&amp;gt;id;                                                                                             &lt;br /&gt;
        if ($count &amp;gt; 1) {                                                                                                           &lt;br /&gt;
            $eventdata-&amp;gt;files = $files;                                                                                             &lt;br /&gt;
        }                                                                                                                           &lt;br /&gt;
        $eventdata-&amp;gt;file = $files;                                    &lt;br /&gt;
        $eventdata-&amp;gt;pathnamehashes = array_keys($files);                                                                            &lt;br /&gt;
        events_trigger(&#039;assessable_file_uploaded&#039;, $eventdata);                                                                     &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
        if ($filesubmission) {                                                                                                      &lt;br /&gt;
            $filesubmission-&amp;gt;numfiles = $this-&amp;gt;count_files($submission-&amp;gt;id,                                                         &lt;br /&gt;
                                                           ASSIGNSUBMISSION_FILE_FILEAREA);                                         &lt;br /&gt;
            return $DB-&amp;gt;update_record(&#039;assignsubmission_file&#039;, $filesubmission);                                                    &lt;br /&gt;
        } else {                                                                                                                    &lt;br /&gt;
            $filesubmission = new stdClass();                                                                                       &lt;br /&gt;
            $filesubmission-&amp;gt;numfiles = $this-&amp;gt;count_files($submission-&amp;gt;id,                                                         &lt;br /&gt;
                                                           ASSIGNSUBMISSION_FILE_FILEAREA);                                         &lt;br /&gt;
            $filesubmission-&amp;gt;submission = $submission-&amp;gt;id;                                                                          &lt;br /&gt;
            $filesubmission-&amp;gt;assignment = $this-&amp;gt;assignment-&amp;gt;get_instance()-&amp;gt;id;                                                    &lt;br /&gt;
            return $DB-&amp;gt;insert_record(&#039;assignsubmission_file&#039;, $filesubmission) &amp;gt; 0;                                                &lt;br /&gt;
        }                                &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;save&amp;quot; function is called to save a user submission. The parameters are the submission object and the data from the submission form. This example calls file_postupdate_standard_filemanager to copy the files from the draft file area to the filearea for this submission, it then uses the event api to trigger an assessable_file_uploaded event for the plagiarism api. It then records the number of files in the plugin specific &amp;quot;assignsubmission_file&amp;quot; table. &lt;br /&gt;
&lt;br /&gt;
==== &#039;&#039;&#039; get_files()&#039;&#039;&#039; ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    public function get_files($submission) {&lt;br /&gt;
        $result = array();                                                                                                          &lt;br /&gt;
        $fs = get_file_storage();                                                                                                   &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
        $files = $fs-&amp;gt;get_area_files($this-&amp;gt;assignment-&amp;gt;get_context()-&amp;gt;id,                                                          &lt;br /&gt;
                                     &#039;assignsubmission_file&#039;,                                                                       &lt;br /&gt;
                                     ASSIGNSUBMISSION_FILE_FILEAREA,                                                                &lt;br /&gt;
                                     $submission-&amp;gt;id,                                                                               &lt;br /&gt;
                                     &#039;timemodified&#039;,                                                                                &lt;br /&gt;
                                     false);                                                                                        &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
        foreach ($files as $file) {                                                                                                 &lt;br /&gt;
            $result[$file-&amp;gt;get_filename()] = $file;                                                                                 &lt;br /&gt;
        }                                                                                                                           &lt;br /&gt;
        return $result;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If this submission plugin produces one or more files, it should implement &amp;quot;get_files&amp;quot; so that the portfolio API can export a list of all the files from all of the plugins for this assignment submission. This is also used by the offline grading feature in the assignment.&lt;br /&gt;
&lt;br /&gt;
==== &#039;&#039;&#039; view_summary()&#039;&#039;&#039; ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    public function view_summary(stdClass $submission, &amp;amp; $showviewlink) {                                                           &lt;br /&gt;
        $count = $this-&amp;gt;count_files($submission-&amp;gt;id, ASSIGNSUBMISSION_FILE_FILEAREA);                                               &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
        // Show we show a link to view all files for this plugin?                                                                   &lt;br /&gt;
        $showviewlink = $count &amp;gt; ASSIGNSUBMISSION_FILE_MAXSUMMARYFILES;                                                             &lt;br /&gt;
        if ($count &amp;lt;= ASSIGNSUBMISSION_FILE_MAXSUMMARYFILES) {                                                                      &lt;br /&gt;
            return $this-&amp;gt;assignment-&amp;gt;render_area_files(&#039;assignsubmission_file&#039;,                                                    &lt;br /&gt;
                                                        ASSIGNSUBMISSION_FILE_FILEAREA,                                             &lt;br /&gt;
                                                        $submission-&amp;gt;id);                                                           &lt;br /&gt;
        } else {                                                                                                                    &lt;br /&gt;
            return get_string(&#039;countfiles&#039;, &#039;assignsubmission_file&#039;, $count);                                                       &lt;br /&gt;
        }                                                                                                                           &lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The view_summary function is called to display a summary of the submission to both markers and students. It counts the number of files submitted and if it is more that a set number, it only displays a count of how many files are in the submission - otherwise it uses a helper function to write the entire list of files. This is because we want to keep the summaries really short so they can be displayed in a table. There will be a link to view the full submission on the submission status page. &lt;br /&gt;
&lt;br /&gt;
==== &#039;&#039;&#039; view()&#039;&#039;&#039; ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    public function view($submission) {&lt;br /&gt;
        return $this-&amp;gt;assignment-&amp;gt;render_area_files(&#039;assignsubmission_file&#039;,                                                        &lt;br /&gt;
                                                    ASSIGNSUBMISSION_FILE_FILEAREA,                                                 &lt;br /&gt;
                                                    $submission-&amp;gt;id);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The view function is called to display the entire submission to both markers and students. In this case it uses the helper function in the assignment class to write the list of files. &lt;br /&gt;
&lt;br /&gt;
==== &#039;&#039;&#039; can_upgrade()&#039;&#039;&#039; ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    public function can_upgrade($type, $version) {&lt;br /&gt;
&lt;br /&gt;
        $uploadsingle_type =&#039;uploadsingle&#039;;&lt;br /&gt;
        $upload_type =&#039;upload&#039;;&lt;br /&gt;
&lt;br /&gt;
        if (($type == $uploadsingle_type || $type == $upload_type) &amp;amp;&amp;amp; $version &amp;gt;= 2011112900) {&lt;br /&gt;
            return true;&lt;br /&gt;
        }&lt;br /&gt;
        return false;&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The can_upgrade function is used to identify old &amp;quot;Assignment 2.2&amp;quot; subtypes that can be upgraded by this plugin. This plugin supports upgrades from the old &amp;quot;upload&amp;quot; and &amp;quot;uploadsingle&amp;quot; assignment subtypes. &lt;br /&gt;
&lt;br /&gt;
==== &#039;&#039;&#039; upgrade_settings()&#039;&#039;&#039; ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    &lt;br /&gt;
    public function upgrade_settings(context $oldcontext, stdClass $oldassignment, &amp;amp; $log) {                                        &lt;br /&gt;
        global $DB;                                                                                                                 &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
        if ($oldassignment-&amp;gt;assignmenttype == &#039;uploadsingle&#039;) {                                                                     &lt;br /&gt;
            $this-&amp;gt;set_config(&#039;maxfilesubmissions&#039;, 1);                                                                             &lt;br /&gt;
            $this-&amp;gt;set_config(&#039;maxsubmissionsizebytes&#039;, $oldassignment-&amp;gt;maxbytes);                                                  &lt;br /&gt;
            return true;                                                                                                            &lt;br /&gt;
        } else if ($oldassignment-&amp;gt;assignmenttype == &#039;upload&#039;) {                                                                    &lt;br /&gt;
            $this-&amp;gt;set_config(&#039;maxfilesubmissions&#039;, $oldassignment-&amp;gt;var1);                                                          &lt;br /&gt;
            $this-&amp;gt;set_config(&#039;maxsubmissionsizebytes&#039;, $oldassignment-&amp;gt;maxbytes);                                                  &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
            // Advanced file upload uses a different setting to do the same thing.                                                  &lt;br /&gt;
            $DB-&amp;gt;set_field(&#039;assign&#039;,                                                                                                &lt;br /&gt;
                           &#039;submissiondrafts&#039;,                                                                                      &lt;br /&gt;
                           $oldassignment-&amp;gt;var4,                                                                                    &lt;br /&gt;
                           array(&#039;id&#039;=&amp;gt;$this-&amp;gt;assignment-&amp;gt;get_instance()-&amp;gt;id));                                                     &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
            // Convert advanced file upload &amp;quot;hide description before due date&amp;quot; setting.                                             &lt;br /&gt;
            $alwaysshow = 0;                                                                                                        &lt;br /&gt;
            if (!$oldassignment-&amp;gt;var3) {                                                                                            &lt;br /&gt;
                $alwaysshow = 1;                                                                                                    &lt;br /&gt;
            }                                                                                                                       &lt;br /&gt;
            $DB-&amp;gt;set_field(&#039;assign&#039;,                                                                                                &lt;br /&gt;
                           &#039;alwaysshowdescription&#039;,                                                                                 &lt;br /&gt;
                           $alwaysshow,                                                                                             &lt;br /&gt;
                           array(&#039;id&#039;=&amp;gt;$this-&amp;gt;assignment-&amp;gt;get_instance()-&amp;gt;id));                                                     &lt;br /&gt;
            return true;                                                                                                            &lt;br /&gt;
        }                                                                                                                           &lt;br /&gt;
    }                     &lt;br /&gt;
&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This function is called once per assignment instance to upgrade the settings from the old assignment to the new mod_assign. In this case it sets the maxbytes, maxfiles and alwaysshowdescription configuration settings. &lt;br /&gt;
&lt;br /&gt;
==== &#039;&#039;&#039; upgrade()&#039;&#039;&#039; ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
    public function upgrade($oldcontext,$oldassignment, $oldsubmission, $submission, &amp;amp; $log) {&lt;br /&gt;
        global $DB;&lt;br /&gt;
&lt;br /&gt;
        $file_submission = new stdClass();&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        $file_submission-&amp;gt;numfiles = $oldsubmission-&amp;gt;numfiles;&lt;br /&gt;
        $file_submission-&amp;gt;submission = $submission-&amp;gt;id;&lt;br /&gt;
        $file_submission-&amp;gt;assignment = $this-&amp;gt;assignment-&amp;gt;get_instance()-&amp;gt;id;&lt;br /&gt;
&lt;br /&gt;
        if (!$DB-&amp;gt;insert_record(&#039;assign_submission_file&#039;, $file_submission) &amp;gt; 0) {&lt;br /&gt;
            $log .= get_string(&#039;couldnotconvertsubmission&#039;, &#039;mod_assign&#039;, $submission-&amp;gt;userid);&lt;br /&gt;
            return false;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        // now copy the area files&lt;br /&gt;
        $this-&amp;gt;assignment-&amp;gt;copy_area_files_for_upgrade($oldcontext-&amp;gt;id,&lt;br /&gt;
                                                        &#039;mod_assignment&#039;,&lt;br /&gt;
                                                        &#039;submission&#039;,&lt;br /&gt;
                                                        $oldsubmission-&amp;gt;id,&lt;br /&gt;
                                                        // New file area&lt;br /&gt;
                                                        $this-&amp;gt;assignment-&amp;gt;get_context()-&amp;gt;id,&lt;br /&gt;
                                                        &#039;mod_assign&#039;,&lt;br /&gt;
                                                        ASSIGN_FILEAREA_SUBMISSION_FILES,&lt;br /&gt;
                                                        $submission-&amp;gt;id);&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
        return true;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;quot;upgrade&amp;quot; function upgrades a single submission from the old assignment type to the new one. In this case it involves copying all the files from the old filearea to the new one. There is a helper function available in the assignment class for this (Note: the copy will be fast as it is just adding rows to the files table). If this function returns false, the upgrade will be aborted and rolled back.&lt;br /&gt;
&lt;br /&gt;
==== &#039;&#039;&#039; get_editor_fields()&#039;&#039;&#039; ====&lt;br /&gt;
&amp;lt;pre&amp;gt;    public function () {                                                                                           &lt;br /&gt;
        return array(&#039;onlinetext&#039; =&amp;gt; get_string(&#039;pluginname&#039;, &#039;assignsubmission_comments&#039;));                                        &lt;br /&gt;
    }       &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This example is from assignsubmission_onlinetext.  If the plugin uses a text-editor it is ideal if the plugin implements &amp;quot;get_editor_fields&amp;quot;. This allows the portfolio to retrieve the text from the plugin when exporting the list of files for a submission. This is required because the text is stored in the plugin specific table that is only known to the plugin itself. If a plugin supports multiple text areas it can return the name of each of them here.&lt;br /&gt;
&lt;br /&gt;
==== &#039;&#039;&#039; get_editor_text()&#039;&#039;&#039; ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    public function get_editor_text($name, $submissionid) {                                                                         &lt;br /&gt;
        if ($name == &#039;onlinetext&#039;) {                                                                                                &lt;br /&gt;
            $onlinetextsubmission = $this-&amp;gt;get_onlinetext_submission($submissionid);                                                &lt;br /&gt;
            if ($onlinetextsubmission) {                                                                                            &lt;br /&gt;
                return $onlinetextsubmission-&amp;gt;onlinetext;                                                                           &lt;br /&gt;
            }                                                                                                                       &lt;br /&gt;
        }                                                                                                                           &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
        return &#039;&#039;;                                                                                                                  &lt;br /&gt;
    }             &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This example is from assignsubmission_onlinetext.  If the plugin uses a text-editor it is ideal if the plugin implements &amp;quot;get_editor_text&amp;quot;. This allows the portfolio to retrieve the text from the plugin when exporting the list of files for a submission. This is required because the text is stored in the plugin specific table that is only known to the plugin itself. The name is used to distinguish between multiple text areas in the one plugin. &lt;br /&gt;
&lt;br /&gt;
==== &#039;&#039;&#039; get_editor_format()&#039;&#039;&#039; ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    public function get_editor_format($name, $submissionid) {&lt;br /&gt;
        if ($name == &#039;onlinetext&#039;) {&lt;br /&gt;
            $onlinetext_submission = $this-&amp;gt;get_onlinetext_submission($submissionid);&lt;br /&gt;
            if ($onlinetext_submission) {&lt;br /&gt;
                return $onlinetext_submission-&amp;gt;onlineformat;&lt;br /&gt;
            }&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        return 0;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This example is from assignsubmission_onlinetext. For the same reason as the previous function, if the plugin uses a text editor, it is ideal if the plugin implements &amp;quot;get_editor_format&amp;quot;. This allows the portfolio to retrieve the text from the plugin when exporting the list of files for a submission. This is required because the text is stored in the plugin specific table that is only known to the plugin itself. The name is used to distinguish between multiple text areas in the one plugin.&lt;br /&gt;
&lt;br /&gt;
==== &#039;&#039;&#039; is_empty()&#039;&#039;&#039; ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    public function is_empty(stdClass $submission) {                                                                                &lt;br /&gt;
        return $this-&amp;gt;count_files($submission-&amp;gt;id, ASSIGNSUBMISSION_FILE_FILEAREA) == 0;                                            &lt;br /&gt;
    }   &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If a plugin has no submission data to show - it can return true from the is_empty function. This prevents a table row being added to the submission summary for this plugin. It is also used to check if a student has tried to save an assignment with no data.&lt;br /&gt;
&lt;br /&gt;
==== &#039;&#039;&#039; get_file_areas()&#039;&#039;&#039; ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   public function get_file_areas() {                                                                                              &lt;br /&gt;
        return array(ASSIGNSUBMISSION_FILE_FILEAREA=&amp;gt;$this-&amp;gt;get_name());                                                            &lt;br /&gt;
    }    &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A plugin should implement get_file_areas if it supports saving of any files to moodle - this allows the file areas to be browsed by the moodle file manager.&lt;br /&gt;
&lt;br /&gt;
==== &#039;&#039;&#039; copy_submission()&#039;&#039;&#039; ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    public function copy_submission(stdClass $sourcesubmission, stdClass $destsubmission) {                                         &lt;br /&gt;
        global $DB;                                                                                                                 &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
        // Copy the files across.                                                                                                   &lt;br /&gt;
        $contextid = $this-&amp;gt;assignment-&amp;gt;get_context()-&amp;gt;id;                                                                          &lt;br /&gt;
        $fs = get_file_storage();                                                                                                   &lt;br /&gt;
        $files = $fs-&amp;gt;get_area_files($contextid,                                                                                    &lt;br /&gt;
                                     &#039;assignsubmission_file&#039;,                                                                       &lt;br /&gt;
                                     ASSIGNSUBMISSION_FILE_FILEAREA,                                                                &lt;br /&gt;
                                     $sourcesubmission-&amp;gt;id,                                                                         &lt;br /&gt;
                                     &#039;id&#039;,                                                                                          &lt;br /&gt;
                                     false);                                                                                        &lt;br /&gt;
        foreach ($files as $file) {                                                                                                 &lt;br /&gt;
            $fieldupdates = array(&#039;itemid&#039; =&amp;gt; $destsubmission-&amp;gt;id);                                                                 &lt;br /&gt;
            $fs-&amp;gt;create_file_from_storedfile($fieldupdates, $file);                                                                 &lt;br /&gt;
        }                                                                                                                           &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
        // Copy the assignsubmission_file record.                                                                                   &lt;br /&gt;
        if ($filesubmission = $this-&amp;gt;get_file_submission($sourcesubmission-&amp;gt;id)) {                                                  &lt;br /&gt;
            unset($filesubmission-&amp;gt;id);                                                                                             &lt;br /&gt;
            $filesubmission-&amp;gt;submission = $destsubmission-&amp;gt;id;                                                                      &lt;br /&gt;
            $DB-&amp;gt;insert_record(&#039;assignsubmission_file&#039;, $filesubmission);                                                           &lt;br /&gt;
        }                                                                                                                           &lt;br /&gt;
        return true;                                                                                                                &lt;br /&gt;
    }                                 &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Since Moodle 2.5 - a students submission can be copied to create a new submission attempt. Plugins should implement this function if they store data associated with the submission (most plugins).&lt;br /&gt;
&lt;br /&gt;
==== &#039;&#039;&#039; format_for_log()&#039;&#039;&#039; ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
   public function (stdClass $submission) {                                                                          &lt;br /&gt;
        // format the info for each submission plugin add_to_log                                                                    &lt;br /&gt;
        $filecount = $this-&amp;gt;count_files($submission-&amp;gt;id, ASSIGNSUBMISSION_FILE_FILEAREA);                                           &lt;br /&gt;
        $fileloginfo = &#039;&#039;;                                                                                                          &lt;br /&gt;
        $fileloginfo .= &#039; the number of file(s) : &#039; . $filecount . &amp;quot; file(s).&amp;lt;br&amp;gt;&amp;quot;;                                                 &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
        return $fileloginfo;                                                                                                        &lt;br /&gt;
    }                      &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The format_for_log function lets a plugin produce a really short summary of a submission suitable for adding to a log message. &lt;br /&gt;
&lt;br /&gt;
==== &#039;&#039;&#039; delete_instance()&#039;&#039;&#039; ====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
    public function delete_instance() {                                                                                             &lt;br /&gt;
        global $DB;                                                                                                                 &lt;br /&gt;
        // will throw exception on failure                                                                                          &lt;br /&gt;
        $DB-&amp;gt;delete_records(&#039;assignsubmission_file&#039;, array(&#039;assignment&#039;=&amp;gt;$this-&amp;gt;assignment-&amp;gt;get_instance()-&amp;gt;id));                   &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
        return true;                                                                                                                &lt;br /&gt;
    }   &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The delete_instance function is called when a plugin is deleted. Note only database records need to be cleaned up - files belonging to fileareas for this assignment will be automatically cleaned up.&lt;br /&gt;
&lt;br /&gt;
=== lib.php ===&lt;br /&gt;
&lt;br /&gt;
This file is the entry point to many standard Moodle APIs for plugins. An example is that in order for a plugin to allow users to download files contained within a filearea belonging to the plugin, they must implement the componentname_pluginfile function in order to perform their own security checks.&lt;br /&gt;
&lt;br /&gt;
See [[File_API]] for more information.&lt;br /&gt;
Example:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
function assignfeedback_file_pluginfile($course,                                                                                    &lt;br /&gt;
                                        $cm,                                                                                        &lt;br /&gt;
                                        context $context,                                                                           &lt;br /&gt;
                                        $filearea,                                                                                  &lt;br /&gt;
                                        $args,                                                                                      &lt;br /&gt;
                                        $forcedownload) {                                                                           &lt;br /&gt;
    global $USER, $DB;                                                                                                              &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
    if ($context-&amp;gt;contextlevel != CONTEXT_MODULE) {                                                                                 &lt;br /&gt;
        return false;                                                                                                               &lt;br /&gt;
    }                                                                                                                               &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
    require_login($course, false, $cm);                                                                                             &lt;br /&gt;
    $itemid = (int)array_shift($args);                                                                                              &lt;br /&gt;
    $record = $DB-&amp;gt;get_record(&#039;assign_grades&#039;, array(&#039;id&#039;=&amp;gt;$itemid), &#039;userid,assignment&#039;, MUST_EXIST);                              &lt;br /&gt;
    $userid = $record-&amp;gt;userid;                                                                                                      &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
    if (!$assign = $DB-&amp;gt;get_record(&#039;assign&#039;, array(&#039;id&#039;=&amp;gt;$cm-&amp;gt;instance))) {                                                         &lt;br /&gt;
        return false;                                                                                                               &lt;br /&gt;
    }                                                                                                                               &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
    if ($assign-&amp;gt;id != $record-&amp;gt;assignment) {                                                                                       &lt;br /&gt;
        return false;                                                                                                               &lt;br /&gt;
    }                                                                                                                               &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
    // Check is users feedback or has grading permission.                                                                           &lt;br /&gt;
    if ($USER-&amp;gt;id != $userid and !has_capability(&#039;mod/assign:grade&#039;, $context)) {                                                   &lt;br /&gt;
        return false;                                                                                                               &lt;br /&gt;
    }                                                                                                                               &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
    $relativepath = implode(&#039;/&#039;, $args);                                                                                            &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
    $fullpath = &amp;quot;/{$context-&amp;gt;id}/assignfeedback_file/$filearea/$itemid/$relativepath&amp;quot;;                                              &lt;br /&gt;
                                                                                                                                    &lt;br /&gt;
    $fs = get_file_storage();                                                                                                       &lt;br /&gt;
    if (!$file = $fs-&amp;gt;get_file_by_hash(sha1($fullpath)) or $file-&amp;gt;is_directory()) {                                                 &lt;br /&gt;
        return false;                                                                                                               &lt;br /&gt;
    }                                                                                                                               &lt;br /&gt;
    // Download MUST be forced - security!                                                                                          &lt;br /&gt;
    send_stored_file($file, 0, 0, true);                                                                                            &lt;br /&gt;
}                                            &lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Useful classes ==&lt;br /&gt;
A submission plugin has access to a number of useful classes in the assignment module. See the phpdocs (or the code) for more information on these classes.&lt;br /&gt;
&lt;br /&gt;
=== assign_plugin ===&lt;br /&gt;
This abstract class is the base class for all assignment plugins (feedback or submission plugins).&lt;br /&gt;
&lt;br /&gt;
It provides access to the assign class which represents the current assignment instance through &amp;quot;$this-&amp;gt;assignment&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
=== assign_submission_plugin ===&lt;br /&gt;
This is the base class all assignment submission plugins must extend. It contains a small number of additional function that only apply to submission plugins. &lt;br /&gt;
&lt;br /&gt;
=== assign ===&lt;br /&gt;
This is the main class for interacting with the assignment module. &lt;br /&gt;
&lt;br /&gt;
It contains public functions that are useful for listing users, loading and updating submissions, loading and updating grades, displaying users etc.&lt;/div&gt;</summary>
		<author><name>Davidbalch</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Map_API&amp;diff=43426</id>
		<title>Map API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Map_API&amp;diff=43426"/>
		<updated>2013-12-16T16:13:53Z</updated>

		<summary type="html">&lt;p&gt;Davidbalch: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Infobox Project&lt;br /&gt;
|name = New Map API&lt;br /&gt;
|state = Proposal/Development in progress&lt;br /&gt;
|tracker = MDL-42522&lt;br /&gt;
|discussion = https://moodle.org/mod/forum/discuss.php?d=245569&lt;br /&gt;
}}&lt;br /&gt;
{{Moodle 2.7}}This page is for collecting feature requests for a possible new API to enable easy use of geographical maps.&lt;br /&gt;
&lt;br /&gt;
== Current status ==&lt;br /&gt;
Moodle + contrib has three separate map implementations:&lt;br /&gt;
# The built-in map in the IP address lookup (under /iplookup)&lt;br /&gt;
# [https://moodle.org/plugins/view.php?plugin=block_online_users_map block_online_users_map] is a contrib module that shows recent users plotted on a world map.&lt;br /&gt;
# [https://moodle.org/plugins/view.php?plugin=block_simple_map block_simple_map] is a contrib module that allows users to search for places specified by a CSV upload&lt;br /&gt;
&lt;br /&gt;
* Additionally, the Database activity has a latlong field, which outputs as links to external mapping services.&lt;br /&gt;
&lt;br /&gt;
== High-level proposal == &lt;br /&gt;
&lt;br /&gt;
A core module to provide an easy way to add maps and mapping features:&lt;br /&gt;
# Display a map with specified size, location, zoom, and tileset.&lt;br /&gt;
# Display one or more markers on the map.&lt;br /&gt;
# Display popups (or other display methods) with additional information when a user clicks on markers. &lt;br /&gt;
# Geocoding (i.e. find lat/long from location name - should adopt existing iplookup solution).  &lt;br /&gt;
# Input of locations by clicking on the map.&lt;br /&gt;
# Reverse geocoding (i.e. find location name from lat/long).&lt;br /&gt;
&lt;br /&gt;
== Existing work (2013-12-10) ==&lt;br /&gt;
&lt;br /&gt;
An alpha level implementation is done, including all proposed features except integration of the existing geocoding system.&lt;br /&gt;
&lt;br /&gt;
=== Map API (local module) ===&lt;br /&gt;
* The [http://leafletjs.com/ Leaflet] javascript map library as a YUI module.†&lt;br /&gt;
* A PHP class to easily: Add maps, display markers and popups, input locations, perform reverse geocoding.&lt;br /&gt;
&lt;br /&gt;
Demonstration at: http://dbtest.conted.ox.ac.uk/moodlemaps/local/map&lt;br /&gt;
&lt;br /&gt;
† There are other map libs, e.g.:&lt;br /&gt;
* OpenLayers - older, larger, with more features than I think we need here.&lt;br /&gt;
* Modest Maps - fairly minimal, with fewer&lt;br /&gt;
* Polymap - looks interesting for data mapping, but not sure how useful that would be in Moodle.&lt;br /&gt;
&lt;br /&gt;
...however, I&#039;m most familiar with Leaflet and feel it hits the right features/weight balance (e.g. includes geoJSON, but avoids complications with projections), so think it&#039;s the right place to start.&lt;br /&gt;
&lt;br /&gt;
=== Database activity submodules ===&lt;br /&gt;
# A modification/fork of the latlong field to enable maps for display and input&lt;br /&gt;
# A new preset (named &amp;quot;Infomap&amp;quot;) to utilise the modified latlong field.&lt;br /&gt;
&lt;br /&gt;
Demonstration at: http://dbtest.conted.ox.ac.uk/moodlemaps/course/view.php?id=2&lt;br /&gt;
&lt;br /&gt;
=== Plugins ===&lt;br /&gt;
&lt;br /&gt;
The local and database modules have been formatted into three modules, awaiting approval: &lt;br /&gt;
* local_map - the API.&lt;br /&gt;
* datafield_latlongmap - a modified latlong field.&lt;br /&gt;
* datapreset_infomap_latlongmap - a preset using datafield_latlongmap.&lt;br /&gt;
&lt;br /&gt;
== Detailed propsal ==&lt;br /&gt;
TBC&lt;/div&gt;</summary>
		<author><name>Davidbalch</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Map_API&amp;diff=43409</id>
		<title>Map API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Map_API&amp;diff=43409"/>
		<updated>2013-12-10T16:16:27Z</updated>

		<summary type="html">&lt;p&gt;Davidbalch: First draft&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Infobox Project&lt;br /&gt;
|name = New Map API&lt;br /&gt;
|state = Proposal/Development in progress&lt;br /&gt;
|tracker = MDL-42522&lt;br /&gt;
}}&lt;br /&gt;
{{Moodle 2.7}}This page is for collecting feature requests for a possible new API to enable easy use of geographical maps.&lt;br /&gt;
&lt;br /&gt;
== Current status ==&lt;br /&gt;
Moodle + contrib has three separate map implementations:&lt;br /&gt;
# The built-in map in the IP address lookup (under /iplookup)&lt;br /&gt;
# [https://moodle.org/plugins/view.php?plugin=block_online_users_map block_online_users_map] is a contrib module that shows recent users plotted on a world map.&lt;br /&gt;
# [https://moodle.org/plugins/view.php?plugin=block_simple_map block_simple_map] is a contrib module that allows users to search for places specified by a CSV upload&lt;br /&gt;
&lt;br /&gt;
* Additionally, the Database activity has a latlong field, which outputs as links to external mapping services.&lt;br /&gt;
&lt;br /&gt;
== High-level proposal == &lt;br /&gt;
&lt;br /&gt;
A core module to provide an easy way to add maps and mapping features:&lt;br /&gt;
# Display a map with specified size, location, zoom, and tileset.&lt;br /&gt;
# Display one or more markers on the map.&lt;br /&gt;
# Display popups (or other display methods) with additional information when a user clicks on markers. &lt;br /&gt;
# Geocoding (i.e. find lat/long from location name - should adopt existing iplookup solution).  &lt;br /&gt;
# Input of locations by clicking on the map.&lt;br /&gt;
# Reverse geocoding (i.e. find location name from lat/long).&lt;br /&gt;
&lt;br /&gt;
== Existing work (2013-12-10) ==&lt;br /&gt;
&lt;br /&gt;
An alpha level implementation is done, including all proposed features except integration of the existing geocoding system.&lt;br /&gt;
&lt;br /&gt;
=== Map API (local module) ===&lt;br /&gt;
* The [http://leafletjs.com/ Leaflet] javascript map library as a YUI module.†&lt;br /&gt;
* A PHP class to easily: Add maps, display markers and popups, input locations, perform reverse geocoding.&lt;br /&gt;
&lt;br /&gt;
Demonstration at: http://dbtest.conted.ox.ac.uk/moodlemaps/local/map&lt;br /&gt;
&lt;br /&gt;
† There are other map libs, e.g.:&lt;br /&gt;
* OpenLayers - older, larger, with more features than I think we need here.&lt;br /&gt;
* Modest Maps - fairly minimal, with fewer&lt;br /&gt;
* Polymap - looks interesting for data mapping, but not sure how useful that would be in Moodle.&lt;br /&gt;
&lt;br /&gt;
...however, I&#039;m most familiar with Leaflet and feel it hits the right features/weight balance (e.g. includes geoJSON, but avoids complications with projections), so think it&#039;s the right place to start.&lt;br /&gt;
&lt;br /&gt;
=== Database activity submodules ===&lt;br /&gt;
# A modification/fork of the latlong field to enable maps for display and input&lt;br /&gt;
# A new preset (named &amp;quot;Infomap&amp;quot;) to utilise the modified latlong field.&lt;br /&gt;
&lt;br /&gt;
Demonstration at: http://dbtest.conted.ox.ac.uk/moodlemaps/course/view.php?id=2&lt;br /&gt;
&lt;br /&gt;
=== Plugins ===&lt;br /&gt;
&lt;br /&gt;
The local and database modules have been formatted into three modules, awaiting approval: &lt;br /&gt;
* local_map - the API.&lt;br /&gt;
* datafield_latlongmap - a modified latlong field.&lt;br /&gt;
* datapreset_infomap_latlongmap - a preset using datafield_latlongmap.&lt;br /&gt;
&lt;br /&gt;
== Detailed propsal ==&lt;br /&gt;
TBC&lt;/div&gt;</summary>
		<author><name>Davidbalch</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Using_the_File_API_in_Moodle_forms&amp;diff=34342</id>
		<title>Using the File API in Moodle forms</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Using_the_File_API_in_Moodle_forms&amp;diff=34342"/>
		<updated>2012-06-28T16:15:29Z</updated>

		<summary type="html">&lt;p&gt;Davidbalch: /* Add file manager element */  Update accepted_types info.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.0}}&lt;br /&gt;
&lt;br /&gt;
This document shows you exactly how to use Moodle forms to get files from users in a standard and secure way.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Overview==&lt;br /&gt;
&lt;br /&gt;
In Moodle 2.0 all files are stored in a central database accessible via the [[File API|File API]], and every file is associated with a component and a &amp;quot;file area&amp;quot; in Moodle, such as a particular module.&lt;br /&gt;
&lt;br /&gt;
A common use case is to provide a form (using Moodle&#039;s [[lib/formslib.php|Forms API]]) which allows users to upload or import files as attachments or media embedded into HTML.&lt;br /&gt;
&lt;br /&gt;
Normally this works like this:&lt;br /&gt;
# User starts creation or re-edits an existing item in Moodle (eg forum post, resource, glossary entry etc)&lt;br /&gt;
# User presses some sort of button to browse for new files to attach or embed&lt;br /&gt;
# User sees our &amp;quot;Choose file...&amp;quot; dialog, which contains one or more repository instances. &lt;br /&gt;
# User chooses a file, the [[Repository API|Repository API]] takes care of copying the file into a &amp;quot;draft file area&amp;quot; within Moodle&lt;br /&gt;
# File appears in the text or as an attachment in the form.&lt;br /&gt;
# When the user hits save, the [[File API|File API]] is invoked to move the file from the draft file area into a permanent file area associated with that data &lt;br /&gt;
&lt;br /&gt;
This document shows you exactly how to use Moodle forms to interact with users in a standard and secure way.&lt;br /&gt;
&lt;br /&gt;
If you just want to write code to manipulate Moodle files internally (without user input) then see [[File API]].&lt;br /&gt;
&lt;br /&gt;
==Form elements== &lt;br /&gt;
&lt;br /&gt;
In Moodle 2.0 there are three file-related form elements for interacting with users:&lt;br /&gt;
&lt;br /&gt;
# filemanager - the way to attach one or more files as a set&lt;br /&gt;
# editor - the way to specify a textarea with a HTML editor, and all the handling of images and movies within that HTML&lt;br /&gt;
# filepicker - a way to specify one file for the case when you want to process the file and throw it away &lt;br /&gt;
&lt;br /&gt;
In Moodle 1.9 there were two other types which are now &#039;&#039;&#039;deprecated&#039;&#039;&#039; (they work, but please do not use these anymore)&lt;br /&gt;
# file - used to just allow a normal file upload from the desktop only.&lt;br /&gt;
# htmleditor - this old method of embedding a HTML editor in a textarea is not able to support repositories etc.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===filepicker===&lt;br /&gt;
&lt;br /&gt;
File picker (&#039;&#039;filepicker&#039;&#039;) is a direct replacement of the older &#039;&#039;file&#039;&#039; formslib element. &lt;br /&gt;
&lt;br /&gt;
It is intended for situations when you want the user to upload &#039;&#039;&#039;one&#039;&#039;&#039; file so you can process it and delete it, such as when you are importing data from a CSV file.&lt;br /&gt;
&lt;br /&gt;
==== Using the filepicker element ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;filepicker&#039;, &#039;userfile&#039;, get_string(&#039;file&#039;), null,&lt;br /&gt;
                   array(&#039;maxbytes&#039; =&amp;gt; $maxbytes, &#039;accepted_types&#039; =&amp;gt; &#039;*&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Obtain the chosen file ====&lt;br /&gt;
&lt;br /&gt;
The API for getting file contents is exactly the same as for &#039;&#039;file&#039;&#039; element.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$content = $mform-&amp;gt;get_file_content(&#039;userfile&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== filemanager ===&lt;br /&gt;
&lt;br /&gt;
The File Manager element improves on file picker by allowing you to manage more than one file.  It is expected that the files will be stored permanently for future use (such as forum and glossary attachments).&lt;br /&gt;
&lt;br /&gt;
==== Add file manager element ====&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;filemanager&#039;, &#039;attachments&#039;, get_string(&#039;attachment&#039;, &#039;moodle&#039;), null,&lt;br /&gt;
                    array(&#039;subdirs&#039; =&amp;gt; 0, &#039;maxbytes&#039; =&amp;gt; $maxbytes, &#039;maxfiles&#039; =&amp;gt; 50,&lt;br /&gt;
                          &#039;accepted_types&#039; =&amp;gt; array(&#039;document&#039;) ));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Here are the fields for filemanager:&lt;br /&gt;
&lt;br /&gt;
;&#039;filemanager&#039;:This is a filemanager element :)&lt;br /&gt;
;elementname:The unique name of the element in the form&lt;br /&gt;
;elementlabel:The label string that users see &lt;br /&gt;
;attributes:(leave it as null)&lt;br /&gt;
;options: an array of further options for the filepicker (see below)&lt;br /&gt;
&lt;br /&gt;
The options array can contain:&lt;br /&gt;
&lt;br /&gt;
;subdirs:(Default 1) Are subdirectories allowed?  (true or false)&lt;br /&gt;
;maxbytes:(Default 0) Restricts the total size of all the files.&lt;br /&gt;
;maxfiles:(Default -1) Restricts the total number of files.&lt;br /&gt;
;accepted_types:(Default *) You can specify what file types are accepted by filemanager.  All current file types are listed in this file: [http://cvs.moodle.org/moodle/lib/filestorage/file_types.mm moodle/lib/filestorage/file_types.mm].  This is a [http://freemind.sourceforge.net/wiki/index.php/Main_Page freemind] file: if it is edited the changes will be immediately reflected in Moodle.&lt;br /&gt;
&lt;br /&gt;
As of 2.3 file_types.mm is no longer used, instead the file types are listed in &amp;lt;code&amp;gt;get_mimetypes_array()&amp;lt;/code&amp;gt; in filelib.php (https://github.com/moodle/moodle/blob/master/lib/filelib.php#L1360).&lt;br /&gt;
&lt;br /&gt;
Example usage:  &#039;&#039;&#039;array(&#039;audio&#039;, &#039;video&#039;, &#039;documents&#039;)&#039;&#039;&#039;, you can include file extensions as well, for example: &#039;&#039;&#039;array(&#039;*.txt&#039;, &#039;*.jpg&#039;, &#039;audio&#039;)&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
==== Load existing files into draft area ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if (empty($entry-&amp;gt;id)) {&lt;br /&gt;
    $entry = new stdClass;&lt;br /&gt;
    $entry-&amp;gt;id = null;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$draftitemid = file_get_submitted_draft_itemid(&#039;attachments&#039;);&lt;br /&gt;
&lt;br /&gt;
file_prepare_draft_area($draftitemid, $context-&amp;gt;id, &#039;mod_glossary&#039;, &#039;attachment&#039;, $entry-&amp;gt;id,&lt;br /&gt;
                        array(&#039;subdirs&#039; =&amp;gt; 0, &#039;maxbytes&#039; =&amp;gt; $maxbytes, &#039;maxfiles&#039; =&amp;gt; 50));&lt;br /&gt;
&lt;br /&gt;
$entry-&amp;gt;attachments = $draftitemid;&lt;br /&gt;
&lt;br /&gt;
$mform-&amp;gt;set_data($entry);&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Store updated set of files ====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if ($data = $mform-&amp;gt;get_data()) {&lt;br /&gt;
    // ... store or update $entry&lt;br /&gt;
    file_save_draft_area_files($data-&amp;gt;attachments, $context-&amp;gt;id, &#039;mod_glossary&#039;, &#039;attachment&#039;,&lt;br /&gt;
                   $entry-&amp;gt;id, array(&#039;subdirs&#039; =&amp;gt; 0, &#039;maxbytes&#039; =&amp;gt; $maxbytes, &#039;maxfiles&#039; =&amp;gt; 50));&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===editor===&lt;br /&gt;
There are two ways of using the editor element in code, the first one is easier but expects some standardized fields. The second method is more low level.&lt;br /&gt;
&lt;br /&gt;
====Simple use====&lt;br /&gt;
# name database fields: &#039;&#039;textfield&#039;&#039;, &#039;&#039;textfieldformat&#039;&#039; (and &#039;&#039;textfieldtrust&#039;&#039; if required)&lt;br /&gt;
# create options array.  note that context is the best, most local context you have available.  &amp;lt;code php&amp;gt;$textfieldoptions = array(&#039;trusttext&#039;=&amp;gt;true, &#039;subdirs&#039;=&amp;gt;true, &#039;maxfiles&#039;=&amp;gt;$maxfiles,&lt;br /&gt;
                          &#039;maxbytes&#039;=&amp;gt;$maxbytes, &#039;context&#039;=&amp;gt;$context);&amp;lt;/code&amp;gt;&lt;br /&gt;
# add editor &#039;&#039;textfield_editor&#039;&#039; to moodle form, pass options through custom data in form constructor, set $data-&amp;gt;id to null if data not exist yet &amp;lt;code php&amp;gt;$mform-&amp;gt;addElement(&#039;editor&#039;, &#039;textfield_editor&#039;, get_string(&#039;fieldname&#039;, &#039;somemodule&#039;),&lt;br /&gt;
                   null, $textfieldoptions);&amp;lt;/code&amp;gt;&lt;br /&gt;
# prepare data &amp;lt;code php&amp;gt;$data = file_prepare_standard_editor($data, &#039;textfield&#039;, $textfieldoptions, $context,&lt;br /&gt;
                                     &#039;mod_somemodule&#039;, &#039;somearea&#039;, $data-&amp;gt;id);&amp;lt;/code&amp;gt;&lt;br /&gt;
# get submitted data and after inserting/updating of data &amp;lt;code php&amp;gt;$data = file_postupdate_standard_editor($data, &#039;textfield&#039;, $textfieldoptions, $context,&lt;br /&gt;
                                        &#039;mod_somemodule&#039;, &#039;somearea&#039;, $data-&amp;gt;id);&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Real world examples are in mod/glossary/edit.php and mod/glossary/comment.php&lt;br /&gt;
&lt;br /&gt;
====Low level use====&lt;br /&gt;
&lt;br /&gt;
When using editor element you  need to preprocess and postprocess the data:&lt;br /&gt;
# detect if form was already submitted (usually means draft is area already exists) - &#039;&#039;file_get_submitted_draft_itemid()&#039;&#039;&lt;br /&gt;
# prepare draft file area, temporary storage of all files attached to the text - &#039;&#039;file_prepare_draft_area()&#039;&#039;&lt;br /&gt;
# convert encoded relative links to absolute links - &#039;&#039;file_prepare_draft_area()&#039;&#039;&lt;br /&gt;
# create form and set current data&lt;br /&gt;
# after submission the changed files must be merged back into original area - &#039;&#039;file_save_draft_area_files()&#039;&#039;&lt;br /&gt;
# absolute links have to be replaced by relative links - &#039;&#039;file_save_draft_area_files()&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
=====Replace old htmleditor with editor=====&lt;br /&gt;
&lt;br /&gt;
The file picker has been integrated with with TinyMCE to make the editor element. This new element should support all types on editors and should be able to switch them on-the-fly. Instances of the old htmleditor element in your forms should be replaced by the new editor element, this may need adding of new format and trusttext columns. For example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;editor&#039;, &#039;entry&#039;, get_string(&#039;definition&#039;, &#039;glossary&#039;), null,&lt;br /&gt;
        array(&#039;maxfiles&#039; =&amp;gt; EDITOR_UNLIMITED_FILES));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
The editor element can take following options: maxfiles, maxbytes, subdirs and changeformat. Please note that the embedded files is optional feature and is not expected be used everywhere.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note&#039;&#039;&#039;: the editor element now includes text format option. You should no longer use the separate format element type.&lt;br /&gt;
&lt;br /&gt;
=====Prepare current data - text and files=====&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if (empty($entry-&amp;gt;id)) {&lt;br /&gt;
  $entry = new object();&lt;br /&gt;
  $entry-&amp;gt;id = null;&lt;br /&gt;
  $entry-&amp;gt;definition = &#039;&#039;;&lt;br /&gt;
  $entry-&amp;gt;format = FORMAT_HTML;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
$draftid_editor = file_get_submitted_draft_itemid(&#039;entry&#039;);&lt;br /&gt;
$currenttext = file_prepare_draft_area($draftid_editor, $context-&amp;gt;id, &#039;mod_glossary&#039;, &#039;entry&#039;,&lt;br /&gt;
                                       $entry-&amp;gt;id, array(&#039;subdirs&#039;=&amp;gt;true), $entry-&amp;gt;definition);&lt;br /&gt;
$entry-&amp;gt;entry = array(&#039;text&#039;=&amp;gt;$currenttext, &#039;format&#039;=&amp;gt;$entry-&amp;gt;format, &#039;itemid&#039;=&amp;gt;$draftid_editor);&lt;br /&gt;
&lt;br /&gt;
$mform-&amp;gt;set_data($entry);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If there are multiple files, they will share the same itemid.&lt;br /&gt;
&lt;br /&gt;
=====Obtain text, format and save draft files=====&lt;br /&gt;
&lt;br /&gt;
To retrieve editor content, you need to use following code:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
if ($fromform = $mform-&amp;gt;get_data()) {&lt;br /&gt;
    // content of editor&lt;br /&gt;
    $messagetext = $fromform-&amp;gt;entry[&#039;text&#039;];&lt;br /&gt;
    // format of content&lt;br /&gt;
    $messageformat  = $fromform-&amp;gt;entry[&#039;format&#039;];&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
When a user selects a file using the file picker, the file is initially stored in a draft file area, and a URL is inserted into the HTML in the editor that lets the person editing the content (but no one else) see the file.&lt;br /&gt;
&lt;br /&gt;
When the user submits the form, we then need to save the draft files to the correct place in permanent storage. (Just like you have to call $DB-&amp;gt;update_record(&#039;tablename&#039;, $data); to have the other parts of the form submission stored correctly.)&lt;br /&gt;
&lt;br /&gt;
The save_files_from_draft_area function and replace absolute links with internal relative links do:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$messagetext = file_save_draft_area_files($draftid_editor, $context-&amp;gt;id, &#039;mod_glossary&#039;, &#039;entry&#039;,&lt;br /&gt;
                                          $entry-&amp;gt;id, array(&#039;subdirs&#039;=&amp;gt;true), $messagetext);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
; $context-&amp;gt;id, &#039;component&#039;, &#039;proper_file_area&#039; and $entry-&amp;gt;id : correspond to the contextid, filearea and itemid columns in the [[File_API#Table:_files|files table]].&lt;br /&gt;
; $messagetext : this is the message text. As the files are saved to the real file area, the URLs in this content are rewritten.&lt;br /&gt;
&lt;br /&gt;
All URLs in content that point to files managed to the File API are converted to a form that starts &#039;@@PLUGINFILE@@/&#039; before the content is stored in the database. That is what we mean by rewriting.&lt;br /&gt;
&lt;br /&gt;
== File serving==&lt;br /&gt;
&lt;br /&gt;
=== Convert internal relative links to absolute links ===&lt;br /&gt;
&lt;br /&gt;
Before text content is displayed to the user, any URLs in the &#039;@@PLUGINFILE@@/&#039; form in the content need to be rewritten to the real URL where the user can access the files. &lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$messagetext = file_rewrite_pluginfile_urls($messagetext, &#039;pluginfile.php&#039;,&lt;br /&gt;
        $context-&amp;gt;id, &#039;mod_mymodule&#039;, &#039;proper_file_area&#039;, $itemid);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
; $messagetext : is the content containing the @@PLUGINFILE@@ URLs from the database.&lt;br /&gt;
; &#039;pluginfile.php&#039; : there are a number of different scripts that can serve files with different permissions checks. You need to specify which one to use.&lt;br /&gt;
; $context-&amp;gt;id, &#039;mod_mymodule&#039;, &#039;proper_file_area&#039;, $itemid : uniquely identifies the file area, as before.&lt;br /&gt;
&lt;br /&gt;
=== Implement file serving access control ===&lt;br /&gt;
&lt;br /&gt;
Attachments and embedded images should have the same access control like the text itself, in majority of cases these files are served using pluginfile.php. Access control is defined in &#039;&#039;module/lib.php&#039;&#039; file in function &#039;&#039;module_pluginfile()&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== File browsing support ==&lt;br /&gt;
Only owner of each file area is allowed to use low level File API function to access files, other parts of Moodle should use file browsing API.&lt;br /&gt;
&lt;br /&gt;
Activities may specify browsing support in own module/lib.php file by implementing functions module_get_file_areas() and module_get_file_info().&lt;br /&gt;
&lt;br /&gt;
== Upgrading your code ==&lt;br /&gt;
Here I will attempt to describe some simple steps you can take to upgrade your file-handling form elements from pre-2.0 code to 2.0. We will use the example of glossary, since it has been used above.&lt;br /&gt;
&lt;br /&gt;
=== Preparing your options ===&lt;br /&gt;
Unless you are happy with the defaults, you will need to define an array of options for each file-handling form element. You could define it at different places, but it&#039;s best to put it in one place and make the array(s) available to other files if they need it. In the majority of cases, this will be in a file like edit.php&lt;br /&gt;
&lt;br /&gt;
Previous code in mod/glossary/edit.php:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform =&amp;amp; new mod_glossary_entry_form(null, compact(&#039;cm&#039;, &#039;glossary&#039;, &#039;hook&#039;, &#039;mode&#039;, &#039;e&#039;, &#039;context&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
New code:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$maxbytes = $course-&amp;gt;maxbytes;&lt;br /&gt;
// Could also use $CFG-&amp;gt;maxbytes if you are not coding within a course context&lt;br /&gt;
&lt;br /&gt;
$definitionoptions = array(&#039;subdirs&#039;=&amp;gt;false, &#039;maxfiles&#039;=&amp;gt;99, &#039;maxbytes&#039;=&amp;gt;$maxbytes, &#039;trusttext&#039;=&amp;gt;true,&lt;br /&gt;
                           &#039;context&#039;=&amp;gt;$context);&lt;br /&gt;
$attachmentoptions = array(&#039;subdirs&#039;=&amp;gt;false, &#039;maxfiles&#039;=&amp;gt;99, &#039;maxbytes&#039;=&amp;gt;$maxbytes);&lt;br /&gt;
$mform = new mod_glossary_entry_form(null, array(&#039;current&#039;=&amp;gt;$entry, &#039;cm&#039;=&amp;gt;$cm, &#039;glossary&#039;=&amp;gt;$glossary,&lt;br /&gt;
                                                 &#039;definitionoptions&#039;=&amp;gt;$definitionoptions, &lt;br /&gt;
                                                 &#039;attachmentoptions&#039;=&amp;gt;$attachmentoptions));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Note that the data being passed to the form constructor have changed also, but this is not part of the file API changes, I just include them to avoid confusion.&lt;br /&gt;
&lt;br /&gt;
These options are for the htmleditor (definition field) and the filemanager (attachment field). They are used by a file called edit_form.php.&lt;br /&gt;
&lt;br /&gt;
=== Element preparation ===&lt;br /&gt;
Before we look at this, however, we need to &amp;quot;prepare&amp;quot; the elements so that they can correctly display existing embedded images and attached files when you are editing a record instead of just creating one. So, let&#039;s take the code we&#039;ve got so far in edit.php and add to it:&lt;br /&gt;
&lt;br /&gt;
Currently upgraded code in edit.php:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform = new mod_glossary_entry_form(null, array(&lt;br /&gt;
        &#039;current&#039;=&amp;gt;$entry, &lt;br /&gt;
        &#039;cm&#039;=&amp;gt;$cm, &lt;br /&gt;
        &#039;glossary&#039;=&amp;gt;$glossary,&lt;br /&gt;
        &#039;definitionoptions&#039;=&amp;gt;$definitionoptions, &lt;br /&gt;
        &#039;attachmentoptions&#039;=&amp;gt;$attachmentoptions));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
New code with element preparation:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$entry = file_prepare_standard_editor($entry, &#039;definition&#039;, $definitionoptions, $context,&lt;br /&gt;
                                      &#039;mod_glossary&#039;, &#039;entry&#039;, $entry-&amp;gt;id);&lt;br /&gt;
$entry = file_prepare_standard_filemanager($entry, &#039;attachment&#039;, $attachmentoptions, $context,&lt;br /&gt;
                                           &#039;mod_glossary&#039;, &#039;attachment&#039;, $entry-&amp;gt;id);&lt;br /&gt;
$mform = new mod_glossary_entry_form(null, array(&#039;current&#039;=&amp;gt;$entry, &#039;cm&#039;=&amp;gt;$cm, &#039;glossary&#039;=&amp;gt;$glossary,&lt;br /&gt;
                                                 &#039;definitionoptions&#039;=&amp;gt;$definitionoptions, &lt;br /&gt;
                                                 &#039;attachmentoptions&#039;=&amp;gt;$attachmentoptions));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Things to note:&lt;br /&gt;
* $entry in this case is simply a stdClass object which may either represent a new glossary entry or an existing one.&lt;br /&gt;
* $entry-&amp;gt;id must be the unique identifier for the current object. If we are creating a new entry, it will be null, but in all cases it must be defined.&lt;br /&gt;
* These two functions (file_prepare_standard_editor and file_prepare_standard_filemanager) are shortcuts functions that take care of some of the tedious setting up for you, but they make a couple of assumptions:&lt;br /&gt;
*# You &#039;&#039;&#039;must&#039;&#039;&#039; name the form element as {element}_editor or {element}_filemanager (see next section)&lt;br /&gt;
*# You &#039;&#039;&#039;must&#039;&#039;&#039; have at least the following fields in the database: {element} and {element}summary, as described earlier in this documentation&lt;br /&gt;
&lt;br /&gt;
We can now look at the upgrades needed in the form definition file.&lt;br /&gt;
&lt;br /&gt;
=== Form definition ===&lt;br /&gt;
Previous code in mod/glossary/edit_form.php:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;htmleditor&#039;, &#039;definition&#039;, get_string(&#039;definition&#039;, &#039;glossary&#039;),&lt;br /&gt;
                    array(&#039;rows&#039;=&amp;gt;20));&lt;br /&gt;
$mform-&amp;gt;setType(&#039;definition&#039;, PARAM_RAW);&lt;br /&gt;
$mform-&amp;gt;addRule(&#039;definition&#039;, null, &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
$mform-&amp;gt;setHelpButton(&#039;definition&#039;, array(&#039;writing&#039;, &#039;richtext&#039;), false, &#039;editorhelpbutton&#039;);&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;format&#039;);&lt;br /&gt;
// a bit further...&lt;br /&gt;
$this-&amp;gt;set_upload_manager(new upload_manager(&#039;attachment&#039;, true, false, $COURSE, false, 0,&lt;br /&gt;
                                             true, true, false));&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;file&#039;, &#039;attachment&#039;, get_string(&#039;attachment&#039;, &#039;forum&#039;));&lt;br /&gt;
$mform-&amp;gt;setHelpButton(&#039;attachment&#039;, array(&#039;attachment&#039;, get_string(&#039;attachment&#039;, &#039;glossary&#039;),&lt;br /&gt;
                      &#039;glossary&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
New code:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$definitionoptions = $this-&amp;gt;_customdata[&#039;definitionoptions&#039;];&lt;br /&gt;
$attachmentoptions = $this-&amp;gt;_customdata[&#039;attachmentoptions&#039;];&lt;br /&gt;
// a bit further...&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;editor&#039;, &#039;definition_editor&#039;, get_string(&#039;definition&#039;, &#039;glossary&#039;), null,&lt;br /&gt;
                   $definitionoptions);&lt;br /&gt;
$mform-&amp;gt;setType(&#039;definition_editor&#039;, PARAM_RAW);&lt;br /&gt;
$mform-&amp;gt;addRule(&#039;definition_editor&#039;, get_string(&#039;required&#039;), &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
// a bit further...&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;filemanager&#039;, &#039;attachment_filemanager&#039;, get_string(&#039;attachment&#039;, &#039;glossary&#039;),&lt;br /&gt;
                   null, $attachmentoptions);&lt;br /&gt;
$mform-&amp;gt;setHelpButton(&#039;attachment_filemanager&#039;, array(&#039;attachment2&#039;,&lt;br /&gt;
                      get_string(&#039;attachment&#039;, &#039;glossary&#039;), &#039;glossary&#039;));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note the following:&lt;br /&gt;
* The format element and the help button are no longer required for the HTML editor element&lt;br /&gt;
* The name of the form element needs to be changed by adding &#039;_editor&#039; or &#039;_manager&#039; to the original name. This is a naming convention that is used by a couple of functions we will look at shortly&lt;br /&gt;
* Make sure $definitionoptions has context parameter, else system context is used and editor will not respect filter settings.&lt;br /&gt;
&lt;br /&gt;
=== Handling submitted data ===&lt;br /&gt;
The final step is to handle the submitted data properly, i.e. retrieve the files and save them to disk, associating them with the record we have just created (a glossary entry in our example). This happens in edit.php:&lt;br /&gt;
&lt;br /&gt;
Previous code in edit.php:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// Section that updates an entry:&lt;br /&gt;
$todb-&amp;gt;id = $e;&lt;br /&gt;
$dir = glossary_file_area_name($todb);&lt;br /&gt;
if ($mform-&amp;gt;save_files($dir) and $newfilename = $mform-&amp;gt;get_new_filename()) {&lt;br /&gt;
    $todb-&amp;gt;attachment = $newfilename;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
// Section that adds an entry:&lt;br /&gt;
if ($todb-&amp;gt;id = insert_record(&amp;quot;glossary_entries&amp;quot;, $todb)) {&lt;br /&gt;
    $e = $todb-&amp;gt;id;&lt;br /&gt;
    $dir = glossary_file_area_name($todb);&lt;br /&gt;
    if ($mform-&amp;gt;save_files($dir) and $newfilename = $mform-&amp;gt;get_new_filename()) {&lt;br /&gt;
        set_field(&amp;quot;glossary_entries&amp;quot;, &amp;quot;attachment&amp;quot;, $newfilename, &amp;quot;id&amp;quot;, $todb-&amp;gt;id);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
New code:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
// $todb was renamed to $entry, and the code was refactored &lt;br /&gt;
// so that the file-handling code is only used once for either an add or an update action.&lt;br /&gt;
// If an entry is being added, $DB-&amp;gt;insert() has already been called, so we have a valid $entry-&amp;gt;id&lt;br /&gt;
$entry = file_postupdate_standard_editor($entry, &#039;definition&#039;, $definitionoptions, $context,&lt;br /&gt;
                                         &#039;mod_glossary&#039;, &#039;entry&#039;, $entry-&amp;gt;id);&lt;br /&gt;
$entry = file_postupdate_standard_filemanager($entry, &#039;attachment&#039;, $attachmentoptions, $context,&lt;br /&gt;
                                              &#039;mod_glossary&#039;, &#039;attachment&#039;, $entry-&amp;gt;id);&lt;br /&gt;
// store the updated value values&lt;br /&gt;
$DB-&amp;gt;update_record(&#039;glossary_entries&#039;, $entry);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Things to note:&lt;br /&gt;
* If you are adding a new record, you will still need to call update_record after calling the file_postupdate* functions&lt;br /&gt;
&lt;br /&gt;
=== Gotchas ===&lt;br /&gt;
A few things to keep in mind:&lt;br /&gt;
* Make sure that you instantiate the moodle form before any call to $OUTPUT-&amp;gt;header()&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[File API]]&lt;br /&gt;
* [[Using the file API]]&lt;br /&gt;
* [[Repository API]]&lt;br /&gt;
* [[Portfolio API]]&lt;br /&gt;
* MDL-14589 - File API Meta issue&lt;br /&gt;
&lt;br /&gt;
[[Category:Files]]&lt;br /&gt;
[[Category:Repositories]]&lt;/div&gt;</summary>
		<author><name>Davidbalch</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Filters_schema&amp;diff=864</id>
		<title>Filters schema</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Filters_schema&amp;diff=864"/>
		<updated>2007-05-04T08:37:21Z</updated>

		<summary type="html">&lt;p&gt;Davidbalch: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The objective of this page is to document all the ideas and improvements to be implemented in the &amp;quot;Filters&amp;quot; functionality of Moodle.&lt;br /&gt;
&lt;br /&gt;
Every idea will be documented here, including its associated &amp;quot;thoughts&amp;quot; and potential &amp;quot;implementations&amp;quot;. Feel free to add/modify everything as you want!&lt;br /&gt;
&lt;br /&gt;
These are the main areas to discuss/analyse/implement:&lt;br /&gt;
&lt;br /&gt;
* [[Course/site filters | Enable/Disable Filters by Course and Site]]&lt;br /&gt;
* [[Course/site filter config | Optional Configuration of Filters by Course and Site]]&lt;br /&gt;
* [[Multilang filter | Changes in the Caching System to Support the Multilang Filter]]&lt;br /&gt;
* [[Enable/Disable Filters by Activities]]&lt;br /&gt;
* Allow filtering data before it will save in database? It will do possible exclude some data from text (like TeX formulas) and save in separate table. It will allow change formula by clicking on it and open popup window.&lt;br /&gt;
* It&#039;d be great if you could set the text cache lifetime for individual filters. This would be handy for filters that use external resources (e.g., Wikipedia) that sometimes have slow response times. I&#039;d guess that you could use a cache lifetime of a week or more for a Wikipedia filter without causing major usability problems.&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
*[http://moodle.org/bugs/bug.php?op=show&amp;amp;bugid=2400 Bug 2400] - a lot of ideas and request have been written here&lt;br /&gt;
http://tracker.moodle.org/browse/MDL-9443 - add support for filtering XHTML resources as well as HTML.&lt;/div&gt;</summary>
		<author><name>Davidbalch</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Course/site_filter_config&amp;diff=871</id>
		<title>Course/site filter config</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Course/site_filter_config&amp;diff=871"/>
		<updated>2007-04-18T10:37:14Z</updated>

		<summary type="html">&lt;p&gt;Davidbalch: Added config idea.&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This topic is to define how to add the possibility of &amp;quot;configure&amp;quot; filters both at site and course level.&lt;br /&gt;
&lt;br /&gt;
* The mp3player may be configured at site level to only start downloading when the play button is pressed.&lt;br /&gt;
* The filters could configured to apply to classes of files by MIME type and/or file extension.&lt;/div&gt;</summary>
		<author><name>Davidbalch</name></author>
	</entry>
</feed>