<?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=Dongsheng</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=Dongsheng"/>
	<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/Special:Contributions/Dongsheng"/>
	<updated>2026-05-16T00:26:19Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.43.5</generator>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Profiling_PHP&amp;diff=61072</id>
		<title>Profiling PHP</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Profiling_PHP&amp;diff=61072"/>
		<updated>2021-08-11T13:18:19Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;PHP has two types of profiler:&lt;br /&gt;
* XHProf is a standard designed for capturing profile traces on live servers. It was originally designed by engineers at Facebook and is suitable for production environments.&lt;br /&gt;
* [http://www.xdebug.org/docs/profiler XDebug] is well known. and understood, and its trace output is supported by tools like [http://kcachegrind.sourceforge.net/html/Home.html KCachegrind]. It is not recommended for production environments.&lt;br /&gt;
&lt;br /&gt;
==Tideways (replacement for XHProf)==&lt;br /&gt;
&lt;br /&gt;
[https://tideways.com/profiler/downloads Tideways] is a PHP7-compatible replacement for XHProf, and support for this is built into Moodle. ([https://tideways.io/ Tideways] is a large, paid-for service, which helps to identify a range of issues with production servers. Whilst the service itself is a paid-for service, the instrumentation tooling is both Open Source, and free.)&lt;br /&gt;
&lt;br /&gt;
When the XHProf PHP extension is installed, the &amp;quot;Profiling&amp;quot;  section is displayed in &amp;quot;Site administration / Development&amp;quot;. And when the profiling setting (profilingenabled) is enabled, a new link &amp;quot;Profiling runs&amp;quot; is also displayed in the Development tab. From this section, apart from access to the profiling runs, you can also import .mpr (Moodle profiling) files. &lt;br /&gt;
&lt;br /&gt;
There are two variants of the plugin. You will almost certainly want the tideways_xhprof plugin.&lt;br /&gt;
&lt;br /&gt;
* [https://tideways.com/profiler/downloads tideways] - the original tool, which includes both xhprof instrumentation, and support for the Tideways service. This version is compatible with:&lt;br /&gt;
** Moodle 3.0.6&lt;br /&gt;
** Moodle 3.1.2&lt;br /&gt;
** Moodle 3.2 onwards&lt;br /&gt;
* [https://github.com/tideways/php-xhprof-extension tideways_xhprof] - a rewritten implementation of the original which only includes the xhprof instrumentation. Compatible with:&lt;br /&gt;
** Moodle 3.1.12&lt;br /&gt;
** Moodle 3.2.9&lt;br /&gt;
** Moodle 3.3.6&lt;br /&gt;
** Moodle 3.4.3&lt;br /&gt;
** Moodle 3.5 onwards&lt;br /&gt;
&lt;br /&gt;
For installation details please see https://github.com/tideways/php-xhprof-extension&lt;br /&gt;
&lt;br /&gt;
See&lt;br /&gt;
* [[Setting up xhprof on Moodle]]&lt;br /&gt;
* http://tjhunt.blogspot.co.uk/2013/05/performance-testing-moodle.html&lt;br /&gt;
* https://docs.google.com/presentation/d/1MV4R71UBgPgzM6I9h_yDnYcxIJlgCoLBUDtJJr-TzNI/present#slide=id.i0 presentation about using XHProf&lt;br /&gt;
&lt;br /&gt;
=== Export Xhprof data to plugins ===&lt;br /&gt;
&lt;br /&gt;
Starting at 3.6, Moodle allows for a plugin to handle the Xhprof data and not insert the traces into the database as implemented in (MDL-63031)[https://tracker.moodle.org/browse/MDL-63031]&lt;br /&gt;
&lt;br /&gt;
To disable Moodle from writing traces to the database, add: &amp;quot;$CFG-&amp;gt;disableprofilingtodatabase = False;&amp;quot; into config.php&lt;br /&gt;
&lt;br /&gt;
Plugins will need to provide a _store_profiling_data function to handle the data.&lt;br /&gt;
&lt;br /&gt;
==Xdebug==&lt;br /&gt;
&lt;br /&gt;
[http://xdebug.org/docs/profiler Xdebug] is a powerful PHP debugging tool. The first release of Xdebug was in 2002, since that it keeps growing and remains popular among PHP developers. Among its major features are stack and function tracing, code coverage analysis, remote debugging and scripts profiling. As the page topic suggests, we will focus on its profiling feature, which provides developer with detailed information about the script performance, helps identifying which parts of the code are slow. Collected information is being stored in cachegrind compatible file and can be analysed using one of external tools, such as [http://kcachegrind.sourceforge.net/html/Home.html KCachegrind], [http://sourceforge.net/projects/wincachegrind/ WinCacheGrind], [http://code.google.com/p/xdebugtoolkit/ xdebugtoolkit] or web-based analyser [https://github.com/jokkedk/webgrind#readme Webgrind]. Xdebug is simple to install and operate, it does not require code changes.&lt;br /&gt;
&lt;br /&gt;
===Installing Xdebug extension===&lt;br /&gt;
&lt;br /&gt;
The first step that has to be done is installing Xdebug extension. This procedure depends on the OS you are using, but general idea is to obtain Xdebug php extension and specify full path to it using &#039;&#039;zend_extension&#039;&#039; setting in &#039;&#039;php.inf&#039;&#039; file. Official [http://www.xdebug.org/docs/install Xdebug installation documentation] explains its installation on Windows, installing through PEAR/PECL and compiling from source.&lt;br /&gt;
Xdebug has been packaged for many Linux distributions, so it can be installed using corresponding package management tool. On Debian or Ubuntu, for example one would need to execute:&lt;br /&gt;
&lt;br /&gt;
 # apt-get install php5-xdebug&lt;br /&gt;
&lt;br /&gt;
This will put &#039;&#039;xdebug.so&#039;&#039; in default php modules directory and create &#039;&#039;/etc/php5/conf.d/xdebug.ini&#039;&#039; file with single line:&lt;br /&gt;
&lt;br /&gt;
 zend_extension=/usr/lib/php5/20090626/xdebug.so&lt;br /&gt;
&lt;br /&gt;
Note that this is equal to specifying the same line in &#039;&#039;php.inf&#039;&#039;, example above just reflects split php configuration.&lt;br /&gt;
&lt;br /&gt;
Once the Xdebug extension is installed and specified in php configuration, one may restart the webserver and information about Xdebug should appear in &#039;&#039;phpinfo()&#039;&#039; function output. If not, make sure that &#039;&#039;zend_extension&#039;&#039; line is not commented out, extension file exists in specified location and refer to webserver logs for more details.&lt;br /&gt;
&lt;br /&gt;
===Configuring Xdebug Profiler===&lt;br /&gt;
&lt;br /&gt;
When Xdebug extension is installed, it is time to configure profiling functionality. (If you are running apache2 as your web server and are using split configuration files such as the default apt-get install mentioned above, these settings should be added to &amp;quot;/etc/php5/apache2/conf.d/xdebug.ini&amp;quot;.)  There are number of parameters related to profiling, all of them start with &#039;&#039;xdebug.profiler_&#039;&#039; prefix. First of all, profiler should be enabled. There are two ways of doing it. One way is keeping it always enabled, so profiling information will be generated on every page request:&lt;br /&gt;
&lt;br /&gt;
 xdebug.profiler_enable=1&lt;br /&gt;
&lt;br /&gt;
Another way is making Xdebug writing profiling information on demand by triggering it with GET/POST or COOKIE variable named &#039;&#039;XDEBUG_PROFILE&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
 xdebug.profiler_enable_trigger = 1&lt;br /&gt;
&lt;br /&gt;
This option is preferable for several reasons: profiling data files are relatively large especially on complex scripts and you may run out of disk-space pretty quick, it allows generating profiling information only when you need it, thus it is easier to find the generated file in output directory, finally, on demand profiling can be done on production servers, though it is not recommended. If you have enabled &#039;&#039;xdebug.profiler_enable_trigger&#039;&#039; option, make sure that &#039;&#039;xdebug.profiler_enable&#039;&#039; is disabled, otherwise this will lead to dump file being generated on each request.&lt;br /&gt;
&lt;br /&gt;
Whatever method of profiling enabling you have chosen, profiling data files will be generated on each request (or request with a trigger parameter) in directory specified with &#039;&#039;xdebug.profiler_output_dir&#039;&#039; directive. By default is it set to &#039;&#039;/tmp&#039;&#039;, but you may change it to any more suitable location.&lt;br /&gt;
&lt;br /&gt;
 xdebug.profiler_output_dir=/tmp&lt;br /&gt;
&lt;br /&gt;
Generated file will be named in accordance with &#039;&#039;xdebug.profiler_output_name&#039;&#039; setting. This setting can handle some specifiers and use them in the file name. Default name pattern is &#039;&#039;cachegrind.out.%p&#039;&#039;; in the actual file name %p will be replaced with process ID value. The fill list of specifier can be found [http://www.xdebug.org/docs/all_settings#trace_output_name here]. More intuitive naming is recommended, so that it is easier to find the file you have just generated in the bunch of others:&lt;br /&gt;
&lt;br /&gt;
 xdebug.profiler_output_name=cachegrind.out.%R.%t&lt;br /&gt;
&lt;br /&gt;
With this naming pattern, timestamp and script name will be reflected in the file name.&lt;br /&gt;
&lt;br /&gt;
When you are done with configuration, it is time to test it. First of all, the webserver has to be restarted, so that the new config will come into effect. Depending on your operating system and web server, you should be able to restart it with a command similar to:&lt;br /&gt;
&lt;br /&gt;
 sudo service apache2 restart&lt;br /&gt;
&lt;br /&gt;
(This should work for apache2 on several flavours of Linux.)&lt;br /&gt;
&lt;br /&gt;
===Profiling a page===&lt;br /&gt;
&lt;br /&gt;
Now assuming that Xdebug was configured to use a trigger for script profiling, open any php page on your server in your browser having added &#039;&#039;XDEBUG_PROFILE&#039;&#039; parameter to URL string:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;http://servername/moodle2/index.php?XDEBUG_PROFILE&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
or if it already has some parameters, just add our trigger to the URL end:&lt;br /&gt;
&lt;br /&gt;
 &amp;lt;nowiki&amp;gt;http://servername/moodle2/mod/forum/view.php?id=5&amp;amp;XDEBUG_PROFILE&amp;lt;/nowiki&amp;gt;&lt;br /&gt;
&lt;br /&gt;
As a result, the new file should be generated in directory you specified with &#039;&#039;xdebug.profiler_output_dir&#039;&#039; directive:&lt;br /&gt;
&lt;br /&gt;
 cachegrind.out._moodle2_mod_forum_view_php.1289838411&lt;br /&gt;
 cachegrind.out._moodle2_index_php.1289837892&lt;br /&gt;
&lt;br /&gt;
Triggering profiling with POST requests or AJAX queries is also possible without code changes. [https://addons.mozilla.org/en-US/firefox/addon/58688/ Easy Xdebug] plugin for FireFox has profiling toggle button that inserts XDEBUG_PROFILE variable into cookie data, thus making profiling enabled for as long as you wish for all requests. Similar plugins exist for [https://chrome.google.com/extensions/detail/eadndfjplgieldjbigjakmdgkmoaaaoc Chrome] and [https://github.com/benmatselby/xdebug-toggler Safari] browsers.&lt;br /&gt;
&lt;br /&gt;
===Analyzing Xdebug profiling files===&lt;br /&gt;
&lt;br /&gt;
As it was pointed out earlier, profiling data is recoded in cachegrind format, so it can be analysed using one of external tools, such as [https://kcachegrind.github.io/html/Home.html KCachegrind], [http://sourceforge.net/projects/wincachegrind/ WinCacheGrind], [http://code.google.com/p/xdebugtoolkit/ xdebugtoolkit] or web-based analyser [https://github.com/jokkedk/webgrind#readme Webgrind]. Using these tools is pretty simple. I prefer KCachegrind which has a feature to show the matching code if web-server is run on the same box. Xdebug profiler documentation page has a [http://xdebug.org/docs/profiler#misc section] about KCachegrind that worth reading for everyone who starts using KCachegrind.&lt;br /&gt;
&lt;br /&gt;
===Quick summary===&lt;br /&gt;
&lt;br /&gt;
1. Install Xdebug 3 extension on your server.&lt;br /&gt;
&lt;br /&gt;
2. Add something like this to your php.ini file:&lt;br /&gt;
 zend_extension = /usr/local/lib/php/pecl/20190902/xdebug.so&lt;br /&gt;
 xdebug.mode = profile&lt;br /&gt;
 xdebug.start_with_request = trigger&lt;br /&gt;
 xdebug.output_dir = /var/tmp&lt;br /&gt;
 xdebug.profiler_output_name = cachegrind.out.%R.%t&lt;br /&gt;
or add something like this to your .htaccess file&lt;br /&gt;
 php_flag  xdebug.mode = profile&lt;br /&gt;
 php_flag  xdebug.start_with_request = trigger&lt;br /&gt;
 php_value xdebug.output_dir /var/tmp&lt;br /&gt;
 php_value xdebug.profiler_output_name cachegrind.out.%R.%t&lt;br /&gt;
&lt;br /&gt;
3. When you want to profile a page, add &amp;lt;tt&amp;gt;&amp;amp;XDEBUG_PROFILE&amp;lt;/tt&amp;gt; to the end of the URL.&lt;br /&gt;
&lt;br /&gt;
4. Open the &amp;lt;tt&amp;gt;cachegrind.out....&amp;lt;/tt&amp;gt; file that is generated with one of the tools mentioned above.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
* [http://moodle.org/mod/forum/discuss.php?d=162045 Forum thread about profiling Moodle 2.0]&lt;br /&gt;
XHProf articles:&lt;br /&gt;
* [https://docs.google.com/present/view?id=dcbkgbgf_45fbg3rnmk&amp;amp; Presentation about XHProf]&lt;br /&gt;
* [http://techportal.ibuildings.com/2009/12/01/profiling-with-xhprof/ Profiling with XHProf]&lt;br /&gt;
* [http://www.open.ac.uk/blogs/XHProf/?page_id=46 XHProf – Profiling and Reporting] from Moodle perspective written by [http://moodle.org/user/view.php?id=264538&amp;amp;course=5 James Brisland]&lt;br /&gt;
Xdebug articles:&lt;br /&gt;
* [http://devzone.zend.com/article/2803-Introducing-xdebug Introducing xdebug]&lt;br /&gt;
* [http://blog.teamlazerbeez.com/2010/05/04/xdebug-quickstart-profiling-in-php/ Xdebug Quickstart: Profiling in PHP]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Charts_API&amp;diff=58770</id>
		<title>Charts API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Charts_API&amp;diff=58770"/>
		<updated>2021-05-11T03:24:21Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 3.2}}&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
The Charts API is a core set of methods intended to provide a simple and yet modern interface to generate dynamic charts.&lt;br /&gt;
&lt;br /&gt;
== Chart types ==&lt;br /&gt;
&lt;br /&gt;
The first step to create a new chart is to create an instance of the desired chart type. &lt;br /&gt;
At the moment bar, line and pie types are supported and using some display changing methods can change how the charts are displayed.&lt;br /&gt;
&lt;br /&gt;
=== Bar === &lt;br /&gt;
To create a new bar chart, just create a new instance of &#039;&#039;&#039;chart_bar&#039;&#039;&#039; class.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new core\chart_bar();&lt;br /&gt;
$chart-&amp;gt;add_series($sales);&lt;br /&gt;
$chart-&amp;gt;add_series($expenses);&lt;br /&gt;
$chart-&amp;gt;set_labels($labels);&lt;br /&gt;
echo $OUTPUT-&amp;gt;render($chart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
[[File:bar_chart.png]]&lt;br /&gt;
&lt;br /&gt;
You might want change how your bar chart are displayed:&lt;br /&gt;
&lt;br /&gt;
==== Stacked Bar ====&lt;br /&gt;
To display stacked bars, you can call &#039;&#039;&#039;set_stacked()&#039;&#039;&#039; method, setting the parameter to true.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new core\chart_bar();&lt;br /&gt;
$chart-&amp;gt;set_stacked(true);&lt;br /&gt;
$chart-&amp;gt;add_series($sales);&lt;br /&gt;
$chart-&amp;gt;add_series($expenses);&lt;br /&gt;
$chart-&amp;gt;set_labels($labels);&lt;br /&gt;
echo $OUTPUT-&amp;gt;render($chart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
[[File:stacked_bar_chart.png]]&lt;br /&gt;
&lt;br /&gt;
==== Horizontal Bar ====&lt;br /&gt;
To display you bar chart in the horizontal position, you need to call &#039;&#039;&#039;set_horizontal()&#039;&#039;&#039;:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new core\chart_bar();&lt;br /&gt;
$chart-&amp;gt;set_horizontal(true); // Calling set_horizontal() passing true as parameter will display horizontal bar charts.&lt;br /&gt;
$chart-&amp;gt;add_series($sales);&lt;br /&gt;
$chart-&amp;gt;add_series($expenses);&lt;br /&gt;
$chart-&amp;gt;set_labels($labels);&lt;br /&gt;
echo $OUTPUT-&amp;gt;render($chart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
[[File:horizontal_bar_chart.png]]&lt;br /&gt;
&lt;br /&gt;
=== Line ===&lt;br /&gt;
To create a new line chart, just create an instance of &#039;&#039;&#039;chart_line&#039;&#039;&#039; class. By default, lines are tensioned. &lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new \core\chart_line();&lt;br /&gt;
$chart-&amp;gt;add_series($sales);&lt;br /&gt;
$chart-&amp;gt;add_series($expenses);&lt;br /&gt;
$chart-&amp;gt;set_labels($labels);&lt;br /&gt;
echo $OUTPUT-&amp;gt;render($chart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
[[File:line_chart.png]]&lt;br /&gt;
&lt;br /&gt;
==== Smooth lines ====&lt;br /&gt;
You might want to change you line chart to display &#039;&#039;&#039;smooth lines&#039;&#039;&#039;:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new \core\chart_line();&lt;br /&gt;
$chart-&amp;gt;set_smooth(true); // Calling set_smooth() passing true as parameter, will display smooth lines.&lt;br /&gt;
$chart-&amp;gt;add_series($sales);&lt;br /&gt;
$chart-&amp;gt;add_series($expenses);&lt;br /&gt;
$chart-&amp;gt;set_labels($labels);&lt;br /&gt;
echo $OUTPUT-&amp;gt;render($chart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
[[File:smooth_line_chart.png]]&lt;br /&gt;
&lt;br /&gt;
=== Pie ===&lt;br /&gt;
To create a pie chart you just need to create a new instance of chart_pie class.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new \core\chart_pie();&lt;br /&gt;
$chart-&amp;gt;add_series($sales); // On pie charts we just need to set one series.&lt;br /&gt;
$chart-&amp;gt;set_labels($labels);&lt;br /&gt;
echo $OUTPUT-&amp;gt;render($chart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
[[File:pie_chart.png]]&lt;br /&gt;
&lt;br /&gt;
==== Doughnut ====&lt;br /&gt;
You might want to change you pie chart to be displayed as doughnut:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new \core\chart_pie();&lt;br /&gt;
$chart-&amp;gt;set_doughnut(true); // Calling set_doughnut(true) we display the chart as a doughnut.&lt;br /&gt;
$chart-&amp;gt;add_series($sales);&lt;br /&gt;
$chart-&amp;gt;set_labels($labels);&lt;br /&gt;
echo $OUTPUT-&amp;gt;render($chart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
[[File:doughnut_pie_chart.png]]&lt;br /&gt;
&lt;br /&gt;
== Series ==&lt;br /&gt;
Series can be defined as a set of values. To set series of a chart, you need to create an instance of &#039;&#039;&#039;chart_series&#039;&#039;&#039; object and pass the title and an array of values.&lt;br /&gt;
The title and the data are displayed when mouse over the specific point representing the serie on the chart.  &lt;br /&gt;
&#039;&#039;&#039;The number of values of the series must be equal to the number of labels.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$serie1 = new core\chart_series(&#039;My series title&#039;, [400, 460, 1120, 540]);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Properties ==&lt;br /&gt;
&lt;br /&gt;
=== Title ===&lt;br /&gt;
It is possible to set a chart title, by calling set_title and passing the title as string method.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart-&amp;gt;set_title(&#039;chart with a title&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Labels ===&lt;br /&gt;
Labels are the description in which the data will be grouped, in the example above labels are an array of years. The number of values on the series must match the number of labels.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart-&amp;gt;set_labels([&#039;2004&#039;, &#039;2005&#039;, &#039;2006&#039;, &#039;2007&#039;]);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Axis ==&lt;br /&gt;
You can customise chart axis (X,Y) by setting title, position and change the step size.&lt;br /&gt;
Firstly, you need to select the axis by the side you want and providing the index and whether you want to create the axis if it does not exists.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new chart_line();&lt;br /&gt;
$xaxis = $chart-&amp;gt;get_xaxis(0, true) // Select the index 0 of X axis and pass true to create the axis if not exists.&lt;br /&gt;
$yaxis = $chart-&amp;gt;get_yaxis(0, true) // Select the index 0 of Y axis and pass true to create the axis if not exists.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Once you get the axis you want change, you can change the attributes you need. &lt;br /&gt;
It is also possible to select multiple axis, for example if you want to have custom steps labels in one side and data in the other.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new chart_line();&lt;br /&gt;
$xaxis1 = $chart-&amp;gt;get_xaxis(0, true) // Select the index 0 of X axis and pass true to create the axis if not exists.&lt;br /&gt;
$yaxis1 = $chart-&amp;gt;get_yaxis(0, true) // Will display the default axis steps information.&lt;br /&gt;
$yaxis2 = $chart-&amp;gt;get_yaxis(1, true) // Select the second Y axis.&lt;br /&gt;
$yaxis2-&amp;gt;set_position(chart_axis::POS_RIGHT);  // Now I can change positioning, labels and etc just on the right side.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Axis title ===&lt;br /&gt;
Axis titles can be displayed on the chart sides and can be used to provide additional information about the chart.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new chart_line();&lt;br /&gt;
$chart-&amp;gt;get_xaxis(0, true)-&amp;gt;set_label(&amp;quot;I&#039;m the label for X&amp;quot;); &lt;br /&gt;
$chart-&amp;gt;get_yaxis(0, true)-&amp;gt;set_label(&amp;quot;I&#039;m the label for Y&amp;quot;); &lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Axis position ===&lt;br /&gt;
You can define the position of axis by calling &#039;&#039;&#039;set_position()&#039;&#039;&#039; and passing the axis position constant (POS_RIGHT, POS_LEFT, POS_BOTTOM, POS_TOP).&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new chart_line();&lt;br /&gt;
&lt;br /&gt;
// Customise X axis.&lt;br /&gt;
$xaxis = new chart_axis();&lt;br /&gt;
$xaxis-&amp;gt;set_label(&amp;quot;I&#039;m X, but at the top&amp;quot;);&lt;br /&gt;
$xaxis-&amp;gt;set_position(chart_axis::POS_TOP); // You can use POS_BOTTOM or POS_TOP for X axis, in this case let&#039;s change the position to the top.&lt;br /&gt;
$chart-&amp;gt;set_xaxis($xaxis);&lt;br /&gt;
&lt;br /&gt;
// Customise Y axis.&lt;br /&gt;
$yaxis = new chart_axis();&lt;br /&gt;
$yaxis-&amp;gt;set_position(chart_axis::POS_RIGHT);  // You can use POS_LEFT or POS_RIGHT for Y axis, in this case let&#039;s change the position to the right side.&lt;br /&gt;
$chart-&amp;gt;set_yaxis($yaxis);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Step size ===&lt;br /&gt;
Step size can be described as the size between the axis labels. You can set the step size of the axis, by calling &#039;&#039;&#039;set_stepsize()&#039;&#039;&#039; and passing the step size number.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new chart_line();&lt;br /&gt;
&lt;br /&gt;
// Customise X axis.&lt;br /&gt;
$xaxis = new chart_axis();&lt;br /&gt;
$axis-&amp;gt;set_stepsize(5); // Chart steps will be displayed as  5, 10, 15, 20...&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Step labels ===&lt;br /&gt;
If you prefer to display custom labels instead of numbers, just get the axis and call set_labels passing an array of labels:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new chart_line();&lt;br /&gt;
$chart-&amp;gt;get_xaxis(0, true);&lt;br /&gt;
$chart-&amp;gt;get_xaxis(1, true)-&amp;gt;set_labels([&#039;Poor&#039;, &#039;Average&#039;, &#039;Good&#039;, &#039;Perfect&#039;]); &lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Min and Max ===&lt;br /&gt;
You can customise the minimum and the maximum value of a axis step size. &lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new chart_line();&lt;br /&gt;
$xaxis = $chart-&amp;gt;get_xaxis(1, true);&lt;br /&gt;
$xaxis-&amp;gt;set_min(1);&lt;br /&gt;
$xaxis-&amp;gt;set_max(100); &lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Mixed chart types ==&lt;br /&gt;
It is possible to combine two types of chart by setting the type of the series differently of the chart type. For example, to create a chart that combine a bar chart with a line chart:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new \core\chart_bar(); // Create a bar chart instance.&lt;br /&gt;
$series1 = new \core\chart_series(&#039;Series 1 (Bar)&#039;, [1000, 1170, 660, 1030]);&lt;br /&gt;
$series2 = new \core\chart_series(&#039;Series 2 (Line)&#039;, [400, 460, 1120, 540]);&lt;br /&gt;
$series2-&amp;gt;set_type(\core\chart_series::TYPE_LINE); // Set the series type to line chart.&lt;br /&gt;
$chart-&amp;gt;add_series($series2);&lt;br /&gt;
$chart-&amp;gt;add_series($series1);&lt;br /&gt;
$chart-&amp;gt;set_labels([&#039;2004&#039;, &#039;2005&#039;, &#039;2006&#039;, &#039;2007&#039;]);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Please note the order you call add_series change the order series are displayed on the chart. In the example above, the first to be displayed will be line chart and in the background the bar chart.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
[[File:bar_line_chart.png]]&lt;br /&gt;
&lt;br /&gt;
== Rendering ==  &lt;br /&gt;
All charts are rendered by render_chart() located on outputrenderers.php, once the chart object is ready, it must be passed to render() or render_chart().&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
echo $OUTPUT-&amp;gt;render($chart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Show chart data table ===&lt;br /&gt;
By default, in order to make chart data accessible to users with special needs, a link at the bottom of the chart will display a table containing all of its data. &lt;br /&gt;
If that information is already displayed on the page in some other fashion, it may not be necessary to display the chart data table. &lt;br /&gt;
To remove it, a &#039;&#039;&#039;false&#039;&#039;&#039; value can be passed as the second parameter when calling the render_chart function:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
echo $OUTPUT-&amp;gt;render_chart($chart, false);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Overriding chart colours ===&lt;br /&gt;
It is possible to override the default set of colours used on charts, you just need to the following setting to config.php and set an array of hex css colours:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$CFG-&amp;gt;chart_colorset = [&#039;#001f3f&#039;, &#039;#01ff70&#039;, &#039;#F012BE&#039;, &#039;#85144b&#039;, &#039;#B10DC9&#039;];&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Legend options ===&lt;br /&gt;
It is possible to customize some aspects of the chart legend such position and visibility. In order to do that you need to call set_legend_options() method&lt;br /&gt;
and pass an array of options supported by ChartJS. &lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart-&amp;gt;set_legend_options([&#039;display&#039; =&amp;gt; false]);  // Hide chart legend.&lt;br /&gt;
$chart-&amp;gt;set_legend_options([&#039;position&#039; =&amp;gt; &#039;left&#039;]);  // Change legend position to left side.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== RTL ===&lt;br /&gt;
Please note numerical ranges should be displayed in LTR, and not in RTL for both RTL and LTR languages, you can wrap the chart with a container with &#039;&#039;&#039;dir&#039;&#039;&#039; tag.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code PHP&amp;gt;&lt;br /&gt;
echo html_writer::tag(&#039;div&#039;, $OUTPUT-&amp;gt;render($chart), [&#039;dir&#039; =&amp;gt; &#039;ltr&#039;]);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Charts_API&amp;diff=58769</id>
		<title>Charts API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Charts_API&amp;diff=58769"/>
		<updated>2021-05-11T03:23:40Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 3.2}}&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
The Charts API is a core set of methods intended to provide a simple and yet modern interface to generate dynamic charts.&lt;br /&gt;
&lt;br /&gt;
== Chart types ==&lt;br /&gt;
&lt;br /&gt;
The first step to create a new chart is to create an instance of the desired chart type. &lt;br /&gt;
At the moment bar, line and pie types are supported and using some display changing methods can change how the charts are displayed.&lt;br /&gt;
&lt;br /&gt;
=== Bar === &lt;br /&gt;
To create a new bar chart, just create a new instance of &#039;&#039;&#039;chart_bar&#039;&#039;&#039; class.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new core\chart_bar();&lt;br /&gt;
$chart-&amp;gt;add_series($sales);&lt;br /&gt;
$chart-&amp;gt;add_series($expenses);&lt;br /&gt;
$chart-&amp;gt;set_labels($labels);&lt;br /&gt;
echo $OUTPUT-&amp;gt;render($chart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
[[File:bar_chart.png]]&lt;br /&gt;
&lt;br /&gt;
You might want change how your bar chart are displayed:&lt;br /&gt;
&lt;br /&gt;
==== Stacked Bar ====&lt;br /&gt;
To display stacked bars, you can call &#039;&#039;&#039;set_stacked()&#039;&#039;&#039; method, setting the parameter to true.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new core\chart_bar();&lt;br /&gt;
$chart-&amp;gt;set_stacked(true);&lt;br /&gt;
$chart-&amp;gt;add_series($sales);&lt;br /&gt;
$chart-&amp;gt;add_series($expenses);&lt;br /&gt;
$chart-&amp;gt;set_labels($labels);&lt;br /&gt;
echo $OUTPUT-&amp;gt;render($chart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
[[File:stacked_bar_chart.png]]&lt;br /&gt;
&lt;br /&gt;
==== Horizontal Bar ====&lt;br /&gt;
To display you bar chart in the horizontal position, you need to call &#039;&#039;&#039;set_horizontal()&#039;&#039;&#039;:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new core\chart_bar();&lt;br /&gt;
$chart-&amp;gt;set_horizontal(true); // Calling set_horizontal() passing true as parameter will display horizontal bar charts.&lt;br /&gt;
$chart-&amp;gt;add_series($sales);&lt;br /&gt;
$chart-&amp;gt;add_series($expenses);&lt;br /&gt;
$chart-&amp;gt;set_labels($labels);&lt;br /&gt;
echo $OUTPUT-&amp;gt;render($chart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
[[File:horizontal_bar_chart.png]]&lt;br /&gt;
&lt;br /&gt;
=== Line ===&lt;br /&gt;
To create a new line chart, just create an instance of &#039;&#039;&#039;chart_line&#039;&#039;&#039; class. By default, lines are tensioned. &lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new \core\chart_line();&lt;br /&gt;
$chart-&amp;gt;add_series($sales);&lt;br /&gt;
$chart-&amp;gt;add_series($expenses);&lt;br /&gt;
$chart-&amp;gt;set_labels($labels);&lt;br /&gt;
echo $OUTPUT-&amp;gt;render($chart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
[[File:line_chart.png]]&lt;br /&gt;
&lt;br /&gt;
==== Smooth lines ====&lt;br /&gt;
You might want to change you line chart to display &#039;&#039;&#039;smooth lines&#039;&#039;&#039;:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new \core\chart_line();&lt;br /&gt;
$chart-&amp;gt;set_smooth(true); // Calling set_smooth() passing true as parameter, will display smooth lines.&lt;br /&gt;
$chart-&amp;gt;add_series($sales);&lt;br /&gt;
$chart-&amp;gt;add_series($expenses);&lt;br /&gt;
$chart-&amp;gt;set_labels($labels);&lt;br /&gt;
echo $OUTPUT-&amp;gt;render($chart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
[[File:smooth_line_chart.png]]&lt;br /&gt;
&lt;br /&gt;
=== Pie ===&lt;br /&gt;
To create a pie chart you just need to create a new instance of chart_pie class.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new \core\chart_pie();&lt;br /&gt;
$chart-&amp;gt;add_series($sales); // On pie charts we just need to set one series.&lt;br /&gt;
$chart-&amp;gt;set_labels($labels);&lt;br /&gt;
echo $OUTPUT-&amp;gt;render($chart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
[[File:pie_chart.png]]&lt;br /&gt;
&lt;br /&gt;
==== Doughnut ====&lt;br /&gt;
You might want to change you pie chart to be displayed as doughnut:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new \core\chart_pie();&lt;br /&gt;
$chart-&amp;gt;set_doughnut(true); // Calling set_doughnut(true) we display the chart as a doughnut.&lt;br /&gt;
$chart-&amp;gt;add_series($sales);&lt;br /&gt;
$chart-&amp;gt;set_labels($labels);&lt;br /&gt;
echo $OUTPUT-&amp;gt;render($chart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
[[File:doughnut_pie_chart.png]]&lt;br /&gt;
&lt;br /&gt;
== Series ==&lt;br /&gt;
Series can be defined as a set of values. To set series of a chart, you need to create an instance of &#039;&#039;&#039;chart_series&#039;&#039;&#039; object and pass the title and an array of values.&lt;br /&gt;
The title and the data are displayed when mouse over the specific point representing the serie on the chart.  &lt;br /&gt;
&#039;&#039;&#039;The number of values of the series must be equal to the number of labels.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$serie1 = new core\chart_series(&#039;My series title&#039;, [400, 460, 1120, 540]);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Properties ==&lt;br /&gt;
&lt;br /&gt;
=== Title ===&lt;br /&gt;
It is possible to set a chart title, by calling set_title and passing the title as string method.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart-&amp;gt;set_title(&#039;chart with a title&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Labels ===&lt;br /&gt;
Labels are the description in which the data will be grouped, in the example above labels are an array of years. The number of values on the series must match the number of labels.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart-&amp;gt;set_labels([&#039;2004&#039;, &#039;2005&#039;, &#039;2006&#039;, &#039;2007&#039;]);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Axis ==&lt;br /&gt;
You can customise chart axis (X,Y) by setting title, position and change the step size.&lt;br /&gt;
Firstly, you need to select the axis by the side you want and providing the index and whether you want to create the axis if it does not exists.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new chart_line();&lt;br /&gt;
$xaxis = $chart-&amp;gt;get_xaxis(0, true) // Select the index 0 of X axis and pass true to create the axis if not exists.&lt;br /&gt;
$yaxis = $chart-&amp;gt;get_yaxis(0, true) // Select the index 0 of Y axis and pass true to create the axis if not exists.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Once you get the axis you want change, you can change the attributes you need. &lt;br /&gt;
It is also possible to select multiple axis, for example if you want to have custom steps labels in one side and data in the other.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new chart_line();&lt;br /&gt;
$xaxis1 = $chart-&amp;gt;get_xaxis(0, true) // Select the index 0 of X axis and pass true to create the axis if not exists.&lt;br /&gt;
$yaxis1 = $chart-&amp;gt;get_yaxis(0, true) // Will display the default axis steps information.&lt;br /&gt;
$yaxis2 = $chart-&amp;gt;get_yaxis(1, true) // Select the second Y axis.&lt;br /&gt;
$yaxis2-&amp;gt;set_position(chart_axis::POS_RIGHT);  // Now I can change positioning, labels and etc just on the right side.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Axis title ===&lt;br /&gt;
Axis titles can be displayed on the chart sides and can be used to provide additional information about the chart.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new chart_line();&lt;br /&gt;
$chart-&amp;gt;get_xaxis(0, true)-&amp;gt;set_label(&amp;quot;I&#039;m the label for X&amp;quot;); &lt;br /&gt;
$chart-&amp;gt;get_yaxis(0, true)-&amp;gt;set_label(&amp;quot;I&#039;m the label for Y&amp;quot;); &lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Axis position ===&lt;br /&gt;
You can define the position of axis by calling &#039;&#039;&#039;set_position()&#039;&#039;&#039; and passing the axis position constant (POS_RIGHT, POS_LEFT, POS_BOTTOM, POS_TOP).&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new chart_line();&lt;br /&gt;
&lt;br /&gt;
// Customise X axis.&lt;br /&gt;
$xaxis = new chart_axis();&lt;br /&gt;
$xaxis-&amp;gt;set_label(&amp;quot;I&#039;m X, but at the top&amp;quot;);&lt;br /&gt;
$xaxis-&amp;gt;set_position(chart_axis::POS_TOP); // You can use POS_BOTTOM or POS_TOP for X axis, in this case let&#039;s change the position to the top.&lt;br /&gt;
$chart-&amp;gt;set_xaxis($xaxis);&lt;br /&gt;
&lt;br /&gt;
// Customise Y axis.&lt;br /&gt;
$yaxis = new chart_axis();&lt;br /&gt;
$yaxis-&amp;gt;set_position(chart_axis::POS_RIGHT);  // You can use POS_LEFT or POS_RIGHT for Y axis, in this case let&#039;s change the position to the right side.&lt;br /&gt;
$chart-&amp;gt;set_yaxis($yaxis);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Step size ===&lt;br /&gt;
Step size can be described as the size between the axis labels. You can set the step size of the axis, by calling &#039;&#039;&#039;set_stepsize()&#039;&#039;&#039; and passing the step size number.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new chart_line();&lt;br /&gt;
&lt;br /&gt;
// Customise X axis.&lt;br /&gt;
$xaxis = new chart_axis();&lt;br /&gt;
$axis-&amp;gt;set_stepsize(5); // Chart steps will be displayed as  5, 10, 15, 20...&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Step labels ===&lt;br /&gt;
If you prefer to display custom labels instead of numbers, just get the axis and call set_labels passing an array of labels:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new chart_line();&lt;br /&gt;
$chart-&amp;gt;get_xaxis(0, true);&lt;br /&gt;
$chart-&amp;gt;get_xaxis(1, true)-&amp;gt;set_labels([&#039;Poor&#039;, &#039;Average&#039;, &#039;Good&#039;, &#039;Perfect&#039;]); &lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Min and Max ===&lt;br /&gt;
You can customise the minimum and the maximum value of a axis step size. &lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new chart_line();&lt;br /&gt;
$xaxis = $chart-&amp;gt;get_xaxis(1, true);&lt;br /&gt;
$xaxis-&amp;gt;set_min(1);&lt;br /&gt;
$xaxis-&amp;gt;set_max(100); &lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Mixed chart types ==&lt;br /&gt;
It is possible to combine two types of chart by setting the type of the series differently of the chart type. For example, to create a chart that combine a bar chart with a line chart:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new \core\chart_bar(); // Create a bar chart instance.&lt;br /&gt;
$series1 = new \core\chart_series(&#039;Series 1 (Bar)&#039;, [1000, 1170, 660, 1030]);&lt;br /&gt;
$series2 = new \core\chart_series(&#039;Series 2 (Line)&#039;, [400, 460, 1120, 540]);&lt;br /&gt;
$series2-&amp;gt;set_type(\core\chart_series::TYPE_LINE); // Set the series type to line chart.&lt;br /&gt;
$chart-&amp;gt;add_series($series2);&lt;br /&gt;
$chart-&amp;gt;add_series($series1);&lt;br /&gt;
$chart-&amp;gt;set_labels([&#039;2004&#039;, &#039;2005&#039;, &#039;2006&#039;, &#039;2007&#039;]);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Please note the order you call add_series change the order series are displayed on the chart. In the example above, the first to be displayed will be line chart and in the background the bar chart.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
[[File:bar_line_chart.png]]&lt;br /&gt;
&lt;br /&gt;
== Rendering ==  &lt;br /&gt;
All charts are rendered by render_chart() located on outputrenderers.php, once the chart object is ready, it must be passed to render() or render_chart().&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
echo $OUTPUT-&amp;gt;render($chart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Show chart data table ===&lt;br /&gt;
By default, in order to make chart data accessible to users with special needs, a link at the bottom of the chart will display a table containing all of its data. &lt;br /&gt;
If that information is already displayed on the page in some other fashion, it may not be necessary to display the chart data table. &lt;br /&gt;
To remove it, a &#039;&#039;&#039;false&#039;&#039;&#039; value can be passed as the second parameter when calling the render_chart function:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
echo $OUTPUT-&amp;gt;render_chart($chart, false);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Overriding chart colours ===&lt;br /&gt;
It is possible to override the default set of colours used on charts, you just need to the following setting to config.php and set an array of hex css colours:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$CFG-&amp;gt;chart_colorset = [&#039;#001f3f&#039;, &#039;#01ff70&#039;, &#039;#F012BE&#039;, &#039;#85144b&#039;, &#039;#B10DC9&#039;];&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Legend options ===&lt;br /&gt;
It is possible to customize some aspects of the chart legend such position and visibility. In order to do that you need to call set_legend_options() method&lt;br /&gt;
and pass an array of options supported by ChartJS. &lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart-&amp;gt;set_legend_options([&#039;display&#039; =&amp;gt; false]);  // Hide chart legend.&lt;br /&gt;
$chart-&amp;gt;set_legend_options([&#039;position&#039; =&amp;gt; &#039;left&#039;]);  // Change legend position to left side.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== RTL ===&lt;br /&gt;
Please note numerical ranges should be displayed in LTR, and not in RTL for both RTL and LTR languages, you can wrap the chart with a container with &amp;lt;code&amp;gt;dir&amp;lt;/code&amp;gt; tag.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code PHP&amp;gt;&lt;br /&gt;
echo html_writer::tag(&#039;div&#039;, $OUTPUT-&amp;gt;render($chart), [&#039;dir&#039; =&amp;gt; &#039;ltr&#039;]);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Charts_API&amp;diff=58768</id>
		<title>Charts API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Charts_API&amp;diff=58768"/>
		<updated>2021-05-11T03:21:24Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 3.2}}&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
The Charts API is a core set of methods intended to provide a simple and yet modern interface to generate dynamic charts.&lt;br /&gt;
&lt;br /&gt;
== Chart types ==&lt;br /&gt;
&lt;br /&gt;
The first step to create a new chart is to create an instance of the desired chart type. &lt;br /&gt;
At the moment bar, line and pie types are supported and using some display changing methods can change how the charts are displayed.&lt;br /&gt;
&lt;br /&gt;
=== Bar === &lt;br /&gt;
To create a new bar chart, just create a new instance of &#039;&#039;&#039;chart_bar&#039;&#039;&#039; class.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new core\chart_bar();&lt;br /&gt;
$chart-&amp;gt;add_series($sales);&lt;br /&gt;
$chart-&amp;gt;add_series($expenses);&lt;br /&gt;
$chart-&amp;gt;set_labels($labels);&lt;br /&gt;
echo $OUTPUT-&amp;gt;render($chart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
[[File:bar_chart.png]]&lt;br /&gt;
&lt;br /&gt;
You might want change how your bar chart are displayed:&lt;br /&gt;
&lt;br /&gt;
==== Stacked Bar ====&lt;br /&gt;
To display stacked bars, you can call &#039;&#039;&#039;set_stacked()&#039;&#039;&#039; method, setting the parameter to true.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new core\chart_bar();&lt;br /&gt;
$chart-&amp;gt;set_stacked(true);&lt;br /&gt;
$chart-&amp;gt;add_series($sales);&lt;br /&gt;
$chart-&amp;gt;add_series($expenses);&lt;br /&gt;
$chart-&amp;gt;set_labels($labels);&lt;br /&gt;
echo $OUTPUT-&amp;gt;render($chart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
[[File:stacked_bar_chart.png]]&lt;br /&gt;
&lt;br /&gt;
==== Horizontal Bar ====&lt;br /&gt;
To display you bar chart in the horizontal position, you need to call &#039;&#039;&#039;set_horizontal()&#039;&#039;&#039;:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new core\chart_bar();&lt;br /&gt;
$chart-&amp;gt;set_horizontal(true); // Calling set_horizontal() passing true as parameter will display horizontal bar charts.&lt;br /&gt;
$chart-&amp;gt;add_series($sales);&lt;br /&gt;
$chart-&amp;gt;add_series($expenses);&lt;br /&gt;
$chart-&amp;gt;set_labels($labels);&lt;br /&gt;
echo $OUTPUT-&amp;gt;render($chart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
[[File:horizontal_bar_chart.png]]&lt;br /&gt;
&lt;br /&gt;
=== Line ===&lt;br /&gt;
To create a new line chart, just create an instance of &#039;&#039;&#039;chart_line&#039;&#039;&#039; class. By default, lines are tensioned. &lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new \core\chart_line();&lt;br /&gt;
$chart-&amp;gt;add_series($sales);&lt;br /&gt;
$chart-&amp;gt;add_series($expenses);&lt;br /&gt;
$chart-&amp;gt;set_labels($labels);&lt;br /&gt;
echo $OUTPUT-&amp;gt;render($chart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
[[File:line_chart.png]]&lt;br /&gt;
&lt;br /&gt;
==== Smooth lines ====&lt;br /&gt;
You might want to change you line chart to display &#039;&#039;&#039;smooth lines&#039;&#039;&#039;:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new \core\chart_line();&lt;br /&gt;
$chart-&amp;gt;set_smooth(true); // Calling set_smooth() passing true as parameter, will display smooth lines.&lt;br /&gt;
$chart-&amp;gt;add_series($sales);&lt;br /&gt;
$chart-&amp;gt;add_series($expenses);&lt;br /&gt;
$chart-&amp;gt;set_labels($labels);&lt;br /&gt;
echo $OUTPUT-&amp;gt;render($chart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
[[File:smooth_line_chart.png]]&lt;br /&gt;
&lt;br /&gt;
=== Pie ===&lt;br /&gt;
To create a pie chart you just need to create a new instance of chart_pie class.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new \core\chart_pie();&lt;br /&gt;
$chart-&amp;gt;add_series($sales); // On pie charts we just need to set one series.&lt;br /&gt;
$chart-&amp;gt;set_labels($labels);&lt;br /&gt;
echo $OUTPUT-&amp;gt;render($chart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
[[File:pie_chart.png]]&lt;br /&gt;
&lt;br /&gt;
==== Doughnut ====&lt;br /&gt;
You might want to change you pie chart to be displayed as doughnut:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new \core\chart_pie();&lt;br /&gt;
$chart-&amp;gt;set_doughnut(true); // Calling set_doughnut(true) we display the chart as a doughnut.&lt;br /&gt;
$chart-&amp;gt;add_series($sales);&lt;br /&gt;
$chart-&amp;gt;set_labels($labels);&lt;br /&gt;
echo $OUTPUT-&amp;gt;render($chart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
[[File:doughnut_pie_chart.png]]&lt;br /&gt;
&lt;br /&gt;
== Series ==&lt;br /&gt;
Series can be defined as a set of values. To set series of a chart, you need to create an instance of &#039;&#039;&#039;chart_series&#039;&#039;&#039; object and pass the title and an array of values.&lt;br /&gt;
The title and the data are displayed when mouse over the specific point representing the serie on the chart.  &lt;br /&gt;
&#039;&#039;&#039;The number of values of the series must be equal to the number of labels.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$serie1 = new core\chart_series(&#039;My series title&#039;, [400, 460, 1120, 540]);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Properties ==&lt;br /&gt;
&lt;br /&gt;
=== Title ===&lt;br /&gt;
It is possible to set a chart title, by calling set_title and passing the title as string method.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart-&amp;gt;set_title(&#039;chart with a title&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Labels ===&lt;br /&gt;
Labels are the description in which the data will be grouped, in the example above labels are an array of years. The number of values on the series must match the number of labels.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart-&amp;gt;set_labels([&#039;2004&#039;, &#039;2005&#039;, &#039;2006&#039;, &#039;2007&#039;]);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Axis ==&lt;br /&gt;
You can customise chart axis (X,Y) by setting title, position and change the step size.&lt;br /&gt;
Firstly, you need to select the axis by the side you want and providing the index and whether you want to create the axis if it does not exists.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new chart_line();&lt;br /&gt;
$xaxis = $chart-&amp;gt;get_xaxis(0, true) // Select the index 0 of X axis and pass true to create the axis if not exists.&lt;br /&gt;
$yaxis = $chart-&amp;gt;get_yaxis(0, true) // Select the index 0 of Y axis and pass true to create the axis if not exists.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Once you get the axis you want change, you can change the attributes you need. &lt;br /&gt;
It is also possible to select multiple axis, for example if you want to have custom steps labels in one side and data in the other.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new chart_line();&lt;br /&gt;
$xaxis1 = $chart-&amp;gt;get_xaxis(0, true) // Select the index 0 of X axis and pass true to create the axis if not exists.&lt;br /&gt;
$yaxis1 = $chart-&amp;gt;get_yaxis(0, true) // Will display the default axis steps information.&lt;br /&gt;
$yaxis2 = $chart-&amp;gt;get_yaxis(1, true) // Select the second Y axis.&lt;br /&gt;
$yaxis2-&amp;gt;set_position(chart_axis::POS_RIGHT);  // Now I can change positioning, labels and etc just on the right side.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Axis title ===&lt;br /&gt;
Axis titles can be displayed on the chart sides and can be used to provide additional information about the chart.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new chart_line();&lt;br /&gt;
$chart-&amp;gt;get_xaxis(0, true)-&amp;gt;set_label(&amp;quot;I&#039;m the label for X&amp;quot;); &lt;br /&gt;
$chart-&amp;gt;get_yaxis(0, true)-&amp;gt;set_label(&amp;quot;I&#039;m the label for Y&amp;quot;); &lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Axis position ===&lt;br /&gt;
You can define the position of axis by calling &#039;&#039;&#039;set_position()&#039;&#039;&#039; and passing the axis position constant (POS_RIGHT, POS_LEFT, POS_BOTTOM, POS_TOP).&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new chart_line();&lt;br /&gt;
&lt;br /&gt;
// Customise X axis.&lt;br /&gt;
$xaxis = new chart_axis();&lt;br /&gt;
$xaxis-&amp;gt;set_label(&amp;quot;I&#039;m X, but at the top&amp;quot;);&lt;br /&gt;
$xaxis-&amp;gt;set_position(chart_axis::POS_TOP); // You can use POS_BOTTOM or POS_TOP for X axis, in this case let&#039;s change the position to the top.&lt;br /&gt;
$chart-&amp;gt;set_xaxis($xaxis);&lt;br /&gt;
&lt;br /&gt;
// Customise Y axis.&lt;br /&gt;
$yaxis = new chart_axis();&lt;br /&gt;
$yaxis-&amp;gt;set_position(chart_axis::POS_RIGHT);  // You can use POS_LEFT or POS_RIGHT for Y axis, in this case let&#039;s change the position to the right side.&lt;br /&gt;
$chart-&amp;gt;set_yaxis($yaxis);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Step size ===&lt;br /&gt;
Step size can be described as the size between the axis labels. You can set the step size of the axis, by calling &#039;&#039;&#039;set_stepsize()&#039;&#039;&#039; and passing the step size number.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new chart_line();&lt;br /&gt;
&lt;br /&gt;
// Customise X axis.&lt;br /&gt;
$xaxis = new chart_axis();&lt;br /&gt;
$axis-&amp;gt;set_stepsize(5); // Chart steps will be displayed as  5, 10, 15, 20...&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Step labels ===&lt;br /&gt;
If you prefer to display custom labels instead of numbers, just get the axis and call set_labels passing an array of labels:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new chart_line();&lt;br /&gt;
$chart-&amp;gt;get_xaxis(0, true);&lt;br /&gt;
$chart-&amp;gt;get_xaxis(1, true)-&amp;gt;set_labels([&#039;Poor&#039;, &#039;Average&#039;, &#039;Good&#039;, &#039;Perfect&#039;]); &lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Min and Max ===&lt;br /&gt;
You can customise the minimum and the maximum value of a axis step size. &lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new chart_line();&lt;br /&gt;
$xaxis = $chart-&amp;gt;get_xaxis(1, true);&lt;br /&gt;
$xaxis-&amp;gt;set_min(1);&lt;br /&gt;
$xaxis-&amp;gt;set_max(100); &lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Mixed chart types ==&lt;br /&gt;
It is possible to combine two types of chart by setting the type of the series differently of the chart type. For example, to create a chart that combine a bar chart with a line chart:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new \core\chart_bar(); // Create a bar chart instance.&lt;br /&gt;
$series1 = new \core\chart_series(&#039;Series 1 (Bar)&#039;, [1000, 1170, 660, 1030]);&lt;br /&gt;
$series2 = new \core\chart_series(&#039;Series 2 (Line)&#039;, [400, 460, 1120, 540]);&lt;br /&gt;
$series2-&amp;gt;set_type(\core\chart_series::TYPE_LINE); // Set the series type to line chart.&lt;br /&gt;
$chart-&amp;gt;add_series($series2);&lt;br /&gt;
$chart-&amp;gt;add_series($series1);&lt;br /&gt;
$chart-&amp;gt;set_labels([&#039;2004&#039;, &#039;2005&#039;, &#039;2006&#039;, &#039;2007&#039;]);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Please note the order you call add_series change the order series are displayed on the chart. In the example above, the first to be displayed will be line chart and in the background the bar chart.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
[[File:bar_line_chart.png]]&lt;br /&gt;
&lt;br /&gt;
== Rendering ==  &lt;br /&gt;
All charts are rendered by render_chart() located on outputrenderers.php, once the chart object is ready, it must be passed to render() or render_chart().&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
echo $OUTPUT-&amp;gt;render($chart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Show chart data table ===&lt;br /&gt;
By default, in order to make chart data accessible to users with special needs, a link at the bottom of the chart will display a table containing all of its data. &lt;br /&gt;
If that information is already displayed on the page in some other fashion, it may not be necessary to display the chart data table. &lt;br /&gt;
To remove it, a &#039;&#039;&#039;false&#039;&#039;&#039; value can be passed as the second parameter when calling the render_chart function:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
echo $OUTPUT-&amp;gt;render_chart($chart, false);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Overriding chart colours ===&lt;br /&gt;
It is possible to override the default set of colours used on charts, you just need to the following setting to config.php and set an array of hex css colours:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$CFG-&amp;gt;chart_colorset = [&#039;#001f3f&#039;, &#039;#01ff70&#039;, &#039;#F012BE&#039;, &#039;#85144b&#039;, &#039;#B10DC9&#039;];&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Legend options ===&lt;br /&gt;
It is possible to customize some aspects of the chart legend such position and visibility. In order to do that you need to call set_legend_options() method&lt;br /&gt;
and pass an array of options supported by ChartJS. &lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart-&amp;gt;set_legend_options([&#039;display&#039; =&amp;gt; false]);  // Hide chart legend.&lt;br /&gt;
$chart-&amp;gt;set_legend_options([&#039;position&#039; =&amp;gt; &#039;left&#039;]);  // Change legend position to left side.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== RTL ===&lt;br /&gt;
Please note numerical ranges should be displayed in LTR, and not in RTL for both RTL and LTR languages, you can wrap the chart with container with dir tag.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code PHP&amp;gt;&lt;br /&gt;
echo html_writer::tag(&#039;div&#039;, $OUTPUT-&amp;gt;render($chart), [&#039;dir&#039; =&amp;gt; &#039;ltr&#039;]);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Charts_API&amp;diff=58767</id>
		<title>Charts API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Charts_API&amp;diff=58767"/>
		<updated>2021-05-11T03:14:51Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 3.2}}&lt;br /&gt;
&lt;br /&gt;
== Overview ==&lt;br /&gt;
&lt;br /&gt;
The Charts API is a core set of methods intended to provide a simple and yet modern interface to generate dynamic charts.&lt;br /&gt;
&lt;br /&gt;
== Chart types ==&lt;br /&gt;
&lt;br /&gt;
The first step to create a new chart is to create an instance of the desired chart type. &lt;br /&gt;
At the moment bar, line and pie types are supported and using some display changing methods can change how the charts are displayed.&lt;br /&gt;
&lt;br /&gt;
=== Bar === &lt;br /&gt;
To create a new bar chart, just create a new instance of &#039;&#039;&#039;chart_bar&#039;&#039;&#039; class.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new core\chart_bar();&lt;br /&gt;
$chart-&amp;gt;add_series($sales);&lt;br /&gt;
$chart-&amp;gt;add_series($expenses);&lt;br /&gt;
$chart-&amp;gt;set_labels($labels);&lt;br /&gt;
echo $OUTPUT-&amp;gt;render($chart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
[[File:bar_chart.png]]&lt;br /&gt;
&lt;br /&gt;
You might want change how your bar chart are displayed:&lt;br /&gt;
&lt;br /&gt;
==== Stacked Bar ====&lt;br /&gt;
To display stacked bars, you can call &#039;&#039;&#039;set_stacked()&#039;&#039;&#039; method, setting the parameter to true.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new core\chart_bar();&lt;br /&gt;
$chart-&amp;gt;set_stacked(true);&lt;br /&gt;
$chart-&amp;gt;add_series($sales);&lt;br /&gt;
$chart-&amp;gt;add_series($expenses);&lt;br /&gt;
$chart-&amp;gt;set_labels($labels);&lt;br /&gt;
echo $OUTPUT-&amp;gt;render($chart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
[[File:stacked_bar_chart.png]]&lt;br /&gt;
&lt;br /&gt;
==== Horizontal Bar ====&lt;br /&gt;
To display you bar chart in the horizontal position, you need to call &#039;&#039;&#039;set_horizontal()&#039;&#039;&#039;:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new core\chart_bar();&lt;br /&gt;
$chart-&amp;gt;set_horizontal(true); // Calling set_horizontal() passing true as parameter will display horizontal bar charts.&lt;br /&gt;
$chart-&amp;gt;add_series($sales);&lt;br /&gt;
$chart-&amp;gt;add_series($expenses);&lt;br /&gt;
$chart-&amp;gt;set_labels($labels);&lt;br /&gt;
echo $OUTPUT-&amp;gt;render($chart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
[[File:horizontal_bar_chart.png]]&lt;br /&gt;
&lt;br /&gt;
=== Line ===&lt;br /&gt;
To create a new line chart, just create an instance of &#039;&#039;&#039;chart_line&#039;&#039;&#039; class. By default, lines are tensioned. &lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new \core\chart_line();&lt;br /&gt;
$chart-&amp;gt;add_series($sales);&lt;br /&gt;
$chart-&amp;gt;add_series($expenses);&lt;br /&gt;
$chart-&amp;gt;set_labels($labels);&lt;br /&gt;
echo $OUTPUT-&amp;gt;render($chart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
[[File:line_chart.png]]&lt;br /&gt;
&lt;br /&gt;
==== Smooth lines ====&lt;br /&gt;
You might want to change you line chart to display &#039;&#039;&#039;smooth lines&#039;&#039;&#039;:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new \core\chart_line();&lt;br /&gt;
$chart-&amp;gt;set_smooth(true); // Calling set_smooth() passing true as parameter, will display smooth lines.&lt;br /&gt;
$chart-&amp;gt;add_series($sales);&lt;br /&gt;
$chart-&amp;gt;add_series($expenses);&lt;br /&gt;
$chart-&amp;gt;set_labels($labels);&lt;br /&gt;
echo $OUTPUT-&amp;gt;render($chart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
[[File:smooth_line_chart.png]]&lt;br /&gt;
&lt;br /&gt;
=== Pie ===&lt;br /&gt;
To create a pie chart you just need to create a new instance of chart_pie class.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new \core\chart_pie();&lt;br /&gt;
$chart-&amp;gt;add_series($sales); // On pie charts we just need to set one series.&lt;br /&gt;
$chart-&amp;gt;set_labels($labels);&lt;br /&gt;
echo $OUTPUT-&amp;gt;render($chart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
[[File:pie_chart.png]]&lt;br /&gt;
&lt;br /&gt;
==== Doughnut ====&lt;br /&gt;
You might want to change you pie chart to be displayed as doughnut:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new \core\chart_pie();&lt;br /&gt;
$chart-&amp;gt;set_doughnut(true); // Calling set_doughnut(true) we display the chart as a doughnut.&lt;br /&gt;
$chart-&amp;gt;add_series($sales);&lt;br /&gt;
$chart-&amp;gt;set_labels($labels);&lt;br /&gt;
echo $OUTPUT-&amp;gt;render($chart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
[[File:doughnut_pie_chart.png]]&lt;br /&gt;
&lt;br /&gt;
== Series ==&lt;br /&gt;
Series can be defined as a set of values. To set series of a chart, you need to create an instance of &#039;&#039;&#039;chart_series&#039;&#039;&#039; object and pass the title and an array of values.&lt;br /&gt;
The title and the data are displayed when mouse over the specific point representing the serie on the chart.  &lt;br /&gt;
&#039;&#039;&#039;The number of values of the series must be equal to the number of labels.&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$serie1 = new core\chart_series(&#039;My series title&#039;, [400, 460, 1120, 540]);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Properties ==&lt;br /&gt;
&lt;br /&gt;
=== Title ===&lt;br /&gt;
It is possible to set a chart title, by calling set_title and passing the title as string method.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart-&amp;gt;set_title(&#039;chart with a title&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Labels ===&lt;br /&gt;
Labels are the description in which the data will be grouped, in the example above labels are an array of years. The number of values on the series must match the number of labels.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart-&amp;gt;set_labels([&#039;2004&#039;, &#039;2005&#039;, &#039;2006&#039;, &#039;2007&#039;]);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Axis ==&lt;br /&gt;
You can customise chart axis (X,Y) by setting title, position and change the step size.&lt;br /&gt;
Firstly, you need to select the axis by the side you want and providing the index and whether you want to create the axis if it does not exists.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new chart_line();&lt;br /&gt;
$xaxis = $chart-&amp;gt;get_xaxis(0, true) // Select the index 0 of X axis and pass true to create the axis if not exists.&lt;br /&gt;
$yaxis = $chart-&amp;gt;get_yaxis(0, true) // Select the index 0 of Y axis and pass true to create the axis if not exists.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Once you get the axis you want change, you can change the attributes you need. &lt;br /&gt;
It is also possible to select multiple axis, for example if you want to have custom steps labels in one side and data in the other.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new chart_line();&lt;br /&gt;
$xaxis1 = $chart-&amp;gt;get_xaxis(0, true) // Select the index 0 of X axis and pass true to create the axis if not exists.&lt;br /&gt;
$yaxis1 = $chart-&amp;gt;get_yaxis(0, true) // Will display the default axis steps information.&lt;br /&gt;
$yaxis2 = $chart-&amp;gt;get_yaxis(1, true) // Select the second Y axis.&lt;br /&gt;
$yaxis2-&amp;gt;set_position(chart_axis::POS_RIGHT);  // Now I can change positioning, labels and etc just on the right side.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Axis title ===&lt;br /&gt;
Axis titles can be displayed on the chart sides and can be used to provide additional information about the chart.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new chart_line();&lt;br /&gt;
$chart-&amp;gt;get_xaxis(0, true)-&amp;gt;set_label(&amp;quot;I&#039;m the label for X&amp;quot;); &lt;br /&gt;
$chart-&amp;gt;get_yaxis(0, true)-&amp;gt;set_label(&amp;quot;I&#039;m the label for Y&amp;quot;); &lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Axis position ===&lt;br /&gt;
You can define the position of axis by calling &#039;&#039;&#039;set_position()&#039;&#039;&#039; and passing the axis position constant (POS_RIGHT, POS_LEFT, POS_BOTTOM, POS_TOP).&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new chart_line();&lt;br /&gt;
&lt;br /&gt;
// Customise X axis.&lt;br /&gt;
$xaxis = new chart_axis();&lt;br /&gt;
$xaxis-&amp;gt;set_label(&amp;quot;I&#039;m X, but at the top&amp;quot;);&lt;br /&gt;
$xaxis-&amp;gt;set_position(chart_axis::POS_TOP); // You can use POS_BOTTOM or POS_TOP for X axis, in this case let&#039;s change the position to the top.&lt;br /&gt;
$chart-&amp;gt;set_xaxis($xaxis);&lt;br /&gt;
&lt;br /&gt;
// Customise Y axis.&lt;br /&gt;
$yaxis = new chart_axis();&lt;br /&gt;
$yaxis-&amp;gt;set_position(chart_axis::POS_RIGHT);  // You can use POS_LEFT or POS_RIGHT for Y axis, in this case let&#039;s change the position to the right side.&lt;br /&gt;
$chart-&amp;gt;set_yaxis($yaxis);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Step size ===&lt;br /&gt;
Step size can be described as the size between the axis labels. You can set the step size of the axis, by calling &#039;&#039;&#039;set_stepsize()&#039;&#039;&#039; and passing the step size number.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new chart_line();&lt;br /&gt;
&lt;br /&gt;
// Customise X axis.&lt;br /&gt;
$xaxis = new chart_axis();&lt;br /&gt;
$axis-&amp;gt;set_stepsize(5); // Chart steps will be displayed as  5, 10, 15, 20...&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Step labels ===&lt;br /&gt;
If you prefer to display custom labels instead of numbers, just get the axis and call set_labels passing an array of labels:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new chart_line();&lt;br /&gt;
$chart-&amp;gt;get_xaxis(0, true);&lt;br /&gt;
$chart-&amp;gt;get_xaxis(1, true)-&amp;gt;set_labels([&#039;Poor&#039;, &#039;Average&#039;, &#039;Good&#039;, &#039;Perfect&#039;]); &lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Min and Max ===&lt;br /&gt;
You can customise the minimum and the maximum value of a axis step size. &lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new chart_line();&lt;br /&gt;
$xaxis = $chart-&amp;gt;get_xaxis(1, true);&lt;br /&gt;
$xaxis-&amp;gt;set_min(1);&lt;br /&gt;
$xaxis-&amp;gt;set_max(100); &lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Mixed chart types ==&lt;br /&gt;
It is possible to combine two types of chart by setting the type of the series differently of the chart type. For example, to create a chart that combine a bar chart with a line chart:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart = new \core\chart_bar(); // Create a bar chart instance.&lt;br /&gt;
$series1 = new \core\chart_series(&#039;Series 1 (Bar)&#039;, [1000, 1170, 660, 1030]);&lt;br /&gt;
$series2 = new \core\chart_series(&#039;Series 2 (Line)&#039;, [400, 460, 1120, 540]);&lt;br /&gt;
$series2-&amp;gt;set_type(\core\chart_series::TYPE_LINE); // Set the series type to line chart.&lt;br /&gt;
$chart-&amp;gt;add_series($series2);&lt;br /&gt;
$chart-&amp;gt;add_series($series1);&lt;br /&gt;
$chart-&amp;gt;set_labels([&#039;2004&#039;, &#039;2005&#039;, &#039;2006&#039;, &#039;2007&#039;]);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Please note the order you call add_series change the order series are displayed on the chart. In the example above, the first to be displayed will be line chart and in the background the bar chart.&lt;br /&gt;
&lt;br /&gt;
Example:&lt;br /&gt;
&lt;br /&gt;
[[File:bar_line_chart.png]]&lt;br /&gt;
&lt;br /&gt;
== Rendering ==  &lt;br /&gt;
All charts are rendered by render_chart() located on outputrenderers.php, once the chart object is ready, it must be passed to render() or render_chart().&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
echo $OUTPUT-&amp;gt;render($chart);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Show chart data table ===&lt;br /&gt;
By default, in order to make chart data accessible to users with special needs, a link at the bottom of the chart will display a table containing all of its data. &lt;br /&gt;
If that information is already displayed on the page in some other fashion, it may not be necessary to display the chart data table. &lt;br /&gt;
To remove it, a &#039;&#039;&#039;false&#039;&#039;&#039; value can be passed as the second parameter when calling the render_chart function:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
echo $OUTPUT-&amp;gt;render_chart($chart, false);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Overriding chart colours ===&lt;br /&gt;
It is possible to override the default set of colours used on charts, you just need to the following setting to config.php and set an array of hex css colours:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$CFG-&amp;gt;chart_colorset = [&#039;#001f3f&#039;, &#039;#01ff70&#039;, &#039;#F012BE&#039;, &#039;#85144b&#039;, &#039;#B10DC9&#039;];&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Legend options ===&lt;br /&gt;
It is possible to customize some aspects of the chart legend such position and visibility. In order to do that you need to call set_legend_options() method&lt;br /&gt;
and pass an array of options supported by ChartJS. &lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$chart-&amp;gt;set_legend_options([&#039;display&#039; =&amp;gt; false]);  // Hide chart legend.&lt;br /&gt;
$chart-&amp;gt;set_legend_options([&#039;position&#039; =&amp;gt; &#039;left&#039;]);  // Change legend position to left side.&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== RTL ===&lt;br /&gt;
Please note numerical ranges should be displayed in LTR, and not in RTL for both RTL and LTR languages, you can wrap the chart with container with dir tag.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code PHP&amp;gt;&lt;br /&gt;
echo html_writer::tag(&#039;div&#039;, $OUTPUT-&amp;gt;render($chart), array_merge([&#039;dir&#039; =&amp;gt; &#039;ltr&#039;]));&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=User:Dongsheng_Cai&amp;diff=47532</id>
		<title>User:Dongsheng Cai</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=User:Dongsheng_Cai&amp;diff=47532"/>
		<updated>2015-03-23T02:56:13Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;I was a Moodle developer working at [http://moodle.com/hq/ Moodle HQ]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Moodle projects I got involved:&lt;br /&gt;
&lt;br /&gt;
* [[Chat module]]&lt;br /&gt;
* [[dev:Repository_API|Moodle Repository API]]&lt;br /&gt;
* [[dev:Comments_2.0|Comments 2.0]]&lt;br /&gt;
* [[dev:Wiki_2.0|Wiki 2]] contributor&lt;br /&gt;
* [[dev:Mobile app|Moodle mobile app for iOS]]&lt;br /&gt;
&lt;br /&gt;
About me:&lt;br /&gt;
* [http://dongsheng.org My website]&lt;br /&gt;
* [http://github.com/dcai Github]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Upgrading_Airnotifier&amp;diff=46536</id>
		<title>Upgrading Airnotifier</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Upgrading_Airnotifier&amp;diff=46536"/>
		<updated>2014-10-18T16:19:03Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Steps to upgrade&lt;br /&gt;
&lt;br /&gt;
* Stop the server, killing the process&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
 ps aux | grep airnotifier&lt;br /&gt;
 kill -9 PROCESS_ID&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Backup the MongoDB databases&lt;br /&gt;
*# Backup AirNotifier settings database &amp;lt;br/&amp;gt;&amp;lt;code&amp;gt;mongodump -h localhost --port 27017 -d airnotifier -o /var/airnotifier/&amp;lt;/code&amp;gt;&lt;br /&gt;
*# Backup each application data&amp;lt;br/&amp;gt;&amp;lt;code&amp;gt;mongodump -h localhost --port 27017 -d commoodlemoodlemobile -o /var/airnotifier/;&amp;lt;/code&amp;gt;&amp;lt;br/&amp;gt;&amp;lt;code&amp;gt;mongodump -h localhost --port 27017 -d commoodlemoodlemobiletest -o /var/airnotifier/&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Pull latest version from git&lt;br /&gt;
&amp;lt;code&amp;gt;&lt;br /&gt;
 cd /opt/airnotifier&lt;br /&gt;
 git pull&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Check that your current version has a version number in the options collection (AirNotifier settings database) (Older AirNotifier versions doesn&#039;t have it), use the shell:&lt;br /&gt;
 mongo&lt;br /&gt;
 use airnotifier&lt;br /&gt;
 db.options.find()&lt;br /&gt;
&lt;br /&gt;
* If there is not a value option, create a new one:&lt;br /&gt;
 db.options.insert({name: &amp;quot;version&amp;quot;, value: 20140101})&lt;br /&gt;
&lt;br /&gt;
* Run the upgrade.py script&lt;br /&gt;
 cd /opt/airnotifier&lt;br /&gt;
 python upgrade.py&lt;br /&gt;
&lt;br /&gt;
* Start the server again&lt;br /&gt;
 ./startserver&lt;br /&gt;
or&lt;br /&gt;
 sudo -u airnotifier python airnotifier.py &amp;gt;&amp;gt; /var/airnotifier/console_log 2&amp;gt;&amp;gt; /var/airnotifier/error_log &amp;amp;&lt;br /&gt;
&lt;br /&gt;
[[Category: Mobile]]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_Push_Notifications&amp;diff=45606</id>
		<title>Moodle Mobile Push Notifications</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_Push_Notifications&amp;diff=45606"/>
		<updated>2014-06-30T00:11:19Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Goal==&lt;br /&gt;
Moodle Mobile should receive Moodle messages as push notifications on iOS and Android.&lt;br /&gt;
&lt;br /&gt;
== Use case and web services ==&lt;br /&gt;
[[Image:Screenshot_2_05_13_5_16_PM.png|thumb]]&lt;br /&gt;
[[Image:Screenshot_2_05_13_5_14_PM.png|thumb]]&lt;br /&gt;
=== currently implemented use case ===&lt;br /&gt;
# The user opens the app and accepts to receive push notifications. The app contacts Apple push notification server and gets a device token. The mobile app PushPlugin plugin does it.&lt;br /&gt;
# The user enters its Moodle credentials. The app gets an [http://airnotifier.github.io AirNotifier] access key from the site. (note that this access key is currently the same for each site =&amp;gt; meaningless). The key is retrieved by calling the webservice message_airnotifier_get_access_key(permissions). create_token is the only possible permission at the moment. &lt;br /&gt;
# The app sends its token to [http://airnotifier.github.io AirNotifier] server - it&#039;s done by a HTTP POST request. &lt;br /&gt;
# The app registers its device to the site - it&#039;s done by a ws call message_airnotifier_add_user_device(appname, devicetoken, devicename)&lt;br /&gt;
# Someone/Something triggers a Moodle message. [http://airnotifier.github.io AirNotifier] provider (Moodle site) sends a payload for a specific device token to airnotifier server - it&#039;s done by a HTTP POST&lt;br /&gt;
# The user receive the notification.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== brainstorming use case ===&lt;br /&gt;
# During [http://airnotifier.github.io AirNotifier] provider installation, the site contacts Airnotifer server to request an access key for itself + an access key for the devices (to send their device token to [http://airnotifier.github.io AirNotifier] server).&lt;br /&gt;
# The user opens the app and accepts to receive push notifications. The app contacts Apple push notification server and gets a device token.&lt;br /&gt;
# The user enters its Moodle credentials. The app gets an [http://airnotifier.github.io AirNotifier] access key from the site. It&#039;s done calling the webservice message_airnotifier_get_access_key(type). An admin could request the site access key, the others only get the device access key.&lt;br /&gt;
# The app sends its device token to [http://airnotifier.github.io AirNotifier] server + the site url (so airnotifier knows the device/site couple, and so [http://airnotifier.github.io AirNotifier] can allow site broadcast) - it&#039;s done by a HTTP POST request. &lt;br /&gt;
# The app registers its device to the site - it&#039;s done by a ws call message_airnotifier_add_user_device(appname, devicetoken, devicename)&lt;br /&gt;
# During this web service, the site confirms the device token to [http://airnotifier.github.io AirNotifier]. It&#039;s done by a HTTP POST request. [http://airnotifier.github.io AirNotifier] server deletes unconfirmed device tokens older than one minute every hours.&lt;br /&gt;
# Someone/Something triggers a Moodle message. [http://airnotifier.github.io AirNotifier] provider (Moodle site) sends a payload for a specific device token to airnotifier server - it&#039;s done by a HTTP POST&lt;br /&gt;
# Time to time, the [http://airnotifier.github.io AirNotifier] server checks Apple feedback and delete the unactive device tokens. &lt;br /&gt;
# When the [http://airnotifier.github.io AirNotifier] provider tries to send a message to a deleted device, it receive an error from [http://airnotifier.github.io AirNotifier] server and stores it.  Then it sends a unique alert to the device user. The device is disabled on the user messaging settings. After a month, the cron processes delete the device.&lt;br /&gt;
&lt;br /&gt;
=== another brainstorming use case ===&lt;br /&gt;
# All the sites (public or privately) already registered in Moodle.net will have automatically keys for using [http://airnotifier.github.io AirNotifier]&lt;br /&gt;
# When registering a new site in Moodle.net there will be a new option for enabling PUSH Notificiations (true by default)&lt;br /&gt;
# If your site is not registered and your Mobile Services were enabled you will see a message in the Moodle Notifications page indicating that &amp;quot;You must register your site in order to be able to send PUSH Notifications&amp;quot;&lt;br /&gt;
# If your site is not registered and you enable Mobile Services you will see a message near to the &amp;quot;Enable Mobile Services&amp;quot; indicating that &amp;quot;You must register your site in order to be able to send PUSH Notifications&amp;quot;&lt;br /&gt;
# If your installation has a very big number of users, during the registration process and in the Notifications page you will see a message indicating &amp;quot;The public PUSH Notification server is limited to Moodle sites with less than N (5k, 20k...) users&amp;quot;&lt;br /&gt;
# The user opens the app and accepts to receive push notifications. The app contacts Apple push notification server and gets a device token. A user can disable at any time notifications for his device&lt;br /&gt;
# Every time the user open the app, the app gets an [http://airnotifier.github.io AirNotifier] access key from the site. It&#039;s done calling the webservice message_airnotifier_get_access_key(type). An admin could request the site access key, the others only get the device access key.&lt;br /&gt;
# The app sends its device token to [http://airnotifier.github.io AirNotifier] server + the site url (so airnotifier knows the device/site couple, and so [http://airnotifier.github.io AirNotifier] can allow site broadcast) - it&#039;s done by a HTTP POST request. This is done only if your device token has changed.&lt;br /&gt;
# The app registers its device to the site - it&#039;s done by a ws call message_airnotifier_add_user_device(appname, devicetoken, devicename, siteURL) This is done every time a user opens the app&lt;br /&gt;
# Since the app is sending its device token every time a user opens the app, a cron job in the Moodle Site can delete old device tokens (so we can avoid the [http://airnotifier.github.io AirNotifier] feedback service in the first stage)&lt;br /&gt;
# Someone/Something triggers a Moodle message. [http://airnotifier.github.io AirNotifier] provider (Moodle site) sends a payload for a specific device token + specific site to airnotifier server - it&#039;s done by a HTTP POST&lt;br /&gt;
# If the user disable the PUSH Notification feature, a WS message_airnotifier_remove_user_device is called, also the [http://airnotifier.github.io AirNotifier] is requested to delete the user device&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Benefits are:&lt;br /&gt;
-We can reuse the current registration process that is secure (as far as I know)&lt;br /&gt;
-The [http://airnotifier.github.io AirNotifier] API for register new sites will be exposed only to Moodle.net (not to all sites)&lt;br /&gt;
-It will be easier notify registered sites that are abusing (Moodle.net can communicate with sites (sending an automatic email to the user admin))&lt;br /&gt;
-Moodle will have statistics of sites using PUSH directly in Moodle.net (no need to ask [http://airnotifier.github.io AirNotifier])&lt;br /&gt;
-The registration process can be improved to retrieve the number of registered devices that are receiving notifications&lt;br /&gt;
-I think that it will &amp;quot;enforce&amp;quot; users to register their sites (for receive security notifications, etc..)&lt;br /&gt;
-Big companies with big Moodle installations usually doesn&#039;t register their Moodle sites, this will force these companies to use their own [http://airnotifier.github.io AirNotifier] server instance&lt;br /&gt;
-Since we have the number of users in a Moodle site, we can automatically disable [http://airnotifier.github.io AirNotifier] for big installations (more thant Nk users)&lt;br /&gt;
-I think that having all the information that the registration provides will help us to &amp;quot;monitor and prevent abuse&amp;quot; of the [http://airnotifier.github.io AirNotifier] server&lt;br /&gt;
&lt;br /&gt;
== Payload content ==&lt;br /&gt;
The current payload content would need a bit of brainstorming. We need to identify what to send as it is extremely short. The app will behave following the content it receives. &lt;br /&gt;
=== Apple ===&lt;br /&gt;
The Apple payload is limited to 256bytes and it includes everything sent. Our current implementation is still quite of a wip:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
{type:&#039;moodle_instantmessage&#039;,id:&#039;8&#039;,urlparams:&#039;{&amp;quot;user&amp;quot;:&amp;quot;2&amp;quot;,&amp;quot;id&amp;quot;:&amp;quot;8&amp;quot;}&#039;,userfrom:&#039;Manager Moodle&#039;,date:&#039;1367477362&#039;,alert:&#039;This is a message text.&#039;,}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
=== GCM ===&lt;br /&gt;
The Apple payload is about 4Kb. As it&#039;s more more permissive than Apple, it is not an issue. The content will be the same than Apple ones, just the format changes.&lt;br /&gt;
&lt;br /&gt;
==Deliverables==&lt;br /&gt;
* push notification support on the mobile app - the app receives the push notification and behaves correctly.&lt;br /&gt;
* The [http://airnotifier.github.io AirNotifier] server - it sends notification to APNS / GCM.&lt;br /&gt;
* The [http://airnotifier.github.io AirNotifier] provider - it sends Moodle messages to [http://airnotifier.github.io AirNotifier] - MDL-36445&lt;br /&gt;
&lt;br /&gt;
=== [http://airnotifier.github.io AirNotifier] messaging provider ===&lt;br /&gt;
In your messaging setting page you can select which of your devices can receive push notifications. MDL-36445&lt;br /&gt;
The provider has usual messaging provider settings.&lt;br /&gt;
&lt;br /&gt;
=== [http://airnotifier.github.io AirNotifier] server ===&lt;br /&gt;
To install the server follow: https://github.com/airnotifier/airnotifier/wiki/Installation&lt;br /&gt;
&lt;br /&gt;
Need to be done:&lt;br /&gt;
* feedback - MOBILE-191&lt;br /&gt;
* load stress&lt;br /&gt;
* IP banning - need to be tested MOBILE-192&lt;br /&gt;
* Site broadcast (site keys) MOBILE-395&lt;br /&gt;
* DDOS attacks&lt;br /&gt;
* iOS support - it works&lt;br /&gt;
* Android support MOBILE-396&lt;br /&gt;
&lt;br /&gt;
=== App push notification support in the mobile app ===&lt;br /&gt;
MOBILE-168&lt;br /&gt;
&lt;br /&gt;
==== The app is closed or is running in the background ====&lt;br /&gt;
It&#039;s a normal OS notification. The user can open the app from it. We need to define what happens when we open the app. See payload content section.&lt;br /&gt;
&lt;br /&gt;
==== The app is opened ====&lt;br /&gt;
The app displays a notification popup. Or better, it&#039;s a small temporary notification message at the top that retarct by itself - less intrusive.&lt;br /&gt;
&lt;br /&gt;
== Risks ==&lt;br /&gt;
These risks have been raised by Dan in MDL-36445:&lt;br /&gt;
* Have we load tested it with large numbers of users/notifications?&lt;br /&gt;
* What is stopping someone downloading Moodle getting the keys to DOS our push notification service (surely blacklisting us with apple)&lt;br /&gt;
* If someone gains access to device ids, could they then use our service to spam users?&lt;br /&gt;
* Are we checking the &#039;feedback service&#039; and removing devices which no longer exist, apple ominously writes &amp;quot;Note: APNs monitors providers for their diligence in checking the feedback service and refraining from sending push notifications to nonexistent applications on devices.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
== Improvements ==&lt;br /&gt;
See Apu&#039;s comment in https://tracker.moodle.org/browse/MDL-36445?focusedCommentId=200343&amp;amp;page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-200343.&lt;br /&gt;
Specially the mention about language.&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_Push_Notifications&amp;diff=45605</id>
		<title>Moodle Mobile Push Notifications</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_Push_Notifications&amp;diff=45605"/>
		<updated>2014-06-29T16:48:11Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Goal==&lt;br /&gt;
Moodle Mobile should receive Moodle messages as push notifications on iOS and Android.&lt;br /&gt;
&lt;br /&gt;
== Use case and web services ==&lt;br /&gt;
[[Image:Screenshot_2_05_13_5_16_PM.png|thumb]]&lt;br /&gt;
[[Image:Screenshot_2_05_13_5_14_PM.png|thumb]]&lt;br /&gt;
=== currently implemented use case ===&lt;br /&gt;
# The user opens the app and accepts to receive push notifications. The app contacts Apple push notification server and gets a device token. The mobile app PushPlugin plugin does it.&lt;br /&gt;
# The user enters its Moodle credentials. The app gets an [http://airnotifier.github.io/airnotifier/ AirNotifier] access key from the site. (note that this access key is currently the same for each site =&amp;gt; meaningless). The key is retrieved by calling the webservice message_airnotifier_get_access_key(permissions). create_token is the only possible permission at the moment. &lt;br /&gt;
# The app sends its token to [http://airnotifier.github.io/airnotifier/ AirNotifier] server - it&#039;s done by a HTTP POST request. &lt;br /&gt;
# The app registers its device to the site - it&#039;s done by a ws call message_airnotifier_add_user_device(appname, devicetoken, devicename)&lt;br /&gt;
# Someone/Something triggers a Moodle message. [http://airnotifier.github.io/airnotifier/ AirNotifier] provider (Moodle site) sends a payload for a specific device token to airnotifier server - it&#039;s done by a HTTP POST&lt;br /&gt;
# The user receive the notification.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== brainstorming use case ===&lt;br /&gt;
# During [http://airnotifier.github.io/airnotifier/ AirNotifier] provider installation, the site contacts Airnotifer server to request an access key for itself + an access key for the devices (to send their device token to [http://airnotifier.github.io/airnotifier/ AirNotifier] server).&lt;br /&gt;
# The user opens the app and accepts to receive push notifications. The app contacts Apple push notification server and gets a device token.&lt;br /&gt;
# The user enters its Moodle credentials. The app gets an [http://airnotifier.github.io/airnotifier/ AirNotifier] access key from the site. It&#039;s done calling the webservice message_airnotifier_get_access_key(type). An admin could request the site access key, the others only get the device access key.&lt;br /&gt;
# The app sends its device token to [http://airnotifier.github.io/airnotifier/ AirNotifier] server + the site url (so airnotifier knows the device/site couple, and so [http://airnotifier.github.io/airnotifier/ AirNotifier] can allow site broadcast) - it&#039;s done by a HTTP POST request. &lt;br /&gt;
# The app registers its device to the site - it&#039;s done by a ws call message_airnotifier_add_user_device(appname, devicetoken, devicename)&lt;br /&gt;
# During this web service, the site confirms the device token to [http://airnotifier.github.io/airnotifier/ AirNotifier]. It&#039;s done by a HTTP POST request. [http://airnotifier.github.io/airnotifier/ AirNotifier] server deletes unconfirmed device tokens older than one minute every hours.&lt;br /&gt;
# Someone/Something triggers a Moodle message. [http://airnotifier.github.io/airnotifier/ AirNotifier] provider (Moodle site) sends a payload for a specific device token to airnotifier server - it&#039;s done by a HTTP POST&lt;br /&gt;
# Time to time, the [http://airnotifier.github.io/airnotifier/ AirNotifier] server checks Apple feedback and delete the unactive device tokens. &lt;br /&gt;
# When the [http://airnotifier.github.io/airnotifier/ AirNotifier] provider tries to send a message to a deleted device, it receive an error from [http://airnotifier.github.io/airnotifier/ AirNotifier] server and stores it.  Then it sends a unique alert to the device user. The device is disabled on the user messaging settings. After a month, the cron processes delete the device.&lt;br /&gt;
&lt;br /&gt;
=== another brainstorming use case ===&lt;br /&gt;
# All the sites (public or privately) already registered in Moodle.net will have automatically keys for using [http://airnotifier.github.io/airnotifier/ AirNotifier]&lt;br /&gt;
# When registering a new site in Moodle.net there will be a new option for enabling PUSH Notificiations (true by default)&lt;br /&gt;
# If your site is not registered and your Mobile Services were enabled you will see a message in the Moodle Notifications page indicating that &amp;quot;You must register your site in order to be able to send PUSH Notifications&amp;quot;&lt;br /&gt;
# If your site is not registered and you enable Mobile Services you will see a message near to the &amp;quot;Enable Mobile Services&amp;quot; indicating that &amp;quot;You must register your site in order to be able to send PUSH Notifications&amp;quot;&lt;br /&gt;
# If your installation has a very big number of users, during the registration process and in the Notifications page you will see a message indicating &amp;quot;The public PUSH Notification server is limited to Moodle sites with less than N (5k, 20k...) users&amp;quot;&lt;br /&gt;
# The user opens the app and accepts to receive push notifications. The app contacts Apple push notification server and gets a device token. A user can disable at any time notifications for his device&lt;br /&gt;
# Every time the user open the app, the app gets an [http://airnotifier.github.io/airnotifier/ AirNotifier] access key from the site. It&#039;s done calling the webservice message_airnotifier_get_access_key(type). An admin could request the site access key, the others only get the device access key.&lt;br /&gt;
# The app sends its device token to [http://airnotifier.github.io/airnotifier/ AirNotifier] server + the site url (so airnotifier knows the device/site couple, and so [http://airnotifier.github.io/airnotifier/ AirNotifier] can allow site broadcast) - it&#039;s done by a HTTP POST request. This is done only if your device token has changed.&lt;br /&gt;
# The app registers its device to the site - it&#039;s done by a ws call message_airnotifier_add_user_device(appname, devicetoken, devicename, siteURL) This is done every time a user opens the app&lt;br /&gt;
# Since the app is sending its device token every time a user opens the app, a cron job in the Moodle Site can delete old device tokens (so we can avoid the [http://airnotifier.github.io/airnotifier/ AirNotifier] feedback service in the first stage)&lt;br /&gt;
# Someone/Something triggers a Moodle message. [http://airnotifier.github.io/airnotifier/ AirNotifier] provider (Moodle site) sends a payload for a specific device token + specific site to airnotifier server - it&#039;s done by a HTTP POST&lt;br /&gt;
# If the user disable the PUSH Notification feature, a WS message_airnotifier_remove_user_device is called, also the [http://airnotifier.github.io/airnotifier/ AirNotifier] is requested to delete the user device&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Benefits are:&lt;br /&gt;
-We can reuse the current registration process that is secure (as far as I know)&lt;br /&gt;
-The [http://airnotifier.github.io/airnotifier/ AirNotifier] API for register new sites will be exposed only to Moodle.net (not to all sites)&lt;br /&gt;
-It will be easier notify registered sites that are abusing (Moodle.net can communicate with sites (sending an automatic email to the user admin))&lt;br /&gt;
-Moodle will have statistics of sites using PUSH directly in Moodle.net (no need to ask [http://airnotifier.github.io/airnotifier/ AirNotifier])&lt;br /&gt;
-The registration process can be improved to retrieve the number of registered devices that are receiving notifications&lt;br /&gt;
-I think that it will &amp;quot;enforce&amp;quot; users to register their sites (for receive security notifications, etc..)&lt;br /&gt;
-Big companies with big Moodle installations usually doesn&#039;t register their Moodle sites, this will force these companies to use their own [http://airnotifier.github.io/airnotifier/ AirNotifier] server instance&lt;br /&gt;
-Since we have the number of users in a Moodle site, we can automatically disable [http://airnotifier.github.io/airnotifier/ AirNotifier] for big installations (more thant Nk users)&lt;br /&gt;
-I think that having all the information that the registration provides will help us to &amp;quot;monitor and prevent abuse&amp;quot; of the [http://airnotifier.github.io/airnotifier/ AirNotifier] server&lt;br /&gt;
&lt;br /&gt;
== Payload content ==&lt;br /&gt;
The current payload content would need a bit of brainstorming. We need to identify what to send as it is extremely short. The app will behave following the content it receives. &lt;br /&gt;
=== Apple ===&lt;br /&gt;
The Apple payload is limited to 256bytes and it includes everything sent. Our current implementation is still quite of a wip:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
{type:&#039;moodle_instantmessage&#039;,id:&#039;8&#039;,urlparams:&#039;{&amp;quot;user&amp;quot;:&amp;quot;2&amp;quot;,&amp;quot;id&amp;quot;:&amp;quot;8&amp;quot;}&#039;,userfrom:&#039;Manager Moodle&#039;,date:&#039;1367477362&#039;,alert:&#039;This is a message text.&#039;,}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
=== GCM ===&lt;br /&gt;
The Apple payload is about 4Kb. As it&#039;s more more permissive than Apple, it is not an issue. The content will be the same than Apple ones, just the format changes.&lt;br /&gt;
&lt;br /&gt;
==Deliverables==&lt;br /&gt;
* push notification support on the mobile app - the app receives the push notification and behaves correctly.&lt;br /&gt;
* The [http://airnotifier.github.io/airnotifier/ AirNotifier] server - it sends notification to APNS / GCM.&lt;br /&gt;
* The [http://airnotifier.github.io/airnotifier/ AirNotifier] provider - it sends Moodle messages to [http://airnotifier.github.io/airnotifier/ AirNotifier] - MDL-36445&lt;br /&gt;
&lt;br /&gt;
=== [http://airnotifier.github.io/airnotifier/ AirNotifier] messaging provider ===&lt;br /&gt;
In your messaging setting page you can select which of your devices can receive push notifications. MDL-36445&lt;br /&gt;
The provider has usual messaging provider settings.&lt;br /&gt;
&lt;br /&gt;
=== [http://airnotifier.github.io/airnotifier/ AirNotifier] server ===&lt;br /&gt;
To install the server follow: https://github.com/airnotifier/airnotifier/wiki/Installation&lt;br /&gt;
&lt;br /&gt;
Need to be done:&lt;br /&gt;
* feedback - MOBILE-191&lt;br /&gt;
* load stress&lt;br /&gt;
* IP banning - need to be tested MOBILE-192&lt;br /&gt;
* Site broadcast (site keys) MOBILE-395&lt;br /&gt;
* DDOS attacks&lt;br /&gt;
* iOS support - it works&lt;br /&gt;
* Android support MOBILE-396&lt;br /&gt;
&lt;br /&gt;
=== App push notification support in the mobile app ===&lt;br /&gt;
MOBILE-168&lt;br /&gt;
&lt;br /&gt;
==== The app is closed or is running in the background ====&lt;br /&gt;
It&#039;s a normal OS notification. The user can open the app from it. We need to define what happens when we open the app. See payload content section.&lt;br /&gt;
&lt;br /&gt;
==== The app is opened ====&lt;br /&gt;
The app displays a notification popup. Or better, it&#039;s a small temporary notification message at the top that retarct by itself - less intrusive.&lt;br /&gt;
&lt;br /&gt;
== Risks ==&lt;br /&gt;
These risks have been raised by Dan in MDL-36445:&lt;br /&gt;
* Have we load tested it with large numbers of users/notifications?&lt;br /&gt;
* What is stopping someone downloading Moodle getting the keys to DOS our push notification service (surely blacklisting us with apple)&lt;br /&gt;
* If someone gains access to device ids, could they then use our service to spam users?&lt;br /&gt;
* Are we checking the &#039;feedback service&#039; and removing devices which no longer exist, apple ominously writes &amp;quot;Note: APNs monitors providers for their diligence in checking the feedback service and refraining from sending push notifications to nonexistent applications on devices.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
== Improvements ==&lt;br /&gt;
See Apu&#039;s comment in https://tracker.moodle.org/browse/MDL-36445?focusedCommentId=200343&amp;amp;page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-200343.&lt;br /&gt;
Specially the mention about language.&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_Push_Notifications&amp;diff=45604</id>
		<title>Moodle Mobile Push Notifications</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_Push_Notifications&amp;diff=45604"/>
		<updated>2014-06-29T16:45:34Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: Undo revision 45603 by Dongsheng (talk)&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Goal==&lt;br /&gt;
Moodle Mobile should receive Moodle messages as push notifications on iOS and Android.&lt;br /&gt;
&lt;br /&gt;
== Use case and web services ==&lt;br /&gt;
[[Image:Screenshot_2_05_13_5_16_PM.png|thumb]]&lt;br /&gt;
[[Image:Screenshot_2_05_13_5_14_PM.png|thumb]]&lt;br /&gt;
=== currently implemented use case ===&lt;br /&gt;
# The user opens the app and accepts to receive push notifications. The app contacts Apple push notification server and gets a device token. The mobile app PushPlugin plugin does it.&lt;br /&gt;
# The user enters its Moodle credentials. The app gets an AirNotifier access key from the site. (note that this access key is currently the same for each site =&amp;gt; meaningless). The key is retrieved by calling the webservice message_airnotifier_get_access_key(permissions). create_token is the only possible permission at the moment. &lt;br /&gt;
# The app sends its token to AirNotifier server - it&#039;s done by a HTTP POST request. &lt;br /&gt;
# The app registers its device to the site - it&#039;s done by a ws call message_airnotifier_add_user_device(appname, devicetoken, devicename)&lt;br /&gt;
# Someone/Something triggers a Moodle message. AirNotifier provider (Moodle site) sends a payload for a specific device token to airnotifier server - it&#039;s done by a HTTP POST&lt;br /&gt;
# The user receive the notification.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== brainstorming use case ===&lt;br /&gt;
# During AirNotifier provider installation, the site contacts Airnotifer server to request an access key for itself + an access key for the devices (to send their device token to AirNotifier server).&lt;br /&gt;
# The user opens the app and accepts to receive push notifications. The app contacts Apple push notification server and gets a device token.&lt;br /&gt;
# The user enters its Moodle credentials. The app gets an AirNotifier access key from the site. It&#039;s done calling the webservice message_airnotifier_get_access_key(type). An admin could request the site access key, the others only get the device access key.&lt;br /&gt;
# The app sends its device token to AirNotifier server + the site url (so airnotifier knows the device/site couple, and so AirNotifier can allow site broadcast) - it&#039;s done by a HTTP POST request. &lt;br /&gt;
# The app registers its device to the site - it&#039;s done by a ws call message_airnotifier_add_user_device(appname, devicetoken, devicename)&lt;br /&gt;
# During this web service, the site confirms the device token to AirNotifier. It&#039;s done by a HTTP POST request. AirNotifier server deletes unconfirmed device tokens older than one minute every hours.&lt;br /&gt;
# Someone/Something triggers a Moodle message. AirNotifier provider (Moodle site) sends a payload for a specific device token to airnotifier server - it&#039;s done by a HTTP POST&lt;br /&gt;
# Time to time, the AirNotifier server checks Apple feedback and delete the unactive device tokens. &lt;br /&gt;
# When the AirNotifier provider tries to send a message to a deleted device, it receive an error from AirNotifier server and stores it.  Then it sends a unique alert to the device user. The device is disabled on the user messaging settings. After a month, the cron processes delete the device.&lt;br /&gt;
&lt;br /&gt;
=== another brainstorming use case ===&lt;br /&gt;
# All the sites (public or privately) already registered in Moodle.net will have automatically keys for using AirNotifier&lt;br /&gt;
# When registering a new site in Moodle.net there will be a new option for enabling PUSH Notificiations (true by default)&lt;br /&gt;
# If your site is not registered and your Mobile Services were enabled you will see a message in the Moodle Notifications page indicating that &amp;quot;You must register your site in order to be able to send PUSH Notifications&amp;quot;&lt;br /&gt;
# If your site is not registered and you enable Mobile Services you will see a message near to the &amp;quot;Enable Mobile Services&amp;quot; indicating that &amp;quot;You must register your site in order to be able to send PUSH Notifications&amp;quot;&lt;br /&gt;
# If your installation has a very big number of users, during the registration process and in the Notifications page you will see a message indicating &amp;quot;The public PUSH Notification server is limited to Moodle sites with less than N (5k, 20k...) users&amp;quot;&lt;br /&gt;
# The user opens the app and accepts to receive push notifications. The app contacts Apple push notification server and gets a device token. A user can disable at any time notifications for his device&lt;br /&gt;
# Every time the user open the app, the app gets an AirNotifier access key from the site. It&#039;s done calling the webservice message_airnotifier_get_access_key(type). An admin could request the site access key, the others only get the device access key.&lt;br /&gt;
# The app sends its device token to AirNotifier server + the site url (so airnotifier knows the device/site couple, and so AirNotifier can allow site broadcast) - it&#039;s done by a HTTP POST request. This is done only if your device token has changed.&lt;br /&gt;
# The app registers its device to the site - it&#039;s done by a ws call message_airnotifier_add_user_device(appname, devicetoken, devicename, siteURL) This is done every time a user opens the app&lt;br /&gt;
# Since the app is sending its device token every time a user opens the app, a cron job in the Moodle Site can delete old device tokens (so we can avoid the AirNotifier feedback service in the first stage)&lt;br /&gt;
# Someone/Something triggers a Moodle message. AirNotifier provider (Moodle site) sends a payload for a specific device token + specific site to airnotifier server - it&#039;s done by a HTTP POST&lt;br /&gt;
# If the user disable the PUSH Notification feature, a WS message_airnotifier_remove_user_device is called, also the AirNotifier is requested to delete the user device&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Benefits are:&lt;br /&gt;
-We can reuse the current registration process that is secure (as far as I know)&lt;br /&gt;
-The AirNotifier API for register new sites will be exposed only to Moodle.net (not to all sites)&lt;br /&gt;
-It will be easier notify registered sites that are abusing (Moodle.net can communicate with sites (sending an automatic email to the user admin))&lt;br /&gt;
-Moodle will have statistics of sites using PUSH directly in Moodle.net (no need to ask AirNotifier)&lt;br /&gt;
-The registration process can be improved to retrieve the number of registered devices that are receiving notifications&lt;br /&gt;
-I think that it will &amp;quot;enforce&amp;quot; users to register their sites (for receive security notifications, etc..)&lt;br /&gt;
-Big companies with big Moodle installations usually doesn&#039;t register their Moodle sites, this will force these companies to use their own AirNotifier server instance&lt;br /&gt;
-Since we have the number of users in a Moodle site, we can automatically disable AirNotifier for big installations (more thant Nk users)&lt;br /&gt;
-I think that having all the information that the registration provides will help us to &amp;quot;monitor and prevent abuse&amp;quot; of the AirNotifier server&lt;br /&gt;
&lt;br /&gt;
== Payload content ==&lt;br /&gt;
The current payload content would need a bit of brainstorming. We need to identify what to send as it is extremely short. The app will behave following the content it receives. &lt;br /&gt;
=== Apple ===&lt;br /&gt;
The Apple payload is limited to 256bytes and it includes everything sent. Our current implementation is still quite of a wip:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
{type:&#039;moodle_instantmessage&#039;,id:&#039;8&#039;,urlparams:&#039;{&amp;quot;user&amp;quot;:&amp;quot;2&amp;quot;,&amp;quot;id&amp;quot;:&amp;quot;8&amp;quot;}&#039;,userfrom:&#039;Manager Moodle&#039;,date:&#039;1367477362&#039;,alert:&#039;This is a message text.&#039;,}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
=== GCM ===&lt;br /&gt;
The Apple payload is about 4Kb. As it&#039;s more more permissive than Apple, it is not an issue. The content will be the same than Apple ones, just the format changes.&lt;br /&gt;
&lt;br /&gt;
==Deliverables==&lt;br /&gt;
* push notification support on the mobile app - the app receives the push notification and behaves correctly.&lt;br /&gt;
* The AirNotifier server - it sends notification to APNS / GCM.&lt;br /&gt;
* The AirNotifier provider - it sends Moodle messages to AirNotifier - MDL-36445&lt;br /&gt;
&lt;br /&gt;
=== AirNotifier messaging provider ===&lt;br /&gt;
In your messaging setting page you can select which of your devices can receive push notifications. MDL-36445&lt;br /&gt;
The provider has usual messaging provider settings.&lt;br /&gt;
&lt;br /&gt;
=== AirNotifier server ===&lt;br /&gt;
To install the server follow: https://github.com/airnotifier/airnotifier/wiki/Installation&lt;br /&gt;
&lt;br /&gt;
Need to be done:&lt;br /&gt;
* feedback - MOBILE-191&lt;br /&gt;
* load stress&lt;br /&gt;
* IP banning - need to be tested MOBILE-192&lt;br /&gt;
* Site broadcast (site keys) MOBILE-395&lt;br /&gt;
* DDOS attacks&lt;br /&gt;
* iOS support - it works&lt;br /&gt;
* Android support MOBILE-396&lt;br /&gt;
&lt;br /&gt;
=== App push notification support in the mobile app ===&lt;br /&gt;
MOBILE-168&lt;br /&gt;
&lt;br /&gt;
==== The app is closed or is running in the background ====&lt;br /&gt;
It&#039;s a normal OS notification. The user can open the app from it. We need to define what happens when we open the app. See payload content section.&lt;br /&gt;
&lt;br /&gt;
==== The app is opened ====&lt;br /&gt;
The app displays a notification popup. Or better, it&#039;s a small temporary notification message at the top that retarct by itself - less intrusive.&lt;br /&gt;
&lt;br /&gt;
== Risks ==&lt;br /&gt;
These risks have been raised by Dan in MDL-36445:&lt;br /&gt;
* Have we load tested it with large numbers of users/notifications?&lt;br /&gt;
* What is stopping someone downloading Moodle getting the keys to DOS our push notification service (surely blacklisting us with apple)&lt;br /&gt;
* If someone gains access to device ids, could they then use our service to spam users?&lt;br /&gt;
* Are we checking the &#039;feedback service&#039; and removing devices which no longer exist, apple ominously writes &amp;quot;Note: APNs monitors providers for their diligence in checking the feedback service and refraining from sending push notifications to nonexistent applications on devices.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
== Improvements ==&lt;br /&gt;
See Apu&#039;s comment in https://tracker.moodle.org/browse/MDL-36445?focusedCommentId=200343&amp;amp;page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-200343.&lt;br /&gt;
Specially the mention about language.&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_Push_Notifications&amp;diff=45603</id>
		<title>Moodle Mobile Push Notifications</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_Push_Notifications&amp;diff=45603"/>
		<updated>2014-06-29T16:45:03Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Goal==&lt;br /&gt;
Moodle Mobile should receive Moodle messages as push notifications on iOS and Android.&lt;br /&gt;
&lt;br /&gt;
== Use case and web services ==&lt;br /&gt;
[[Image:Screenshot_2_05_13_5_16_PM.png|thumb]]&lt;br /&gt;
[[Image:Screenshot_2_05_13_5_14_PM.png|thumb]]&lt;br /&gt;
=== currently implemented use case ===&lt;br /&gt;
# The user opens the app and accepts to receive push notifications. The app contacts Apple push notification server and gets a device token. The mobile app PushPlugin plugin does it.&lt;br /&gt;
# The user enters its Moodle credentials. The app gets an [AirNotifier](http://airnotifier.github.io/airnotifier/) access key from the site. (note that this access key is currently the same for each site =&amp;gt; meaningless). The key is retrieved by calling the webservice message_airnotifier_get_access_key(permissions). create_token is the only possible permission at the moment. &lt;br /&gt;
# The app sends its token to [AirNotifier](http://airnotifier.github.io/airnotifier/) server - it&#039;s done by a HTTP POST request. &lt;br /&gt;
# The app registers its device to the site - it&#039;s done by a ws call message_airnotifier_add_user_device(appname, devicetoken, devicename)&lt;br /&gt;
# Someone/Something triggers a Moodle message. [AirNotifier](http://airnotifier.github.io/airnotifier/) provider (Moodle site) sends a payload for a specific device token to airnotifier server - it&#039;s done by a HTTP POST&lt;br /&gt;
# The user receive the notification.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== brainstorming use case ===&lt;br /&gt;
# During [AirNotifier](http://airnotifier.github.io/airnotifier/) provider installation, the site contacts Airnotifer server to request an access key for itself + an access key for the devices (to send their device token to [AirNotifier](http://airnotifier.github.io/airnotifier/) server).&lt;br /&gt;
# The user opens the app and accepts to receive push notifications. The app contacts Apple push notification server and gets a device token.&lt;br /&gt;
# The user enters its Moodle credentials. The app gets an [AirNotifier](http://airnotifier.github.io/airnotifier/) access key from the site. It&#039;s done calling the webservice message_airnotifier_get_access_key(type). An admin could request the site access key, the others only get the device access key.&lt;br /&gt;
# The app sends its device token to [AirNotifier](http://airnotifier.github.io/airnotifier/) server + the site url (so airnotifier knows the device/site couple, and so [AirNotifier](http://airnotifier.github.io/airnotifier/) can allow site broadcast) - it&#039;s done by a HTTP POST request. &lt;br /&gt;
# The app registers its device to the site - it&#039;s done by a ws call message_airnotifier_add_user_device(appname, devicetoken, devicename)&lt;br /&gt;
# During this web service, the site confirms the device token to [AirNotifier](http://airnotifier.github.io/airnotifier/). It&#039;s done by a HTTP POST request. [AirNotifier](http://airnotifier.github.io/airnotifier/) server deletes unconfirmed device tokens older than one minute every hours.&lt;br /&gt;
# Someone/Something triggers a Moodle message. [AirNotifier](http://airnotifier.github.io/airnotifier/) provider (Moodle site) sends a payload for a specific device token to airnotifier server - it&#039;s done by a HTTP POST&lt;br /&gt;
# Time to time, the [AirNotifier](http://airnotifier.github.io/airnotifier/) server checks Apple feedback and delete the unactive device tokens. &lt;br /&gt;
# When the [AirNotifier](http://airnotifier.github.io/airnotifier/) provider tries to send a message to a deleted device, it receive an error from [AirNotifier](http://airnotifier.github.io/airnotifier/) server and stores it.  Then it sends a unique alert to the device user. The device is disabled on the user messaging settings. After a month, the cron processes delete the device.&lt;br /&gt;
&lt;br /&gt;
=== another brainstorming use case ===&lt;br /&gt;
# All the sites (public or privately) already registered in Moodle.net will have automatically keys for using [AirNotifier](http://airnotifier.github.io/airnotifier/)&lt;br /&gt;
# When registering a new site in Moodle.net there will be a new option for enabling PUSH Notificiations (true by default)&lt;br /&gt;
# If your site is not registered and your Mobile Services were enabled you will see a message in the Moodle Notifications page indicating that &amp;quot;You must register your site in order to be able to send PUSH Notifications&amp;quot;&lt;br /&gt;
# If your site is not registered and you enable Mobile Services you will see a message near to the &amp;quot;Enable Mobile Services&amp;quot; indicating that &amp;quot;You must register your site in order to be able to send PUSH Notifications&amp;quot;&lt;br /&gt;
# If your installation has a very big number of users, during the registration process and in the Notifications page you will see a message indicating &amp;quot;The public PUSH Notification server is limited to Moodle sites with less than N (5k, 20k...) users&amp;quot;&lt;br /&gt;
# The user opens the app and accepts to receive push notifications. The app contacts Apple push notification server and gets a device token. A user can disable at any time notifications for his device&lt;br /&gt;
# Every time the user open the app, the app gets an [AirNotifier](http://airnotifier.github.io/airnotifier/) access key from the site. It&#039;s done calling the webservice message_airnotifier_get_access_key(type). An admin could request the site access key, the others only get the device access key.&lt;br /&gt;
# The app sends its device token to [AirNotifier](http://airnotifier.github.io/airnotifier/) server + the site url (so airnotifier knows the device/site couple, and so [AirNotifier](http://airnotifier.github.io/airnotifier/) can allow site broadcast) - it&#039;s done by a HTTP POST request. This is done only if your device token has changed.&lt;br /&gt;
# The app registers its device to the site - it&#039;s done by a ws call message_airnotifier_add_user_device(appname, devicetoken, devicename, siteURL) This is done every time a user opens the app&lt;br /&gt;
# Since the app is sending its device token every time a user opens the app, a cron job in the Moodle Site can delete old device tokens (so we can avoid the [AirNotifier](http://airnotifier.github.io/airnotifier/) feedback service in the first stage)&lt;br /&gt;
# Someone/Something triggers a Moodle message. [AirNotifier](http://airnotifier.github.io/airnotifier/) provider (Moodle site) sends a payload for a specific device token + specific site to airnotifier server - it&#039;s done by a HTTP POST&lt;br /&gt;
# If the user disable the PUSH Notification feature, a WS message_airnotifier_remove_user_device is called, also the [AirNotifier](http://airnotifier.github.io/airnotifier/) is requested to delete the user device&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Benefits are:&lt;br /&gt;
-We can reuse the current registration process that is secure (as far as I know)&lt;br /&gt;
-The [AirNotifier](http://airnotifier.github.io/airnotifier/) API for register new sites will be exposed only to Moodle.net (not to all sites)&lt;br /&gt;
-It will be easier notify registered sites that are abusing (Moodle.net can communicate with sites (sending an automatic email to the user admin))&lt;br /&gt;
-Moodle will have statistics of sites using PUSH directly in Moodle.net (no need to ask [AirNotifier](http://airnotifier.github.io/airnotifier/))&lt;br /&gt;
-The registration process can be improved to retrieve the number of registered devices that are receiving notifications&lt;br /&gt;
-I think that it will &amp;quot;enforce&amp;quot; users to register their sites (for receive security notifications, etc..)&lt;br /&gt;
-Big companies with big Moodle installations usually doesn&#039;t register their Moodle sites, this will force these companies to use their own [AirNotifier](http://airnotifier.github.io/airnotifier/) server instance&lt;br /&gt;
-Since we have the number of users in a Moodle site, we can automatically disable [AirNotifier](http://airnotifier.github.io/airnotifier/) for big installations (more thant Nk users)&lt;br /&gt;
-I think that having all the information that the registration provides will help us to &amp;quot;monitor and prevent abuse&amp;quot; of the [AirNotifier](http://airnotifier.github.io/airnotifier/) server&lt;br /&gt;
&lt;br /&gt;
== Payload content ==&lt;br /&gt;
The current payload content would need a bit of brainstorming. We need to identify what to send as it is extremely short. The app will behave following the content it receives. &lt;br /&gt;
=== Apple ===&lt;br /&gt;
The Apple payload is limited to 256bytes and it includes everything sent. Our current implementation is still quite of a wip:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
{type:&#039;moodle_instantmessage&#039;,id:&#039;8&#039;,urlparams:&#039;{&amp;quot;user&amp;quot;:&amp;quot;2&amp;quot;,&amp;quot;id&amp;quot;:&amp;quot;8&amp;quot;}&#039;,userfrom:&#039;Manager Moodle&#039;,date:&#039;1367477362&#039;,alert:&#039;This is a message text.&#039;,}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
=== GCM ===&lt;br /&gt;
The Apple payload is about 4Kb. As it&#039;s more more permissive than Apple, it is not an issue. The content will be the same than Apple ones, just the format changes.&lt;br /&gt;
&lt;br /&gt;
==Deliverables==&lt;br /&gt;
* push notification support on the mobile app - the app receives the push notification and behaves correctly.&lt;br /&gt;
* The [AirNotifier](http://airnotifier.github.io/airnotifier/) server - it sends notification to APNS / GCM.&lt;br /&gt;
* The [AirNotifier](http://airnotifier.github.io/airnotifier/) provider - it sends Moodle messages to [AirNotifier](http://airnotifier.github.io/airnotifier/) - MDL-36445&lt;br /&gt;
&lt;br /&gt;
=== [AirNotifier](http://airnotifier.github.io/airnotifier/) messaging provider ===&lt;br /&gt;
In your messaging setting page you can select which of your devices can receive push notifications. MDL-36445&lt;br /&gt;
The provider has usual messaging provider settings.&lt;br /&gt;
&lt;br /&gt;
=== [AirNotifier](http://airnotifier.github.io/airnotifier/) server ===&lt;br /&gt;
To install the server follow: https://github.com/airnotifier/airnotifier/wiki/Installation&lt;br /&gt;
&lt;br /&gt;
Need to be done:&lt;br /&gt;
* feedback - MOBILE-191&lt;br /&gt;
* load stress&lt;br /&gt;
* IP banning - need to be tested MOBILE-192&lt;br /&gt;
* Site broadcast (site keys) MOBILE-395&lt;br /&gt;
* DDOS attacks&lt;br /&gt;
* iOS support - it works&lt;br /&gt;
* Android support MOBILE-396&lt;br /&gt;
&lt;br /&gt;
=== App push notification support in the mobile app ===&lt;br /&gt;
MOBILE-168&lt;br /&gt;
&lt;br /&gt;
==== The app is closed or is running in the background ====&lt;br /&gt;
It&#039;s a normal OS notification. The user can open the app from it. We need to define what happens when we open the app. See payload content section.&lt;br /&gt;
&lt;br /&gt;
==== The app is opened ====&lt;br /&gt;
The app displays a notification popup. Or better, it&#039;s a small temporary notification message at the top that retarct by itself - less intrusive.&lt;br /&gt;
&lt;br /&gt;
== Risks ==&lt;br /&gt;
These risks have been raised by Dan in MDL-36445:&lt;br /&gt;
* Have we load tested it with large numbers of users/notifications?&lt;br /&gt;
* What is stopping someone downloading Moodle getting the keys to DOS our push notification service (surely blacklisting us with apple)&lt;br /&gt;
* If someone gains access to device ids, could they then use our service to spam users?&lt;br /&gt;
* Are we checking the &#039;feedback service&#039; and removing devices which no longer exist, apple ominously writes &amp;quot;Note: APNs monitors providers for their diligence in checking the feedback service and refraining from sending push notifications to nonexistent applications on devices.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
== Improvements ==&lt;br /&gt;
See Apu&#039;s comment in https://tracker.moodle.org/browse/MDL-36445?focusedCommentId=200343&amp;amp;page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-200343.&lt;br /&gt;
Specially the mention about language.&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_Push_Notifications&amp;diff=45602</id>
		<title>Moodle Mobile Push Notifications</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_Mobile_Push_Notifications&amp;diff=45602"/>
		<updated>2014-06-29T16:38:35Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Goal==&lt;br /&gt;
Moodle Mobile should receive Moodle messages as push notifications on iOS and Android.&lt;br /&gt;
&lt;br /&gt;
== Use case and web services ==&lt;br /&gt;
[[Image:Screenshot_2_05_13_5_16_PM.png|thumb]]&lt;br /&gt;
[[Image:Screenshot_2_05_13_5_14_PM.png|thumb]]&lt;br /&gt;
=== currently implemented use case ===&lt;br /&gt;
# The user opens the app and accepts to receive push notifications. The app contacts Apple push notification server and gets a device token. The mobile app PushPlugin plugin does it.&lt;br /&gt;
# The user enters its Moodle credentials. The app gets an AirNotifier access key from the site. (note that this access key is currently the same for each site =&amp;gt; meaningless). The key is retrieved by calling the webservice message_airnotifier_get_access_key(permissions). create_token is the only possible permission at the moment. &lt;br /&gt;
# The app sends its token to AirNotifier server - it&#039;s done by a HTTP POST request. &lt;br /&gt;
# The app registers its device to the site - it&#039;s done by a ws call message_airnotifier_add_user_device(appname, devicetoken, devicename)&lt;br /&gt;
# Someone/Something triggers a Moodle message. AirNotifier provider (Moodle site) sends a payload for a specific device token to airnotifier server - it&#039;s done by a HTTP POST&lt;br /&gt;
# The user receive the notification.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== brainstorming use case ===&lt;br /&gt;
# During AirNotifier provider installation, the site contacts Airnotifer server to request an access key for itself + an access key for the devices (to send their device token to AirNotifier server).&lt;br /&gt;
# The user opens the app and accepts to receive push notifications. The app contacts Apple push notification server and gets a device token.&lt;br /&gt;
# The user enters its Moodle credentials. The app gets an AirNotifier access key from the site. It&#039;s done calling the webservice message_airnotifier_get_access_key(type). An admin could request the site access key, the others only get the device access key.&lt;br /&gt;
# The app sends its device token to AirNotifier server + the site url (so airnotifier knows the device/site couple, and so AirNotifier can allow site broadcast) - it&#039;s done by a HTTP POST request. &lt;br /&gt;
# The app registers its device to the site - it&#039;s done by a ws call message_airnotifier_add_user_device(appname, devicetoken, devicename)&lt;br /&gt;
# During this web service, the site confirms the device token to AirNotifier. It&#039;s done by a HTTP POST request. AirNotifier server deletes unconfirmed device tokens older than one minute every hours.&lt;br /&gt;
# Someone/Something triggers a Moodle message. AirNotifier provider (Moodle site) sends a payload for a specific device token to airnotifier server - it&#039;s done by a HTTP POST&lt;br /&gt;
# Time to time, the AirNotifier server checks Apple feedback and delete the unactive device tokens. &lt;br /&gt;
# When the AirNotifier provider tries to send a message to a deleted device, it receive an error from AirNotifier server and stores it.  Then it sends a unique alert to the device user. The device is disabled on the user messaging settings. After a month, the cron processes delete the device.&lt;br /&gt;
&lt;br /&gt;
=== another brainstorming use case ===&lt;br /&gt;
# All the sites (public or privately) already registered in Moodle.net will have automatically keys for using AirNotifier&lt;br /&gt;
# When registering a new site in Moodle.net there will be a new option for enabling PUSH Notificiations (true by default)&lt;br /&gt;
# If your site is not registered and your Mobile Services were enabled you will see a message in the Moodle Notifications page indicating that &amp;quot;You must register your site in order to be able to send PUSH Notifications&amp;quot;&lt;br /&gt;
# If your site is not registered and you enable Mobile Services you will see a message near to the &amp;quot;Enable Mobile Services&amp;quot; indicating that &amp;quot;You must register your site in order to be able to send PUSH Notifications&amp;quot;&lt;br /&gt;
# If your installation has a very big number of users, during the registration process and in the Notifications page you will see a message indicating &amp;quot;The public PUSH Notification server is limited to Moodle sites with less than N (5k, 20k...) users&amp;quot;&lt;br /&gt;
# The user opens the app and accepts to receive push notifications. The app contacts Apple push notification server and gets a device token. A user can disable at any time notifications for his device&lt;br /&gt;
# Every time the user open the app, the app gets an AirNotifier access key from the site. It&#039;s done calling the webservice message_airnotifier_get_access_key(type). An admin could request the site access key, the others only get the device access key.&lt;br /&gt;
# The app sends its device token to AirNotifier server + the site url (so airnotifier knows the device/site couple, and so AirNotifier can allow site broadcast) - it&#039;s done by a HTTP POST request. This is done only if your device token has changed.&lt;br /&gt;
# The app registers its device to the site - it&#039;s done by a ws call message_airnotifier_add_user_device(appname, devicetoken, devicename, siteURL) This is done every time a user opens the app&lt;br /&gt;
# Since the app is sending its device token every time a user opens the app, a cron job in the Moodle Site can delete old device tokens (so we can avoid the AirNotifier feedback service in the first stage)&lt;br /&gt;
# Someone/Something triggers a Moodle message. AirNotifier provider (Moodle site) sends a payload for a specific device token + specific site to airnotifier server - it&#039;s done by a HTTP POST&lt;br /&gt;
# If the user disable the PUSH Notification feature, a WS message_airnotifier_remove_user_device is called, also the AirNotifier is requested to delete the user device&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Benefits are:&lt;br /&gt;
-We can reuse the current registration process that is secure (as far as I know)&lt;br /&gt;
-The AirNotifier API for register new sites will be exposed only to Moodle.net (not to all sites)&lt;br /&gt;
-It will be easier notify registered sites that are abusing (Moodle.net can communicate with sites (sending an automatic email to the user admin))&lt;br /&gt;
-Moodle will have statistics of sites using PUSH directly in Moodle.net (no need to ask AirNotifier)&lt;br /&gt;
-The registration process can be improved to retrieve the number of registered devices that are receiving notifications&lt;br /&gt;
-I think that it will &amp;quot;enforce&amp;quot; users to register their sites (for receive security notifications, etc..)&lt;br /&gt;
-Big companies with big Moodle installations usually doesn&#039;t register their Moodle sites, this will force these companies to use their own AirNotifier server instance&lt;br /&gt;
-Since we have the number of users in a Moodle site, we can automatically disable AirNotifier for big installations (more thant Nk users)&lt;br /&gt;
-I think that having all the information that the registration provides will help us to &amp;quot;monitor and prevent abuse&amp;quot; of the AirNotifier server&lt;br /&gt;
&lt;br /&gt;
== Payload content ==&lt;br /&gt;
The current payload content would need a bit of brainstorming. We need to identify what to send as it is extremely short. The app will behave following the content it receives. &lt;br /&gt;
=== Apple ===&lt;br /&gt;
The Apple payload is limited to 256bytes and it includes everything sent. Our current implementation is still quite of a wip:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
{type:&#039;moodle_instantmessage&#039;,id:&#039;8&#039;,urlparams:&#039;{&amp;quot;user&amp;quot;:&amp;quot;2&amp;quot;,&amp;quot;id&amp;quot;:&amp;quot;8&amp;quot;}&#039;,userfrom:&#039;Manager Moodle&#039;,date:&#039;1367477362&#039;,alert:&#039;This is a message text.&#039;,}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
=== GCM ===&lt;br /&gt;
The Apple payload is about 4Kb. As it&#039;s more more permissive than Apple, it is not an issue. The content will be the same than Apple ones, just the format changes.&lt;br /&gt;
&lt;br /&gt;
==Deliverables==&lt;br /&gt;
* push notification support on the mobile app - the app receives the push notification and behaves correctly.&lt;br /&gt;
* The AirNotifier server - it sends notification to APNS / GCM.&lt;br /&gt;
* The AirNotifier provider - it sends Moodle messages to AirNotifier - MDL-36445&lt;br /&gt;
&lt;br /&gt;
=== AirNotifier messaging provider ===&lt;br /&gt;
In your messaging setting page you can select which of your devices can receive push notifications. MDL-36445&lt;br /&gt;
The provider has usual messaging provider settings.&lt;br /&gt;
&lt;br /&gt;
=== AirNotifier server ===&lt;br /&gt;
To install the server follow: https://github.com/airnotifier/airnotifier/wiki/Installation&lt;br /&gt;
&lt;br /&gt;
Need to be done:&lt;br /&gt;
* feedback - MOBILE-191&lt;br /&gt;
* load stress&lt;br /&gt;
* IP banning - need to be tested MOBILE-192&lt;br /&gt;
* Site broadcast (site keys) MOBILE-395&lt;br /&gt;
* DDOS attacks&lt;br /&gt;
* iOS support - it works&lt;br /&gt;
* Android support MOBILE-396&lt;br /&gt;
&lt;br /&gt;
=== App push notification support in the mobile app ===&lt;br /&gt;
MOBILE-168&lt;br /&gt;
&lt;br /&gt;
==== The app is closed or is running in the background ====&lt;br /&gt;
It&#039;s a normal OS notification. The user can open the app from it. We need to define what happens when we open the app. See payload content section.&lt;br /&gt;
&lt;br /&gt;
==== The app is opened ====&lt;br /&gt;
The app displays a notification popup. Or better, it&#039;s a small temporary notification message at the top that retarct by itself - less intrusive.&lt;br /&gt;
&lt;br /&gt;
== Risks ==&lt;br /&gt;
These risks have been raised by Dan in MDL-36445:&lt;br /&gt;
* Have we load tested it with large numbers of users/notifications?&lt;br /&gt;
* What is stopping someone downloading Moodle getting the keys to DOS our push notification service (surely blacklisting us with apple)&lt;br /&gt;
* If someone gains access to device ids, could they then use our service to spam users?&lt;br /&gt;
* Are we checking the &#039;feedback service&#039; and removing devices which no longer exist, apple ominously writes &amp;quot;Note: APNs monitors providers for their diligence in checking the feedback service and refraining from sending push notifications to nonexistent applications on devices.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
== Improvements ==&lt;br /&gt;
See Apu&#039;s comment in https://tracker.moodle.org/browse/MDL-36445?focusedCommentId=200343&amp;amp;page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-200343.&lt;br /&gt;
Specially the mention about language.&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_2.3_release_notes&amp;diff=33998</id>
		<title>Moodle 2.3 release notes</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_2.3_release_notes&amp;diff=33998"/>
		<updated>2012-05-24T08:01:24Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: /* Core API changes */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Releases]] &amp;gt; {{FULLPAGENAME}}&lt;br /&gt;
 &lt;br /&gt;
Release date: 18th June 2012  (expected)&lt;br /&gt;
 &lt;br /&gt;
Here is [http://tracker.moodle.org/secure/IssueNavigator!executeAdvanced.jspa?jqlQuery=project+%3D+mdl+AND+resolution+%3D+fixed+AND+fixVersion+in+%28%222.3%22%29+ORDER+BY+priority+DESC&amp;amp;runQuery=true&amp;amp;clear=true the full list of fixed issues in 2.3].&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
Many thanks to [http://moodle.org/dev/contributions.php?version=2.3.x everyone that worked on the new features in this release], particularly:&lt;br /&gt;
&lt;br /&gt;
* Everyone at [http://moodle.com/hq/team Moodle HQ]&lt;br /&gt;
* Tim Hunt and Sam Marshall from [http://www.open.ac.uk/ The Open University]&lt;br /&gt;
* Damyon and Raymond from [http://www.netspot.com.au/ NetSpot]&lt;br /&gt;
* Andrew and Ruslan from [http://www.luns.net.uk/ Lancaster University Network Services]&lt;br /&gt;
* Davo Smith from [http://www.synergy-learning.com/ Synergy Learning]&lt;br /&gt;
* Main web services contributors: Fabio Souto, Juan Leyva, Paul Charsley, Yang Yang&lt;br /&gt;
* ...&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Major new features===&lt;br /&gt;
&lt;br /&gt;
====Files usability====&lt;br /&gt;
(insert screenshots with realistic example files)&lt;br /&gt;
&lt;br /&gt;
* Nicer-looking File Picker with fewer clicks&lt;br /&gt;
* Images now display as true thumbnails in the file picker and file manager&lt;br /&gt;
* Other files have pretty icons for most file types &lt;br /&gt;
* Files view can be easily toggled between icons view, or a table view with sizes and dates, or a hierarchical list view.&lt;br /&gt;
* You can now drag and drop files directly from your desktop straight into file areas!&lt;br /&gt;
* File info (eg license information, sizes, dates) can be easily edited and viewed in a popup dialog&lt;br /&gt;
* Files can be created as &amp;quot;aliases/shortcuts&amp;quot; of other files.  This allows you to, for example, use a single file in your private files area multiple times in all your courses.  If you update the original file then all the aliases will automatically update!&lt;br /&gt;
* Aliases are easily identifiable in the file manager interface.&lt;br /&gt;
(MDL-31907)&lt;br /&gt;
&lt;br /&gt;
====Repository improvements====&lt;br /&gt;
&lt;br /&gt;
* MDL-28666 - If a repository supports it then it&#039;s possible to make an alias/shortcut to a file in an external repository.  If the file is updated in the repository, then this change is reflected in Moodle.  The fie remains under Moodle access control however, and the original URL is not usually revealed.   In the 2.3 core release, this is supported by Equella, Filesystem, user private, coursefiles and box.net repositories.&lt;br /&gt;
* The repository plugin is now able to take over the whole right-hand pane of the file picker and provide it&#039;s own searching/browsing interface.&lt;br /&gt;
* MDL-31675 - Server files repository now available for database, forum and glossary activities.&lt;br /&gt;
&lt;br /&gt;
====Improvements to editing course pages ====&lt;br /&gt;
&lt;br /&gt;
* MDL-31263 - Blocks drag and drop returns&lt;br /&gt;
* MDL-31052 - All AJAX editing on the course pages has been modernised and cleaned up.  It&#039;s on by default now too.&lt;br /&gt;
* MDL-32508 - Sections can now be displayed to all users as &amp;quot;one section per page&amp;quot; (via a new [https://docs.moodle.org/23/en/Course_settings course setting &#039;Course layout&#039;]) with full navigation.  Currently only supported by Weeks and Topics formats.  (AKA death to scroll-of-death)&lt;br /&gt;
* MDL-30617 - An optional new popup [https://docs.moodle.org/23/en/Course_homepage &amp;quot;Activity chooser&amp;quot;] has been added with full introduction, examples and links about each activity or resource module.&lt;br /&gt;
* MDL-22504 - You can now drag files straight into the course page and they will be added as resources.&lt;br /&gt;
* MDL-31215 - You can edit the name of any activity without having to enter the activity settings (works particularly well with drag and drop).&lt;br /&gt;
* MDL-31121 - You can now add/remove sections direct from the course page.&lt;br /&gt;
&lt;br /&gt;
====Assignment module====&lt;br /&gt;
&lt;br /&gt;
* MDL-26997 -Complete rewrite of the assignment module from scratch, by [http://www.netspot.com.au/ NetSpot] (Moodle Partner in Australia)&lt;br /&gt;
* Assignment subtypes no longer needed&lt;br /&gt;
* MDL-31731 - New &#039;marking guide&#039; advanced grading method, where a teacher enters a comment per criterion and a mark up to a maximum&lt;br /&gt;
&lt;br /&gt;
====Book module====&lt;br /&gt;
&lt;br /&gt;
* MDL-32709 - The most popular third-party resource module ever, [https://docs.moodle.org/23/en/Book_module Book], by our very own Petr Skoda, finally joins core.  Welcome!&lt;br /&gt;
&lt;br /&gt;
====Quiz module====&lt;br /&gt;
 &lt;br /&gt;
* MDL-3030 - More robust handling of quiz attempts that are not submitted by the deadline.&lt;br /&gt;
* MDL-3054 &amp;amp; MDL-11047 - There is now an option for teacher to force students to answer the quiz questions strictly in order. As part of this, the quiz remembers which page the student was last on, and will take them back there when they resume an attempt.&lt;br /&gt;
&lt;br /&gt;
====SCORM module====&lt;br /&gt;
* MDL-29745 - new graph report plugin&lt;br /&gt;
&lt;br /&gt;
====Workshop module====&lt;br /&gt;
&lt;br /&gt;
* MDL-26099 - Option to make the workshop switch to the assessment phase automatically after the submissions deadline (including automatic allocation of submissions for assessment)&lt;br /&gt;
* MDL-25660 - Workshop submission deadlines are shown in the calendar&lt;br /&gt;
* MDL-27508 - Improved support for pagination and filtering workshop submissions by group&lt;br /&gt;
* MDL-32638 - Workshop supports file browsing via Server files repository (including improved access control when serving submission files)&lt;br /&gt;
&lt;br /&gt;
====Available update notifications====&lt;br /&gt;
&lt;br /&gt;
MDL-20438 - Admins can check for any updates available for core code and for any contributed plugins installed on the site (from the [http://moodle.org/plugins plugins directory]) via buttons on the notifications and plugins overview pages.&lt;br /&gt;
&lt;br /&gt;
===Other highlights===&lt;br /&gt;
&lt;br /&gt;
* MDL-31121 - Option in [https://docs.moodle.org/23/en/File_module_settings file resource settings] to display file size and/or type on course page&lt;br /&gt;
* MDL-32009 - Admin option for uninstalling messaging outputs and report of messaging output statuses on plugins overview page&lt;br /&gt;
* MDL-29941 - Admin option to enable a CSS optimiser that analyses and refactors CSS before caching it.&lt;br /&gt;
&lt;br /&gt;
===Security issues===&lt;br /&gt;
 &lt;br /&gt;
All security issues that were fixed in 2.2.x and 2.1.x were also fixed in 2.3. &lt;br /&gt;
 &lt;br /&gt;
===For developers: API changes===&lt;br /&gt;
&lt;br /&gt;
====Core API changes====&lt;br /&gt;
* MDL-31902 All xxx_get_participants() functions are removed from core&lt;br /&gt;
* As a part of MDL-32471, the signature of send_stored_file() has been [http://git.moodle.org/gw?p=moodle.git;a=commitdiff;h=796495fed29f12e4a81bd406558d8eeffd0e64ac modified]. The last two parameters $filename and $dontdie were replaced with a single array containing additional options for the file serving. The pluginfile callbacks in plugins are supposed to transfer these options from the caller to send_stored_file() - see the note below.&lt;br /&gt;
* MDL-28666 Files API changes, added ability to create file reference using file_storage::create_file_from_reference() method, update file record attributes using stored_file class&lt;br /&gt;
* MDL-28666 Repository API changes, added new APIs repository::get_file_reference(), repository::get_file_by_reference(), repository::get_reference_details(), repository::send_file(), the new APIs make serving files from external repository possible&lt;br /&gt;
&lt;br /&gt;
====Plugin API changes====&lt;br /&gt;
&lt;br /&gt;
* As a part of MDL-32471, the API of the plugin function xyz_pluginfile() has been extended. There is a new array parameter passed to these callbacks containing additional options for the file serving. The array should be re-passed to send_stored_file(). The change is pretty trivial - see [http://git.moodle.org/gw?p=moodle.git;a=commitdiff;h=261cbbacc15ef1732a357d689908c91c15e0617a examples].&lt;br /&gt;
&lt;br /&gt;
====Webservice changes====&lt;br /&gt;
Few changes could break existing web service clients in 2.3 - untill this version we tried not to break anything. However these changes will make the client&#039;s developer life easier, so we prefered to do them now than later. Please take in consideration these improvements and retest your clients:&lt;br /&gt;
* [https://docs.moodle.org/dev/Errors_handling_in_web_services Error codes and Warnings]&lt;br /&gt;
* All text fields have an additional format field as parameter and return value (MDL-32581)&lt;br /&gt;
* Thanks to the increasing number of contributions, we improved our [https://docs.moodle.org/dev/How_to_contribute_a_web_service_function_to_core contributor web service guide]&lt;br /&gt;
* From 2.3, all web service functions integrated in master will land (when possible) in supported minor versions (e.g. 2.3.1, 2.3.2...).&lt;br /&gt;
* Many [http://tracker.moodle.org/browse/MDL-31253 fixes] and new [http://tracker.moodle.org/browse/MDL-29934 API functions].&lt;br /&gt;
&lt;br /&gt;
====Unit tests====&lt;br /&gt;
&lt;br /&gt;
We have switched completely to using [[PHPUnit]] for all our unit tests now.  All existing simpletests have been rewritten, and new tests have been added.  &lt;br /&gt;
&lt;br /&gt;
We intend to move towards a completely unit-test-driven development methodology (where the tests are written first!) for significant new code, and we also encourage all developers to implement unit tests covering at least the core features of their code.&lt;br /&gt;
&lt;br /&gt;
Moodle HQ run these tests on an automated basis for all new code submitted for integration, as well as on each weekly release.&lt;br /&gt;
&lt;br /&gt;
==== Community hub changes ====&lt;br /&gt;
Some bug fixes and improvements in [http://tracker.moodle.org/browse/MDL-30247  core] and in the [http://tracker.moodle.org/browse/CONTRIB-3348 plugin]. Hub administrators must update their hub to the most recent version regarding CONTRIB-3646. &lt;br /&gt;
 &lt;br /&gt;
&amp;lt;noinclude&amp;gt;==See also==&lt;br /&gt;
&lt;br /&gt;
* [https://docs.moodle.org/23/en/Category:New_features User documentation of new features in Moodle 2.3]&lt;br /&gt;
* [https://docs.moodle.org/23/en/Upgrading_to_Moodle_2.3 Upgrading to Moodle 2.3] - information for admins who are upgrading from earlier versions&lt;br /&gt;
*[[Moodle 2.2 release notes]]&lt;br /&gt;
 &lt;br /&gt;
[[Category:Release notes]]&lt;br /&gt;
[[Category:Moodle 2.3]]&lt;br /&gt;
&lt;br /&gt;
[[fr:Notes de mise à jour de Moodle 2.3]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Moodle_2.3_release_notes&amp;diff=33921</id>
		<title>Moodle 2.3 release notes</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Moodle_2.3_release_notes&amp;diff=33921"/>
		<updated>2012-05-21T03:46:47Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: /* Repository improvements */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Releases]] &amp;gt; {{FULLPAGENAME}}&lt;br /&gt;
 &lt;br /&gt;
Release date: 18th June 2012  (expected)&lt;br /&gt;
 &lt;br /&gt;
Here is [http://tracker.moodle.org/secure/IssueNavigator!executeAdvanced.jspa?jqlQuery=project+%3D+mdl+AND+resolution+%3D+fixed+AND+fixVersion+in+%28%222.3%22%29+ORDER+BY+priority+DESC&amp;amp;runQuery=true&amp;amp;clear=true the full list of fixed issues in 2.3].&lt;br /&gt;
 &lt;br /&gt;
&lt;br /&gt;
Many thanks to [http://moodle.org/dev/contributions.php?version=2.3.x everyone that worked on the new features in this release], particularly:&lt;br /&gt;
&lt;br /&gt;
* Everyone at [http://moodle.com/hq/team Moodle HQ]&lt;br /&gt;
* Tim Hunt and Sam Marshall from OU&lt;br /&gt;
* Damyon and Raymond from [http://www.netspot.com.au/ NetSpot]&lt;br /&gt;
* Andrew and Ruslan from [http://www.luns.net.uk/ Lancaster University Network Services]&lt;br /&gt;
* Davo Smith from [http://www.synergy-learning.com/ Synergy Learning]&lt;br /&gt;
* ...&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Major new features===&lt;br /&gt;
&lt;br /&gt;
====Files usability====&lt;br /&gt;
(insert screenshots with realistic example files)&lt;br /&gt;
&lt;br /&gt;
* Nicer-looking File Picker with fewer clicks&lt;br /&gt;
* Images now display as true thumbnails throughout Moodle&lt;br /&gt;
* Other files have pretty icons for most file types &lt;br /&gt;
* Files view can be easily toggled between icons view, or a table view with sizes and dates, or a hierarchical list view.&lt;br /&gt;
* You can now drag and drop files directly from your desktop straight into file areas!&lt;br /&gt;
* File info (eg license information, sizes, dates) can be easily edited and viewed in a popup dialog&lt;br /&gt;
* Files can be created as &amp;quot;aliases/shortcuts&amp;quot; of other files.  This allows you to, for example, use a single file in your private files area multiple times in all your courses.  If you update the original file then all the aliases will automatically update!&lt;br /&gt;
* Aliases are easily identifiable in the file manager interface.&lt;br /&gt;
(MDL-31907)&lt;br /&gt;
&lt;br /&gt;
====Repository improvements====&lt;br /&gt;
* If a repository supports it then it&#039;s possible to make an alias/shortcut to a file in an external repository.  If the file is updated in the repository, then this change is reflected in Moodle.  The fie remains under Moodle access control however, and the original URL is not usually revealed.   In the 2.3 core release, this is supported by Equella, Filesystem, user private, coursefiles, box.net repositories.&lt;br /&gt;
* The repository plugin is now able to take over the whole right-hand pane of the file picker and provide it&#039;s own searching/browsing interface.&lt;br /&gt;
(MDL-28666)&lt;br /&gt;
&lt;br /&gt;
====Improvements to editing course pages ====&lt;br /&gt;
* MDL-31263 - Blocks drag and drop returns&lt;br /&gt;
* MDL-31052 - All AJAX editing on the course pages has been modernised and cleaned up.  It&#039;s on by default now too.&lt;br /&gt;
* MDL-32508 - Sections can now be displayed to all users as &amp;quot;one section per page&amp;quot; (via a course setting) with full navigation.  Currently only supported by Weeks and Topics formats.  (AKA death to scroll-of-death)&lt;br /&gt;
* MDL-30617 - An optional new popup &amp;quot;Activity chooser&amp;quot; has been added with full introduction, examples and links about each activity or resource module.&lt;br /&gt;
* MDL-22504 - You can now drag files, links or even plain text straight into the course page and they will be added as resources.&lt;br /&gt;
* MDL-31215 - You can edit the name of any activity without having to enter the activity settings (works particularly well with drag and drop).&lt;br /&gt;
* MDL-31121 - You can now add/remove sections direct from the course page.&lt;br /&gt;
&lt;br /&gt;
====Book module====&lt;br /&gt;
* The most popular third-party resource module ever, &amp;quot;Book&amp;quot; by our very own Petr Skoda, finally joins core.  Welcome!&lt;br /&gt;
(MDL-32709)&lt;br /&gt;
&lt;br /&gt;
====Assignment module====&lt;br /&gt;
&lt;br /&gt;
* MDL-26997 -Complete rewrite of the assignment module from scratch, by [http://www.netspot.com.au/ NetSpot] (Moodle Partner in Australia)&lt;br /&gt;
* Assignment subtypes no longer needed&lt;br /&gt;
* MDL-31731 - New &#039;marking guide&#039; advanced grading method, where a teacher enters a comment per criterion and a mark up to a maximum&lt;br /&gt;
&lt;br /&gt;
====Quiz module====&lt;br /&gt;
 &lt;br /&gt;
* MDL-3030 - More robust handling of quiz attempts that are not submitted by the deadline.&lt;br /&gt;
* MDL-3054 &amp;amp; MDL-11047 - There is now an option for teacher to force students to answer the quiz questions strictly in order. As part of this, the quiz remembers which page the student was last on, and will take them back there when they resume an attempt.&lt;br /&gt;
&lt;br /&gt;
====Available update notifications====&lt;br /&gt;
&lt;br /&gt;
MDL-20438 - Admins can check for any updates available for core code and for any contributed plugins installed on the site (from the [http://moodle.org/plugins plugins directory]) via buttons on the notifications and plugins overview pages.&lt;br /&gt;
&lt;br /&gt;
===Other highlights===&lt;br /&gt;
&lt;br /&gt;
* MDL-26099 - Option to make the workshop switch to the assessment phase automatically after the submissions deadline (including automatic allocation of submissions for assessment)&lt;br /&gt;
* MDL-25660 - Workshop submission deadlines are shown in the calendar&lt;br /&gt;
* MDL-31121 - Option to display file size and/or type on course page for file resource&lt;br /&gt;
* MDL-32009 - Admin option for uninstalling messaging outputs and report of messaging output statuses on plugins overview page&lt;br /&gt;
&lt;br /&gt;
===Security issues===&lt;br /&gt;
 &lt;br /&gt;
All security issues that were fixed in 2.2.x and 2.1.x were also fixed in 2.3. &lt;br /&gt;
 &lt;br /&gt;
===For developers: API changes===&lt;br /&gt;
&lt;br /&gt;
====Core API changes====&lt;br /&gt;
* MDL-31902 All xxx_get_participants() functions are removed from core&lt;br /&gt;
&lt;br /&gt;
====Plugin API changes====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====Webservice changes====&lt;br /&gt;
Few changes could break existing web service clients in 2.3 - untill this version we tried not to break anything. However these changes will make the client&#039;s developer life easier, so we prefered to do them now than later. Please take in consideration these improvements and retest your clients:&lt;br /&gt;
* [https://docs.moodle.org/dev/Errors_handling_in_web_services Error codes and Warnings]&lt;br /&gt;
* All text fields have an additional format field as parameter and return value (MDL-32581)&lt;br /&gt;
* Thanks to the increasing number of contributions, we improved our [https://docs.moodle.org/dev/How_to_contribute_a_web_service_function_to_core contributor web service guide]&lt;br /&gt;
* From 2.3, all web service functions integrated in master will land (when possible) in supported minor versions (e.g. 2.3.1, 2.3.2...).&lt;br /&gt;
* Many [http://tracker.moodle.org/browse/MDL-31253 fixes] and new [http://tracker.moodle.org/browse/MDL-29934 API functions].&lt;br /&gt;
&lt;br /&gt;
====Unit tests====&lt;br /&gt;
&lt;br /&gt;
We have switched completely to using [[PHPUnit]] for all our unit tests now.  All existing simpletests have been rewritten, and new tests have been added.  &lt;br /&gt;
&lt;br /&gt;
We intend to move towards a completely unit-test-driven development methodology (where the tests are written first!) for significant new code, and we also encourage all developers to implement unit tests covering at least the core features of their code.&lt;br /&gt;
&lt;br /&gt;
Moodle HQ will shortly be running these tests on an automated basis for all new code submitted for integration, as well as on each weekly release.&lt;br /&gt;
&lt;br /&gt;
==== Community hub changes ====&lt;br /&gt;
Some bug fixes and improvements in [http://tracker.moodle.org/browse/MDL-30247  core] and in the [http://tracker.moodle.org/browse/CONTRIB-3348 plugin]. Hub administrators must update their hub to the most recent version regarding CONTRIB-3646. &lt;br /&gt;
 &lt;br /&gt;
&amp;lt;noinclude&amp;gt;==See also==&lt;br /&gt;
&lt;br /&gt;
* [https://docs.moodle.org/23/en/Category:New_features User documentation of new features in Moodle 2.3]&lt;br /&gt;
* [https://docs.moodle.org/23/en/Upgrading_to_Moodle_2.3 Upgrading to Moodle 2.3] - information for admins who are upgrading from earlier versions&lt;br /&gt;
*[[Moodle 2.2 release notes]]&lt;br /&gt;
 &lt;br /&gt;
[[Category:Release notes]]&lt;br /&gt;
[[Category:Moodle 2.3]]&lt;br /&gt;
&lt;br /&gt;
[[fr:Notes de mise à jour de Moodle 2.3]]&lt;br /&gt;
&amp;lt;/noinclude&amp;gt;&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=33734</id>
		<title>Improved support for external File content</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=33734"/>
		<updated>2012-05-10T10:47:14Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: /* Repository API changes */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Work in progress}}&lt;br /&gt;
{{Infobox Project&lt;br /&gt;
|name = File synching&lt;br /&gt;
|state = Planning&lt;br /&gt;
|tracker =  MDL-28666&lt;br /&gt;
|discussion = TODO&lt;br /&gt;
|assignee = Dongsheng Cai&lt;br /&gt;
}}&lt;br /&gt;
{{Moodle 2.3}}&lt;br /&gt;
&lt;br /&gt;
==Problems with Files in 2.0==&lt;br /&gt;
&lt;br /&gt;
* It is not currently easy to use a file in multiple places throughout Moodle and update them all at once&lt;br /&gt;
* It is not currently easy to create a simple shared &amp;quot;course repository&amp;quot; for teachers to use&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Example use cases that will become possible==&lt;br /&gt;
&lt;br /&gt;
A teacher wants to upload a file once and use it in multiple courses.  When they update the file, it should be updated in all their courses automatically.&lt;br /&gt;
&lt;br /&gt;
# The teacher uploads the file to their private file area (or other repository).&lt;br /&gt;
# In each place they want to add the file, they use the file picker to select the file from their private files area (or other repo) and select &amp;quot;link to latest version&amp;quot;.&lt;br /&gt;
# Replacing the file will automatically mean the linked copies use it.&lt;br /&gt;
# If teacher picked a file which is an alias already, moodle will copy the alias, not create an alias to alias.&lt;br /&gt;
&lt;br /&gt;
Several teachers want to create a shared repository of files together&lt;br /&gt;
# One teacher adds a course repository block to the course&lt;br /&gt;
# Using the block, the teacher creates an instance of a &amp;quot;?filesystem? repository&amp;quot; inside the course (essentially a folder).&lt;br /&gt;
# The content of the repository can be edited via the &amp;quot;Course repositories&amp;quot; block.&lt;br /&gt;
# All of the teachers in that course can now see that appear in their filepickers and select files &lt;br /&gt;
# Files can be &amp;quot;linked&amp;quot; as above&lt;br /&gt;
&lt;br /&gt;
A student wants to submit a linked file as an assignment, so they can continue updating it after the assignment due date.&lt;br /&gt;
# This will not be possible, because the assignment will not allow linking to files.&lt;br /&gt;
# The student is forced to upload a copy of a file and this is protected by the assignment module.&lt;br /&gt;
&lt;br /&gt;
==Solution summary==&lt;br /&gt;
&lt;br /&gt;
The basic idea is to allow the CONTENT of files to be stored outside of the filepool, while all the metadata and access is controlled by Moodle exactly the same as it is now.&lt;br /&gt;
&lt;br /&gt;
# Extend Files API with a new &amp;quot;reference&amp;quot; concept, with all files now having a &amp;quot;repositoryid&amp;quot; (the repository instance that the file came from) and a &amp;quot;reference&amp;quot; (the address in that repository of the content of the file).&lt;br /&gt;
# All files &#039;&#039;&#039;copied&#039;&#039;&#039; into Moodle don&#039;t have record in `files_reference` table, but others can specify a UUID (usually a URL with file-specific tokens in it, created by the original user who placed the file) in reference and repostiroyid columns.&lt;br /&gt;
# Improve the filesystem repository plugin to make it easier to create folder repositories on the fly, via a block.&lt;br /&gt;
# Add support to the filepicker UI to add &amp;quot;linking&amp;quot; to more repositories that support it, including the server files and filesystem repository.&lt;br /&gt;
# Virtual files are served via pluginfile.php URLs just like normal files:&lt;br /&gt;
## pluginfile.php uses module callback to determine access (as now), and if it passes then&lt;br /&gt;
## pluginfile.php calls a file logging subsystem to log the fact that this file is being served (useful for copyright reporting, for example)&lt;br /&gt;
## If the file is not &#039;local&#039;, then pluginfile.php uses the relevant repository callback to get the content of the file and streams it to the user with appropriate mimetypes etc&lt;br /&gt;
## The repositories have a way to cache this content in the normal Moodle filepool, to avoid repeated downloads.&lt;br /&gt;
## If an external repository is down or not configured, then the repository plugin can choose to just serve the local cached version (useful for restored backups and disaster tolerance)&lt;br /&gt;
# When a file is linked to another file in the local location, then it has a location of &amp;quot;filepool&amp;quot;.  We need to pay attention to these during:&lt;br /&gt;
## Garbage cleanups &lt;br /&gt;
## Update local caches&lt;br /&gt;
&lt;br /&gt;
Note that the original URL of files in external repositories are never revealed to users.&lt;br /&gt;
&lt;br /&gt;
==Details==&lt;br /&gt;
&lt;br /&gt;
=== File picker walk-through ===&lt;br /&gt;
[[File:File_picker_using_repository_reference_SD.png]]&lt;br /&gt;
# User clicks the &amp;quot;Insert image&amp;quot; button in TinyMCE, then launches our File Picker in the dialog&lt;br /&gt;
# User chooses a repository which supports UUID direct file references (eg Equella, Alfresco etc)&lt;br /&gt;
# A. File picker will ask repository plugin for customised repository UI if supported&lt;br /&gt;
# B. File Picker use repository UI and &amp;lt;object&amp;gt; type to display customised UI in file picker right pane&lt;br /&gt;
# User selects a file from the repository, if is customised UI, file picker Javascript API will be used to trigger file selection event (with all file related information), the repository plugin gives you two choices in the file picker interface  http://tracker.moodle.org/secure/attachment/25823/2011-11-09+15.23.png http://tracker.moodle.org/secure/attachment/25825/File+picker+options.png&lt;br /&gt;
#* C. Use the current version: this will copy the file to Moodle (as now).  No need to reference external content.&lt;br /&gt;
#* C. Use the latest version: this will use a file reference, so that the most recent version is always pulled from the repository&lt;br /&gt;
# D. If &amp;quot;Use the latest version&amp;quot; selected, Repository API will create a file reference, if selected file is already an alias, repository API will copy this alias, not create an alias to alias, repository plugin can cache external files in moodle local directory, but this is optional&lt;br /&gt;
# E. Repository API ask File API to create a file with file parameters and reference.  File API stores the reference and repository instance id in `mdl_files_reference` table and returns the file URL which looks the same as any other Moodle file URL.&lt;br /&gt;
# File picker gives the file URL to TinyMCE&lt;br /&gt;
# When TinyMCE displays the resource, it will cause the browser to call the file URL, which contains pluginfile.php.&lt;br /&gt;
# Pluginfile.php uses File API to send file contents to browsers, if File API detects the requested file is not ordinary moodle files which is located at external repository, File API will ask Repository API for file content, Repository API will firstly look for the cached file, if file is too old or not found (removed  by cron checking), Repository API will fetch the resource (and cache it), then return to File API. Alternatively, Repository API could disable caching, asking for fresh content all the time.&lt;br /&gt;
&lt;br /&gt;
=== File request walk-through ===&lt;br /&gt;
[[File:File_Request_SD.png]]&lt;br /&gt;
&lt;br /&gt;
# A. User request a file&lt;br /&gt;
# B. File API detects if the request file is regular moodle files or located at external repository, if it&#039;s external files, File API will ask repository API to grab the file&lt;br /&gt;
# C. File API collects the file reference information from database, it could be stored in php serialised or JSON format&lt;br /&gt;
# D. File API passes raw file reference information to repository plugin&lt;br /&gt;
# E. Repository plugin will firstly check if file available locally&lt;br /&gt;
# F. If not repository plugin will use file reference information to grab the file&lt;br /&gt;
# G. Repository API returns file content to File API to serve the file&lt;br /&gt;
&lt;br /&gt;
=== Database changes ===&lt;br /&gt;
&lt;br /&gt;
We can create another table for left joining, this requires File API query this table when locating files:&lt;br /&gt;
* New file table `mdl_files_reference`&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `fileid` foreign key of `mdl_files`.`id`&lt;br /&gt;
** `repositoryid`&lt;br /&gt;
** `reference` - can be URL, UUID or other data format, repository plugin callbacks know the meaning of this field. File reference should be cached when adding to moodle, contenthash should be accurate.&lt;br /&gt;
&lt;br /&gt;
* `mdl_files_log` table for files access log, File API should have a new function to insert records to this table&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `userid` (0 if it&#039;s guest), &lt;br /&gt;
** `timeaccess`, &lt;br /&gt;
** `fileid`&lt;br /&gt;
&lt;br /&gt;
=== File API changes ===&lt;br /&gt;
* pluginfile.php and draftfile.php (it&#039;s actually send_stored_file()): when file isn&#039;t local file (`repositoryid` isn&#039;t zero), stored_file instance should ask repository plugin‘s send the file contents, Repository API decide return the cached copy or fresh contents&lt;br /&gt;
* Preserve file reference information when call file_storage::create_file_from_storedfile&lt;br /&gt;
* file_storage::get_area_files should retrieve file reference information, also file_storage::get_file_by_id, file_storage::get_file_by_hash&lt;br /&gt;
* When call file_prepare_draft_area(), it should keep files&#039; reference field, we also need to the original file information when creating draft file copies, it will be used by filemanager element to look up references to original file, to make this happen, we have to create a temp file only known by filemanager: mdl_files_draft_info, it has following fields:&lt;br /&gt;
** id&lt;br /&gt;
** draftfileid&lt;br /&gt;
** name&lt;br /&gt;
** value&lt;br /&gt;
So when filemanager wants to decide whether or not the draft file&#039;s original file has references, so firstly, filemanager looks up this table by providing draftfileid and name=&amp;quot;originalfileparams&amp;quot;, the value field will be the stored_file params, the filemanager will be able to ask Files API to provide all references. In this way, we don&#039;t have to inject Files API to know more information, it&#039;s only the duty of filemanager to get the original file.&lt;br /&gt;
&lt;br /&gt;
* file_save_draft_files should preserve file reference information&lt;br /&gt;
* new method: file_storage::create_file_from_reference&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function create_file_from_reference($file_record, $repoisitoryid, $reference, array $options = NULL)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
* Cron: we probably need a Repository API function to cache/update external files&lt;br /&gt;
* stored_file class&lt;br /&gt;
** set_author()&lt;br /&gt;
** set_license()&lt;br /&gt;
** replace_content_with(stored_file $storedfile)&lt;br /&gt;
** rename($filepath, $filename): rename files&lt;br /&gt;
** delete_references(): delete all reference information&lt;br /&gt;
** get_reference_details($ref): Get human readable reference information&lt;br /&gt;
&lt;br /&gt;
=== Repository API changes ===&lt;br /&gt;
&lt;br /&gt;
* Additions to FILE_INTERNAL and FILE_EXTERNAL, we need another type FILE_REFERENCE = 4 // 0100, repository plugin needs to declare what types of files are supported&lt;br /&gt;
&lt;br /&gt;
* When users ask to create a reference (instead of copying) in file picker, Repository API try to cache the file in filepool(special file area for repository), after file downloaded, repository API should ask Files API to create a virtual file in `mdl_files` table, the existing fields stay the same, extra file reference information is stored in `mdl_files_reference` table, link or other format that repository plugins know. File API return the stored_file object, repository API generate the moodle url for users just like other moodle files without revealing the internal reference&lt;br /&gt;
&lt;br /&gt;
* Making repository plugin upgrade and versioning possible, repository plugin may need to update `reference` in `mdl_files_reference` table if reference info changed. (we already have db/upgrade.php, make sure all new plugins have one)&lt;br /&gt;
&lt;br /&gt;
* Cron will use repository plugin callbacks to clean up cache files, repoistory::cron($repositoryid)&lt;br /&gt;
&lt;br /&gt;
* public function send_file($storedfile, $lifetime=86400 , $filter=0, $forcedownload=false, array $options = null): Serve repository files&lt;br /&gt;
&lt;br /&gt;
* repository::get_file_reference($str): Create the file reference&lt;br /&gt;
&lt;br /&gt;
* repository::get_file_by_reference($ref): Get an external file by providing reference, $ref is the file record in files_reference table, it has reference, lastsync and lifetime fields, repository plugin could decide what to do based on this information, the return value will be an object:&lt;br /&gt;
** $fileinfo-&amp;gt;handle, returns a file handler&lt;br /&gt;
** $fileinfo-&amp;gt;contenthash, this returns an existing moodle file&lt;br /&gt;
** $fileinfo-&amp;gt;content, returns file content&lt;br /&gt;
** $fileinfo-&amp;gt;filepath, returns a file path&lt;br /&gt;
Repository API will handle different types of return automatically.&lt;br /&gt;
&lt;br /&gt;
* repository::sync_individual_file(stored_file $file): Decide whether or not the stored_file instance should be synced&lt;br /&gt;
&lt;br /&gt;
* repository::get_reference_details($ref): Convert the reference info to human readable format&lt;br /&gt;
&lt;br /&gt;
=== Repository plugins changes ===&lt;br /&gt;
* Server files: store file parameters in `reference` field&lt;br /&gt;
* Private files: same as server files plugin&lt;br /&gt;
* Alfresco: Store UUID in `reference` field, but alfresco will change UUID once tomcat restart, may need other information to locate files&lt;br /&gt;
* Flickr private: needs flickr secret and token and photo id to locate the files&lt;br /&gt;
* File system: store file path in `reference` field&lt;br /&gt;
* s3: store file path, s3 repository will use secret and token to fetch the file from s3 no matter files are public or not.&lt;br /&gt;
* EQUELLA&lt;br /&gt;
*# Admin installs EQUELLA and setup parameters for single sign on&lt;br /&gt;
*# Teacher clicks EQUELLA instance, EQUELLA will return an URL of repository UI (plugin code)&lt;br /&gt;
*# Teacher pick a file from EQUELLA, EQUELLA repository UI will revoke file picker JavaScript API to notify moodle download this resource (plugin code)&lt;br /&gt;
*# Repository API stores file UUID and SSO userid, creates file reference in moodle file pool&lt;br /&gt;
*# EQUELLA plugin implements method to download contents using stored UUID and SSO userid&lt;br /&gt;
*# EQUELLA plugin implements method to update/invalidate cached resources (by cron)&lt;br /&gt;
&lt;br /&gt;
=== Content caching ===&lt;br /&gt;
* Create moodledata/repostory/cache directory&lt;br /&gt;
* Generate hash based on file URL and request parameters (provided by repository plugin), not content hash because we cannot send content hash to external repository for file information&lt;br /&gt;
* Cached files are stored using hash code as file name&lt;br /&gt;
&lt;br /&gt;
==== class repository_cache ====&lt;br /&gt;
&lt;br /&gt;
Returned stored_file instance or file path&lt;br /&gt;
&lt;br /&gt;
* store($url, $string_to_be_hashed)&lt;br /&gt;
* get($string_to_be_hashed)&lt;br /&gt;
&lt;br /&gt;
=== Filepicker Javascript API for customizing===&lt;br /&gt;
* File picker should be able to dislable file references by taking an option&lt;br /&gt;
* Support &amp;lt;object&amp;gt; tag in filepicker container&lt;br /&gt;
* Provide Javascript API to allow plugin communicate with filepicker&lt;br /&gt;
** Notify file picker to download file &lt;br /&gt;
** Notify file picker to pop up authentication page&lt;br /&gt;
&lt;br /&gt;
=== File manager to handle virtual files===&lt;br /&gt;
Not much trouble here, need to make sure draftfile.php can serve external resources, because all files managed by file manager is in draft area&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Project]]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=33732</id>
		<title>Improved support for external File content</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=33732"/>
		<updated>2012-05-10T10:30:38Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: /* File API changes */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Work in progress}}&lt;br /&gt;
{{Infobox Project&lt;br /&gt;
|name = File synching&lt;br /&gt;
|state = Planning&lt;br /&gt;
|tracker =  MDL-28666&lt;br /&gt;
|discussion = TODO&lt;br /&gt;
|assignee = Dongsheng Cai&lt;br /&gt;
}}&lt;br /&gt;
{{Moodle 2.3}}&lt;br /&gt;
&lt;br /&gt;
==Problems with Files in 2.0==&lt;br /&gt;
&lt;br /&gt;
* It is not currently easy to use a file in multiple places throughout Moodle and update them all at once&lt;br /&gt;
* It is not currently easy to create a simple shared &amp;quot;course repository&amp;quot; for teachers to use&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Example use cases that will become possible==&lt;br /&gt;
&lt;br /&gt;
A teacher wants to upload a file once and use it in multiple courses.  When they update the file, it should be updated in all their courses automatically.&lt;br /&gt;
&lt;br /&gt;
# The teacher uploads the file to their private file area (or other repository).&lt;br /&gt;
# In each place they want to add the file, they use the file picker to select the file from their private files area (or other repo) and select &amp;quot;link to latest version&amp;quot;.&lt;br /&gt;
# Replacing the file will automatically mean the linked copies use it.&lt;br /&gt;
# If teacher picked a file which is an alias already, moodle will copy the alias, not create an alias to alias.&lt;br /&gt;
&lt;br /&gt;
Several teachers want to create a shared repository of files together&lt;br /&gt;
# One teacher adds a course repository block to the course&lt;br /&gt;
# Using the block, the teacher creates an instance of a &amp;quot;?filesystem? repository&amp;quot; inside the course (essentially a folder).&lt;br /&gt;
# The content of the repository can be edited via the &amp;quot;Course repositories&amp;quot; block.&lt;br /&gt;
# All of the teachers in that course can now see that appear in their filepickers and select files &lt;br /&gt;
# Files can be &amp;quot;linked&amp;quot; as above&lt;br /&gt;
&lt;br /&gt;
A student wants to submit a linked file as an assignment, so they can continue updating it after the assignment due date.&lt;br /&gt;
# This will not be possible, because the assignment will not allow linking to files.&lt;br /&gt;
# The student is forced to upload a copy of a file and this is protected by the assignment module.&lt;br /&gt;
&lt;br /&gt;
==Solution summary==&lt;br /&gt;
&lt;br /&gt;
The basic idea is to allow the CONTENT of files to be stored outside of the filepool, while all the metadata and access is controlled by Moodle exactly the same as it is now.&lt;br /&gt;
&lt;br /&gt;
# Extend Files API with a new &amp;quot;reference&amp;quot; concept, with all files now having a &amp;quot;repositoryid&amp;quot; (the repository instance that the file came from) and a &amp;quot;reference&amp;quot; (the address in that repository of the content of the file).&lt;br /&gt;
# All files &#039;&#039;&#039;copied&#039;&#039;&#039; into Moodle don&#039;t have record in `files_reference` table, but others can specify a UUID (usually a URL with file-specific tokens in it, created by the original user who placed the file) in reference and repostiroyid columns.&lt;br /&gt;
# Improve the filesystem repository plugin to make it easier to create folder repositories on the fly, via a block.&lt;br /&gt;
# Add support to the filepicker UI to add &amp;quot;linking&amp;quot; to more repositories that support it, including the server files and filesystem repository.&lt;br /&gt;
# Virtual files are served via pluginfile.php URLs just like normal files:&lt;br /&gt;
## pluginfile.php uses module callback to determine access (as now), and if it passes then&lt;br /&gt;
## pluginfile.php calls a file logging subsystem to log the fact that this file is being served (useful for copyright reporting, for example)&lt;br /&gt;
## If the file is not &#039;local&#039;, then pluginfile.php uses the relevant repository callback to get the content of the file and streams it to the user with appropriate mimetypes etc&lt;br /&gt;
## The repositories have a way to cache this content in the normal Moodle filepool, to avoid repeated downloads.&lt;br /&gt;
## If an external repository is down or not configured, then the repository plugin can choose to just serve the local cached version (useful for restored backups and disaster tolerance)&lt;br /&gt;
# When a file is linked to another file in the local location, then it has a location of &amp;quot;filepool&amp;quot;.  We need to pay attention to these during:&lt;br /&gt;
## Garbage cleanups &lt;br /&gt;
## Update local caches&lt;br /&gt;
&lt;br /&gt;
Note that the original URL of files in external repositories are never revealed to users.&lt;br /&gt;
&lt;br /&gt;
==Details==&lt;br /&gt;
&lt;br /&gt;
=== File picker walk-through ===&lt;br /&gt;
[[File:File_picker_using_repository_reference_SD.png]]&lt;br /&gt;
# User clicks the &amp;quot;Insert image&amp;quot; button in TinyMCE, then launches our File Picker in the dialog&lt;br /&gt;
# User chooses a repository which supports UUID direct file references (eg Equella, Alfresco etc)&lt;br /&gt;
# A. File picker will ask repository plugin for customised repository UI if supported&lt;br /&gt;
# B. File Picker use repository UI and &amp;lt;object&amp;gt; type to display customised UI in file picker right pane&lt;br /&gt;
# User selects a file from the repository, if is customised UI, file picker Javascript API will be used to trigger file selection event (with all file related information), the repository plugin gives you two choices in the file picker interface  http://tracker.moodle.org/secure/attachment/25823/2011-11-09+15.23.png http://tracker.moodle.org/secure/attachment/25825/File+picker+options.png&lt;br /&gt;
#* C. Use the current version: this will copy the file to Moodle (as now).  No need to reference external content.&lt;br /&gt;
#* C. Use the latest version: this will use a file reference, so that the most recent version is always pulled from the repository&lt;br /&gt;
# D. If &amp;quot;Use the latest version&amp;quot; selected, Repository API will create a file reference, if selected file is already an alias, repository API will copy this alias, not create an alias to alias, repository plugin can cache external files in moodle local directory, but this is optional&lt;br /&gt;
# E. Repository API ask File API to create a file with file parameters and reference.  File API stores the reference and repository instance id in `mdl_files_reference` table and returns the file URL which looks the same as any other Moodle file URL.&lt;br /&gt;
# File picker gives the file URL to TinyMCE&lt;br /&gt;
# When TinyMCE displays the resource, it will cause the browser to call the file URL, which contains pluginfile.php.&lt;br /&gt;
# Pluginfile.php uses File API to send file contents to browsers, if File API detects the requested file is not ordinary moodle files which is located at external repository, File API will ask Repository API for file content, Repository API will firstly look for the cached file, if file is too old or not found (removed  by cron checking), Repository API will fetch the resource (and cache it), then return to File API. Alternatively, Repository API could disable caching, asking for fresh content all the time.&lt;br /&gt;
&lt;br /&gt;
=== File request walk-through ===&lt;br /&gt;
[[File:File_Request_SD.png]]&lt;br /&gt;
&lt;br /&gt;
# A. User request a file&lt;br /&gt;
# B. File API detects if the request file is regular moodle files or located at external repository, if it&#039;s external files, File API will ask repository API to grab the file&lt;br /&gt;
# C. File API collects the file reference information from database, it could be stored in php serialised or JSON format&lt;br /&gt;
# D. File API passes raw file reference information to repository plugin&lt;br /&gt;
# E. Repository plugin will firstly check if file available locally&lt;br /&gt;
# F. If not repository plugin will use file reference information to grab the file&lt;br /&gt;
# G. Repository API returns file content to File API to serve the file&lt;br /&gt;
&lt;br /&gt;
=== Database changes ===&lt;br /&gt;
&lt;br /&gt;
We can create another table for left joining, this requires File API query this table when locating files:&lt;br /&gt;
* New file table `mdl_files_reference`&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `fileid` foreign key of `mdl_files`.`id`&lt;br /&gt;
** `repositoryid`&lt;br /&gt;
** `reference` - can be URL, UUID or other data format, repository plugin callbacks know the meaning of this field. File reference should be cached when adding to moodle, contenthash should be accurate.&lt;br /&gt;
&lt;br /&gt;
* `mdl_files_log` table for files access log, File API should have a new function to insert records to this table&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `userid` (0 if it&#039;s guest), &lt;br /&gt;
** `timeaccess`, &lt;br /&gt;
** `fileid`&lt;br /&gt;
&lt;br /&gt;
=== File API changes ===&lt;br /&gt;
* pluginfile.php and draftfile.php (it&#039;s actually send_stored_file()): when file isn&#039;t local file (`repositoryid` isn&#039;t zero), stored_file instance should ask repository plugin‘s send the file contents, Repository API decide return the cached copy or fresh contents&lt;br /&gt;
* Preserve file reference information when call file_storage::create_file_from_storedfile&lt;br /&gt;
* file_storage::get_area_files should retrieve file reference information, also file_storage::get_file_by_id, file_storage::get_file_by_hash&lt;br /&gt;
* When call file_prepare_draft_area(), it should keep files&#039; reference field, we also need to the original file information when creating draft file copies, it will be used by filemanager element to look up references to original file, to make this happen, we have to create a temp file only known by filemanager: mdl_files_draft_info, it has following fields:&lt;br /&gt;
** id&lt;br /&gt;
** draftfileid&lt;br /&gt;
** name&lt;br /&gt;
** value&lt;br /&gt;
So when filemanager wants to decide whether or not the draft file&#039;s original file has references, so firstly, filemanager looks up this table by providing draftfileid and name=&amp;quot;originalfileparams&amp;quot;, the value field will be the stored_file params, the filemanager will be able to ask Files API to provide all references. In this way, we don&#039;t have to inject Files API to know more information, it&#039;s only the duty of filemanager to get the original file.&lt;br /&gt;
&lt;br /&gt;
* file_save_draft_files should preserve file reference information&lt;br /&gt;
* new method: file_storage::create_file_from_reference&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function create_file_from_reference($file_record, $repoisitoryid, $reference, array $options = NULL)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
* Cron: we probably need a Repository API function to cache/update external files&lt;br /&gt;
* stored_file class&lt;br /&gt;
** set_author()&lt;br /&gt;
** set_license()&lt;br /&gt;
** replace_content_with(stored_file $storedfile)&lt;br /&gt;
** rename($filepath, $filename): rename files&lt;br /&gt;
** delete_references(): delete all reference information&lt;br /&gt;
** get_reference_details($ref): Get human readable reference information&lt;br /&gt;
&lt;br /&gt;
=== Repository API changes ===&lt;br /&gt;
&lt;br /&gt;
* Additions to FILE_INTERNAL and FILE_EXTERNAL, we need another type FILE_REFERENCE = 4 // 0100, repository plugin needs to declare what types of files are supported&lt;br /&gt;
&lt;br /&gt;
* When users ask to create a reference (instead of copying) in file picker, Repository API try to cache the file in filepool(special file area for repository), after file downloaded, repository API should ask Files API to create a virtual file in `mdl_files` table, the existing fields stay the same, extra file reference information is stored in `mdl_files_reference` table, link or other format that repository plugins know. File API return the stored_file object, repository API generate the moodle url for users just like other moodle files without revealing the internal reference&lt;br /&gt;
&lt;br /&gt;
* Making repository plugin upgrade and versioning possible, repository plugin may need to update `reference` in `mdl_files_reference` table if reference info changed. (we already have db/upgrade.php, make sure all new plugins have one)&lt;br /&gt;
&lt;br /&gt;
* Cron will use repository plugin callbacks to clean up cache files, repoistory::cron($repositoryid)&lt;br /&gt;
&lt;br /&gt;
* repository::send_file(stored_file $stored_file): Serve repository files&lt;br /&gt;
&lt;br /&gt;
* repository::get_file_reference($str): Get a file reference&lt;br /&gt;
&lt;br /&gt;
* repository::get_file_by_reference($ref): Get an external file by providing reference &lt;br /&gt;
&lt;br /&gt;
* repository::sync_individual_file(stored_file $file): Decide whether or not the stored_file instance should be synced&lt;br /&gt;
&lt;br /&gt;
* repository::get_reference_details($ref): Convert the reference info to human readable format&lt;br /&gt;
&lt;br /&gt;
=== Repository plugins changes ===&lt;br /&gt;
* Server files: store file parameters in `reference` field&lt;br /&gt;
* Private files: same as server files plugin&lt;br /&gt;
* Alfresco: Store UUID in `reference` field, but alfresco will change UUID once tomcat restart, may need other information to locate files&lt;br /&gt;
* Flickr private: needs flickr secret and token and photo id to locate the files&lt;br /&gt;
* File system: store file path in `reference` field&lt;br /&gt;
* s3: store file path, s3 repository will use secret and token to fetch the file from s3 no matter files are public or not.&lt;br /&gt;
* EQUELLA&lt;br /&gt;
*# Admin installs EQUELLA and setup parameters for single sign on&lt;br /&gt;
*# Teacher clicks EQUELLA instance, EQUELLA will return an URL of repository UI (plugin code)&lt;br /&gt;
*# Teacher pick a file from EQUELLA, EQUELLA repository UI will revoke file picker JavaScript API to notify moodle download this resource (plugin code)&lt;br /&gt;
*# Repository API stores file UUID and SSO userid, creates file reference in moodle file pool&lt;br /&gt;
*# EQUELLA plugin implements method to download contents using stored UUID and SSO userid&lt;br /&gt;
*# EQUELLA plugin implements method to update/invalidate cached resources (by cron)&lt;br /&gt;
&lt;br /&gt;
=== Content caching ===&lt;br /&gt;
* Create moodledata/repostory/cache directory&lt;br /&gt;
* Generate hash based on file URL and request parameters (provided by repository plugin), not content hash because we cannot send content hash to external repository for file information&lt;br /&gt;
* Cached files are stored using hash code as file name&lt;br /&gt;
&lt;br /&gt;
==== class repository_cache ====&lt;br /&gt;
&lt;br /&gt;
Returned stored_file instance or file path&lt;br /&gt;
&lt;br /&gt;
* store($url, $string_to_be_hashed)&lt;br /&gt;
* get($string_to_be_hashed)&lt;br /&gt;
&lt;br /&gt;
=== Filepicker Javascript API for customizing===&lt;br /&gt;
* File picker should be able to dislable file references by taking an option&lt;br /&gt;
* Support &amp;lt;object&amp;gt; tag in filepicker container&lt;br /&gt;
* Provide Javascript API to allow plugin communicate with filepicker&lt;br /&gt;
** Notify file picker to download file &lt;br /&gt;
** Notify file picker to pop up authentication page&lt;br /&gt;
&lt;br /&gt;
=== File manager to handle virtual files===&lt;br /&gt;
Not much trouble here, need to make sure draftfile.php can serve external resources, because all files managed by file manager is in draft area&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Project]]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=33694</id>
		<title>Improved support for external File content</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=33694"/>
		<updated>2012-05-08T08:21:18Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: /* File API changes */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Work in progress}}&lt;br /&gt;
{{Infobox Project&lt;br /&gt;
|name = File synching&lt;br /&gt;
|state = Planning&lt;br /&gt;
|tracker =  MDL-28666&lt;br /&gt;
|discussion = TODO&lt;br /&gt;
|assignee = Dongsheng Cai&lt;br /&gt;
}}&lt;br /&gt;
{{Moodle 2.3}}&lt;br /&gt;
&lt;br /&gt;
==Problems with Files in 2.0==&lt;br /&gt;
&lt;br /&gt;
* It is not currently easy to use a file in multiple places throughout Moodle and update them all at once&lt;br /&gt;
* It is not currently easy to create a simple shared &amp;quot;course repository&amp;quot; for teachers to use&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Example use cases that will become possible==&lt;br /&gt;
&lt;br /&gt;
A teacher wants to upload a file once and use it in multiple courses.  When they update the file, it should be updated in all their courses automatically.&lt;br /&gt;
&lt;br /&gt;
# The teacher uploads the file to their private file area (or other repository).&lt;br /&gt;
# In each place they want to add the file, they use the file picker to select the file from their private files area (or other repo) and select &amp;quot;link to latest version&amp;quot;.&lt;br /&gt;
# Replacing the file will automatically mean the linked copies use it.&lt;br /&gt;
# If teacher picked a file which is an alias already, moodle will copy the alias, not create an alias to alias.&lt;br /&gt;
&lt;br /&gt;
Several teachers want to create a shared repository of files together&lt;br /&gt;
# One teacher adds a course repository block to the course&lt;br /&gt;
# Using the block, the teacher creates an instance of a &amp;quot;?filesystem? repository&amp;quot; inside the course (essentially a folder).&lt;br /&gt;
# The content of the repository can be edited via the &amp;quot;Course repositories&amp;quot; block.&lt;br /&gt;
# All of the teachers in that course can now see that appear in their filepickers and select files &lt;br /&gt;
# Files can be &amp;quot;linked&amp;quot; as above&lt;br /&gt;
&lt;br /&gt;
A student wants to submit a linked file as an assignment, so they can continue updating it after the assignment due date.&lt;br /&gt;
# This will not be possible, because the assignment will not allow linking to files.&lt;br /&gt;
# The student is forced to upload a copy of a file and this is protected by the assignment module.&lt;br /&gt;
&lt;br /&gt;
==Solution summary==&lt;br /&gt;
&lt;br /&gt;
The basic idea is to allow the CONTENT of files to be stored outside of the filepool, while all the metadata and access is controlled by Moodle exactly the same as it is now.&lt;br /&gt;
&lt;br /&gt;
# Extend Files API with a new &amp;quot;reference&amp;quot; concept, with all files now having a &amp;quot;repositoryid&amp;quot; (the repository instance that the file came from) and a &amp;quot;reference&amp;quot; (the address in that repository of the content of the file).&lt;br /&gt;
# All files &#039;&#039;&#039;copied&#039;&#039;&#039; into Moodle don&#039;t have record in `files_reference` table, but others can specify a UUID (usually a URL with file-specific tokens in it, created by the original user who placed the file) in reference and repostiroyid columns.&lt;br /&gt;
# Improve the filesystem repository plugin to make it easier to create folder repositories on the fly, via a block.&lt;br /&gt;
# Add support to the filepicker UI to add &amp;quot;linking&amp;quot; to more repositories that support it, including the server files and filesystem repository.&lt;br /&gt;
# Virtual files are served via pluginfile.php URLs just like normal files:&lt;br /&gt;
## pluginfile.php uses module callback to determine access (as now), and if it passes then&lt;br /&gt;
## pluginfile.php calls a file logging subsystem to log the fact that this file is being served (useful for copyright reporting, for example)&lt;br /&gt;
## If the file is not &#039;local&#039;, then pluginfile.php uses the relevant repository callback to get the content of the file and streams it to the user with appropriate mimetypes etc&lt;br /&gt;
## The repositories have a way to cache this content in the normal Moodle filepool, to avoid repeated downloads.&lt;br /&gt;
## If an external repository is down or not configured, then the repository plugin can choose to just serve the local cached version (useful for restored backups and disaster tolerance)&lt;br /&gt;
# When a file is linked to another file in the local location, then it has a location of &amp;quot;filepool&amp;quot;.  We need to pay attention to these during:&lt;br /&gt;
## Garbage cleanups &lt;br /&gt;
## Update local caches&lt;br /&gt;
&lt;br /&gt;
Note that the original URL of files in external repositories are never revealed to users.&lt;br /&gt;
&lt;br /&gt;
==Details==&lt;br /&gt;
&lt;br /&gt;
=== File picker walk-through ===&lt;br /&gt;
[[File:File_picker_using_repository_reference_SD.png]]&lt;br /&gt;
# User clicks the &amp;quot;Insert image&amp;quot; button in TinyMCE, then launches our File Picker in the dialog&lt;br /&gt;
# User chooses a repository which supports UUID direct file references (eg Equella, Alfresco etc)&lt;br /&gt;
# A. File picker will ask repository plugin for customised repository UI if supported&lt;br /&gt;
# B. File Picker use repository UI and &amp;lt;object&amp;gt; type to display customised UI in file picker right pane&lt;br /&gt;
# User selects a file from the repository, if is customised UI, file picker Javascript API will be used to trigger file selection event (with all file related information), the repository plugin gives you two choices in the file picker interface  http://tracker.moodle.org/secure/attachment/25823/2011-11-09+15.23.png http://tracker.moodle.org/secure/attachment/25825/File+picker+options.png&lt;br /&gt;
#* C. Use the current version: this will copy the file to Moodle (as now).  No need to reference external content.&lt;br /&gt;
#* C. Use the latest version: this will use a file reference, so that the most recent version is always pulled from the repository&lt;br /&gt;
# D. If &amp;quot;Use the latest version&amp;quot; selected, Repository API will create a file reference, if selected file is already an alias, repository API will copy this alias, not create an alias to alias, repository plugin can cache external files in moodle local directory, but this is optional&lt;br /&gt;
# E. Repository API ask File API to create a file with file parameters and reference.  File API stores the reference and repository instance id in `mdl_files_reference` table and returns the file URL which looks the same as any other Moodle file URL.&lt;br /&gt;
# File picker gives the file URL to TinyMCE&lt;br /&gt;
# When TinyMCE displays the resource, it will cause the browser to call the file URL, which contains pluginfile.php.&lt;br /&gt;
# Pluginfile.php uses File API to send file contents to browsers, if File API detects the requested file is not ordinary moodle files which is located at external repository, File API will ask Repository API for file content, Repository API will firstly look for the cached file, if file is too old or not found (removed  by cron checking), Repository API will fetch the resource (and cache it), then return to File API. Alternatively, Repository API could disable caching, asking for fresh content all the time.&lt;br /&gt;
&lt;br /&gt;
=== File request walk-through ===&lt;br /&gt;
[[File:File_Request_SD.png]]&lt;br /&gt;
&lt;br /&gt;
# A. User request a file&lt;br /&gt;
# B. File API detects if the request file is regular moodle files or located at external repository, if it&#039;s external files, File API will ask repository API to grab the file&lt;br /&gt;
# C. File API collects the file reference information from database, it could be stored in php serialised or JSON format&lt;br /&gt;
# D. File API passes raw file reference information to repository plugin&lt;br /&gt;
# E. Repository plugin will firstly check if file available locally&lt;br /&gt;
# F. If not repository plugin will use file reference information to grab the file&lt;br /&gt;
# G. Repository API returns file content to File API to serve the file&lt;br /&gt;
&lt;br /&gt;
=== Database changes ===&lt;br /&gt;
&lt;br /&gt;
We can create another table for left joining, this requires File API query this table when locating files:&lt;br /&gt;
* New file table `mdl_files_reference`&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `fileid` foreign key of `mdl_files`.`id`&lt;br /&gt;
** `repositoryid`&lt;br /&gt;
** `reference` - can be URL, UUID or other data format, repository plugin callbacks know the meaning of this field. File reference should be cached when adding to moodle, contenthash should be accurate.&lt;br /&gt;
&lt;br /&gt;
* `mdl_files_log` table for files access log, File API should have a new function to insert records to this table&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `userid` (0 if it&#039;s guest), &lt;br /&gt;
** `timeaccess`, &lt;br /&gt;
** `fileid`&lt;br /&gt;
&lt;br /&gt;
=== File API changes ===&lt;br /&gt;
* pluginfile.php and draftfile.php (it&#039;s actually send_stored_file()): when file isn&#039;t local file (`repositoryid` isn&#039;t zero), stored_file instance should ask repository plugin‘s send the file contents, Repository API decide return the cached copy or fresh contents&lt;br /&gt;
* Preserve file reference information when call file_storage::create_file_from_storedfile&lt;br /&gt;
* file_storage::get_area_files should retrieve file reference information, also file_storage::get_file_by_id, file_storage::get_file_by_hash&lt;br /&gt;
* When call file_prepare_draft_area(), it should keep linking files&#039; repository information&lt;br /&gt;
* file_save_draft_files should preserve file reference information&lt;br /&gt;
* new method: file_storage::create_file_from_reference&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function create_file_from_reference($file_record, $repoisitoryid, $reference, array $options = NULL)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
* Cron: we probably need a Repository API function to cache/update external files&lt;br /&gt;
* stored_file class&lt;br /&gt;
** set_author()&lt;br /&gt;
** set_license()&lt;br /&gt;
** replace_content_with(stored_file $storedfile)&lt;br /&gt;
** rename($filepath, $filename): rename files&lt;br /&gt;
** delete_references(): delete all reference information&lt;br /&gt;
** get_reference_details($ref): Get human readable reference information&lt;br /&gt;
&lt;br /&gt;
=== Repository API changes ===&lt;br /&gt;
&lt;br /&gt;
* Additions to FILE_INTERNAL and FILE_EXTERNAL, we need another type FILE_REFERENCE = 4 // 0100, repository plugin needs to declare what types of files are supported&lt;br /&gt;
&lt;br /&gt;
* When users ask to create a reference (instead of copying) in file picker, Repository API try to cache the file in filepool(special file area for repository), after file downloaded, repository API should ask Files API to create a virtual file in `mdl_files` table, the existing fields stay the same, extra file reference information is stored in `mdl_files_reference` table, link or other format that repository plugins know. File API return the stored_file object, repository API generate the moodle url for users just like other moodle files without revealing the internal reference&lt;br /&gt;
&lt;br /&gt;
* Making repository plugin upgrade and versioning possible, repository plugin may need to update `reference` in `mdl_files_reference` table if reference info changed. (we already have db/upgrade.php, make sure all new plugins have one)&lt;br /&gt;
&lt;br /&gt;
* Cron will use repository plugin callbacks to clean up cache files, repoistory::cron($repositoryid)&lt;br /&gt;
&lt;br /&gt;
* repository::send_file(stored_file $stored_file): Serve repository files&lt;br /&gt;
&lt;br /&gt;
* repository::get_file_reference($str): Get a file reference&lt;br /&gt;
&lt;br /&gt;
* repository::get_file_by_reference($ref): Get an external file by providing reference &lt;br /&gt;
&lt;br /&gt;
* repository::sync_individual_file(stored_file $file): Decide whether or not the stored_file instance should be synced&lt;br /&gt;
&lt;br /&gt;
* repository::get_reference_details($ref): Convert the reference info to human readable format&lt;br /&gt;
&lt;br /&gt;
=== Repository plugins changes ===&lt;br /&gt;
* Server files: store file parameters in `reference` field&lt;br /&gt;
* Private files: same as server files plugin&lt;br /&gt;
* Alfresco: Store UUID in `reference` field, but alfresco will change UUID once tomcat restart, may need other information to locate files&lt;br /&gt;
* Flickr private: needs flickr secret and token and photo id to locate the files&lt;br /&gt;
* File system: store file path in `reference` field&lt;br /&gt;
* s3: store file path, s3 repository will use secret and token to fetch the file from s3 no matter files are public or not.&lt;br /&gt;
* EQUELLA&lt;br /&gt;
*# Admin installs EQUELLA and setup parameters for single sign on&lt;br /&gt;
*# Teacher clicks EQUELLA instance, EQUELLA will return an URL of repository UI (plugin code)&lt;br /&gt;
*# Teacher pick a file from EQUELLA, EQUELLA repository UI will revoke file picker JavaScript API to notify moodle download this resource (plugin code)&lt;br /&gt;
*# Repository API stores file UUID and SSO userid, creates file reference in moodle file pool&lt;br /&gt;
*# EQUELLA plugin implements method to download contents using stored UUID and SSO userid&lt;br /&gt;
*# EQUELLA plugin implements method to update/invalidate cached resources (by cron)&lt;br /&gt;
&lt;br /&gt;
=== Content caching ===&lt;br /&gt;
* Create moodledata/repostory/cache directory&lt;br /&gt;
* Generate hash based on file URL and request parameters (provided by repository plugin), not content hash because we cannot send content hash to external repository for file information&lt;br /&gt;
* Cached files are stored using hash code as file name&lt;br /&gt;
&lt;br /&gt;
==== class repository_cache ====&lt;br /&gt;
&lt;br /&gt;
Returned stored_file instance or file path&lt;br /&gt;
&lt;br /&gt;
* store($url, $string_to_be_hashed)&lt;br /&gt;
* get($string_to_be_hashed)&lt;br /&gt;
&lt;br /&gt;
=== Filepicker Javascript API for customizing===&lt;br /&gt;
* File picker should be able to dislable file references by taking an option&lt;br /&gt;
* Support &amp;lt;object&amp;gt; tag in filepicker container&lt;br /&gt;
* Provide Javascript API to allow plugin communicate with filepicker&lt;br /&gt;
** Notify file picker to download file &lt;br /&gt;
** Notify file picker to pop up authentication page&lt;br /&gt;
&lt;br /&gt;
=== File manager to handle virtual files===&lt;br /&gt;
Not much trouble here, need to make sure draftfile.php can serve external resources, because all files managed by file manager is in draft area&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Project]]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=33664</id>
		<title>Improved support for external File content</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=33664"/>
		<updated>2012-05-07T09:56:33Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: /* Repository API changes */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Work in progress}}&lt;br /&gt;
{{Infobox Project&lt;br /&gt;
|name = File synching&lt;br /&gt;
|state = Planning&lt;br /&gt;
|tracker =  MDL-28666&lt;br /&gt;
|discussion = TODO&lt;br /&gt;
|assignee = Dongsheng Cai&lt;br /&gt;
}}&lt;br /&gt;
{{Moodle 2.3}}&lt;br /&gt;
&lt;br /&gt;
==Problems with Files in 2.0==&lt;br /&gt;
&lt;br /&gt;
* It is not currently easy to use a file in multiple places throughout Moodle and update them all at once&lt;br /&gt;
* It is not currently easy to create a simple shared &amp;quot;course repository&amp;quot; for teachers to use&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Example use cases that will become possible==&lt;br /&gt;
&lt;br /&gt;
A teacher wants to upload a file once and use it in multiple courses.  When they update the file, it should be updated in all their courses automatically.&lt;br /&gt;
&lt;br /&gt;
# The teacher uploads the file to their private file area (or other repository).&lt;br /&gt;
# In each place they want to add the file, they use the file picker to select the file from their private files area (or other repo) and select &amp;quot;link to latest version&amp;quot;.&lt;br /&gt;
# Replacing the file will automatically mean the linked copies use it.&lt;br /&gt;
# If teacher picked a file which is an alias already, moodle will copy the alias, not create an alias to alias.&lt;br /&gt;
&lt;br /&gt;
Several teachers want to create a shared repository of files together&lt;br /&gt;
# One teacher adds a course repository block to the course&lt;br /&gt;
# Using the block, the teacher creates an instance of a &amp;quot;?filesystem? repository&amp;quot; inside the course (essentially a folder).&lt;br /&gt;
# The content of the repository can be edited via the &amp;quot;Course repositories&amp;quot; block.&lt;br /&gt;
# All of the teachers in that course can now see that appear in their filepickers and select files &lt;br /&gt;
# Files can be &amp;quot;linked&amp;quot; as above&lt;br /&gt;
&lt;br /&gt;
A student wants to submit a linked file as an assignment, so they can continue updating it after the assignment due date.&lt;br /&gt;
# This will not be possible, because the assignment will not allow linking to files.&lt;br /&gt;
# The student is forced to upload a copy of a file and this is protected by the assignment module.&lt;br /&gt;
&lt;br /&gt;
==Solution summary==&lt;br /&gt;
&lt;br /&gt;
The basic idea is to allow the CONTENT of files to be stored outside of the filepool, while all the metadata and access is controlled by Moodle exactly the same as it is now.&lt;br /&gt;
&lt;br /&gt;
# Extend Files API with a new &amp;quot;reference&amp;quot; concept, with all files now having a &amp;quot;repositoryid&amp;quot; (the repository instance that the file came from) and a &amp;quot;reference&amp;quot; (the address in that repository of the content of the file).&lt;br /&gt;
# All files &#039;&#039;&#039;copied&#039;&#039;&#039; into Moodle don&#039;t have record in `files_reference` table, but others can specify a UUID (usually a URL with file-specific tokens in it, created by the original user who placed the file) in reference and repostiroyid columns.&lt;br /&gt;
# Improve the filesystem repository plugin to make it easier to create folder repositories on the fly, via a block.&lt;br /&gt;
# Add support to the filepicker UI to add &amp;quot;linking&amp;quot; to more repositories that support it, including the server files and filesystem repository.&lt;br /&gt;
# Virtual files are served via pluginfile.php URLs just like normal files:&lt;br /&gt;
## pluginfile.php uses module callback to determine access (as now), and if it passes then&lt;br /&gt;
## pluginfile.php calls a file logging subsystem to log the fact that this file is being served (useful for copyright reporting, for example)&lt;br /&gt;
## If the file is not &#039;local&#039;, then pluginfile.php uses the relevant repository callback to get the content of the file and streams it to the user with appropriate mimetypes etc&lt;br /&gt;
## The repositories have a way to cache this content in the normal Moodle filepool, to avoid repeated downloads.&lt;br /&gt;
## If an external repository is down or not configured, then the repository plugin can choose to just serve the local cached version (useful for restored backups and disaster tolerance)&lt;br /&gt;
# When a file is linked to another file in the local location, then it has a location of &amp;quot;filepool&amp;quot;.  We need to pay attention to these during:&lt;br /&gt;
## Garbage cleanups &lt;br /&gt;
## Update local caches&lt;br /&gt;
&lt;br /&gt;
Note that the original URL of files in external repositories are never revealed to users.&lt;br /&gt;
&lt;br /&gt;
==Details==&lt;br /&gt;
&lt;br /&gt;
=== File picker walk-through ===&lt;br /&gt;
[[File:File_picker_using_repository_reference_SD.png]]&lt;br /&gt;
# User clicks the &amp;quot;Insert image&amp;quot; button in TinyMCE, then launches our File Picker in the dialog&lt;br /&gt;
# User chooses a repository which supports UUID direct file references (eg Equella, Alfresco etc)&lt;br /&gt;
# A. File picker will ask repository plugin for customised repository UI if supported&lt;br /&gt;
# B. File Picker use repository UI and &amp;lt;object&amp;gt; type to display customised UI in file picker right pane&lt;br /&gt;
# User selects a file from the repository, if is customised UI, file picker Javascript API will be used to trigger file selection event (with all file related information), the repository plugin gives you two choices in the file picker interface  http://tracker.moodle.org/secure/attachment/25823/2011-11-09+15.23.png http://tracker.moodle.org/secure/attachment/25825/File+picker+options.png&lt;br /&gt;
#* C. Use the current version: this will copy the file to Moodle (as now).  No need to reference external content.&lt;br /&gt;
#* C. Use the latest version: this will use a file reference, so that the most recent version is always pulled from the repository&lt;br /&gt;
# D. If &amp;quot;Use the latest version&amp;quot; selected, Repository API will create a file reference, if selected file is already an alias, repository API will copy this alias, not create an alias to alias, repository plugin can cache external files in moodle local directory, but this is optional&lt;br /&gt;
# E. Repository API ask File API to create a file with file parameters and reference.  File API stores the reference and repository instance id in `mdl_files_reference` table and returns the file URL which looks the same as any other Moodle file URL.&lt;br /&gt;
# File picker gives the file URL to TinyMCE&lt;br /&gt;
# When TinyMCE displays the resource, it will cause the browser to call the file URL, which contains pluginfile.php.&lt;br /&gt;
# Pluginfile.php uses File API to send file contents to browsers, if File API detects the requested file is not ordinary moodle files which is located at external repository, File API will ask Repository API for file content, Repository API will firstly look for the cached file, if file is too old or not found (removed  by cron checking), Repository API will fetch the resource (and cache it), then return to File API. Alternatively, Repository API could disable caching, asking for fresh content all the time.&lt;br /&gt;
&lt;br /&gt;
=== File request walk-through ===&lt;br /&gt;
[[File:File_Request_SD.png]]&lt;br /&gt;
&lt;br /&gt;
# A. User request a file&lt;br /&gt;
# B. File API detects if the request file is regular moodle files or located at external repository, if it&#039;s external files, File API will ask repository API to grab the file&lt;br /&gt;
# C. File API collects the file reference information from database, it could be stored in php serialised or JSON format&lt;br /&gt;
# D. File API passes raw file reference information to repository plugin&lt;br /&gt;
# E. Repository plugin will firstly check if file available locally&lt;br /&gt;
# F. If not repository plugin will use file reference information to grab the file&lt;br /&gt;
# G. Repository API returns file content to File API to serve the file&lt;br /&gt;
&lt;br /&gt;
=== Database changes ===&lt;br /&gt;
&lt;br /&gt;
We can create another table for left joining, this requires File API query this table when locating files:&lt;br /&gt;
* New file table `mdl_files_reference`&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `fileid` foreign key of `mdl_files`.`id`&lt;br /&gt;
** `repositoryid`&lt;br /&gt;
** `reference` - can be URL, UUID or other data format, repository plugin callbacks know the meaning of this field. File reference should be cached when adding to moodle, contenthash should be accurate.&lt;br /&gt;
&lt;br /&gt;
* `mdl_files_log` table for files access log, File API should have a new function to insert records to this table&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `userid` (0 if it&#039;s guest), &lt;br /&gt;
** `timeaccess`, &lt;br /&gt;
** `fileid`&lt;br /&gt;
&lt;br /&gt;
=== File API changes ===&lt;br /&gt;
* pluginfile.php and draftfile.php (it&#039;s actually send_stored_file()): when file isn&#039;t local file (`repositoryid` isn&#039;t zero), stored_file instance should ask repository plugin‘s send the file contents, Repository API decide return the cached copy or fresh contents&lt;br /&gt;
* Preserve file reference information when call file_storage::create_file_from_storedfile&lt;br /&gt;
* file_storage::get_area_files should retrieve file reference information, also file_storage::get_file_by_id, file_storage::get_file_by_hash&lt;br /&gt;
* When call file_prepare_draft_area(), it should keep linking files&#039; repository information&lt;br /&gt;
* file_save_draft_files should preserve file reference information&lt;br /&gt;
* new method: file_storage::create_file_from_reference&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function create_file_from_reference($file_record, $repoisitoryid, $reference, array $options = NULL)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
* Cron: we probably need a Repository API function to cache/update external files&lt;br /&gt;
* stored_file class&lt;br /&gt;
** set_author()&lt;br /&gt;
** set_license()&lt;br /&gt;
** replace_content_with(stored_file $storedfile)&lt;br /&gt;
** rename($filepath, $filename): rename files&lt;br /&gt;
** delete_references(): delete all reference information&lt;br /&gt;
&lt;br /&gt;
=== Repository API changes ===&lt;br /&gt;
&lt;br /&gt;
* Additions to FILE_INTERNAL and FILE_EXTERNAL, we need another type FILE_REFERENCE = 4 // 0100, repository plugin needs to declare what types of files are supported&lt;br /&gt;
&lt;br /&gt;
* When users ask to create a reference (instead of copying) in file picker, Repository API try to cache the file in filepool(special file area for repository), after file downloaded, repository API should ask Files API to create a virtual file in `mdl_files` table, the existing fields stay the same, extra file reference information is stored in `mdl_files_reference` table, link or other format that repository plugins know. File API return the stored_file object, repository API generate the moodle url for users just like other moodle files without revealing the internal reference&lt;br /&gt;
&lt;br /&gt;
* Making repository plugin upgrade and versioning possible, repository plugin may need to update `reference` in `mdl_files_reference` table if reference info changed. (we already have db/upgrade.php, make sure all new plugins have one)&lt;br /&gt;
&lt;br /&gt;
* Cron will use repository plugin callbacks to clean up cache files, repoistory::cron($repositoryid)&lt;br /&gt;
&lt;br /&gt;
* repository::send_file(stored_file $stored_file): Serve repository files&lt;br /&gt;
&lt;br /&gt;
* repository::get_file_reference($str): Get a file reference&lt;br /&gt;
&lt;br /&gt;
* repository::get_file_by_reference($ref): Get an external file by providing reference &lt;br /&gt;
&lt;br /&gt;
* repository::sync_individual_file(stored_file $file): Decide whether or not the stored_file instance should be synced&lt;br /&gt;
&lt;br /&gt;
* repository::get_reference_details($ref): Convert the reference info to human readable format&lt;br /&gt;
&lt;br /&gt;
=== Repository plugins changes ===&lt;br /&gt;
* Server files: store file parameters in `reference` field&lt;br /&gt;
* Private files: same as server files plugin&lt;br /&gt;
* Alfresco: Store UUID in `reference` field, but alfresco will change UUID once tomcat restart, may need other information to locate files&lt;br /&gt;
* Flickr private: needs flickr secret and token and photo id to locate the files&lt;br /&gt;
* File system: store file path in `reference` field&lt;br /&gt;
* s3: store file path, s3 repository will use secret and token to fetch the file from s3 no matter files are public or not.&lt;br /&gt;
* EQUELLA&lt;br /&gt;
*# Admin installs EQUELLA and setup parameters for single sign on&lt;br /&gt;
*# Teacher clicks EQUELLA instance, EQUELLA will return an URL of repository UI (plugin code)&lt;br /&gt;
*# Teacher pick a file from EQUELLA, EQUELLA repository UI will revoke file picker JavaScript API to notify moodle download this resource (plugin code)&lt;br /&gt;
*# Repository API stores file UUID and SSO userid, creates file reference in moodle file pool&lt;br /&gt;
*# EQUELLA plugin implements method to download contents using stored UUID and SSO userid&lt;br /&gt;
*# EQUELLA plugin implements method to update/invalidate cached resources (by cron)&lt;br /&gt;
&lt;br /&gt;
=== Content caching ===&lt;br /&gt;
* Create moodledata/repostory/cache directory&lt;br /&gt;
* Generate hash based on file URL and request parameters (provided by repository plugin), not content hash because we cannot send content hash to external repository for file information&lt;br /&gt;
* Cached files are stored using hash code as file name&lt;br /&gt;
&lt;br /&gt;
==== class repository_cache ====&lt;br /&gt;
&lt;br /&gt;
Returned stored_file instance or file path&lt;br /&gt;
&lt;br /&gt;
* store($url, $string_to_be_hashed)&lt;br /&gt;
* get($string_to_be_hashed)&lt;br /&gt;
&lt;br /&gt;
=== Filepicker Javascript API for customizing===&lt;br /&gt;
* File picker should be able to dislable file references by taking an option&lt;br /&gt;
* Support &amp;lt;object&amp;gt; tag in filepicker container&lt;br /&gt;
* Provide Javascript API to allow plugin communicate with filepicker&lt;br /&gt;
** Notify file picker to download file &lt;br /&gt;
** Notify file picker to pop up authentication page&lt;br /&gt;
&lt;br /&gt;
=== File manager to handle virtual files===&lt;br /&gt;
Not much trouble here, need to make sure draftfile.php can serve external resources, because all files managed by file manager is in draft area&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Project]]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=33663</id>
		<title>Improved support for external File content</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=33663"/>
		<updated>2012-05-07T09:18:38Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: /* File API changes */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Work in progress}}&lt;br /&gt;
{{Infobox Project&lt;br /&gt;
|name = File synching&lt;br /&gt;
|state = Planning&lt;br /&gt;
|tracker =  MDL-28666&lt;br /&gt;
|discussion = TODO&lt;br /&gt;
|assignee = Dongsheng Cai&lt;br /&gt;
}}&lt;br /&gt;
{{Moodle 2.3}}&lt;br /&gt;
&lt;br /&gt;
==Problems with Files in 2.0==&lt;br /&gt;
&lt;br /&gt;
* It is not currently easy to use a file in multiple places throughout Moodle and update them all at once&lt;br /&gt;
* It is not currently easy to create a simple shared &amp;quot;course repository&amp;quot; for teachers to use&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Example use cases that will become possible==&lt;br /&gt;
&lt;br /&gt;
A teacher wants to upload a file once and use it in multiple courses.  When they update the file, it should be updated in all their courses automatically.&lt;br /&gt;
&lt;br /&gt;
# The teacher uploads the file to their private file area (or other repository).&lt;br /&gt;
# In each place they want to add the file, they use the file picker to select the file from their private files area (or other repo) and select &amp;quot;link to latest version&amp;quot;.&lt;br /&gt;
# Replacing the file will automatically mean the linked copies use it.&lt;br /&gt;
# If teacher picked a file which is an alias already, moodle will copy the alias, not create an alias to alias.&lt;br /&gt;
&lt;br /&gt;
Several teachers want to create a shared repository of files together&lt;br /&gt;
# One teacher adds a course repository block to the course&lt;br /&gt;
# Using the block, the teacher creates an instance of a &amp;quot;?filesystem? repository&amp;quot; inside the course (essentially a folder).&lt;br /&gt;
# The content of the repository can be edited via the &amp;quot;Course repositories&amp;quot; block.&lt;br /&gt;
# All of the teachers in that course can now see that appear in their filepickers and select files &lt;br /&gt;
# Files can be &amp;quot;linked&amp;quot; as above&lt;br /&gt;
&lt;br /&gt;
A student wants to submit a linked file as an assignment, so they can continue updating it after the assignment due date.&lt;br /&gt;
# This will not be possible, because the assignment will not allow linking to files.&lt;br /&gt;
# The student is forced to upload a copy of a file and this is protected by the assignment module.&lt;br /&gt;
&lt;br /&gt;
==Solution summary==&lt;br /&gt;
&lt;br /&gt;
The basic idea is to allow the CONTENT of files to be stored outside of the filepool, while all the metadata and access is controlled by Moodle exactly the same as it is now.&lt;br /&gt;
&lt;br /&gt;
# Extend Files API with a new &amp;quot;reference&amp;quot; concept, with all files now having a &amp;quot;repositoryid&amp;quot; (the repository instance that the file came from) and a &amp;quot;reference&amp;quot; (the address in that repository of the content of the file).&lt;br /&gt;
# All files &#039;&#039;&#039;copied&#039;&#039;&#039; into Moodle don&#039;t have record in `files_reference` table, but others can specify a UUID (usually a URL with file-specific tokens in it, created by the original user who placed the file) in reference and repostiroyid columns.&lt;br /&gt;
# Improve the filesystem repository plugin to make it easier to create folder repositories on the fly, via a block.&lt;br /&gt;
# Add support to the filepicker UI to add &amp;quot;linking&amp;quot; to more repositories that support it, including the server files and filesystem repository.&lt;br /&gt;
# Virtual files are served via pluginfile.php URLs just like normal files:&lt;br /&gt;
## pluginfile.php uses module callback to determine access (as now), and if it passes then&lt;br /&gt;
## pluginfile.php calls a file logging subsystem to log the fact that this file is being served (useful for copyright reporting, for example)&lt;br /&gt;
## If the file is not &#039;local&#039;, then pluginfile.php uses the relevant repository callback to get the content of the file and streams it to the user with appropriate mimetypes etc&lt;br /&gt;
## The repositories have a way to cache this content in the normal Moodle filepool, to avoid repeated downloads.&lt;br /&gt;
## If an external repository is down or not configured, then the repository plugin can choose to just serve the local cached version (useful for restored backups and disaster tolerance)&lt;br /&gt;
# When a file is linked to another file in the local location, then it has a location of &amp;quot;filepool&amp;quot;.  We need to pay attention to these during:&lt;br /&gt;
## Garbage cleanups &lt;br /&gt;
## Update local caches&lt;br /&gt;
&lt;br /&gt;
Note that the original URL of files in external repositories are never revealed to users.&lt;br /&gt;
&lt;br /&gt;
==Details==&lt;br /&gt;
&lt;br /&gt;
=== File picker walk-through ===&lt;br /&gt;
[[File:File_picker_using_repository_reference_SD.png]]&lt;br /&gt;
# User clicks the &amp;quot;Insert image&amp;quot; button in TinyMCE, then launches our File Picker in the dialog&lt;br /&gt;
# User chooses a repository which supports UUID direct file references (eg Equella, Alfresco etc)&lt;br /&gt;
# A. File picker will ask repository plugin for customised repository UI if supported&lt;br /&gt;
# B. File Picker use repository UI and &amp;lt;object&amp;gt; type to display customised UI in file picker right pane&lt;br /&gt;
# User selects a file from the repository, if is customised UI, file picker Javascript API will be used to trigger file selection event (with all file related information), the repository plugin gives you two choices in the file picker interface  http://tracker.moodle.org/secure/attachment/25823/2011-11-09+15.23.png http://tracker.moodle.org/secure/attachment/25825/File+picker+options.png&lt;br /&gt;
#* C. Use the current version: this will copy the file to Moodle (as now).  No need to reference external content.&lt;br /&gt;
#* C. Use the latest version: this will use a file reference, so that the most recent version is always pulled from the repository&lt;br /&gt;
# D. If &amp;quot;Use the latest version&amp;quot; selected, Repository API will create a file reference, if selected file is already an alias, repository API will copy this alias, not create an alias to alias, repository plugin can cache external files in moodle local directory, but this is optional&lt;br /&gt;
# E. Repository API ask File API to create a file with file parameters and reference.  File API stores the reference and repository instance id in `mdl_files_reference` table and returns the file URL which looks the same as any other Moodle file URL.&lt;br /&gt;
# File picker gives the file URL to TinyMCE&lt;br /&gt;
# When TinyMCE displays the resource, it will cause the browser to call the file URL, which contains pluginfile.php.&lt;br /&gt;
# Pluginfile.php uses File API to send file contents to browsers, if File API detects the requested file is not ordinary moodle files which is located at external repository, File API will ask Repository API for file content, Repository API will firstly look for the cached file, if file is too old or not found (removed  by cron checking), Repository API will fetch the resource (and cache it), then return to File API. Alternatively, Repository API could disable caching, asking for fresh content all the time.&lt;br /&gt;
&lt;br /&gt;
=== File request walk-through ===&lt;br /&gt;
[[File:File_Request_SD.png]]&lt;br /&gt;
&lt;br /&gt;
# A. User request a file&lt;br /&gt;
# B. File API detects if the request file is regular moodle files or located at external repository, if it&#039;s external files, File API will ask repository API to grab the file&lt;br /&gt;
# C. File API collects the file reference information from database, it could be stored in php serialised or JSON format&lt;br /&gt;
# D. File API passes raw file reference information to repository plugin&lt;br /&gt;
# E. Repository plugin will firstly check if file available locally&lt;br /&gt;
# F. If not repository plugin will use file reference information to grab the file&lt;br /&gt;
# G. Repository API returns file content to File API to serve the file&lt;br /&gt;
&lt;br /&gt;
=== Database changes ===&lt;br /&gt;
&lt;br /&gt;
We can create another table for left joining, this requires File API query this table when locating files:&lt;br /&gt;
* New file table `mdl_files_reference`&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `fileid` foreign key of `mdl_files`.`id`&lt;br /&gt;
** `repositoryid`&lt;br /&gt;
** `reference` - can be URL, UUID or other data format, repository plugin callbacks know the meaning of this field. File reference should be cached when adding to moodle, contenthash should be accurate.&lt;br /&gt;
&lt;br /&gt;
* `mdl_files_log` table for files access log, File API should have a new function to insert records to this table&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `userid` (0 if it&#039;s guest), &lt;br /&gt;
** `timeaccess`, &lt;br /&gt;
** `fileid`&lt;br /&gt;
&lt;br /&gt;
=== File API changes ===&lt;br /&gt;
* pluginfile.php and draftfile.php (it&#039;s actually send_stored_file()): when file isn&#039;t local file (`repositoryid` isn&#039;t zero), stored_file instance should ask repository plugin‘s send the file contents, Repository API decide return the cached copy or fresh contents&lt;br /&gt;
* Preserve file reference information when call file_storage::create_file_from_storedfile&lt;br /&gt;
* file_storage::get_area_files should retrieve file reference information, also file_storage::get_file_by_id, file_storage::get_file_by_hash&lt;br /&gt;
* When call file_prepare_draft_area(), it should keep linking files&#039; repository information&lt;br /&gt;
* file_save_draft_files should preserve file reference information&lt;br /&gt;
* new method: file_storage::create_file_from_reference&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function create_file_from_reference($file_record, $repoisitoryid, $reference, array $options = NULL)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
* Cron: we probably need a Repository API function to cache/update external files&lt;br /&gt;
* stored_file class&lt;br /&gt;
** set_author()&lt;br /&gt;
** set_license()&lt;br /&gt;
** replace_content_with(stored_file $storedfile)&lt;br /&gt;
** rename($filepath, $filename): rename files&lt;br /&gt;
** delete_references(): delete all reference information&lt;br /&gt;
&lt;br /&gt;
=== Repository API changes ===&lt;br /&gt;
&lt;br /&gt;
* Additions to FILE_INTERNAL and FILE_EXTERNAL, we need another type FILE_REFERENCE = 4 // 0100, repository plugin needs to declare what types of files are supported&lt;br /&gt;
&lt;br /&gt;
* When users ask to create a reference (instead of copying) in file picker, Repository API try to cache the file in filepool(special file area for repository), after file downloaded, repository API should ask Files API to create a virtual file in `mdl_files` table, the existing fields stay the same, extra file reference information is stored in `mdl_files_reference` table, link or other format that repository plugins know. File API return the stored_file object, repository API generate the moodle url for users just like other moodle files without revealing the internal reference&lt;br /&gt;
&lt;br /&gt;
* Making repository plugin upgrade and versioning possible, repository plugin may need to update `reference` in `mdl_files_reference` table if reference info changed. (we already have db/upgrade.php, make sure all new plugins have one)&lt;br /&gt;
&lt;br /&gt;
* Cron will use repository plugin callbacks to clean up cache files, repoistory::cron($repositoryid)&lt;br /&gt;
&lt;br /&gt;
* repository::send_file(stored_file $stored_file)&lt;br /&gt;
&lt;br /&gt;
* repository::get_file_reference($ref)&lt;br /&gt;
&lt;br /&gt;
=== Repository plugins changes ===&lt;br /&gt;
* Server files: store file parameters in `reference` field&lt;br /&gt;
* Private files: same as server files plugin&lt;br /&gt;
* Alfresco: Store UUID in `reference` field, but alfresco will change UUID once tomcat restart, may need other information to locate files&lt;br /&gt;
* Flickr private: needs flickr secret and token and photo id to locate the files&lt;br /&gt;
* File system: store file path in `reference` field&lt;br /&gt;
* s3: store file path, s3 repository will use secret and token to fetch the file from s3 no matter files are public or not.&lt;br /&gt;
* EQUELLA&lt;br /&gt;
*# Admin installs EQUELLA and setup parameters for single sign on&lt;br /&gt;
*# Teacher clicks EQUELLA instance, EQUELLA will return an URL of repository UI (plugin code)&lt;br /&gt;
*# Teacher pick a file from EQUELLA, EQUELLA repository UI will revoke file picker JavaScript API to notify moodle download this resource (plugin code)&lt;br /&gt;
*# Repository API stores file UUID and SSO userid, creates file reference in moodle file pool&lt;br /&gt;
*# EQUELLA plugin implements method to download contents using stored UUID and SSO userid&lt;br /&gt;
*# EQUELLA plugin implements method to update/invalidate cached resources (by cron)&lt;br /&gt;
&lt;br /&gt;
=== Content caching ===&lt;br /&gt;
* Create moodledata/repostory/cache directory&lt;br /&gt;
* Generate hash based on file URL and request parameters (provided by repository plugin), not content hash because we cannot send content hash to external repository for file information&lt;br /&gt;
* Cached files are stored using hash code as file name&lt;br /&gt;
&lt;br /&gt;
==== class repository_cache ====&lt;br /&gt;
&lt;br /&gt;
Returned stored_file instance or file path&lt;br /&gt;
&lt;br /&gt;
* store($url, $string_to_be_hashed)&lt;br /&gt;
* get($string_to_be_hashed)&lt;br /&gt;
&lt;br /&gt;
=== Filepicker Javascript API for customizing===&lt;br /&gt;
* File picker should be able to dislable file references by taking an option&lt;br /&gt;
* Support &amp;lt;object&amp;gt; tag in filepicker container&lt;br /&gt;
* Provide Javascript API to allow plugin communicate with filepicker&lt;br /&gt;
** Notify file picker to download file &lt;br /&gt;
** Notify file picker to pop up authentication page&lt;br /&gt;
&lt;br /&gt;
=== File manager to handle virtual files===&lt;br /&gt;
Not much trouble here, need to make sure draftfile.php can serve external resources, because all files managed by file manager is in draft area&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Project]]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Repository_API&amp;diff=33603</id>
		<title>Repository API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Repository_API&amp;diff=33603"/>
		<updated>2012-04-26T08:20:16Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: /* supported_returntypes() */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle_2.0}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;p class=&amp;quot;note&amp;quot;&amp;gt;&lt;br /&gt;
The page is open for everyone so everyone can help correct mistakes and help with the evolution of this document.  However, if you have questions to ask, problems to report or major changes to suggest, please add them to the [[Development_talk:Repository_API|page comments]], or start a discussion in the [http://moodle.org/mod/forum/view.php?id=1807 Repositories forum]. We&#039;ll endeavor to merge all such suggestions into the further development and fix all kinds of problems.&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that parts of this document have been now split off into a separate [[File_API]]&lt;br /&gt;
&lt;br /&gt;
==Objectives==&lt;br /&gt;
&lt;br /&gt;
# Allow all Moodle users to easily bring content into Moodle from external repositories&lt;br /&gt;
# Provide a consistent interface to any external repository, for any Moodle module&lt;br /&gt;
&lt;br /&gt;
==Use cases==&lt;br /&gt;
&lt;br /&gt;
===Teacher adding an external file as a new resource===&lt;br /&gt;
&lt;br /&gt;
# Teacher wants to add a new resource to a course &lt;br /&gt;
# Teacher clicks the &amp;quot;Choose a resource&amp;quot; button&lt;br /&gt;
# Teacher is presented with a simple file picker to choose a file (with a menu to switch between multiple configured repositories)&lt;br /&gt;
# Teacher chooses a file in an external repository&lt;br /&gt;
# File is COPIED into Moodle and stored by the resource module&lt;br /&gt;
# File is marked as owned by that user&lt;br /&gt;
# Whenever someone wants to view that file, the resource module controls access  (see [[File API]] )&lt;br /&gt;
&lt;br /&gt;
===Teacher linking to an external file as a new resource (think video repository) ===&lt;br /&gt;
&lt;br /&gt;
# Teacher wants to display a file in the repository &lt;br /&gt;
# Teacher clicks the &amp;quot;Choose a resource&amp;quot; button&lt;br /&gt;
# Teacher is presented with a simple file picker to choose a file (with a menu to switch between multiple configured repositories)&lt;br /&gt;
# Teacher chooses a file in an external repository&lt;br /&gt;
# Link to the file is COPIED into Moodle and stored by the resource module&lt;br /&gt;
# Link is marked as owned by that user&lt;br /&gt;
# Whenever someone wants to follow that link, the resource module controls access  (see [[File API]] )&lt;br /&gt;
&lt;br /&gt;
===Student submitting an assignment===&lt;br /&gt;
# Student needs to submit an assignment and presses the &amp;quot;Choose files&amp;quot; button&lt;br /&gt;
# Student sees a &amp;quot;file picker&amp;quot; where they can see files listed on any of several configured repositories ([https://docs.moodle.org/en/Image:Filepicker_login.jpg file picker login], [https://docs.moodle.org/en/Image:Filepicker_browser.jpg file picker browser], [https://docs.moodle.org/en/Image:Filepicker_search.jpg file picker search])&lt;br /&gt;
# Student chooses MySpace from the list&lt;br /&gt;
# Student is prompted to enter MySpace username/password (if admin allows it, a checkbox could be there to &amp;quot;remember this for next time&amp;quot; but remember security)&lt;br /&gt;
# Student sees their files in MySpace and chooses one or more&lt;br /&gt;
# Files are copied from MySpace to Moodle &lt;br /&gt;
# Assignment module controls the permissions so that only the Student and assignment graders can see the file (other students would not have permission).&lt;br /&gt;
&lt;br /&gt;
===Student attaching an image to a forum===&lt;br /&gt;
# Student needs to attach an image and presses the &amp;quot;Choose files&amp;quot; button in the posting screen&lt;br /&gt;
# Student sees a &amp;quot;file picker&amp;quot; where they can see files listed on any of several configured repositories&lt;br /&gt;
# Student chooses Mahara from the list&lt;br /&gt;
# Student is prompted to enter Mahara username/password&lt;br /&gt;
# Student sees their files in Mahara and chooses one image&lt;br /&gt;
# Image is copied to Moodle &lt;br /&gt;
# Image file is attached to forum post by Forum module (by reference)&lt;br /&gt;
# Forum module controls permissions so that anyone who can read that forum can see that file&lt;br /&gt;
&lt;br /&gt;
===Student attaching the same image in another forum===&lt;br /&gt;
&lt;br /&gt;
# Student needs to submit an assignment and presses the &amp;quot;Choose files&amp;quot; button&lt;br /&gt;
# Student sees a &amp;quot;file picker&amp;quot; where they can see files listed on any of several configured repositories&lt;br /&gt;
# Student chooses &amp;quot;Local files&amp;quot; from the list and sees all the files they&#039;ve permission to use&lt;br /&gt;
# A COPY of the image file is attached to forum post by Forum module&lt;br /&gt;
# Forum module controls access to this file.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Please add more use cases in this same format&lt;br /&gt;
&lt;br /&gt;
==Mock screenshots==&lt;br /&gt;
When you first call up the file picker and choose a repository, you might be asked to log in (if saving of passwords is not allowed):&lt;br /&gt;
&lt;br /&gt;
[[Image:Filepicker_login.jpg]]&lt;br /&gt;
&lt;br /&gt;
Browsing files could look something like this:&lt;br /&gt;
&lt;br /&gt;
[[Image:Filepicker_browser.jpg]]&lt;br /&gt;
&lt;br /&gt;
And you can also search:&lt;br /&gt;
&lt;br /&gt;
[[Image:Filepicker_search.jpg]]&lt;br /&gt;
&lt;br /&gt;
==General architecture==&lt;br /&gt;
&lt;br /&gt;
Each repository plugin (a standard Moodle plugin stored under /repository/xxx) will subclass the standard API and override methods specific to that repository.&lt;br /&gt;
&lt;br /&gt;
As is usual in Moodle, there will be admin settings to disable/enable certain repository plugins as standard, as well as user settings so that users can add their own personal repositories to the standard list (eg [http://briefcase.yahoo.com Yahoo Briefcase] or [http://docs.google.com Google Docs]) and to select their default repository.&lt;br /&gt;
&lt;br /&gt;
Once a repository has been used the file will usually be copied into Moodle there and then.  However there will also be options to:&lt;br /&gt;
* only return the URL to the file if it&#039;s desired to keep it external (but this does present security and integrity risks), or&lt;br /&gt;
* refresh the local file copy regularly and automatically&lt;br /&gt;
* refresh the file manually if desired&lt;br /&gt;
&lt;br /&gt;
Once in Moodle, it is subject to the [[File API]] for access control like any other file.&lt;br /&gt;
&lt;br /&gt;
==Repository requirements==&lt;br /&gt;
&lt;br /&gt;
From the Moodle point of view, each repository is just a hierarchy of nodes.&lt;br /&gt;
&lt;br /&gt;
The repository MUST provide:&lt;br /&gt;
# A URI to download each node (eg file).&lt;br /&gt;
# A list of the nodes (eg files and directories) under a given node (eg directory).  This allows Moodle to construct a standard browse interface (much like a standard OS file picker).&lt;br /&gt;
&lt;br /&gt;
The repository can OPTIONALLY:&lt;br /&gt;
# Require some authentication credentials &lt;br /&gt;
# Provide more metadata about each node (mime type, size, dates, related files, dublin core stuff, etc)&lt;br /&gt;
# Describe a search facility (so that Moodle can construct a search form)&lt;br /&gt;
# Provide copyright and usage rules (or just information about the rules)&lt;br /&gt;
&lt;br /&gt;
==Repository plugins==&lt;br /&gt;
&lt;br /&gt;
Some plugins I&#039;d like to see developed for the first version are:&lt;br /&gt;
* box - an interface to [http://box.net box.net]&lt;br /&gt;
* mahara - an interface to a Mahara installation&lt;br /&gt;
* Server Files - very similar to the current course-based file manager, except user-based&lt;br /&gt;
* Remote Moodle - an interface to another Moodle site, accessed over a secure mnet connection&lt;br /&gt;
* googledocs - an interface to [http://docs.google.com Google Docs]&lt;br /&gt;
* s3 - an interface to [http://www.amazon.com/gp/browse.html?node=16427261 Amazon S3]&lt;br /&gt;
* flickr - an interface to [http://flickr.com flickr]&lt;br /&gt;
* WebDAV - to access arbitrary external WebDAV servers&lt;br /&gt;
* merlot - an interface to the learning materials in [http://www.merlot.org/merlot/materials.htm Merlot.org]&lt;br /&gt;
* File System - a plugin to list files on local file system, of course, you can mount remote files to this local directory&lt;br /&gt;
* youtube - an interface to [http://youtube.com YouTube]&lt;br /&gt;
* jsr170 - an interface that can talk to anything that supports jsr170 (eg [http://www.alfresco.com/ Alfresco])&lt;br /&gt;
* oki - an OKI emulator allowing us to access things with OKI interfaces,like [http://www.fedora.info/ Fedora]&lt;br /&gt;
* briefcase - an interface to [http://briefcase.yahoo.com/ Yahoo Briefcase]&lt;br /&gt;
* myspace - an interface to MySpace files (perhaps via [http://www.programmableweb.com/api/myspace this MySpace API])&lt;br /&gt;
* skydrive - an interface to Microsoft&#039;s [http://skydrive.live.com/ SkyDrive] files&lt;br /&gt;
* Dropbox - an interface to Dropbox files [http://www.dropbox.com]&lt;br /&gt;
* facebook - an interface to Facebook files&lt;br /&gt;
* [http://www.dspace.org/ Dspace] - a repository from MIT&lt;br /&gt;
* DOOR - another popular open source repository&lt;br /&gt;
* SMB shares - An interface for windows shares e.g. personal folders on network drives. Would need to link with LDAP as usernames will often be wholly/partially the same as network folder names. This could be done using SAMBA, but would also need to work on windows machines natively. See [http://moodle.org/mod/data/view.php?d=13&amp;amp;rid=991 this block] for a linux implementation.&lt;br /&gt;
&lt;br /&gt;
==Tables==&lt;br /&gt;
&lt;br /&gt;
=== repository ===&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;Default&#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;id&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|autoincrementing &lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;type&#039;&#039;&#039;&lt;br /&gt;
|varchar(255)&lt;br /&gt;
|&lt;br /&gt;
|The type of the repository &lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;visible&#039;&#039;&#039;&lt;br /&gt;
|tinyint(1)&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|sortorder&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== repository_instances ===&lt;br /&gt;
&lt;br /&gt;
This table contains one entry for every configured external repository instance.&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;Default&#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;id&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|autoincrementing &lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|name&lt;br /&gt;
|varchar 255&lt;br /&gt;
|&lt;br /&gt;
|A custom name for this repository (non-unique)&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;typeid&#039;&#039;&#039; &lt;br /&gt;
|int(10)&lt;br /&gt;
| &lt;br /&gt;
|The id of repository type&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 person who created this repository instance&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;contextid&#039;&#039;&#039; &lt;br /&gt;
|int(10)&lt;br /&gt;
| &lt;br /&gt;
|The context that this repository is available to ( = system context for site-wide ones)&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|username&lt;br /&gt;
|varchar(255)&lt;br /&gt;
| &lt;br /&gt;
|username to log in with, if required (almost never!)&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|password&lt;br /&gt;
|varchar(255)&lt;br /&gt;
| &lt;br /&gt;
|password to log in with, if required (almost never!)&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|timecreated&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|The time this repository was created&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|timemodified&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|The last time the repository was modified&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== repository_instance_config ===&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;Default&#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;id&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|autoincrementing &lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;instanceid&#039;&#039;&#039;&lt;br /&gt;
|int(int)&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;name&#039;&#039;&#039;&lt;br /&gt;
|varchar(255)&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|value&lt;br /&gt;
|Text&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===File types===&lt;br /&gt;
&lt;br /&gt;
The context at which someone is inserting a file may require certain file types (eg uploading a new user profile image is only looking for images).  &lt;br /&gt;
&lt;br /&gt;
To support this, the calling code needs to be able to specify the required mimetypes, and the listing code should be able to filter the results based on these mimetypes.  Ideally the repository itself can do the filtering for ultimate speed (though not all repositories will support this).&lt;br /&gt;
&lt;br /&gt;
We will have to develop special new mimetypes for Moodle files like backups (application/vnd.moodle.backup) and IMS learning design (application/vnd.moodle.imsld) etc&lt;br /&gt;
&lt;br /&gt;
==Technical walkthrough==&lt;br /&gt;
&lt;br /&gt;
(See also the functional spec for the [[Repository_File_Picker]] )&lt;br /&gt;
&lt;br /&gt;
There are two main cases where the repository API will be used: as part of a Moodleform to add a file and as part of the HTML editor to add a media element into some HTML).  We also have to cater for the using Moodleforms without Javascript.&lt;br /&gt;
&lt;br /&gt;
In all of these cases the files will be uploaded to Moodle while using the file picker dialog and stored in a temporary file area owned by the currently active user.  It is only AFTER the submission of the entire Moodleform that we will know the full context, itemids to store the file properly, so at this time the file will be copied into the correct filearea.&lt;br /&gt;
&lt;br /&gt;
===Case 1: As part of a Moodleform with Javascript===&lt;br /&gt;
&lt;br /&gt;
1. Moodle module code calls a &amp;quot;filepicker&amp;quot; moodleform item whenever a file is required, which includes the following information to pass to the File API:&lt;br /&gt;
&lt;br /&gt;
 eg $mform-&amp;gt;addElement(&#039;filepicker&#039;, &#039;uniqueelementid&#039;, $fullname, $data)&lt;br /&gt;
 &lt;br /&gt;
2. When rendering the form, Moodle will display a read-only filename field with an &#039;&#039;&#039;&amp;quot;Add file&amp;quot;&#039;&#039;&#039; button next to it.  There will also be a hidden field to store a file reference later (this is what actually gets used, the filename field is just for users to see something).&lt;br /&gt;
&lt;br /&gt;
3. When the add file button is pressed, the form will be &amp;quot;replaced&amp;quot; in the page by a larger resizeable area containing an AJAX file picker.  (After picking the display can be closed).   (There could be a user option to make this a popup window instead, if required)&lt;br /&gt;
&lt;br /&gt;
4. The AJAX file picker interface will list all the active repositories as a menu, and list files in one of several formats (like Windows/Mac/Linux): Details, Names, Icons.&lt;br /&gt;
&lt;br /&gt;
5. For each plugin, the AJAX interface will prompt the user to login first (if required) asking the plugin to log in behind the scenes.  It&#039;ll also ask the plugin to return listing data in response to clicks and searches.  &lt;br /&gt;
&lt;br /&gt;
6. Finally, when the user selects a file and clicks the &amp;quot;Select&amp;quot; button, the AJAX interface will trigger a method in the plugin that will fetch the file and call the File Storage API to &#039;&#039;&#039;store&#039;&#039;&#039; the file using the &#039;&#039;&#039;uniqueelementid&#039;&#039;&#039; and the current user info.  While this is happening, the interface should show some sort of progress bar (ideally) or at least a &amp;quot;loading file&amp;quot; image/sign/message.  &lt;br /&gt;
&lt;br /&gt;
7. After a file has finally been selected we will have a file ID which we can pass back to the original Moodle form (to the hidden field named &#039;&#039;&#039;uniqueelementid_formid&#039;&#039;&#039;).  The picker can then rename the read-only filename field before it hides itself.&lt;br /&gt;
&lt;br /&gt;
8. Submitting the form will trigger the mform processing for this field, which will check fields, create things in the module etc.  Once this has been finally successful the developer must call an mform function to &amp;quot;fix&amp;quot; the info for each file and &amp;quot;move&amp;quot; it into the module file area:&lt;br /&gt;
&lt;br /&gt;
  eg $mform-&amp;gt;store_local_file(&#039;uniqueelementid&#039;, $context, $filearename, $itemid, $filepath);&lt;br /&gt;
&lt;br /&gt;
9. Cron jobs in File Storage api should automatically delete any files in the user&#039;s tempfile area that are older than 7 days or move them into a trash can in the user&#039;s file area (perhaps).&lt;br /&gt;
&lt;br /&gt;
===Case 2: As part of a Moodleform without Javascript===&lt;br /&gt;
&lt;br /&gt;
Steps 1-2 are the same as for the case with Javascript.&lt;br /&gt;
&lt;br /&gt;
3. The add file button is a submit button for the form with a different value.  When the add file button is pressed,&lt;br /&gt;
* the whole form will be &#039;&#039;submitted&#039;&#039; to the original location (but with a different submit button value)&lt;br /&gt;
* moodleforms get_data() will detect this is a &amp;quot;repository save&amp;quot; and can save the full POST info in the current session tagged with the id of the openfile element, together with the URL to return to&lt;br /&gt;
* moodleforms get_data() then redirects the user to a new page showing the main picker interface&lt;br /&gt;
&lt;br /&gt;
4. The file picker interface will have to be a completely new and separate interface from the AJAX one.  It could be a long hierarchy listing, or reload a lot.&lt;br /&gt;
&lt;br /&gt;
5. Finally, when the user selects a file and submits using the &amp;quot;Select&amp;quot; button to picker.php, it will trigger a method in the plugin that will fetch the file and call the [[File_API|File API]] to store the file using the filearea and context information we already had.   While this is happening, the interface can show some sort of progress bar (ideally) or at least a &amp;quot;loading file&amp;quot; image/sign/message.&lt;br /&gt;
&lt;br /&gt;
6. After this, picker.php will redirect/continue back to the original form page.  The form can be constructed as usual, however, when the form is rendered using display() method moodleforms should now look for relevant saved content in the session and use that to override any content in the form (and then delete the saved info in the session).&lt;br /&gt;
&lt;br /&gt;
Steps 8-9 are the same as for the case with Javascript.&lt;br /&gt;
&lt;br /&gt;
===Case 3: As part of a HTML editor===&lt;br /&gt;
&lt;br /&gt;
The key thing here is a move away from storing any absolute URLs to files in our HTML texts.  Instead we&#039;ll store relative names.&lt;br /&gt;
&lt;br /&gt;
1. The moodleform for a textarea (HTML editor) will require a path to the filearea associated with this HTML.  eg &#039;&#039;&#039;wwwroot/pluginfile.php/13/content/0/&#039;&#039;&#039;.  This would have to be the user_draft area if the filearea doesn&#039;t exist yet  eg &#039;&#039;&#039;wwwroot/draftfile.php/userid/tempfile/uniquelementid&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
2. All textarea content will also need to have str_replace done on it to replace @@pluginfile@@/somefilenames.jpg in the content to use this path so that it comes up right in the editor.  (Note this also needs to be done on every format_text command too when showing this text.)&lt;br /&gt;
&lt;br /&gt;
3. The path parameter also needs to be added to the editor configuration in the current page.&lt;br /&gt;
&lt;br /&gt;
4. Editor plugins can be modified to look for these variables in the editor configuration.&lt;br /&gt;
&lt;br /&gt;
5. When adding an image or other media element,  the same AJAX repository picker will show up as a popup div to allow people to pick from any repository and choose files to download.  The repository picker is responsible for downloading the file in real-time, storing it as a user temporary file if the filearea doesn&#039;t already exist, prefixing the supplied path to the filename and returning a URL back to the dialog text input before closing.&lt;br /&gt;
&lt;br /&gt;
6. On submission, and after the HTML is stored, we might now have a new permanent filearea, so we&#039;ll need to update any associated temporary files to make sure they have the proper file area information.&lt;br /&gt;
&lt;br /&gt;
==Repository plugins==&lt;br /&gt;
&lt;br /&gt;
===Required elements===&lt;br /&gt;
&lt;br /&gt;
Each repository plugin is required to contain the following elements:&lt;br /&gt;
&lt;br /&gt;
====class repository()====&lt;br /&gt;
&lt;br /&gt;
This class implements the interface to a particular repository, for browsing, selecting and updating files.  The base class (repository) is defined in /repository/lib.php, while each repository defines an inherited class (eg repository_alfresco) in /repository/repositoryname/repository.class.php&lt;br /&gt;
&lt;br /&gt;
===Optional elements===&lt;br /&gt;
&lt;br /&gt;
Repositories can redefine any of these methods as required (and in some instances, MUST redefine them):&lt;br /&gt;
&lt;br /&gt;
====__construct($repositoryid, $contextid, $options=array(), $readonly)====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;MUST&#039;&#039;&#039; redefine&lt;br /&gt;
&lt;br /&gt;
Accept necessary parameters, and do initialization of repository.&lt;br /&gt;
&lt;br /&gt;
====get_file($url, $file = &#039;&#039;)====&lt;br /&gt;
&lt;br /&gt;
Given a URL, download a file from there, save the file in a temporary directory.&lt;br /&gt;
&lt;br /&gt;
====get_link($info)====&lt;br /&gt;
Get the url of external resource&lt;br /&gt;
&lt;br /&gt;
====get_listing($path=&#039;/&#039;, $page=&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;)====&lt;br /&gt;
&lt;br /&gt;
Given a path, and perhaps a search, get a listing of files. In the case of AJAX file picker, this function should return json format Javascript array.&lt;br /&gt;
&lt;br /&gt;
====search($keyword)====&lt;br /&gt;
Search repository by given keyword, it will return an array of the same format of get_listing&lt;br /&gt;
&lt;br /&gt;
====print_login()====&lt;br /&gt;
&lt;br /&gt;
Show the login screen, if required. In the case of AJAX file picker, this function should return json format array which defined the login form.&lt;br /&gt;
&lt;br /&gt;
====print_search==== &lt;br /&gt;
&lt;br /&gt;
Print the search form, it will return a json string&lt;br /&gt;
&lt;br /&gt;
====get_meta()====&lt;br /&gt;
Return information for creating ajax request, it is private function, you don&#039;t need to rewrite it.&lt;br /&gt;
&lt;br /&gt;
====create()====&lt;br /&gt;
Create an instance&lt;br /&gt;
&lt;br /&gt;
====delete()====&lt;br /&gt;
Delete this instance from `repository` table&lt;br /&gt;
&lt;br /&gt;
====hide()====&lt;br /&gt;
Hide a repository instance from file picker list&lt;br /&gt;
&lt;br /&gt;
====set_option()====&lt;br /&gt;
set options in data1-data5 fields, can be overrided&lt;br /&gt;
&lt;br /&gt;
====get_option()====&lt;br /&gt;
get option list or a specific option from database&lt;br /&gt;
&lt;br /&gt;
====get_type_option_names()====&lt;br /&gt;
If this plugin needs admin settings, please refine this function to return option names.&lt;br /&gt;
&lt;br /&gt;
====type_config_form()====&lt;br /&gt;
If get_type_option_names return non empty array, this function &#039;&#039;&#039;MUST&#039;&#039;&#039; redefine, it will help to build the setting form.&lt;br /&gt;
&lt;br /&gt;
====type_form_validation()====&lt;br /&gt;
This function can be used for validating the data submitted by plugin setting form.&lt;br /&gt;
&lt;br /&gt;
====get_instance_option_names()====&lt;br /&gt;
If plugin instance needs settings, this function will return instance option names.&lt;br /&gt;
&lt;br /&gt;
====instance_config_form()====&lt;br /&gt;
If get_instance_option_names return non empty array, this function &#039;&#039;&#039;MUST&#039;&#039;&#039; redefine, it will help to build the instance setting form.&lt;br /&gt;
&lt;br /&gt;
====instance_form_validation()====&lt;br /&gt;
This function can be used for validating the data submitted by instance setting form.&lt;br /&gt;
&lt;br /&gt;
====supported_filetypes()====&lt;br /&gt;
What file types are supported by this repository plugin, it will return an array, the file type name is defined in a [http://freemind.sourceforge.net/wiki/index.php/Main_Page freemind] file in lib/file/file_types.mm&lt;br /&gt;
&lt;br /&gt;
====supported_returntypes()====&lt;br /&gt;
The repository plugin could return external link, copy files to moodle or create file references to external repository. If the plugin supports file links only, developer should override this function to return FILE_EXTERNAL, if the plugin supports copying files only, it should return FILE_INTERNAL, if supports creating file references only, it returns FILE_REFERENCE. If repository supports more than one return types, use bit operation, for example &amp;lt;code php&amp;gt;return FILE_INTERNAL | FILE_REFERENCE;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On the other hand, form elements, such as filemanager and filepicker can indicate what types of file can be accepted, for example, in assignment module, only copying file is accepted (to prevent cheating), so when adding filemanager element to moodle form, we use this option: &amp;lt;code php&amp;gt;$options[&#039;return_types&#039;] = FILE_INTERNAL;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Moodle 2.3 onward, filemanager&#039;s default return_types is FILE_INTERNAL | FILE_REFERENCE&lt;br /&gt;
&lt;br /&gt;
====filter()====&lt;br /&gt;
Filter file listing to exclude specific file types&lt;br /&gt;
&lt;br /&gt;
===icon.png===&lt;br /&gt;
&lt;br /&gt;
A logo that represents the repository.  Ideally square but we should handle all sizes.&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
* [[Repository Administration Specification]]&lt;br /&gt;
* [[Repository Interface for Moodle/Course/User]]&lt;br /&gt;
* [[Repository plugins]]&lt;br /&gt;
* [[Repository File Picker]]&lt;br /&gt;
* [[File API]]&lt;br /&gt;
* [[Portfolio API]]&lt;br /&gt;
* MDL-13766 and MDL-16543 Repository API Meta issues&lt;br /&gt;
&lt;br /&gt;
[[Category:Repositories]]&lt;br /&gt;
[[Category:Interfaces]]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Repository_API&amp;diff=33602</id>
		<title>Repository API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Repository_API&amp;diff=33602"/>
		<updated>2012-04-26T08:17:25Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: /* supported_returntypes() */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle_2.0}}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;p class=&amp;quot;note&amp;quot;&amp;gt;&lt;br /&gt;
The page is open for everyone so everyone can help correct mistakes and help with the evolution of this document.  However, if you have questions to ask, problems to report or major changes to suggest, please add them to the [[Development_talk:Repository_API|page comments]], or start a discussion in the [http://moodle.org/mod/forum/view.php?id=1807 Repositories forum]. We&#039;ll endeavor to merge all such suggestions into the further development and fix all kinds of problems.&lt;br /&gt;
&amp;lt;/p&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Note that parts of this document have been now split off into a separate [[File_API]]&lt;br /&gt;
&lt;br /&gt;
==Objectives==&lt;br /&gt;
&lt;br /&gt;
# Allow all Moodle users to easily bring content into Moodle from external repositories&lt;br /&gt;
# Provide a consistent interface to any external repository, for any Moodle module&lt;br /&gt;
&lt;br /&gt;
==Use cases==&lt;br /&gt;
&lt;br /&gt;
===Teacher adding an external file as a new resource===&lt;br /&gt;
&lt;br /&gt;
# Teacher wants to add a new resource to a course &lt;br /&gt;
# Teacher clicks the &amp;quot;Choose a resource&amp;quot; button&lt;br /&gt;
# Teacher is presented with a simple file picker to choose a file (with a menu to switch between multiple configured repositories)&lt;br /&gt;
# Teacher chooses a file in an external repository&lt;br /&gt;
# File is COPIED into Moodle and stored by the resource module&lt;br /&gt;
# File is marked as owned by that user&lt;br /&gt;
# Whenever someone wants to view that file, the resource module controls access  (see [[File API]] )&lt;br /&gt;
&lt;br /&gt;
===Teacher linking to an external file as a new resource (think video repository) ===&lt;br /&gt;
&lt;br /&gt;
# Teacher wants to display a file in the repository &lt;br /&gt;
# Teacher clicks the &amp;quot;Choose a resource&amp;quot; button&lt;br /&gt;
# Teacher is presented with a simple file picker to choose a file (with a menu to switch between multiple configured repositories)&lt;br /&gt;
# Teacher chooses a file in an external repository&lt;br /&gt;
# Link to the file is COPIED into Moodle and stored by the resource module&lt;br /&gt;
# Link is marked as owned by that user&lt;br /&gt;
# Whenever someone wants to follow that link, the resource module controls access  (see [[File API]] )&lt;br /&gt;
&lt;br /&gt;
===Student submitting an assignment===&lt;br /&gt;
# Student needs to submit an assignment and presses the &amp;quot;Choose files&amp;quot; button&lt;br /&gt;
# Student sees a &amp;quot;file picker&amp;quot; where they can see files listed on any of several configured repositories ([https://docs.moodle.org/en/Image:Filepicker_login.jpg file picker login], [https://docs.moodle.org/en/Image:Filepicker_browser.jpg file picker browser], [https://docs.moodle.org/en/Image:Filepicker_search.jpg file picker search])&lt;br /&gt;
# Student chooses MySpace from the list&lt;br /&gt;
# Student is prompted to enter MySpace username/password (if admin allows it, a checkbox could be there to &amp;quot;remember this for next time&amp;quot; but remember security)&lt;br /&gt;
# Student sees their files in MySpace and chooses one or more&lt;br /&gt;
# Files are copied from MySpace to Moodle &lt;br /&gt;
# Assignment module controls the permissions so that only the Student and assignment graders can see the file (other students would not have permission).&lt;br /&gt;
&lt;br /&gt;
===Student attaching an image to a forum===&lt;br /&gt;
# Student needs to attach an image and presses the &amp;quot;Choose files&amp;quot; button in the posting screen&lt;br /&gt;
# Student sees a &amp;quot;file picker&amp;quot; where they can see files listed on any of several configured repositories&lt;br /&gt;
# Student chooses Mahara from the list&lt;br /&gt;
# Student is prompted to enter Mahara username/password&lt;br /&gt;
# Student sees their files in Mahara and chooses one image&lt;br /&gt;
# Image is copied to Moodle &lt;br /&gt;
# Image file is attached to forum post by Forum module (by reference)&lt;br /&gt;
# Forum module controls permissions so that anyone who can read that forum can see that file&lt;br /&gt;
&lt;br /&gt;
===Student attaching the same image in another forum===&lt;br /&gt;
&lt;br /&gt;
# Student needs to submit an assignment and presses the &amp;quot;Choose files&amp;quot; button&lt;br /&gt;
# Student sees a &amp;quot;file picker&amp;quot; where they can see files listed on any of several configured repositories&lt;br /&gt;
# Student chooses &amp;quot;Local files&amp;quot; from the list and sees all the files they&#039;ve permission to use&lt;br /&gt;
# A COPY of the image file is attached to forum post by Forum module&lt;br /&gt;
# Forum module controls access to this file.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
Please add more use cases in this same format&lt;br /&gt;
&lt;br /&gt;
==Mock screenshots==&lt;br /&gt;
When you first call up the file picker and choose a repository, you might be asked to log in (if saving of passwords is not allowed):&lt;br /&gt;
&lt;br /&gt;
[[Image:Filepicker_login.jpg]]&lt;br /&gt;
&lt;br /&gt;
Browsing files could look something like this:&lt;br /&gt;
&lt;br /&gt;
[[Image:Filepicker_browser.jpg]]&lt;br /&gt;
&lt;br /&gt;
And you can also search:&lt;br /&gt;
&lt;br /&gt;
[[Image:Filepicker_search.jpg]]&lt;br /&gt;
&lt;br /&gt;
==General architecture==&lt;br /&gt;
&lt;br /&gt;
Each repository plugin (a standard Moodle plugin stored under /repository/xxx) will subclass the standard API and override methods specific to that repository.&lt;br /&gt;
&lt;br /&gt;
As is usual in Moodle, there will be admin settings to disable/enable certain repository plugins as standard, as well as user settings so that users can add their own personal repositories to the standard list (eg [http://briefcase.yahoo.com Yahoo Briefcase] or [http://docs.google.com Google Docs]) and to select their default repository.&lt;br /&gt;
&lt;br /&gt;
Once a repository has been used the file will usually be copied into Moodle there and then.  However there will also be options to:&lt;br /&gt;
* only return the URL to the file if it&#039;s desired to keep it external (but this does present security and integrity risks), or&lt;br /&gt;
* refresh the local file copy regularly and automatically&lt;br /&gt;
* refresh the file manually if desired&lt;br /&gt;
&lt;br /&gt;
Once in Moodle, it is subject to the [[File API]] for access control like any other file.&lt;br /&gt;
&lt;br /&gt;
==Repository requirements==&lt;br /&gt;
&lt;br /&gt;
From the Moodle point of view, each repository is just a hierarchy of nodes.&lt;br /&gt;
&lt;br /&gt;
The repository MUST provide:&lt;br /&gt;
# A URI to download each node (eg file).&lt;br /&gt;
# A list of the nodes (eg files and directories) under a given node (eg directory).  This allows Moodle to construct a standard browse interface (much like a standard OS file picker).&lt;br /&gt;
&lt;br /&gt;
The repository can OPTIONALLY:&lt;br /&gt;
# Require some authentication credentials &lt;br /&gt;
# Provide more metadata about each node (mime type, size, dates, related files, dublin core stuff, etc)&lt;br /&gt;
# Describe a search facility (so that Moodle can construct a search form)&lt;br /&gt;
# Provide copyright and usage rules (or just information about the rules)&lt;br /&gt;
&lt;br /&gt;
==Repository plugins==&lt;br /&gt;
&lt;br /&gt;
Some plugins I&#039;d like to see developed for the first version are:&lt;br /&gt;
* box - an interface to [http://box.net box.net]&lt;br /&gt;
* mahara - an interface to a Mahara installation&lt;br /&gt;
* Server Files - very similar to the current course-based file manager, except user-based&lt;br /&gt;
* Remote Moodle - an interface to another Moodle site, accessed over a secure mnet connection&lt;br /&gt;
* googledocs - an interface to [http://docs.google.com Google Docs]&lt;br /&gt;
* s3 - an interface to [http://www.amazon.com/gp/browse.html?node=16427261 Amazon S3]&lt;br /&gt;
* flickr - an interface to [http://flickr.com flickr]&lt;br /&gt;
* WebDAV - to access arbitrary external WebDAV servers&lt;br /&gt;
* merlot - an interface to the learning materials in [http://www.merlot.org/merlot/materials.htm Merlot.org]&lt;br /&gt;
* File System - a plugin to list files on local file system, of course, you can mount remote files to this local directory&lt;br /&gt;
* youtube - an interface to [http://youtube.com YouTube]&lt;br /&gt;
* jsr170 - an interface that can talk to anything that supports jsr170 (eg [http://www.alfresco.com/ Alfresco])&lt;br /&gt;
* oki - an OKI emulator allowing us to access things with OKI interfaces,like [http://www.fedora.info/ Fedora]&lt;br /&gt;
* briefcase - an interface to [http://briefcase.yahoo.com/ Yahoo Briefcase]&lt;br /&gt;
* myspace - an interface to MySpace files (perhaps via [http://www.programmableweb.com/api/myspace this MySpace API])&lt;br /&gt;
* skydrive - an interface to Microsoft&#039;s [http://skydrive.live.com/ SkyDrive] files&lt;br /&gt;
* Dropbox - an interface to Dropbox files [http://www.dropbox.com]&lt;br /&gt;
* facebook - an interface to Facebook files&lt;br /&gt;
* [http://www.dspace.org/ Dspace] - a repository from MIT&lt;br /&gt;
* DOOR - another popular open source repository&lt;br /&gt;
* SMB shares - An interface for windows shares e.g. personal folders on network drives. Would need to link with LDAP as usernames will often be wholly/partially the same as network folder names. This could be done using SAMBA, but would also need to work on windows machines natively. See [http://moodle.org/mod/data/view.php?d=13&amp;amp;rid=991 this block] for a linux implementation.&lt;br /&gt;
&lt;br /&gt;
==Tables==&lt;br /&gt;
&lt;br /&gt;
=== repository ===&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;Default&#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;id&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|autoincrementing &lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;type&#039;&#039;&#039;&lt;br /&gt;
|varchar(255)&lt;br /&gt;
|&lt;br /&gt;
|The type of the repository &lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;visible&#039;&#039;&#039;&lt;br /&gt;
|tinyint(1)&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|sortorder&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== repository_instances ===&lt;br /&gt;
&lt;br /&gt;
This table contains one entry for every configured external repository instance.&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;Default&#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;id&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|autoincrementing &lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|name&lt;br /&gt;
|varchar 255&lt;br /&gt;
|&lt;br /&gt;
|A custom name for this repository (non-unique)&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;typeid&#039;&#039;&#039; &lt;br /&gt;
|int(10)&lt;br /&gt;
| &lt;br /&gt;
|The id of repository type&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 person who created this repository instance&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;contextid&#039;&#039;&#039; &lt;br /&gt;
|int(10)&lt;br /&gt;
| &lt;br /&gt;
|The context that this repository is available to ( = system context for site-wide ones)&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|username&lt;br /&gt;
|varchar(255)&lt;br /&gt;
| &lt;br /&gt;
|username to log in with, if required (almost never!)&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|password&lt;br /&gt;
|varchar(255)&lt;br /&gt;
| &lt;br /&gt;
|password to log in with, if required (almost never!)&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|timecreated&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|The time this repository was created&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|timemodified&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|The last time the repository was modified&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== repository_instance_config ===&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;Default&#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;id&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|autoincrementing &lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;instanceid&#039;&#039;&#039;&lt;br /&gt;
|int(int)&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;name&#039;&#039;&#039;&lt;br /&gt;
|varchar(255)&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|value&lt;br /&gt;
|Text&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
===File types===&lt;br /&gt;
&lt;br /&gt;
The context at which someone is inserting a file may require certain file types (eg uploading a new user profile image is only looking for images).  &lt;br /&gt;
&lt;br /&gt;
To support this, the calling code needs to be able to specify the required mimetypes, and the listing code should be able to filter the results based on these mimetypes.  Ideally the repository itself can do the filtering for ultimate speed (though not all repositories will support this).&lt;br /&gt;
&lt;br /&gt;
We will have to develop special new mimetypes for Moodle files like backups (application/vnd.moodle.backup) and IMS learning design (application/vnd.moodle.imsld) etc&lt;br /&gt;
&lt;br /&gt;
==Technical walkthrough==&lt;br /&gt;
&lt;br /&gt;
(See also the functional spec for the [[Repository_File_Picker]] )&lt;br /&gt;
&lt;br /&gt;
There are two main cases where the repository API will be used: as part of a Moodleform to add a file and as part of the HTML editor to add a media element into some HTML).  We also have to cater for the using Moodleforms without Javascript.&lt;br /&gt;
&lt;br /&gt;
In all of these cases the files will be uploaded to Moodle while using the file picker dialog and stored in a temporary file area owned by the currently active user.  It is only AFTER the submission of the entire Moodleform that we will know the full context, itemids to store the file properly, so at this time the file will be copied into the correct filearea.&lt;br /&gt;
&lt;br /&gt;
===Case 1: As part of a Moodleform with Javascript===&lt;br /&gt;
&lt;br /&gt;
1. Moodle module code calls a &amp;quot;filepicker&amp;quot; moodleform item whenever a file is required, which includes the following information to pass to the File API:&lt;br /&gt;
&lt;br /&gt;
 eg $mform-&amp;gt;addElement(&#039;filepicker&#039;, &#039;uniqueelementid&#039;, $fullname, $data)&lt;br /&gt;
 &lt;br /&gt;
2. When rendering the form, Moodle will display a read-only filename field with an &#039;&#039;&#039;&amp;quot;Add file&amp;quot;&#039;&#039;&#039; button next to it.  There will also be a hidden field to store a file reference later (this is what actually gets used, the filename field is just for users to see something).&lt;br /&gt;
&lt;br /&gt;
3. When the add file button is pressed, the form will be &amp;quot;replaced&amp;quot; in the page by a larger resizeable area containing an AJAX file picker.  (After picking the display can be closed).   (There could be a user option to make this a popup window instead, if required)&lt;br /&gt;
&lt;br /&gt;
4. The AJAX file picker interface will list all the active repositories as a menu, and list files in one of several formats (like Windows/Mac/Linux): Details, Names, Icons.&lt;br /&gt;
&lt;br /&gt;
5. For each plugin, the AJAX interface will prompt the user to login first (if required) asking the plugin to log in behind the scenes.  It&#039;ll also ask the plugin to return listing data in response to clicks and searches.  &lt;br /&gt;
&lt;br /&gt;
6. Finally, when the user selects a file and clicks the &amp;quot;Select&amp;quot; button, the AJAX interface will trigger a method in the plugin that will fetch the file and call the File Storage API to &#039;&#039;&#039;store&#039;&#039;&#039; the file using the &#039;&#039;&#039;uniqueelementid&#039;&#039;&#039; and the current user info.  While this is happening, the interface should show some sort of progress bar (ideally) or at least a &amp;quot;loading file&amp;quot; image/sign/message.  &lt;br /&gt;
&lt;br /&gt;
7. After a file has finally been selected we will have a file ID which we can pass back to the original Moodle form (to the hidden field named &#039;&#039;&#039;uniqueelementid_formid&#039;&#039;&#039;).  The picker can then rename the read-only filename field before it hides itself.&lt;br /&gt;
&lt;br /&gt;
8. Submitting the form will trigger the mform processing for this field, which will check fields, create things in the module etc.  Once this has been finally successful the developer must call an mform function to &amp;quot;fix&amp;quot; the info for each file and &amp;quot;move&amp;quot; it into the module file area:&lt;br /&gt;
&lt;br /&gt;
  eg $mform-&amp;gt;store_local_file(&#039;uniqueelementid&#039;, $context, $filearename, $itemid, $filepath);&lt;br /&gt;
&lt;br /&gt;
9. Cron jobs in File Storage api should automatically delete any files in the user&#039;s tempfile area that are older than 7 days or move them into a trash can in the user&#039;s file area (perhaps).&lt;br /&gt;
&lt;br /&gt;
===Case 2: As part of a Moodleform without Javascript===&lt;br /&gt;
&lt;br /&gt;
Steps 1-2 are the same as for the case with Javascript.&lt;br /&gt;
&lt;br /&gt;
3. The add file button is a submit button for the form with a different value.  When the add file button is pressed,&lt;br /&gt;
* the whole form will be &#039;&#039;submitted&#039;&#039; to the original location (but with a different submit button value)&lt;br /&gt;
* moodleforms get_data() will detect this is a &amp;quot;repository save&amp;quot; and can save the full POST info in the current session tagged with the id of the openfile element, together with the URL to return to&lt;br /&gt;
* moodleforms get_data() then redirects the user to a new page showing the main picker interface&lt;br /&gt;
&lt;br /&gt;
4. The file picker interface will have to be a completely new and separate interface from the AJAX one.  It could be a long hierarchy listing, or reload a lot.&lt;br /&gt;
&lt;br /&gt;
5. Finally, when the user selects a file and submits using the &amp;quot;Select&amp;quot; button to picker.php, it will trigger a method in the plugin that will fetch the file and call the [[File_API|File API]] to store the file using the filearea and context information we already had.   While this is happening, the interface can show some sort of progress bar (ideally) or at least a &amp;quot;loading file&amp;quot; image/sign/message.&lt;br /&gt;
&lt;br /&gt;
6. After this, picker.php will redirect/continue back to the original form page.  The form can be constructed as usual, however, when the form is rendered using display() method moodleforms should now look for relevant saved content in the session and use that to override any content in the form (and then delete the saved info in the session).&lt;br /&gt;
&lt;br /&gt;
Steps 8-9 are the same as for the case with Javascript.&lt;br /&gt;
&lt;br /&gt;
===Case 3: As part of a HTML editor===&lt;br /&gt;
&lt;br /&gt;
The key thing here is a move away from storing any absolute URLs to files in our HTML texts.  Instead we&#039;ll store relative names.&lt;br /&gt;
&lt;br /&gt;
1. The moodleform for a textarea (HTML editor) will require a path to the filearea associated with this HTML.  eg &#039;&#039;&#039;wwwroot/pluginfile.php/13/content/0/&#039;&#039;&#039;.  This would have to be the user_draft area if the filearea doesn&#039;t exist yet  eg &#039;&#039;&#039;wwwroot/draftfile.php/userid/tempfile/uniquelementid&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
2. All textarea content will also need to have str_replace done on it to replace @@pluginfile@@/somefilenames.jpg in the content to use this path so that it comes up right in the editor.  (Note this also needs to be done on every format_text command too when showing this text.)&lt;br /&gt;
&lt;br /&gt;
3. The path parameter also needs to be added to the editor configuration in the current page.&lt;br /&gt;
&lt;br /&gt;
4. Editor plugins can be modified to look for these variables in the editor configuration.&lt;br /&gt;
&lt;br /&gt;
5. When adding an image or other media element,  the same AJAX repository picker will show up as a popup div to allow people to pick from any repository and choose files to download.  The repository picker is responsible for downloading the file in real-time, storing it as a user temporary file if the filearea doesn&#039;t already exist, prefixing the supplied path to the filename and returning a URL back to the dialog text input before closing.&lt;br /&gt;
&lt;br /&gt;
6. On submission, and after the HTML is stored, we might now have a new permanent filearea, so we&#039;ll need to update any associated temporary files to make sure they have the proper file area information.&lt;br /&gt;
&lt;br /&gt;
==Repository plugins==&lt;br /&gt;
&lt;br /&gt;
===Required elements===&lt;br /&gt;
&lt;br /&gt;
Each repository plugin is required to contain the following elements:&lt;br /&gt;
&lt;br /&gt;
====class repository()====&lt;br /&gt;
&lt;br /&gt;
This class implements the interface to a particular repository, for browsing, selecting and updating files.  The base class (repository) is defined in /repository/lib.php, while each repository defines an inherited class (eg repository_alfresco) in /repository/repositoryname/repository.class.php&lt;br /&gt;
&lt;br /&gt;
===Optional elements===&lt;br /&gt;
&lt;br /&gt;
Repositories can redefine any of these methods as required (and in some instances, MUST redefine them):&lt;br /&gt;
&lt;br /&gt;
====__construct($repositoryid, $contextid, $options=array(), $readonly)====&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;MUST&#039;&#039;&#039; redefine&lt;br /&gt;
&lt;br /&gt;
Accept necessary parameters, and do initialization of repository.&lt;br /&gt;
&lt;br /&gt;
====get_file($url, $file = &#039;&#039;)====&lt;br /&gt;
&lt;br /&gt;
Given a URL, download a file from there, save the file in a temporary directory.&lt;br /&gt;
&lt;br /&gt;
====get_link($info)====&lt;br /&gt;
Get the url of external resource&lt;br /&gt;
&lt;br /&gt;
====get_listing($path=&#039;/&#039;, $page=&#039;&#039;&#039;&#039;&#039;&#039;&#039;&#039;)====&lt;br /&gt;
&lt;br /&gt;
Given a path, and perhaps a search, get a listing of files. In the case of AJAX file picker, this function should return json format Javascript array.&lt;br /&gt;
&lt;br /&gt;
====search($keyword)====&lt;br /&gt;
Search repository by given keyword, it will return an array of the same format of get_listing&lt;br /&gt;
&lt;br /&gt;
====print_login()====&lt;br /&gt;
&lt;br /&gt;
Show the login screen, if required. In the case of AJAX file picker, this function should return json format array which defined the login form.&lt;br /&gt;
&lt;br /&gt;
====print_search==== &lt;br /&gt;
&lt;br /&gt;
Print the search form, it will return a json string&lt;br /&gt;
&lt;br /&gt;
====get_meta()====&lt;br /&gt;
Return information for creating ajax request, it is private function, you don&#039;t need to rewrite it.&lt;br /&gt;
&lt;br /&gt;
====create()====&lt;br /&gt;
Create an instance&lt;br /&gt;
&lt;br /&gt;
====delete()====&lt;br /&gt;
Delete this instance from `repository` table&lt;br /&gt;
&lt;br /&gt;
====hide()====&lt;br /&gt;
Hide a repository instance from file picker list&lt;br /&gt;
&lt;br /&gt;
====set_option()====&lt;br /&gt;
set options in data1-data5 fields, can be overrided&lt;br /&gt;
&lt;br /&gt;
====get_option()====&lt;br /&gt;
get option list or a specific option from database&lt;br /&gt;
&lt;br /&gt;
====get_type_option_names()====&lt;br /&gt;
If this plugin needs admin settings, please refine this function to return option names.&lt;br /&gt;
&lt;br /&gt;
====type_config_form()====&lt;br /&gt;
If get_type_option_names return non empty array, this function &#039;&#039;&#039;MUST&#039;&#039;&#039; redefine, it will help to build the setting form.&lt;br /&gt;
&lt;br /&gt;
====type_form_validation()====&lt;br /&gt;
This function can be used for validating the data submitted by plugin setting form.&lt;br /&gt;
&lt;br /&gt;
====get_instance_option_names()====&lt;br /&gt;
If plugin instance needs settings, this function will return instance option names.&lt;br /&gt;
&lt;br /&gt;
====instance_config_form()====&lt;br /&gt;
If get_instance_option_names return non empty array, this function &#039;&#039;&#039;MUST&#039;&#039;&#039; redefine, it will help to build the instance setting form.&lt;br /&gt;
&lt;br /&gt;
====instance_form_validation()====&lt;br /&gt;
This function can be used for validating the data submitted by instance setting form.&lt;br /&gt;
&lt;br /&gt;
====supported_filetypes()====&lt;br /&gt;
What file types are supported by this repository plugin, it will return an array, the file type name is defined in a [http://freemind.sourceforge.net/wiki/index.php/Main_Page freemind] file in lib/file/file_types.mm&lt;br /&gt;
&lt;br /&gt;
====supported_returntypes()====&lt;br /&gt;
The repository plugin could support external link, copying files to moodle, create a file reference to external repository. If the plugin support file link only, developer should override this function to return FILE_EXTERNAL, if plugin support copying file only, it should return FILE_INTERNAL, if support create file reference only, it returns FILE_REFERENCE. If repository support more than one return types, use bit operation, for example &amp;lt;code php&amp;gt;return FILE_INTERNAL | FILE_REFERENCE;&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
On the other hand, form elements filemanager and filepicker can indicate what types of file are accepted, for example, in assignment module, only FILE_INTERNAL accepted (to prevent cheating), so when adding filemanager element: &amp;lt;code php&amp;gt;$options[&#039;return_types&#039;] = FILE_INTERNAL;&amp;lt;/code&amp;gt;, moodle 2.3 onward, filemanager&#039;s default return_types is FILE_INTERNAL | FILE_REFERENCE&lt;br /&gt;
&lt;br /&gt;
====filter()====&lt;br /&gt;
Filter file listing to exclude specific file types&lt;br /&gt;
&lt;br /&gt;
===icon.png===&lt;br /&gt;
&lt;br /&gt;
A logo that represents the repository.  Ideally square but we should handle all sizes.&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
* [[Repository Administration Specification]]&lt;br /&gt;
* [[Repository Interface for Moodle/Course/User]]&lt;br /&gt;
* [[Repository plugins]]&lt;br /&gt;
* [[Repository File Picker]]&lt;br /&gt;
* [[File API]]&lt;br /&gt;
* [[Portfolio API]]&lt;br /&gt;
* MDL-13766 and MDL-16543 Repository API Meta issues&lt;br /&gt;
&lt;br /&gt;
[[Category:Repositories]]&lt;br /&gt;
[[Category:Interfaces]]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=32643</id>
		<title>Improved support for external File content</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=32643"/>
		<updated>2012-03-06T09:41:24Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: /* File picker walk-through */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Work in progress}}&lt;br /&gt;
{{Infobox Project&lt;br /&gt;
|name = File synching&lt;br /&gt;
|state = Planning&lt;br /&gt;
|tracker =  MDL-28666&lt;br /&gt;
|discussion = TODO&lt;br /&gt;
|assignee = [[User:Martin Dougiamas|Martin Dougiamas]]&lt;br /&gt;
}}&lt;br /&gt;
{{Moodle 2.2}}&lt;br /&gt;
&lt;br /&gt;
==Problems with Files in 2.0==&lt;br /&gt;
&lt;br /&gt;
* It is not currently easy to use a file in multiple places throughout Moodle and update them all at once&lt;br /&gt;
* It is not currently easy to create a simple shared &amp;quot;course repository&amp;quot; for teachers to use&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Example use cases that will become possible==&lt;br /&gt;
&lt;br /&gt;
A teacher wants to upload a file once and use it in multiple courses.  When they update the file, it should be updated in all their courses automatically.&lt;br /&gt;
&lt;br /&gt;
# The teacher uploads the file to their private file area (or other repository).&lt;br /&gt;
# In each place they want to add the file, they use the file picker to select the file from their private files area (or other repo) and select &amp;quot;link to latest version&amp;quot;.&lt;br /&gt;
# Replacing the file will automatically mean the linked copies use it.&lt;br /&gt;
# If teacher picked a file which is an alias already, moodle will copy the alias, not create an alias to alias.&lt;br /&gt;
&lt;br /&gt;
Several teachers want to create a shared repository of files together&lt;br /&gt;
# One teacher adds a course repository block to the course&lt;br /&gt;
# Using the block, the teacher creates an instance of a &amp;quot;?filesystem? repository&amp;quot; inside the course (essentially a folder).&lt;br /&gt;
# The content of the repository can be edited via the &amp;quot;Course repositories&amp;quot; block.&lt;br /&gt;
# All of the teachers in that course can now see that appear in their filepickers and select files &lt;br /&gt;
# Files can be &amp;quot;linked&amp;quot; as above&lt;br /&gt;
&lt;br /&gt;
A student wants to submit a linked file as an assignment, so they can continue updating it after the assignment due date.&lt;br /&gt;
# This will not be possible, because the assignment will not allow linking to files.&lt;br /&gt;
# The student is forced to upload a copy of a file and this is protected by the assignment module.&lt;br /&gt;
&lt;br /&gt;
==Solution summary==&lt;br /&gt;
&lt;br /&gt;
The basic idea is to allow the CONTENT of files to be stored outside of the filepool, while all the metadata and access is controlled by Moodle exactly the same as it is now.&lt;br /&gt;
&lt;br /&gt;
# Extend Files API with a new &amp;quot;reference&amp;quot; concept, with all files now having a &amp;quot;repositoryid&amp;quot; (the repository instance that the file came from) and a &amp;quot;reference&amp;quot; (the address in that repository of the content of the file).&lt;br /&gt;
# All files &#039;&#039;&#039;copied&#039;&#039;&#039; into Moodle don&#039;t have record in `files_reference` table, but others can specify a UUID (usually a URL with file-specific tokens in it, created by the original user who placed the file) in reference and repostiroyid columns.&lt;br /&gt;
# Improve the filesystem repository plugin to make it easier to create folder repositories on the fly, via a block.&lt;br /&gt;
# Add support to the filepicker UI to add &amp;quot;linking&amp;quot; to more repositories that support it, including the server files and filesystem repository.&lt;br /&gt;
# Virtual files are served via pluginfile.php URLs just like normal files:&lt;br /&gt;
## pluginfile.php uses module callback to determine access (as now), and if it passes then&lt;br /&gt;
## pluginfile.php calls a file logging subsystem to log the fact that this file is being served (useful for copyright reporting, for example)&lt;br /&gt;
## If the file is not &#039;local&#039;, then pluginfile.php uses the relevant repository callback to get the content of the file and streams it to the user with appropriate mimetypes etc&lt;br /&gt;
## The repositories have a way to cache this content in the normal Moodle filepool, to avoid repeated downloads.&lt;br /&gt;
## If an external repository is down or not configured, then the repository plugin can choose to just serve the local cached version (useful for restored backups and disaster tolerance)&lt;br /&gt;
# When a file is linked to another file in the local location, then it has a location of &amp;quot;filepool&amp;quot;.  We need to pay attention to these during:&lt;br /&gt;
## Garbage cleanups &lt;br /&gt;
## Update local caches&lt;br /&gt;
&lt;br /&gt;
Note that the original URL of files in external repositories are never revealed to users.&lt;br /&gt;
&lt;br /&gt;
==Details==&lt;br /&gt;
&lt;br /&gt;
=== File picker walk-through ===&lt;br /&gt;
[[File:File_picker_using_repository_reference_SD.png]]&lt;br /&gt;
# User clicks the &amp;quot;Insert image&amp;quot; button in TinyMCE, then launches our File Picker in the dialog&lt;br /&gt;
# User chooses a repository which supports UUID direct file references (eg Equella, Alfresco etc)&lt;br /&gt;
# A. File picker will ask repository plugin for customised repository UI if supported&lt;br /&gt;
# B. File Picker use repository UI and &amp;lt;object&amp;gt; type to display customised UI in file picker right pane&lt;br /&gt;
# User selects a file from the repository, if is customised UI, file picker Javascript API will be used to trigger file selection event (with all file related information), the repository plugin gives you two choices in the file picker interface  http://tracker.moodle.org/secure/attachment/25823/2011-11-09+15.23.png http://tracker.moodle.org/secure/attachment/25825/File+picker+options.png&lt;br /&gt;
#* C. Use the current version: this will copy the file to Moodle (as now).  No need to reference external content.&lt;br /&gt;
#* C. Use the latest version: this will use a file reference, so that the most recent version is always pulled from the repository&lt;br /&gt;
# D. If &amp;quot;Use the latest version&amp;quot; selected, Repository API will create a file reference, if selected file is already an alias, repository API will copy this alias, not create an alias to alias, repository plugin can cache external files in moodle local directory, but this is optional&lt;br /&gt;
# E. Repository API ask File API to create a file with file parameters and reference.  File API stores the reference and repository instance id in `mdl_files_reference` table and returns the file URL which looks the same as any other Moodle file URL.&lt;br /&gt;
# File picker gives the file URL to TinyMCE&lt;br /&gt;
# When TinyMCE displays the resource, it will cause the browser to call the file URL, which contains pluginfile.php.&lt;br /&gt;
# Pluginfile.php uses File API to send file contents to browsers, if File API detects the requested file is not ordinary moodle files which is located at external repository, File API will ask Repository API for file content, Repository API will firstly look for the cached file, if file is too old or not found (removed  by cron checking), Repository API will fetch the resource (and cache it), then return to File API. Alternatively, Repository API could disable caching, asking for fresh content all the time.&lt;br /&gt;
&lt;br /&gt;
=== File request walk-through ===&lt;br /&gt;
[[File:File_Request_SD.png]]&lt;br /&gt;
&lt;br /&gt;
# A. User request a file&lt;br /&gt;
# B. File API detects if the request file is regular moodle files or located at external repository, if it&#039;s external files, File API will ask repository API to grab the file&lt;br /&gt;
# C. File API collects the file reference information from database, it could be stored in php serialised or JSON format&lt;br /&gt;
# D. File API passes raw file reference information to repository plugin&lt;br /&gt;
# E. Repository plugin will firstly check if file available locally&lt;br /&gt;
# F. If not repository plugin will use file reference information to grab the file&lt;br /&gt;
# G. Repository API returns file content to File API to serve the file&lt;br /&gt;
&lt;br /&gt;
=== Database changes ===&lt;br /&gt;
&lt;br /&gt;
We can create another table for left joining, this requires File API query this table when locating files:&lt;br /&gt;
* New file table `mdl_files_reference`&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `fileid` foreign key of `mdl_files`.`id`&lt;br /&gt;
** `repositoryid`&lt;br /&gt;
** `reference` - can be URL, UUID or other data format, repository plugin callbacks know the meaning of this field. File reference should be cached when adding to moodle, contenthash should be accurate.&lt;br /&gt;
&lt;br /&gt;
* `mdl_files_log` table for files access log, File API should have a new function to insert records to this table&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `userid` (0 if it&#039;s guest), &lt;br /&gt;
** `timeaccess`, &lt;br /&gt;
** `fileid`&lt;br /&gt;
&lt;br /&gt;
=== File API changes ===&lt;br /&gt;
* pluginfile.php and draftfile.php (it&#039;s actually send_stored_file()): when file isn&#039;t local file (`repositoryid` isn&#039;t zero), stored_file instance should ask repository plugin‘s send the file contents, Repository API decide return the cached copy or fresh contents&lt;br /&gt;
* Preserve file reference information when call file_storage::create_file_from_storedfile&lt;br /&gt;
* file_storage::get_area_files should retrieve file reference information, also file_storage::get_file_by_id, file_storage::get_file_by_hash&lt;br /&gt;
* When call file_prepare_draft_area(), it should keep linking files&#039; repository information&lt;br /&gt;
* file_save_draft_files should preserve file reference information&lt;br /&gt;
* new method: file_storage::create_file_from_reference&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function create_file_from_reference($file_record, $repoisitoryid, $reference, array $options = NULL)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
* Cron: we probably need a Repository API function to cache/update external files&lt;br /&gt;
&lt;br /&gt;
=== Repository API changes ===&lt;br /&gt;
&lt;br /&gt;
* Additions to FILE_INTERNAL and FILE_EXTERNAL, we need another type FILE_REFERENCE = 4 // 0100, repository plugin needs to declare what types of files are supported&lt;br /&gt;
&lt;br /&gt;
* When users ask to create a reference (instead of copying) in file picker, Repository API try to cache the file in filepool(special file area for repository), after file downloaded, repository API should ask Files API to create a virtual file in `mdl_files` table, the existing fields stay the same, extra file reference information is stored in `mdl_files_reference` table, link or other format that repository plugins know. File API return the stored_file object, repository API generate the moodle url for users just like other moodle files without revealing the internal reference&lt;br /&gt;
&lt;br /&gt;
* Making repository plugin upgrade and versioning possible, repository plugin may need to update `reference` in `mdl_files_reference` table if reference info changed. (we already have db/upgrade.php, make sure all new plugins have one)&lt;br /&gt;
&lt;br /&gt;
* Cron will use repository plugin callbacks to clean up cache files, repoistory::cron($repositoryid)&lt;br /&gt;
&lt;br /&gt;
* repository::send_file(stored_file $stored_file)&lt;br /&gt;
&lt;br /&gt;
* repository::get_file_reference($ref)&lt;br /&gt;
&lt;br /&gt;
=== Repository plugins changes ===&lt;br /&gt;
* Server files: store file parameters in `reference` field&lt;br /&gt;
* Private files: same as server files plugin&lt;br /&gt;
* Alfresco: Store UUID in `reference` field, but alfresco will change UUID once tomcat restart, may need other information to locate files&lt;br /&gt;
* Flickr private: needs flickr secret and token and photo id to locate the files&lt;br /&gt;
* File system: store file path in `reference` field&lt;br /&gt;
* s3: store file path, s3 repository will use secret and token to fetch the file from s3 no matter files are public or not.&lt;br /&gt;
* EQUELLA&lt;br /&gt;
*# Admin installs EQUELLA and setup parameters for single sign on&lt;br /&gt;
*# Teacher clicks EQUELLA instance, EQUELLA will return an URL of repository UI (plugin code)&lt;br /&gt;
*# Teacher pick a file from EQUELLA, EQUELLA repository UI will revoke file picker JavaScript API to notify moodle download this resource (plugin code)&lt;br /&gt;
*# Repository API stores file UUID and SSO userid, creates file reference in moodle file pool&lt;br /&gt;
*# EQUELLA plugin implements method to download contents using stored UUID and SSO userid&lt;br /&gt;
*# EQUELLA plugin implements method to update/invalidate cached resources (by cron)&lt;br /&gt;
&lt;br /&gt;
=== Content caching ===&lt;br /&gt;
* Create moodledata/repostory/cache directory&lt;br /&gt;
* Generate hash based on file URL and request parameters (provided by repository plugin), not content hash because we cannot send content hash to external repository for file information&lt;br /&gt;
* Cached files are stored using hash code as file name&lt;br /&gt;
&lt;br /&gt;
==== class repository_cache ====&lt;br /&gt;
&lt;br /&gt;
Returned stored_file instance or file path&lt;br /&gt;
&lt;br /&gt;
* store($url, $string_to_be_hashed)&lt;br /&gt;
* get($string_to_be_hashed)&lt;br /&gt;
&lt;br /&gt;
=== Filepicker Javascript API for customizing===&lt;br /&gt;
* File picker should be able to dislable file references by taking an option&lt;br /&gt;
* Support &amp;lt;object&amp;gt; tag in filepicker container&lt;br /&gt;
* Provide Javascript API to allow plugin communicate with filepicker&lt;br /&gt;
** Notify file picker to download file &lt;br /&gt;
** Notify file picker to pop up authentication page&lt;br /&gt;
&lt;br /&gt;
=== File manager to handle virtual files===&lt;br /&gt;
Not much trouble here, need to make sure draftfile.php can serve external resources, because all files managed by file manager is in draft area&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Project]]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=32642</id>
		<title>Improved support for external File content</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=32642"/>
		<updated>2012-03-06T09:28:24Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: /* Solution summary */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Work in progress}}&lt;br /&gt;
{{Infobox Project&lt;br /&gt;
|name = File synching&lt;br /&gt;
|state = Planning&lt;br /&gt;
|tracker =  MDL-28666&lt;br /&gt;
|discussion = TODO&lt;br /&gt;
|assignee = [[User:Martin Dougiamas|Martin Dougiamas]]&lt;br /&gt;
}}&lt;br /&gt;
{{Moodle 2.2}}&lt;br /&gt;
&lt;br /&gt;
==Problems with Files in 2.0==&lt;br /&gt;
&lt;br /&gt;
* It is not currently easy to use a file in multiple places throughout Moodle and update them all at once&lt;br /&gt;
* It is not currently easy to create a simple shared &amp;quot;course repository&amp;quot; for teachers to use&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Example use cases that will become possible==&lt;br /&gt;
&lt;br /&gt;
A teacher wants to upload a file once and use it in multiple courses.  When they update the file, it should be updated in all their courses automatically.&lt;br /&gt;
&lt;br /&gt;
# The teacher uploads the file to their private file area (or other repository).&lt;br /&gt;
# In each place they want to add the file, they use the file picker to select the file from their private files area (or other repo) and select &amp;quot;link to latest version&amp;quot;.&lt;br /&gt;
# Replacing the file will automatically mean the linked copies use it.&lt;br /&gt;
# If teacher picked a file which is an alias already, moodle will copy the alias, not create an alias to alias.&lt;br /&gt;
&lt;br /&gt;
Several teachers want to create a shared repository of files together&lt;br /&gt;
# One teacher adds a course repository block to the course&lt;br /&gt;
# Using the block, the teacher creates an instance of a &amp;quot;?filesystem? repository&amp;quot; inside the course (essentially a folder).&lt;br /&gt;
# The content of the repository can be edited via the &amp;quot;Course repositories&amp;quot; block.&lt;br /&gt;
# All of the teachers in that course can now see that appear in their filepickers and select files &lt;br /&gt;
# Files can be &amp;quot;linked&amp;quot; as above&lt;br /&gt;
&lt;br /&gt;
A student wants to submit a linked file as an assignment, so they can continue updating it after the assignment due date.&lt;br /&gt;
# This will not be possible, because the assignment will not allow linking to files.&lt;br /&gt;
# The student is forced to upload a copy of a file and this is protected by the assignment module.&lt;br /&gt;
&lt;br /&gt;
==Solution summary==&lt;br /&gt;
&lt;br /&gt;
The basic idea is to allow the CONTENT of files to be stored outside of the filepool, while all the metadata and access is controlled by Moodle exactly the same as it is now.&lt;br /&gt;
&lt;br /&gt;
# Extend Files API with a new &amp;quot;reference&amp;quot; concept, with all files now having a &amp;quot;repositoryid&amp;quot; (the repository instance that the file came from) and a &amp;quot;reference&amp;quot; (the address in that repository of the content of the file).&lt;br /&gt;
# All files &#039;&#039;&#039;copied&#039;&#039;&#039; into Moodle don&#039;t have record in `files_reference` table, but others can specify a UUID (usually a URL with file-specific tokens in it, created by the original user who placed the file) in reference and repostiroyid columns.&lt;br /&gt;
# Improve the filesystem repository plugin to make it easier to create folder repositories on the fly, via a block.&lt;br /&gt;
# Add support to the filepicker UI to add &amp;quot;linking&amp;quot; to more repositories that support it, including the server files and filesystem repository.&lt;br /&gt;
# Virtual files are served via pluginfile.php URLs just like normal files:&lt;br /&gt;
## pluginfile.php uses module callback to determine access (as now), and if it passes then&lt;br /&gt;
## pluginfile.php calls a file logging subsystem to log the fact that this file is being served (useful for copyright reporting, for example)&lt;br /&gt;
## If the file is not &#039;local&#039;, then pluginfile.php uses the relevant repository callback to get the content of the file and streams it to the user with appropriate mimetypes etc&lt;br /&gt;
## The repositories have a way to cache this content in the normal Moodle filepool, to avoid repeated downloads.&lt;br /&gt;
## If an external repository is down or not configured, then the repository plugin can choose to just serve the local cached version (useful for restored backups and disaster tolerance)&lt;br /&gt;
# When a file is linked to another file in the local location, then it has a location of &amp;quot;filepool&amp;quot;.  We need to pay attention to these during:&lt;br /&gt;
## Garbage cleanups &lt;br /&gt;
## Update local caches&lt;br /&gt;
&lt;br /&gt;
Note that the original URL of files in external repositories are never revealed to users.&lt;br /&gt;
&lt;br /&gt;
==Details==&lt;br /&gt;
&lt;br /&gt;
=== File picker walk-through ===&lt;br /&gt;
[[File:File_picker_using_repository_reference_SD.png]]&lt;br /&gt;
# User clicks the &amp;quot;Insert image&amp;quot; button in TinyMCE, then launches our File Picker in the dialog&lt;br /&gt;
# User chooses a repository which supports UUID direct file references (eg Equella, Alfresco etc)&lt;br /&gt;
# A. File picker will ask repository plugin for customised repository UI if supported&lt;br /&gt;
# B. File Picker use repository UI and &amp;lt;object&amp;gt; type to display customised UI in file picker right pane&lt;br /&gt;
# User selects a file from the repository, if is customised UI, file picker Javascript API will be used to trigger file selection event (with all file related information), the repository plugin gives you two choices in the file picker interface  http://tracker.moodle.org/secure/attachment/25823/2011-11-09+15.23.png http://tracker.moodle.org/secure/attachment/25825/File+picker+options.png&lt;br /&gt;
#* C. Use the current version: this will copy the file to Moodle (as now).  No need to reference external content.&lt;br /&gt;
#* C. Use the latest version: this will use a file reference, so that the most recent version is always pulled from the repository&lt;br /&gt;
# D. If &amp;quot;Use the latest version&amp;quot; selected, Repository API will download the file as usual but store it for caching, we could locate this cached file when we need it, cron will take responsibility of invalidating/updating it&lt;br /&gt;
# E. Repository API ask File API to create a file with file parameters and reference.  File API stores the reference and repository instance id in `mdl_files_reference` table and returns the file URL which looks the same as any other Moodle file URL.&lt;br /&gt;
# File picker gives the file URL to TinyMCE&lt;br /&gt;
# When TinyMCE displays the resource, it will cause the browser to call the file URL, which contains pluginfile.php.&lt;br /&gt;
# Pluginfile.php uses File API to send file contents to browsers, if File API detects the requested file is not ordinary moodle files which is located at external repository, File API will ask Repository API for file content, Repository API will firstly look for the cached file, if file is too old or not found (removed  by cron checking), Repository API will fetch the resource (and cache it), then return to File API. Alternatively, Repository API could disable caching, asking for fresh content all the time.&lt;br /&gt;
&lt;br /&gt;
=== File request walk-through ===&lt;br /&gt;
[[File:File_Request_SD.png]]&lt;br /&gt;
&lt;br /&gt;
# A. User request a file&lt;br /&gt;
# B. File API detects if the request file is regular moodle files or located at external repository, if it&#039;s external files, File API will ask repository API to grab the file&lt;br /&gt;
# C. File API collects the file reference information from database, it could be stored in php serialised or JSON format&lt;br /&gt;
# D. File API passes raw file reference information to repository plugin&lt;br /&gt;
# E. Repository plugin will firstly check if file available locally&lt;br /&gt;
# F. If not repository plugin will use file reference information to grab the file&lt;br /&gt;
# G. Repository API returns file content to File API to serve the file&lt;br /&gt;
&lt;br /&gt;
=== Database changes ===&lt;br /&gt;
&lt;br /&gt;
We can create another table for left joining, this requires File API query this table when locating files:&lt;br /&gt;
* New file table `mdl_files_reference`&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `fileid` foreign key of `mdl_files`.`id`&lt;br /&gt;
** `repositoryid`&lt;br /&gt;
** `reference` - can be URL, UUID or other data format, repository plugin callbacks know the meaning of this field. File reference should be cached when adding to moodle, contenthash should be accurate.&lt;br /&gt;
&lt;br /&gt;
* `mdl_files_log` table for files access log, File API should have a new function to insert records to this table&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `userid` (0 if it&#039;s guest), &lt;br /&gt;
** `timeaccess`, &lt;br /&gt;
** `fileid`&lt;br /&gt;
&lt;br /&gt;
=== File API changes ===&lt;br /&gt;
* pluginfile.php and draftfile.php (it&#039;s actually send_stored_file()): when file isn&#039;t local file (`repositoryid` isn&#039;t zero), stored_file instance should ask repository plugin‘s send the file contents, Repository API decide return the cached copy or fresh contents&lt;br /&gt;
* Preserve file reference information when call file_storage::create_file_from_storedfile&lt;br /&gt;
* file_storage::get_area_files should retrieve file reference information, also file_storage::get_file_by_id, file_storage::get_file_by_hash&lt;br /&gt;
* When call file_prepare_draft_area(), it should keep linking files&#039; repository information&lt;br /&gt;
* file_save_draft_files should preserve file reference information&lt;br /&gt;
* new method: file_storage::create_file_from_reference&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function create_file_from_reference($file_record, $repoisitoryid, $reference, array $options = NULL)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
* Cron: we probably need a Repository API function to cache/update external files&lt;br /&gt;
&lt;br /&gt;
=== Repository API changes ===&lt;br /&gt;
&lt;br /&gt;
* Additions to FILE_INTERNAL and FILE_EXTERNAL, we need another type FILE_REFERENCE = 4 // 0100, repository plugin needs to declare what types of files are supported&lt;br /&gt;
&lt;br /&gt;
* When users ask to create a reference (instead of copying) in file picker, Repository API try to cache the file in filepool(special file area for repository), after file downloaded, repository API should ask Files API to create a virtual file in `mdl_files` table, the existing fields stay the same, extra file reference information is stored in `mdl_files_reference` table, link or other format that repository plugins know. File API return the stored_file object, repository API generate the moodle url for users just like other moodle files without revealing the internal reference&lt;br /&gt;
&lt;br /&gt;
* Making repository plugin upgrade and versioning possible, repository plugin may need to update `reference` in `mdl_files_reference` table if reference info changed. (we already have db/upgrade.php, make sure all new plugins have one)&lt;br /&gt;
&lt;br /&gt;
* Cron will use repository plugin callbacks to clean up cache files, repoistory::cron($repositoryid)&lt;br /&gt;
&lt;br /&gt;
* repository::send_file(stored_file $stored_file)&lt;br /&gt;
&lt;br /&gt;
* repository::get_file_reference($ref)&lt;br /&gt;
&lt;br /&gt;
=== Repository plugins changes ===&lt;br /&gt;
* Server files: store file parameters in `reference` field&lt;br /&gt;
* Private files: same as server files plugin&lt;br /&gt;
* Alfresco: Store UUID in `reference` field, but alfresco will change UUID once tomcat restart, may need other information to locate files&lt;br /&gt;
* Flickr private: needs flickr secret and token and photo id to locate the files&lt;br /&gt;
* File system: store file path in `reference` field&lt;br /&gt;
* s3: store file path, s3 repository will use secret and token to fetch the file from s3 no matter files are public or not.&lt;br /&gt;
* EQUELLA&lt;br /&gt;
*# Admin installs EQUELLA and setup parameters for single sign on&lt;br /&gt;
*# Teacher clicks EQUELLA instance, EQUELLA will return an URL of repository UI (plugin code)&lt;br /&gt;
*# Teacher pick a file from EQUELLA, EQUELLA repository UI will revoke file picker JavaScript API to notify moodle download this resource (plugin code)&lt;br /&gt;
*# Repository API stores file UUID and SSO userid, creates file reference in moodle file pool&lt;br /&gt;
*# EQUELLA plugin implements method to download contents using stored UUID and SSO userid&lt;br /&gt;
*# EQUELLA plugin implements method to update/invalidate cached resources (by cron)&lt;br /&gt;
&lt;br /&gt;
=== Content caching ===&lt;br /&gt;
* Create moodledata/repostory/cache directory&lt;br /&gt;
* Generate hash based on file URL and request parameters (provided by repository plugin), not content hash because we cannot send content hash to external repository for file information&lt;br /&gt;
* Cached files are stored using hash code as file name&lt;br /&gt;
&lt;br /&gt;
==== class repository_cache ====&lt;br /&gt;
&lt;br /&gt;
Returned stored_file instance or file path&lt;br /&gt;
&lt;br /&gt;
* store($url, $string_to_be_hashed)&lt;br /&gt;
* get($string_to_be_hashed)&lt;br /&gt;
&lt;br /&gt;
=== Filepicker Javascript API for customizing===&lt;br /&gt;
* File picker should be able to dislable file references by taking an option&lt;br /&gt;
* Support &amp;lt;object&amp;gt; tag in filepicker container&lt;br /&gt;
* Provide Javascript API to allow plugin communicate with filepicker&lt;br /&gt;
** Notify file picker to download file &lt;br /&gt;
** Notify file picker to pop up authentication page&lt;br /&gt;
&lt;br /&gt;
=== File manager to handle virtual files===&lt;br /&gt;
Not much trouble here, need to make sure draftfile.php can serve external resources, because all files managed by file manager is in draft area&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Project]]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=32641</id>
		<title>Improved support for external File content</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=32641"/>
		<updated>2012-03-06T09:27:37Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: /* Repository API changes */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Work in progress}}&lt;br /&gt;
{{Infobox Project&lt;br /&gt;
|name = File synching&lt;br /&gt;
|state = Planning&lt;br /&gt;
|tracker =  MDL-28666&lt;br /&gt;
|discussion = TODO&lt;br /&gt;
|assignee = [[User:Martin Dougiamas|Martin Dougiamas]]&lt;br /&gt;
}}&lt;br /&gt;
{{Moodle 2.2}}&lt;br /&gt;
&lt;br /&gt;
==Problems with Files in 2.0==&lt;br /&gt;
&lt;br /&gt;
* It is not currently easy to use a file in multiple places throughout Moodle and update them all at once&lt;br /&gt;
* It is not currently easy to create a simple shared &amp;quot;course repository&amp;quot; for teachers to use&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Example use cases that will become possible==&lt;br /&gt;
&lt;br /&gt;
A teacher wants to upload a file once and use it in multiple courses.  When they update the file, it should be updated in all their courses automatically.&lt;br /&gt;
&lt;br /&gt;
# The teacher uploads the file to their private file area (or other repository).&lt;br /&gt;
# In each place they want to add the file, they use the file picker to select the file from their private files area (or other repo) and select &amp;quot;link to latest version&amp;quot;.&lt;br /&gt;
# Replacing the file will automatically mean the linked copies use it.&lt;br /&gt;
# If teacher picked a file which is an alias already, moodle will copy the alias, not create an alias to alias.&lt;br /&gt;
&lt;br /&gt;
Several teachers want to create a shared repository of files together&lt;br /&gt;
# One teacher adds a course repository block to the course&lt;br /&gt;
# Using the block, the teacher creates an instance of a &amp;quot;?filesystem? repository&amp;quot; inside the course (essentially a folder).&lt;br /&gt;
# The content of the repository can be edited via the &amp;quot;Course repositories&amp;quot; block.&lt;br /&gt;
# All of the teachers in that course can now see that appear in their filepickers and select files &lt;br /&gt;
# Files can be &amp;quot;linked&amp;quot; as above&lt;br /&gt;
&lt;br /&gt;
A student wants to submit a linked file as an assignment, so they can continue updating it after the assignment due date.&lt;br /&gt;
# This will not be possible, because the assignment will not allow linking to files.&lt;br /&gt;
# The student is forced to upload a copy of a file and this is protected by the assignment module.&lt;br /&gt;
&lt;br /&gt;
==Solution summary==&lt;br /&gt;
&lt;br /&gt;
The basic idea is to allow the CONTENT of files to be stored outside of the filepool, while all the metadata and access is controlled by Moodle exactly the same as it is now.&lt;br /&gt;
&lt;br /&gt;
# Extend Files API with a new &amp;quot;reference&amp;quot; concept, with all files now having a &amp;quot;repositoryid&amp;quot; (the repository instance that the file came from) and a &amp;quot;reference&amp;quot; (the address in that repository of the content of the file).&lt;br /&gt;
# All files &#039;&#039;&#039;copied&#039;&#039;&#039; into Moodle don&#039;t have record in `files_reference` table, but others can specify a UUID (usually a URL with file-specific tokens in it, created by the original user who placed the file) in reference and repostiroyid columns.&lt;br /&gt;
# Improve the filesystem repository plugin to make it easier to create folder repositories on the fly, via a block.&lt;br /&gt;
# Add support to the filepicker UI to add &amp;quot;linking&amp;quot; to more repositories that support it, including the server files and filesystem repository.&lt;br /&gt;
# Virtual files are served via pluginfile.php URLs just like normal files:&lt;br /&gt;
## pluginfile.php uses module callback to determine access (as now), and if it passes then&lt;br /&gt;
## pluginfile.php calls a file logging subsystem to log the fact that this file is being served (useful for copyright reporting, for example)&lt;br /&gt;
## If the file is not &#039;local&#039;, then pluginfile.php uses the relevant repository callback to get the content of the file and streams it to the user with appropriate mimetypes etc&lt;br /&gt;
## The repositories have a way to cache this content in the normal Moodle filepool, to avoid repeated downloads.&lt;br /&gt;
## If an external repository is down or not configured, then the repository plugin can choose to just serve the local cached version (useful for restored backups and disaster tolerance)&lt;br /&gt;
# When a file is linked to another another file in the local location, then it has a location of &amp;quot;filepool&amp;quot;.  We need to pay attention to these during:&lt;br /&gt;
## Garbage cleanups &lt;br /&gt;
## Deleting the original file should create real copies of that file where required (user will be informed).&lt;br /&gt;
&lt;br /&gt;
Note that the original URL of files in external repositories are never revealed to users.&lt;br /&gt;
&lt;br /&gt;
==Details==&lt;br /&gt;
&lt;br /&gt;
=== File picker walk-through ===&lt;br /&gt;
[[File:File_picker_using_repository_reference_SD.png]]&lt;br /&gt;
# User clicks the &amp;quot;Insert image&amp;quot; button in TinyMCE, then launches our File Picker in the dialog&lt;br /&gt;
# User chooses a repository which supports UUID direct file references (eg Equella, Alfresco etc)&lt;br /&gt;
# A. File picker will ask repository plugin for customised repository UI if supported&lt;br /&gt;
# B. File Picker use repository UI and &amp;lt;object&amp;gt; type to display customised UI in file picker right pane&lt;br /&gt;
# User selects a file from the repository, if is customised UI, file picker Javascript API will be used to trigger file selection event (with all file related information), the repository plugin gives you two choices in the file picker interface  http://tracker.moodle.org/secure/attachment/25823/2011-11-09+15.23.png http://tracker.moodle.org/secure/attachment/25825/File+picker+options.png&lt;br /&gt;
#* C. Use the current version: this will copy the file to Moodle (as now).  No need to reference external content.&lt;br /&gt;
#* C. Use the latest version: this will use a file reference, so that the most recent version is always pulled from the repository&lt;br /&gt;
# D. If &amp;quot;Use the latest version&amp;quot; selected, Repository API will download the file as usual but store it for caching, we could locate this cached file when we need it, cron will take responsibility of invalidating/updating it&lt;br /&gt;
# E. Repository API ask File API to create a file with file parameters and reference.  File API stores the reference and repository instance id in `mdl_files_reference` table and returns the file URL which looks the same as any other Moodle file URL.&lt;br /&gt;
# File picker gives the file URL to TinyMCE&lt;br /&gt;
# When TinyMCE displays the resource, it will cause the browser to call the file URL, which contains pluginfile.php.&lt;br /&gt;
# Pluginfile.php uses File API to send file contents to browsers, if File API detects the requested file is not ordinary moodle files which is located at external repository, File API will ask Repository API for file content, Repository API will firstly look for the cached file, if file is too old or not found (removed  by cron checking), Repository API will fetch the resource (and cache it), then return to File API. Alternatively, Repository API could disable caching, asking for fresh content all the time.&lt;br /&gt;
&lt;br /&gt;
=== File request walk-through ===&lt;br /&gt;
[[File:File_Request_SD.png]]&lt;br /&gt;
&lt;br /&gt;
# A. User request a file&lt;br /&gt;
# B. File API detects if the request file is regular moodle files or located at external repository, if it&#039;s external files, File API will ask repository API to grab the file&lt;br /&gt;
# C. File API collects the file reference information from database, it could be stored in php serialised or JSON format&lt;br /&gt;
# D. File API passes raw file reference information to repository plugin&lt;br /&gt;
# E. Repository plugin will firstly check if file available locally&lt;br /&gt;
# F. If not repository plugin will use file reference information to grab the file&lt;br /&gt;
# G. Repository API returns file content to File API to serve the file&lt;br /&gt;
&lt;br /&gt;
=== Database changes ===&lt;br /&gt;
&lt;br /&gt;
We can create another table for left joining, this requires File API query this table when locating files:&lt;br /&gt;
* New file table `mdl_files_reference`&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `fileid` foreign key of `mdl_files`.`id`&lt;br /&gt;
** `repositoryid`&lt;br /&gt;
** `reference` - can be URL, UUID or other data format, repository plugin callbacks know the meaning of this field. File reference should be cached when adding to moodle, contenthash should be accurate.&lt;br /&gt;
&lt;br /&gt;
* `mdl_files_log` table for files access log, File API should have a new function to insert records to this table&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `userid` (0 if it&#039;s guest), &lt;br /&gt;
** `timeaccess`, &lt;br /&gt;
** `fileid`&lt;br /&gt;
&lt;br /&gt;
=== File API changes ===&lt;br /&gt;
* pluginfile.php and draftfile.php (it&#039;s actually send_stored_file()): when file isn&#039;t local file (`repositoryid` isn&#039;t zero), stored_file instance should ask repository plugin‘s send the file contents, Repository API decide return the cached copy or fresh contents&lt;br /&gt;
* Preserve file reference information when call file_storage::create_file_from_storedfile&lt;br /&gt;
* file_storage::get_area_files should retrieve file reference information, also file_storage::get_file_by_id, file_storage::get_file_by_hash&lt;br /&gt;
* When call file_prepare_draft_area(), it should keep linking files&#039; repository information&lt;br /&gt;
* file_save_draft_files should preserve file reference information&lt;br /&gt;
* new method: file_storage::create_file_from_reference&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function create_file_from_reference($file_record, $repoisitoryid, $reference, array $options = NULL)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
* Cron: we probably need a Repository API function to cache/update external files&lt;br /&gt;
&lt;br /&gt;
=== Repository API changes ===&lt;br /&gt;
&lt;br /&gt;
* Additions to FILE_INTERNAL and FILE_EXTERNAL, we need another type FILE_REFERENCE = 4 // 0100, repository plugin needs to declare what types of files are supported&lt;br /&gt;
&lt;br /&gt;
* When users ask to create a reference (instead of copying) in file picker, Repository API try to cache the file in filepool(special file area for repository), after file downloaded, repository API should ask Files API to create a virtual file in `mdl_files` table, the existing fields stay the same, extra file reference information is stored in `mdl_files_reference` table, link or other format that repository plugins know. File API return the stored_file object, repository API generate the moodle url for users just like other moodle files without revealing the internal reference&lt;br /&gt;
&lt;br /&gt;
* Making repository plugin upgrade and versioning possible, repository plugin may need to update `reference` in `mdl_files_reference` table if reference info changed. (we already have db/upgrade.php, make sure all new plugins have one)&lt;br /&gt;
&lt;br /&gt;
* Cron will use repository plugin callbacks to clean up cache files, repoistory::cron($repositoryid)&lt;br /&gt;
&lt;br /&gt;
* repository::send_file(stored_file $stored_file)&lt;br /&gt;
&lt;br /&gt;
* repository::get_file_reference($ref)&lt;br /&gt;
&lt;br /&gt;
=== Repository plugins changes ===&lt;br /&gt;
* Server files: store file parameters in `reference` field&lt;br /&gt;
* Private files: same as server files plugin&lt;br /&gt;
* Alfresco: Store UUID in `reference` field, but alfresco will change UUID once tomcat restart, may need other information to locate files&lt;br /&gt;
* Flickr private: needs flickr secret and token and photo id to locate the files&lt;br /&gt;
* File system: store file path in `reference` field&lt;br /&gt;
* s3: store file path, s3 repository will use secret and token to fetch the file from s3 no matter files are public or not.&lt;br /&gt;
* EQUELLA&lt;br /&gt;
*# Admin installs EQUELLA and setup parameters for single sign on&lt;br /&gt;
*# Teacher clicks EQUELLA instance, EQUELLA will return an URL of repository UI (plugin code)&lt;br /&gt;
*# Teacher pick a file from EQUELLA, EQUELLA repository UI will revoke file picker JavaScript API to notify moodle download this resource (plugin code)&lt;br /&gt;
*# Repository API stores file UUID and SSO userid, creates file reference in moodle file pool&lt;br /&gt;
*# EQUELLA plugin implements method to download contents using stored UUID and SSO userid&lt;br /&gt;
*# EQUELLA plugin implements method to update/invalidate cached resources (by cron)&lt;br /&gt;
&lt;br /&gt;
=== Content caching ===&lt;br /&gt;
* Create moodledata/repostory/cache directory&lt;br /&gt;
* Generate hash based on file URL and request parameters (provided by repository plugin), not content hash because we cannot send content hash to external repository for file information&lt;br /&gt;
* Cached files are stored using hash code as file name&lt;br /&gt;
&lt;br /&gt;
==== class repository_cache ====&lt;br /&gt;
&lt;br /&gt;
Returned stored_file instance or file path&lt;br /&gt;
&lt;br /&gt;
* store($url, $string_to_be_hashed)&lt;br /&gt;
* get($string_to_be_hashed)&lt;br /&gt;
&lt;br /&gt;
=== Filepicker Javascript API for customizing===&lt;br /&gt;
* File picker should be able to dislable file references by taking an option&lt;br /&gt;
* Support &amp;lt;object&amp;gt; tag in filepicker container&lt;br /&gt;
* Provide Javascript API to allow plugin communicate with filepicker&lt;br /&gt;
** Notify file picker to download file &lt;br /&gt;
** Notify file picker to pop up authentication page&lt;br /&gt;
&lt;br /&gt;
=== File manager to handle virtual files===&lt;br /&gt;
Not much trouble here, need to make sure draftfile.php can serve external resources, because all files managed by file manager is in draft area&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Project]]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=32640</id>
		<title>Improved support for external File content</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=32640"/>
		<updated>2012-03-06T09:27:17Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: /* File API changes */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Work in progress}}&lt;br /&gt;
{{Infobox Project&lt;br /&gt;
|name = File synching&lt;br /&gt;
|state = Planning&lt;br /&gt;
|tracker =  MDL-28666&lt;br /&gt;
|discussion = TODO&lt;br /&gt;
|assignee = [[User:Martin Dougiamas|Martin Dougiamas]]&lt;br /&gt;
}}&lt;br /&gt;
{{Moodle 2.2}}&lt;br /&gt;
&lt;br /&gt;
==Problems with Files in 2.0==&lt;br /&gt;
&lt;br /&gt;
* It is not currently easy to use a file in multiple places throughout Moodle and update them all at once&lt;br /&gt;
* It is not currently easy to create a simple shared &amp;quot;course repository&amp;quot; for teachers to use&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Example use cases that will become possible==&lt;br /&gt;
&lt;br /&gt;
A teacher wants to upload a file once and use it in multiple courses.  When they update the file, it should be updated in all their courses automatically.&lt;br /&gt;
&lt;br /&gt;
# The teacher uploads the file to their private file area (or other repository).&lt;br /&gt;
# In each place they want to add the file, they use the file picker to select the file from their private files area (or other repo) and select &amp;quot;link to latest version&amp;quot;.&lt;br /&gt;
# Replacing the file will automatically mean the linked copies use it.&lt;br /&gt;
# If teacher picked a file which is an alias already, moodle will copy the alias, not create an alias to alias.&lt;br /&gt;
&lt;br /&gt;
Several teachers want to create a shared repository of files together&lt;br /&gt;
# One teacher adds a course repository block to the course&lt;br /&gt;
# Using the block, the teacher creates an instance of a &amp;quot;?filesystem? repository&amp;quot; inside the course (essentially a folder).&lt;br /&gt;
# The content of the repository can be edited via the &amp;quot;Course repositories&amp;quot; block.&lt;br /&gt;
# All of the teachers in that course can now see that appear in their filepickers and select files &lt;br /&gt;
# Files can be &amp;quot;linked&amp;quot; as above&lt;br /&gt;
&lt;br /&gt;
A student wants to submit a linked file as an assignment, so they can continue updating it after the assignment due date.&lt;br /&gt;
# This will not be possible, because the assignment will not allow linking to files.&lt;br /&gt;
# The student is forced to upload a copy of a file and this is protected by the assignment module.&lt;br /&gt;
&lt;br /&gt;
==Solution summary==&lt;br /&gt;
&lt;br /&gt;
The basic idea is to allow the CONTENT of files to be stored outside of the filepool, while all the metadata and access is controlled by Moodle exactly the same as it is now.&lt;br /&gt;
&lt;br /&gt;
# Extend Files API with a new &amp;quot;reference&amp;quot; concept, with all files now having a &amp;quot;repositoryid&amp;quot; (the repository instance that the file came from) and a &amp;quot;reference&amp;quot; (the address in that repository of the content of the file).&lt;br /&gt;
# All files &#039;&#039;&#039;copied&#039;&#039;&#039; into Moodle don&#039;t have record in `files_reference` table, but others can specify a UUID (usually a URL with file-specific tokens in it, created by the original user who placed the file) in reference and repostiroyid columns.&lt;br /&gt;
# Improve the filesystem repository plugin to make it easier to create folder repositories on the fly, via a block.&lt;br /&gt;
# Add support to the filepicker UI to add &amp;quot;linking&amp;quot; to more repositories that support it, including the server files and filesystem repository.&lt;br /&gt;
# Virtual files are served via pluginfile.php URLs just like normal files:&lt;br /&gt;
## pluginfile.php uses module callback to determine access (as now), and if it passes then&lt;br /&gt;
## pluginfile.php calls a file logging subsystem to log the fact that this file is being served (useful for copyright reporting, for example)&lt;br /&gt;
## If the file is not &#039;local&#039;, then pluginfile.php uses the relevant repository callback to get the content of the file and streams it to the user with appropriate mimetypes etc&lt;br /&gt;
## The repositories have a way to cache this content in the normal Moodle filepool, to avoid repeated downloads.&lt;br /&gt;
## If an external repository is down or not configured, then the repository plugin can choose to just serve the local cached version (useful for restored backups and disaster tolerance)&lt;br /&gt;
# When a file is linked to another another file in the local location, then it has a location of &amp;quot;filepool&amp;quot;.  We need to pay attention to these during:&lt;br /&gt;
## Garbage cleanups &lt;br /&gt;
## Deleting the original file should create real copies of that file where required (user will be informed).&lt;br /&gt;
&lt;br /&gt;
Note that the original URL of files in external repositories are never revealed to users.&lt;br /&gt;
&lt;br /&gt;
==Details==&lt;br /&gt;
&lt;br /&gt;
=== File picker walk-through ===&lt;br /&gt;
[[File:File_picker_using_repository_reference_SD.png]]&lt;br /&gt;
# User clicks the &amp;quot;Insert image&amp;quot; button in TinyMCE, then launches our File Picker in the dialog&lt;br /&gt;
# User chooses a repository which supports UUID direct file references (eg Equella, Alfresco etc)&lt;br /&gt;
# A. File picker will ask repository plugin for customised repository UI if supported&lt;br /&gt;
# B. File Picker use repository UI and &amp;lt;object&amp;gt; type to display customised UI in file picker right pane&lt;br /&gt;
# User selects a file from the repository, if is customised UI, file picker Javascript API will be used to trigger file selection event (with all file related information), the repository plugin gives you two choices in the file picker interface  http://tracker.moodle.org/secure/attachment/25823/2011-11-09+15.23.png http://tracker.moodle.org/secure/attachment/25825/File+picker+options.png&lt;br /&gt;
#* C. Use the current version: this will copy the file to Moodle (as now).  No need to reference external content.&lt;br /&gt;
#* C. Use the latest version: this will use a file reference, so that the most recent version is always pulled from the repository&lt;br /&gt;
# D. If &amp;quot;Use the latest version&amp;quot; selected, Repository API will download the file as usual but store it for caching, we could locate this cached file when we need it, cron will take responsibility of invalidating/updating it&lt;br /&gt;
# E. Repository API ask File API to create a file with file parameters and reference.  File API stores the reference and repository instance id in `mdl_files_reference` table and returns the file URL which looks the same as any other Moodle file URL.&lt;br /&gt;
# File picker gives the file URL to TinyMCE&lt;br /&gt;
# When TinyMCE displays the resource, it will cause the browser to call the file URL, which contains pluginfile.php.&lt;br /&gt;
# Pluginfile.php uses File API to send file contents to browsers, if File API detects the requested file is not ordinary moodle files which is located at external repository, File API will ask Repository API for file content, Repository API will firstly look for the cached file, if file is too old or not found (removed  by cron checking), Repository API will fetch the resource (and cache it), then return to File API. Alternatively, Repository API could disable caching, asking for fresh content all the time.&lt;br /&gt;
&lt;br /&gt;
=== File request walk-through ===&lt;br /&gt;
[[File:File_Request_SD.png]]&lt;br /&gt;
&lt;br /&gt;
# A. User request a file&lt;br /&gt;
# B. File API detects if the request file is regular moodle files or located at external repository, if it&#039;s external files, File API will ask repository API to grab the file&lt;br /&gt;
# C. File API collects the file reference information from database, it could be stored in php serialised or JSON format&lt;br /&gt;
# D. File API passes raw file reference information to repository plugin&lt;br /&gt;
# E. Repository plugin will firstly check if file available locally&lt;br /&gt;
# F. If not repository plugin will use file reference information to grab the file&lt;br /&gt;
# G. Repository API returns file content to File API to serve the file&lt;br /&gt;
&lt;br /&gt;
=== Database changes ===&lt;br /&gt;
&lt;br /&gt;
We can create another table for left joining, this requires File API query this table when locating files:&lt;br /&gt;
* New file table `mdl_files_reference`&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `fileid` foreign key of `mdl_files`.`id`&lt;br /&gt;
** `repositoryid`&lt;br /&gt;
** `reference` - can be URL, UUID or other data format, repository plugin callbacks know the meaning of this field. File reference should be cached when adding to moodle, contenthash should be accurate.&lt;br /&gt;
&lt;br /&gt;
* `mdl_files_log` table for files access log, File API should have a new function to insert records to this table&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `userid` (0 if it&#039;s guest), &lt;br /&gt;
** `timeaccess`, &lt;br /&gt;
** `fileid`&lt;br /&gt;
&lt;br /&gt;
=== File API changes ===&lt;br /&gt;
* pluginfile.php and draftfile.php (it&#039;s actually send_stored_file()): when file isn&#039;t local file (`repositoryid` isn&#039;t zero), stored_file instance should ask repository plugin‘s send the file contents, Repository API decide return the cached copy or fresh contents&lt;br /&gt;
* Preserve file reference information when call file_storage::create_file_from_storedfile&lt;br /&gt;
* file_storage::get_area_files should retrieve file reference information, also file_storage::get_file_by_id, file_storage::get_file_by_hash&lt;br /&gt;
* When call file_prepare_draft_area(), it should keep linking files&#039; repository information&lt;br /&gt;
* file_save_draft_files should preserve file reference information&lt;br /&gt;
* new method: file_storage::create_file_from_reference&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function create_file_from_reference($file_record, $repoisitoryid, $reference, array $options = NULL)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
* Cron: we probably need a Repository API function to cache/update external files&lt;br /&gt;
&lt;br /&gt;
=== Repository API changes ===&lt;br /&gt;
&lt;br /&gt;
* Additions to FILE_INTERNAL and FILE_EXTERNAL, we need another type FILE_REFERENCE = 4 // 0100, repository plugin needs to declare what types of files are supported&lt;br /&gt;
&lt;br /&gt;
* When users ask to create a reference (instead of copying) in file picker, Repository API try to cache the file in filepool(special file area for repository), after file downloaded, repository API should ask Files API to create a virtual file in `mdl_files` table, the existing fields stay the same, extra file reference information is stored in `mdl_files_reference` table, link or other format that repository plugins know. File API return the stored_file object, repository API generate the moodle url for users just like other moodle files without revealing the internal reference&lt;br /&gt;
&lt;br /&gt;
* Making repository plugin upgrade and versioning possible, repository plugin may need to update `reference` in `mdl_files_reference` table if reference info changed. (we already have db/upgrade.php, make sure all new plugins have one)&lt;br /&gt;
&lt;br /&gt;
* When deleting repository instance, all files imported by this instance will have to be converted to actual files, this has to be done by a File API function&lt;br /&gt;
&lt;br /&gt;
* Cron will use repository plugin callbacks to clean up cache files, repoistory::cron($repositoryid)&lt;br /&gt;
&lt;br /&gt;
* repository::send_file(stored_file $stored_file)&lt;br /&gt;
&lt;br /&gt;
* repository::get_file_reference($ref)&lt;br /&gt;
&lt;br /&gt;
=== Repository plugins changes ===&lt;br /&gt;
* Server files: store file parameters in `reference` field&lt;br /&gt;
* Private files: same as server files plugin&lt;br /&gt;
* Alfresco: Store UUID in `reference` field, but alfresco will change UUID once tomcat restart, may need other information to locate files&lt;br /&gt;
* Flickr private: needs flickr secret and token and photo id to locate the files&lt;br /&gt;
* File system: store file path in `reference` field&lt;br /&gt;
* s3: store file path, s3 repository will use secret and token to fetch the file from s3 no matter files are public or not.&lt;br /&gt;
* EQUELLA&lt;br /&gt;
*# Admin installs EQUELLA and setup parameters for single sign on&lt;br /&gt;
*# Teacher clicks EQUELLA instance, EQUELLA will return an URL of repository UI (plugin code)&lt;br /&gt;
*# Teacher pick a file from EQUELLA, EQUELLA repository UI will revoke file picker JavaScript API to notify moodle download this resource (plugin code)&lt;br /&gt;
*# Repository API stores file UUID and SSO userid, creates file reference in moodle file pool&lt;br /&gt;
*# EQUELLA plugin implements method to download contents using stored UUID and SSO userid&lt;br /&gt;
*# EQUELLA plugin implements method to update/invalidate cached resources (by cron)&lt;br /&gt;
&lt;br /&gt;
=== Content caching ===&lt;br /&gt;
* Create moodledata/repostory/cache directory&lt;br /&gt;
* Generate hash based on file URL and request parameters (provided by repository plugin), not content hash because we cannot send content hash to external repository for file information&lt;br /&gt;
* Cached files are stored using hash code as file name&lt;br /&gt;
&lt;br /&gt;
==== class repository_cache ====&lt;br /&gt;
&lt;br /&gt;
Returned stored_file instance or file path&lt;br /&gt;
&lt;br /&gt;
* store($url, $string_to_be_hashed)&lt;br /&gt;
* get($string_to_be_hashed)&lt;br /&gt;
&lt;br /&gt;
=== Filepicker Javascript API for customizing===&lt;br /&gt;
* File picker should be able to dislable file references by taking an option&lt;br /&gt;
* Support &amp;lt;object&amp;gt; tag in filepicker container&lt;br /&gt;
* Provide Javascript API to allow plugin communicate with filepicker&lt;br /&gt;
** Notify file picker to download file &lt;br /&gt;
** Notify file picker to pop up authentication page&lt;br /&gt;
&lt;br /&gt;
=== File manager to handle virtual files===&lt;br /&gt;
Not much trouble here, need to make sure draftfile.php can serve external resources, because all files managed by file manager is in draft area&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Project]]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=32639</id>
		<title>Improved support for external File content</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=32639"/>
		<updated>2012-03-06T09:26:44Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: /* Example use cases that will become possible */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Work in progress}}&lt;br /&gt;
{{Infobox Project&lt;br /&gt;
|name = File synching&lt;br /&gt;
|state = Planning&lt;br /&gt;
|tracker =  MDL-28666&lt;br /&gt;
|discussion = TODO&lt;br /&gt;
|assignee = [[User:Martin Dougiamas|Martin Dougiamas]]&lt;br /&gt;
}}&lt;br /&gt;
{{Moodle 2.2}}&lt;br /&gt;
&lt;br /&gt;
==Problems with Files in 2.0==&lt;br /&gt;
&lt;br /&gt;
* It is not currently easy to use a file in multiple places throughout Moodle and update them all at once&lt;br /&gt;
* It is not currently easy to create a simple shared &amp;quot;course repository&amp;quot; for teachers to use&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Example use cases that will become possible==&lt;br /&gt;
&lt;br /&gt;
A teacher wants to upload a file once and use it in multiple courses.  When they update the file, it should be updated in all their courses automatically.&lt;br /&gt;
&lt;br /&gt;
# The teacher uploads the file to their private file area (or other repository).&lt;br /&gt;
# In each place they want to add the file, they use the file picker to select the file from their private files area (or other repo) and select &amp;quot;link to latest version&amp;quot;.&lt;br /&gt;
# Replacing the file will automatically mean the linked copies use it.&lt;br /&gt;
# If teacher picked a file which is an alias already, moodle will copy the alias, not create an alias to alias.&lt;br /&gt;
&lt;br /&gt;
Several teachers want to create a shared repository of files together&lt;br /&gt;
# One teacher adds a course repository block to the course&lt;br /&gt;
# Using the block, the teacher creates an instance of a &amp;quot;?filesystem? repository&amp;quot; inside the course (essentially a folder).&lt;br /&gt;
# The content of the repository can be edited via the &amp;quot;Course repositories&amp;quot; block.&lt;br /&gt;
# All of the teachers in that course can now see that appear in their filepickers and select files &lt;br /&gt;
# Files can be &amp;quot;linked&amp;quot; as above&lt;br /&gt;
&lt;br /&gt;
A student wants to submit a linked file as an assignment, so they can continue updating it after the assignment due date.&lt;br /&gt;
# This will not be possible, because the assignment will not allow linking to files.&lt;br /&gt;
# The student is forced to upload a copy of a file and this is protected by the assignment module.&lt;br /&gt;
&lt;br /&gt;
==Solution summary==&lt;br /&gt;
&lt;br /&gt;
The basic idea is to allow the CONTENT of files to be stored outside of the filepool, while all the metadata and access is controlled by Moodle exactly the same as it is now.&lt;br /&gt;
&lt;br /&gt;
# Extend Files API with a new &amp;quot;reference&amp;quot; concept, with all files now having a &amp;quot;repositoryid&amp;quot; (the repository instance that the file came from) and a &amp;quot;reference&amp;quot; (the address in that repository of the content of the file).&lt;br /&gt;
# All files &#039;&#039;&#039;copied&#039;&#039;&#039; into Moodle don&#039;t have record in `files_reference` table, but others can specify a UUID (usually a URL with file-specific tokens in it, created by the original user who placed the file) in reference and repostiroyid columns.&lt;br /&gt;
# Improve the filesystem repository plugin to make it easier to create folder repositories on the fly, via a block.&lt;br /&gt;
# Add support to the filepicker UI to add &amp;quot;linking&amp;quot; to more repositories that support it, including the server files and filesystem repository.&lt;br /&gt;
# Virtual files are served via pluginfile.php URLs just like normal files:&lt;br /&gt;
## pluginfile.php uses module callback to determine access (as now), and if it passes then&lt;br /&gt;
## pluginfile.php calls a file logging subsystem to log the fact that this file is being served (useful for copyright reporting, for example)&lt;br /&gt;
## If the file is not &#039;local&#039;, then pluginfile.php uses the relevant repository callback to get the content of the file and streams it to the user with appropriate mimetypes etc&lt;br /&gt;
## The repositories have a way to cache this content in the normal Moodle filepool, to avoid repeated downloads.&lt;br /&gt;
## If an external repository is down or not configured, then the repository plugin can choose to just serve the local cached version (useful for restored backups and disaster tolerance)&lt;br /&gt;
# When a file is linked to another another file in the local location, then it has a location of &amp;quot;filepool&amp;quot;.  We need to pay attention to these during:&lt;br /&gt;
## Garbage cleanups &lt;br /&gt;
## Deleting the original file should create real copies of that file where required (user will be informed).&lt;br /&gt;
&lt;br /&gt;
Note that the original URL of files in external repositories are never revealed to users.&lt;br /&gt;
&lt;br /&gt;
==Details==&lt;br /&gt;
&lt;br /&gt;
=== File picker walk-through ===&lt;br /&gt;
[[File:File_picker_using_repository_reference_SD.png]]&lt;br /&gt;
# User clicks the &amp;quot;Insert image&amp;quot; button in TinyMCE, then launches our File Picker in the dialog&lt;br /&gt;
# User chooses a repository which supports UUID direct file references (eg Equella, Alfresco etc)&lt;br /&gt;
# A. File picker will ask repository plugin for customised repository UI if supported&lt;br /&gt;
# B. File Picker use repository UI and &amp;lt;object&amp;gt; type to display customised UI in file picker right pane&lt;br /&gt;
# User selects a file from the repository, if is customised UI, file picker Javascript API will be used to trigger file selection event (with all file related information), the repository plugin gives you two choices in the file picker interface  http://tracker.moodle.org/secure/attachment/25823/2011-11-09+15.23.png http://tracker.moodle.org/secure/attachment/25825/File+picker+options.png&lt;br /&gt;
#* C. Use the current version: this will copy the file to Moodle (as now).  No need to reference external content.&lt;br /&gt;
#* C. Use the latest version: this will use a file reference, so that the most recent version is always pulled from the repository&lt;br /&gt;
# D. If &amp;quot;Use the latest version&amp;quot; selected, Repository API will download the file as usual but store it for caching, we could locate this cached file when we need it, cron will take responsibility of invalidating/updating it&lt;br /&gt;
# E. Repository API ask File API to create a file with file parameters and reference.  File API stores the reference and repository instance id in `mdl_files_reference` table and returns the file URL which looks the same as any other Moodle file URL.&lt;br /&gt;
# File picker gives the file URL to TinyMCE&lt;br /&gt;
# When TinyMCE displays the resource, it will cause the browser to call the file URL, which contains pluginfile.php.&lt;br /&gt;
# Pluginfile.php uses File API to send file contents to browsers, if File API detects the requested file is not ordinary moodle files which is located at external repository, File API will ask Repository API for file content, Repository API will firstly look for the cached file, if file is too old or not found (removed  by cron checking), Repository API will fetch the resource (and cache it), then return to File API. Alternatively, Repository API could disable caching, asking for fresh content all the time.&lt;br /&gt;
&lt;br /&gt;
=== File request walk-through ===&lt;br /&gt;
[[File:File_Request_SD.png]]&lt;br /&gt;
&lt;br /&gt;
# A. User request a file&lt;br /&gt;
# B. File API detects if the request file is regular moodle files or located at external repository, if it&#039;s external files, File API will ask repository API to grab the file&lt;br /&gt;
# C. File API collects the file reference information from database, it could be stored in php serialised or JSON format&lt;br /&gt;
# D. File API passes raw file reference information to repository plugin&lt;br /&gt;
# E. Repository plugin will firstly check if file available locally&lt;br /&gt;
# F. If not repository plugin will use file reference information to grab the file&lt;br /&gt;
# G. Repository API returns file content to File API to serve the file&lt;br /&gt;
&lt;br /&gt;
=== Database changes ===&lt;br /&gt;
&lt;br /&gt;
We can create another table for left joining, this requires File API query this table when locating files:&lt;br /&gt;
* New file table `mdl_files_reference`&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `fileid` foreign key of `mdl_files`.`id`&lt;br /&gt;
** `repositoryid`&lt;br /&gt;
** `reference` - can be URL, UUID or other data format, repository plugin callbacks know the meaning of this field. File reference should be cached when adding to moodle, contenthash should be accurate.&lt;br /&gt;
&lt;br /&gt;
* `mdl_files_log` table for files access log, File API should have a new function to insert records to this table&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `userid` (0 if it&#039;s guest), &lt;br /&gt;
** `timeaccess`, &lt;br /&gt;
** `fileid`&lt;br /&gt;
&lt;br /&gt;
=== File API changes ===&lt;br /&gt;
* pluginfile.php and draftfile.php (it&#039;s actually send_stored_file()): when file isn&#039;t local file (`repositoryid` isn&#039;t zero), stored_file instance should ask repository plugin‘s send the file contents, Repository API decide return the cached copy or fresh contents&lt;br /&gt;
* Preserve file reference information when call file_storage::create_file_from_storedfile&lt;br /&gt;
* file_storage::get_area_files should retrieve file reference information, also file_storage::get_file_by_id, file_storage::get_file_by_hash&lt;br /&gt;
* stored_file::delete() should detect repository files, and process it properly, and implement a method to update file, not delete then create&lt;br /&gt;
* When call file_prepare_draft_area(), it should keep linking files&#039; repository information&lt;br /&gt;
* file_save_draft_files should preserve file reference information&lt;br /&gt;
* Adding callback to files API delete function, if the actual file get deleted, callback needs to check all linked files, and convert them to actually files&lt;br /&gt;
* new method: file_storage::create_file_from_reference&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function create_file_from_reference($file_record, $repoisitoryid, $reference, array $options = NULL)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
* Cron: we probably need a Repository API function to cache/update external files&lt;br /&gt;
&lt;br /&gt;
=== Repository API changes ===&lt;br /&gt;
&lt;br /&gt;
* Additions to FILE_INTERNAL and FILE_EXTERNAL, we need another type FILE_REFERENCE = 4 // 0100, repository plugin needs to declare what types of files are supported&lt;br /&gt;
&lt;br /&gt;
* When users ask to create a reference (instead of copying) in file picker, Repository API try to cache the file in filepool(special file area for repository), after file downloaded, repository API should ask Files API to create a virtual file in `mdl_files` table, the existing fields stay the same, extra file reference information is stored in `mdl_files_reference` table, link or other format that repository plugins know. File API return the stored_file object, repository API generate the moodle url for users just like other moodle files without revealing the internal reference&lt;br /&gt;
&lt;br /&gt;
* Making repository plugin upgrade and versioning possible, repository plugin may need to update `reference` in `mdl_files_reference` table if reference info changed. (we already have db/upgrade.php, make sure all new plugins have one)&lt;br /&gt;
&lt;br /&gt;
* When deleting repository instance, all files imported by this instance will have to be converted to actual files, this has to be done by a File API function&lt;br /&gt;
&lt;br /&gt;
* Cron will use repository plugin callbacks to clean up cache files, repoistory::cron($repositoryid)&lt;br /&gt;
&lt;br /&gt;
* repository::send_file(stored_file $stored_file)&lt;br /&gt;
&lt;br /&gt;
* repository::get_file_reference($ref)&lt;br /&gt;
&lt;br /&gt;
=== Repository plugins changes ===&lt;br /&gt;
* Server files: store file parameters in `reference` field&lt;br /&gt;
* Private files: same as server files plugin&lt;br /&gt;
* Alfresco: Store UUID in `reference` field, but alfresco will change UUID once tomcat restart, may need other information to locate files&lt;br /&gt;
* Flickr private: needs flickr secret and token and photo id to locate the files&lt;br /&gt;
* File system: store file path in `reference` field&lt;br /&gt;
* s3: store file path, s3 repository will use secret and token to fetch the file from s3 no matter files are public or not.&lt;br /&gt;
* EQUELLA&lt;br /&gt;
*# Admin installs EQUELLA and setup parameters for single sign on&lt;br /&gt;
*# Teacher clicks EQUELLA instance, EQUELLA will return an URL of repository UI (plugin code)&lt;br /&gt;
*# Teacher pick a file from EQUELLA, EQUELLA repository UI will revoke file picker JavaScript API to notify moodle download this resource (plugin code)&lt;br /&gt;
*# Repository API stores file UUID and SSO userid, creates file reference in moodle file pool&lt;br /&gt;
*# EQUELLA plugin implements method to download contents using stored UUID and SSO userid&lt;br /&gt;
*# EQUELLA plugin implements method to update/invalidate cached resources (by cron)&lt;br /&gt;
&lt;br /&gt;
=== Content caching ===&lt;br /&gt;
* Create moodledata/repostory/cache directory&lt;br /&gt;
* Generate hash based on file URL and request parameters (provided by repository plugin), not content hash because we cannot send content hash to external repository for file information&lt;br /&gt;
* Cached files are stored using hash code as file name&lt;br /&gt;
&lt;br /&gt;
==== class repository_cache ====&lt;br /&gt;
&lt;br /&gt;
Returned stored_file instance or file path&lt;br /&gt;
&lt;br /&gt;
* store($url, $string_to_be_hashed)&lt;br /&gt;
* get($string_to_be_hashed)&lt;br /&gt;
&lt;br /&gt;
=== Filepicker Javascript API for customizing===&lt;br /&gt;
* File picker should be able to dislable file references by taking an option&lt;br /&gt;
* Support &amp;lt;object&amp;gt; tag in filepicker container&lt;br /&gt;
* Provide Javascript API to allow plugin communicate with filepicker&lt;br /&gt;
** Notify file picker to download file &lt;br /&gt;
** Notify file picker to pop up authentication page&lt;br /&gt;
&lt;br /&gt;
=== File manager to handle virtual files===&lt;br /&gt;
Not much trouble here, need to make sure draftfile.php can serve external resources, because all files managed by file manager is in draft area&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Project]]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Files_usability_2.3&amp;diff=32365</id>
		<title>Files usability 2.3</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Files_usability_2.3&amp;diff=32365"/>
		<updated>2012-02-20T08:50:07Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: /* Solution C5: Simplify &amp;quot;Server Files&amp;quot; */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Infobox Project&lt;br /&gt;
|name = Files Usability 2.3&lt;br /&gt;
|state = Specifications, feedback sought&lt;br /&gt;
|tracker = TBA&lt;br /&gt;
|discussion = &lt;br /&gt;
|assignee = Moodle HQ DEV team&lt;br /&gt;
}}&lt;br /&gt;
{{Moodle 2.3}}&lt;br /&gt;
&lt;br /&gt;
==Background==&lt;br /&gt;
&lt;br /&gt;
The file handling of Moodle 1.x was a very simple system with basically one server directory of files per course and very basic access controls.  This system had the benefits of being simple, which meant it was easy to understand and to perform hacks around, but it&#039;s naivety had a lot of disadvantages in security, consistency, disk use, activity portability, and in some cases even led to dataloss.   It also encouraged a certain workflow of &amp;quot;dumping&amp;quot; huge amounts of content into the course in an unstructured way.&lt;br /&gt;
&lt;br /&gt;
The Files system was re-designed in Moodle 2.0 to solve these problems by introducing a model where files are directly associated with texts and &amp;quot;file areas&amp;quot; within different plugins in Moodle, and access to them is finely controlled by those same plugins.   For some examples, the assignment module is now able to control access to assignment submissions depending on due dates and so on.   Backups now accurately contain all the files they need, and multiple copies of the same file take up no more disk space than a single copy.  Files can be drawn as easily from external repositories as from your own computer.  The files system in Moodle 2.0 is more capable and detailed than pretty much any other system around.&lt;br /&gt;
&lt;br /&gt;
However, the interfaces to CONTROL all this had to increase significantly in complexity, and this, combined with limited development time and technical constraints in web browsers have led to a large drop in usability.   Not only do Moodle users have to contend with a different mindset when adding their resources to Moodle, but they also had to contend with a long list of unfamiliar interface issues.&lt;br /&gt;
&lt;br /&gt;
This project aims to make large improvements in the interface when dealing with files in Moodle and improve usability significantly in Moodle 2.3.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Critical problems==&lt;br /&gt;
&lt;br /&gt;
This short list of problems has been identified from user feedback via Moodle partners, tracker issues with votes, user surveys, usability studies and common sense.  It&#039;s not exhaustive, but covers the most severe issues that we think need to be fixed.   If you&#039;d like to comment about this list, please use the discussion page for this project or comment directly on the linked tracker issues.&lt;br /&gt;
&lt;br /&gt;
===Problem A: Uploading files take a lot of clicks ===&lt;br /&gt;
&lt;br /&gt;
The most common case for most people is to upload files from their own desktop (not a repository).   Since the file picker must be used for this, this can take a lot of clicks, and only one file can be uploaded at a time (unless you know how to use zip).&lt;br /&gt;
&lt;br /&gt;
====Solution A1: Allow drag+drop files from desktop.====&lt;br /&gt;
&lt;br /&gt;
We can add the ability to drag files from the desktop in multiple places.  See other issue below about licensing.&lt;br /&gt;
&lt;br /&gt;
# File manager form elements - this is already implemented in 2.3&lt;br /&gt;
# Into file picker (private files)&lt;br /&gt;
# Into file picker (upload)&lt;br /&gt;
# Into course sections (auto-creation of file resources)&lt;br /&gt;
# Into HTML editor window?   (could add to filearea and insert link/image into text?)&lt;br /&gt;
&lt;br /&gt;
====Solution A2: Add &amp;quot;quick upload&amp;quot; buttons that bring up dialog as quickly as possible====&lt;br /&gt;
&lt;br /&gt;
We can add small buttons that take the user directly to a browse-and-upload button as fast as possible, in the following places:&lt;br /&gt;
&lt;br /&gt;
# File manager form element&lt;br /&gt;
# HTML editor image dialog&lt;br /&gt;
# HTML editor media dialog&lt;br /&gt;
&lt;br /&gt;
====Solution A3: Allow multiple files to be uploaded at the same time====&lt;br /&gt;
&lt;br /&gt;
We can add the ability to browse/select multiple files from the desktop via one form, along with a way to update license information for all of them before saving.&lt;br /&gt;
&lt;br /&gt;
This requires some sort of parameter from the calling form to be passed to the file picker, indicating how many new files are expected/allowed.&lt;br /&gt;
&lt;br /&gt;
====Solution A4: More stickiness in file picker between invocations====&lt;br /&gt;
&lt;br /&gt;
The file picker should remember more of your settings from the last time you used it. &lt;br /&gt;
&lt;br /&gt;
# Last Icon/List view &lt;br /&gt;
# Last server files location&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
====Solution A5: Add infrastructure for default licensing, and license editing====&lt;br /&gt;
&lt;br /&gt;
We need some infrastructure for licensing to set default licenses at site/course/user level, because there will be no opportunity to select that at upload time during a drag and drop.   File manager form elements should have an &amp;quot;Info&amp;quot; item (in the context menu for each item) that gives a dialog to edit name/license.&lt;br /&gt;
&lt;br /&gt;
===Problem B: Private files management===&lt;br /&gt;
&lt;br /&gt;
The &#039;Private files&amp;quot; area is the main place that all users have to store files privately in Moodle.  It is like a mini-repository that people can use to store files before sharing them in courses and other public places.&lt;br /&gt;
&lt;br /&gt;
====Solution B1: Prevent leaving the Private Files page until changes are saved====&lt;br /&gt;
&lt;br /&gt;
A big problem for people currently is that they can leave the filemanager page before saving their changes.  A simple fix is to detect this situation just like Gmail and our Tracker do, and throw up a dialog to confirm they want to do this.  The &amp;quot;Save&amp;quot; button can also be more a lot more obvious (should we also offer auto-save?)&lt;br /&gt;
&lt;br /&gt;
====Solution B2: Allow multiple uploads directly via Filepicker repository page====&lt;br /&gt;
 &lt;br /&gt;
In the file picker, we should allow people to upload files directly into their Private files and then select it for use in whatever filearea they were editing, all in one step.&lt;br /&gt;
&lt;br /&gt;
====Solution B3: Allow users to &amp;quot;link&amp;quot; to files in their private files====&lt;br /&gt;
&lt;br /&gt;
Add the linking ability to private files, so that people can manage and update multiple copies of files.&lt;br /&gt;
&lt;br /&gt;
===Problem C: File picker looks &amp;quot;ugly&amp;quot;===&lt;br /&gt;
&lt;br /&gt;
Many people say that they just don&#039;t like the &amp;quot;MS Windows&amp;quot; look of the current filepicker.  This look has been somewhat dictated by the choice of YUI2 to implement it, but it can be improved a lot.&lt;br /&gt;
&lt;br /&gt;
====Solution C1: Improve default graphics styling/layout for the dialog====&lt;br /&gt;
&lt;br /&gt;
It needs to look more like the rest of the Moodle interface.  &lt;br /&gt;
&lt;br /&gt;
It should use lightboxing to prevent access to the page, and should be larger by default.&lt;br /&gt;
&lt;br /&gt;
Thumbnails and titles should flow more nicely with less cropping.&lt;br /&gt;
&lt;br /&gt;
====Solution C2: Implement real thumbnail images where possible====&lt;br /&gt;
&lt;br /&gt;
Currently we show images based on the mimetype of the file.  For images, we should generate and cache small, efficient thumbnail images of the file for viewing in the file picker via lazy-loading.&lt;br /&gt;
&lt;br /&gt;
Along with this, we should increase the length of pages as much as possible and reduce the number of pages.  Scrolling works better than paging.&lt;br /&gt;
&lt;br /&gt;
====Solution C3: Add better CSS/renderers so themes can style it better====&lt;br /&gt;
&lt;br /&gt;
====Solution C4: Add better descriptions/help for options====&lt;br /&gt;
&lt;br /&gt;
Options all need help icons with translated popup help.  &amp;quot;linking files&amp;quot; needs to be better explained.&lt;br /&gt;
&lt;br /&gt;
====Solution C5: Simplify &amp;quot;Server Files&amp;quot;====&lt;br /&gt;
&lt;br /&gt;
# Server files can be improved by tweaking file tree structures to make them more logical&lt;br /&gt;
# Reducing empty folders&lt;br /&gt;
# Shall we list all files in modules? MDL-31675&lt;br /&gt;
&lt;br /&gt;
===Problem D: File area management=== &lt;br /&gt;
&lt;br /&gt;
====Solution D1: A dialog in the HTML editor to manage embedded files directly====&lt;br /&gt;
&lt;br /&gt;
We need a way to edit the filearea that is associated with a HTML text.  We currently do have one for non-Javascript users but it&#039;s never seen.  We need a proper one available in the HTML editor, probably accessed as a popup dialog from a button with a yellow folder on it.   Warnings need to be shown to the effect that renaming or removing files can break the HTML text.&lt;br /&gt;
&lt;br /&gt;
====Solution D2: Add the ability to &amp;quot;replace&amp;quot; files.====&lt;br /&gt;
&lt;br /&gt;
When a file is added to the file area with the same name as one that exists, a dialog should be shown allowing the user to &amp;quot;replace the existing file&amp;quot; or &amp;quot;rename the new file&amp;quot;.  The dialog should also indicate how many &amp;quot;linked copies&amp;quot; will be affected, if that is the case.  &lt;br /&gt;
&lt;br /&gt;
&amp;quot;Replace&amp;quot; means that the old file record is updated with the new content (perhaps with a version increment?) - this means that any LINKS to that file are retained, and it becomes possible to update many copies at once.  This needs to be a new function in the file API.&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Files_usability_2.3&amp;diff=32276</id>
		<title>Files usability 2.3</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Files_usability_2.3&amp;diff=32276"/>
		<updated>2012-02-16T08:52:04Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Infobox Project&lt;br /&gt;
|name = Files Usability 2.3&lt;br /&gt;
|state = Specifications, feedback sought&lt;br /&gt;
|tracker = TBA&lt;br /&gt;
|discussion = &lt;br /&gt;
|assignee = Moodle HQ DEV team&lt;br /&gt;
}}&lt;br /&gt;
{{Moodle 2.3}}&lt;br /&gt;
&lt;br /&gt;
==Background==&lt;br /&gt;
&lt;br /&gt;
The file handling of Moodle 1.x was a very simple system with basically one server directory of files per course and very basic access controls.  This system had the benefits of being simple, which meant it was easy to understand and to perform hacks around, but it&#039;s naivety had a lot of disadvantages in security, consistency, disk use, activity portability, and in some cases even led to dataloss.   It also encouraged a certain workflow of &amp;quot;dumping&amp;quot; huge amounts of content into the course in an unstructured way.&lt;br /&gt;
&lt;br /&gt;
The Files system was re-designed in Moodle 2.0 to solve these problems by introducing a model where files are directly associated with texts and &amp;quot;file areas&amp;quot; within different plugins in Moodle, and access to them is finely controlled by those same plugins.   For some examples, the assignment module is now able to control access to assignment submissions depending on due dates and so on.   Backups now accurately contain all the files they need, and multiple copies of the same file take up no more disk space than a single copy.  Files can be drawn as easily from external repositories as from your own computer.  The files system in Moodle 2.0 is more capable and detailed than pretty much any other system around.&lt;br /&gt;
&lt;br /&gt;
However, the interfaces to CONTROL all this had to increase significantly in complexity, and this, combined with limited development time and technical constraints in web browsers have led to a large drop in usability.   Not only do Moodle users have to contend with a different mindset when adding their resources to Moodle, but they also had to contend with a long list of unfamiliar interface issues.&lt;br /&gt;
&lt;br /&gt;
This project aims to make large improvements in the interface when dealing with files in Moodle and improve usability significantly in Moodle 2.3.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Critical problems==&lt;br /&gt;
&lt;br /&gt;
This short list of problems has been identified from user feedback via Moodle partners, tracker issues with votes, user surveys, usability studies and common sense.  It&#039;s not exhaustive, but covers the most severe issues that we think need to be fixed.   If you&#039;d like to comment about this list, please use the discussion page for this project or comment directly on the linked tracker issues.&lt;br /&gt;
&lt;br /&gt;
===Problem A: Uploading files take a lot of clicks ===&lt;br /&gt;
&lt;br /&gt;
The most common case for most people is to upload files from their own desktop (not a repository).   Since the file picker must be used for this, this can take a lot of clicks, and only one file can be uploaded at a time (unless you know how to use zip).&lt;br /&gt;
&lt;br /&gt;
====Solution A1: Allow drag+drop files from desktop.====&lt;br /&gt;
&lt;br /&gt;
We can add the ability to drag files from the desktop in multiple places.  This needs some infrastructure for licensing to set default licenses at site/course/user level, because there is no opportunity to select that at upload time.   File manager form elements should have an &amp;quot;Info&amp;quot; item as well that gives a dialog to edit name/license.&lt;br /&gt;
&lt;br /&gt;
# File manager form elements - this is already implemented in 2.3&lt;br /&gt;
# Into file picker (private files)&lt;br /&gt;
# Into file picker (upload)&lt;br /&gt;
# Into course sections (auto-creation of file resources)&lt;br /&gt;
&lt;br /&gt;
====Solution A2: Add &amp;quot;quick upload&amp;quot; buttons that bring up dialog as quickly as possible====&lt;br /&gt;
&lt;br /&gt;
We can add small buttons that take the user directly to a browse-and-upload button as fast as possible, in the following places:&lt;br /&gt;
&lt;br /&gt;
# File manager form element&lt;br /&gt;
# HTML editor image dialog&lt;br /&gt;
# HTML editor media dialog&lt;br /&gt;
&lt;br /&gt;
====Solution A3: Allow multiple files to be uploaded at the same time====&lt;br /&gt;
&lt;br /&gt;
We can add the ability to browse/select multiple files from the desktop via one form, along with a way to update license information for all of them before saving.&lt;br /&gt;
&lt;br /&gt;
====Solution A4: More stickiness in file picker between invocations====&lt;br /&gt;
&lt;br /&gt;
The file picker should remember more of your settings from the last time you used it. &lt;br /&gt;
&lt;br /&gt;
# Icon/List view &lt;br /&gt;
# Server files location (tweaking file tree structures and reducing empty folders)&lt;br /&gt;
&lt;br /&gt;
===Problem B: Private files management===&lt;br /&gt;
&lt;br /&gt;
====Solution B1: Prevent leaving the Private Files page until changes are saved====&lt;br /&gt;
&lt;br /&gt;
====Solution B2: Allow multiple uploads directly via Filepicker repository page====&lt;br /&gt;
&lt;br /&gt;
====Solution B3: Allow users to &amp;quot;link&amp;quot; to files in their private files====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Problem C: File picker looks &amp;quot;ugly&amp;quot;===&lt;br /&gt;
&lt;br /&gt;
Many people say that they just don&#039;t like the &amp;quot;MS Windows&amp;quot; look of the current filepicker.  This look has been somewhat dictated by the choice of YUI2 to implement it, but it can be improved a lot.&lt;br /&gt;
&lt;br /&gt;
====Solution C1: Improve default graphics styling/layout for the dialog====&lt;br /&gt;
&lt;br /&gt;
====Solution C2: Implement real thumbnail images where possible====&lt;br /&gt;
&lt;br /&gt;
Currently we show images based on the mimetype of the file.  For images, we should generate and cache small, efficient thumbnail images of the file for viewing in the file picker via lazy-loading.&lt;br /&gt;
&lt;br /&gt;
Along with this, we should increase the length of pages as much as possible and reduce the number of pages.  Scrolling works better than paging.&lt;br /&gt;
&lt;br /&gt;
====Solution C3: Add better CSS/renderers so themes can style it better====&lt;br /&gt;
&lt;br /&gt;
====Solution C4: Add better descriptions/help for options====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Problem D: File area management=== &lt;br /&gt;
&lt;br /&gt;
====Solution D1: A dialog in the HTML editor to manage embedded files directly====&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=32156</id>
		<title>Improved support for external File content</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=32156"/>
		<updated>2012-02-13T05:41:38Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Work in progress}}&lt;br /&gt;
{{Infobox Project&lt;br /&gt;
|name = File synching&lt;br /&gt;
|state = Planning&lt;br /&gt;
|tracker =  MDL-28666&lt;br /&gt;
|discussion = TODO&lt;br /&gt;
|assignee = [[User:Martin Dougiamas|Martin Dougiamas]]&lt;br /&gt;
}}&lt;br /&gt;
{{Moodle 2.2}}&lt;br /&gt;
&lt;br /&gt;
==Problems with Files in 2.0==&lt;br /&gt;
&lt;br /&gt;
* It is not currently easy to use a file in multiple places throughout Moodle and update them all at once&lt;br /&gt;
* It is not currently easy to create a simple shared &amp;quot;course repository&amp;quot; for teachers to use&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Example use cases that will become possible==&lt;br /&gt;
&lt;br /&gt;
A teacher wants to upload a file once and use it in multiple courses.  When they update the file, it should be updated in all their courses automatically.&lt;br /&gt;
&lt;br /&gt;
# The teacher uploads the file to their private file area (or other repository).&lt;br /&gt;
# In each place they want to add the file, they use the file picker to select the file from their private files area (or other repo) and select &amp;quot;link to latest version&amp;quot;.&lt;br /&gt;
# Replacing the file will automatically mean the linked copies use it.&lt;br /&gt;
# Deleting the original file will force the linked copies to be static copies (the teacher will be informed of all the copies before proceeding with the delete).&lt;br /&gt;
&lt;br /&gt;
Several teachers want to create a shared repository of files together&lt;br /&gt;
# One teacher adds a course repository block to the course&lt;br /&gt;
# Using the block, the teacher creates an instance of a &amp;quot;?filesystem? repository&amp;quot; inside the course (essentially a folder).&lt;br /&gt;
# The content of the repository can be edited via the &amp;quot;Course repositories&amp;quot; block.&lt;br /&gt;
# All of the teachers in that course can now see that appear in their filepickers and select files &lt;br /&gt;
# Files can be &amp;quot;linked&amp;quot; as above&lt;br /&gt;
&lt;br /&gt;
A student wants to submit a linked file as an assignment, so they can continue updating it after the assignment due date.&lt;br /&gt;
# This will not be possible, because the assignment will not allow linking to files.&lt;br /&gt;
# The student is forced to upload a copy of a file and this is protected by the assignment module.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Solution summary==&lt;br /&gt;
&lt;br /&gt;
The basic idea is to allow the CONTENT of files to be stored outside of the filepool, while all the metadata and access is controlled by Moodle exactly the same as it is now.&lt;br /&gt;
&lt;br /&gt;
# Extend Files API with a new &amp;quot;reference&amp;quot; concept, with all files now having a &amp;quot;repositoryid&amp;quot; (the repository instance that the file came from) and a &amp;quot;reference&amp;quot; (the address in that repository of the content of the file).&lt;br /&gt;
# All files &#039;&#039;&#039;copied&#039;&#039;&#039; into Moodle don&#039;t have record in `files_reference` table, but others can specify a UUID (usually a URL with file-specific tokens in it, created by the original user who placed the file) in reference and repostiroyid columns.&lt;br /&gt;
# Improve the filesystem repository plugin to make it easier to create folder repositories on the fly, via a block.&lt;br /&gt;
# Add support to the filepicker UI to add &amp;quot;linking&amp;quot; to more repositories that support it, including the server files and filesystem repository.&lt;br /&gt;
# Virtual files are served via pluginfile.php URLs just like normal files:&lt;br /&gt;
## pluginfile.php uses module callback to determine access (as now), and if it passes then&lt;br /&gt;
## pluginfile.php calls a file logging subsystem to log the fact that this file is being served (useful for copyright reporting, for example)&lt;br /&gt;
## If the file is not &#039;local&#039;, then pluginfile.php uses the relevant repository callback to get the content of the file and streams it to the user with appropriate mimetypes etc&lt;br /&gt;
## The repositories have a way to cache this content in the normal Moodle filepool, to avoid repeated downloads.&lt;br /&gt;
## If an external repository is down or not configured, then the repository plugin can choose to just serve the local cached version (useful for restored backups and disaster tolerance)&lt;br /&gt;
# When a file is linked to another another file in the local location, then it has a location of &amp;quot;filepool&amp;quot;.  We need to pay attention to these during:&lt;br /&gt;
## Garbage cleanups &lt;br /&gt;
## Deleting the original file should create real copies of that file where required (user will be informed).&lt;br /&gt;
&lt;br /&gt;
Note that the original URL of files in external repositories are never revealed to users.&lt;br /&gt;
&lt;br /&gt;
==Details==&lt;br /&gt;
&lt;br /&gt;
=== File picker walk-through ===&lt;br /&gt;
[[File:File_picker_using_repository_reference_SD.png]]&lt;br /&gt;
# User clicks the &amp;quot;Insert image&amp;quot; button in TinyMCE, then launches our File Picker in the dialog&lt;br /&gt;
# User chooses a repository which supports UUID direct file references (eg Equella, Alfresco etc)&lt;br /&gt;
# A. File picker will ask repository plugin for customised repository UI if supported&lt;br /&gt;
# B. File Picker use repository UI and &amp;lt;object&amp;gt; type to display customised UI in file picker right pane&lt;br /&gt;
# User selects a file from the repository, if is customised UI, file picker Javascript API will be used to trigger file selection event (with all file related information), the repository plugin gives you two choices in the file picker interface  http://tracker.moodle.org/secure/attachment/25823/2011-11-09+15.23.png http://tracker.moodle.org/secure/attachment/25825/File+picker+options.png&lt;br /&gt;
#* C. Use the current version: this will copy the file to Moodle (as now).  No need to reference external content.&lt;br /&gt;
#* C. Use the latest version: this will use a file reference, so that the most recent version is always pulled from the repository&lt;br /&gt;
# D. If &amp;quot;Use the latest version&amp;quot; selected, Repository API will download the file as usual but store it for caching, we could locate this cached file when we need it, cron will take responsibility of invalidating/updating it&lt;br /&gt;
# E. Repository API ask File API to create a file with file parameters and reference.  File API stores the reference and repository instance id in `mdl_files_reference` table and returns the file URL which looks the same as any other Moodle file URL.&lt;br /&gt;
# File picker gives the file URL to TinyMCE&lt;br /&gt;
# When TinyMCE displays the resource, it will cause the browser to call the file URL, which contains pluginfile.php.&lt;br /&gt;
# Pluginfile.php uses File API to send file contents to browsers, if File API detects the requested file is not ordinary moodle files which is located at external repository, File API will ask Repository API for file content, Repository API will firstly look for the cached file, if file is too old or not found (removed  by cron checking), Repository API will fetch the resource (and cache it), then return to File API. Alternatively, Repository API could disable caching, asking for fresh content all the time.&lt;br /&gt;
&lt;br /&gt;
=== File request walk-through ===&lt;br /&gt;
[[File:File_Request_SD.png]]&lt;br /&gt;
&lt;br /&gt;
# A. User request a file&lt;br /&gt;
# B. File API detects if the request file is regular moodle files or located at external repository, if it&#039;s external files, File API will ask repository API to grab the file&lt;br /&gt;
# C. File API collects the file reference information from database, it could be stored in php serialised or JSON format&lt;br /&gt;
# D. File API passes raw file reference information to repository plugin&lt;br /&gt;
# E. Repository plugin will firstly check if file available locally&lt;br /&gt;
# F. If not repository plugin will use file reference information to grab the file&lt;br /&gt;
# G. Repository API returns file content to File API to serve the file&lt;br /&gt;
&lt;br /&gt;
=== Database changes ===&lt;br /&gt;
&lt;br /&gt;
We can create another table for left joining, this requires File API query this table when locating files:&lt;br /&gt;
* New file table `mdl_files_reference`&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `fileid` foreign key of `mdl_files`.`id`&lt;br /&gt;
** `repositoryid`&lt;br /&gt;
** `reference` - can be URL, UUID or other data format, repository plugin callbacks know the meaning of this field. File reference should be cached when adding to moodle, contenthash should be accurate.&lt;br /&gt;
&lt;br /&gt;
* `mdl_files_log` table for files access log, File API should have a new function to insert records to this table&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `userid` (0 if it&#039;s guest), &lt;br /&gt;
** `timeaccess`, &lt;br /&gt;
** `fileid`&lt;br /&gt;
&lt;br /&gt;
=== File API changes ===&lt;br /&gt;
* pluginfile.php and draftfile.php (it&#039;s actually send_stored_file()): when file isn&#039;t local file (`repositoryid` isn&#039;t zero), stored_file instance should ask repository plugin‘s send the file contents, Repository API decide return the cached copy or fresh contents&lt;br /&gt;
* Preserve file reference information when call file_storage::create_file_from_storedfile&lt;br /&gt;
* file_storage::get_area_files should retrieve file reference information, also file_storage::get_file_by_id, file_storage::get_file_by_hash&lt;br /&gt;
* stored_file::delete() should detect repository files, and process it properly, and implement a method to update file, not delete then create&lt;br /&gt;
* When call file_prepare_draft_area(), it should keep linking files&#039; repository information&lt;br /&gt;
* file_save_draft_files should preserve file reference information&lt;br /&gt;
* Adding callback to files API delete function, if the actual file get deleted, callback needs to check all linked files, and convert them to actually files&lt;br /&gt;
* new method: file_storage::create_file_from_reference&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function create_file_from_reference($file_record, $repoisitoryid, $reference, array $options = NULL)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
* Cron: we probably need a Repository API function to cache/update external files&lt;br /&gt;
&lt;br /&gt;
=== Repository API changes ===&lt;br /&gt;
&lt;br /&gt;
* Additions to FILE_INTERNAL and FILE_EXTERNAL, we need another type FILE_REFERENCE = 4 // 0100, repository plugin needs to declare what types of files are supported&lt;br /&gt;
&lt;br /&gt;
* When users ask to create a reference (instead of copying) in file picker, Repository API try to cache the file in filepool(special file area for repository), after file downloaded, repository API should ask Files API to create a virtual file in `mdl_files` table, the existing fields stay the same, extra file reference information is stored in `mdl_files_reference` table, link or other format that repository plugins know. File API return the stored_file object, repository API generate the moodle url for users just like other moodle files without revealing the internal reference&lt;br /&gt;
&lt;br /&gt;
* Making repository plugin upgrade and versioning possible, repository plugin may need to update `reference` in `mdl_files_reference` table if reference info changed. (we already have db/upgrade.php, make sure all new plugins have one)&lt;br /&gt;
&lt;br /&gt;
* When deleting repository instance, all files imported by this instance will have to be converted to actual files, this has to be done by a File API function&lt;br /&gt;
&lt;br /&gt;
* Cron will use repository plugin callbacks to clean up cache files, repoistory::cron($repositoryid)&lt;br /&gt;
&lt;br /&gt;
* repository::send_file(stored_file $stored_file)&lt;br /&gt;
&lt;br /&gt;
* repository::get_file_reference($ref)&lt;br /&gt;
&lt;br /&gt;
=== Repository plugins changes ===&lt;br /&gt;
* Server files: store file parameters in `reference` field&lt;br /&gt;
* Private files: same as server files plugin&lt;br /&gt;
* Alfresco: Store UUID in `reference` field, but alfresco will change UUID once tomcat restart, may need other information to locate files&lt;br /&gt;
* Flickr private: needs flickr secret and token and photo id to locate the files&lt;br /&gt;
* File system: store file path in `reference` field&lt;br /&gt;
* s3: store file path, s3 repository will use secret and token to fetch the file from s3 no matter files are public or not.&lt;br /&gt;
* EQUELLA&lt;br /&gt;
*# Admin installs EQUELLA and setup parameters for single sign on&lt;br /&gt;
*# Teacher clicks EQUELLA instance, EQUELLA will return an URL of repository UI (plugin code)&lt;br /&gt;
*# Teacher pick a file from EQUELLA, EQUELLA repository UI will revoke file picker JavaScript API to notify moodle download this resource (plugin code)&lt;br /&gt;
*# Repository API stores file UUID and SSO userid, creates file reference in moodle file pool&lt;br /&gt;
*# EQUELLA plugin implements method to download contents using stored UUID and SSO userid&lt;br /&gt;
*# EQUELLA plugin implements method to update/invalidate cached resources (by cron)&lt;br /&gt;
&lt;br /&gt;
=== Content caching ===&lt;br /&gt;
* Create moodledata/repostory/cache directory&lt;br /&gt;
* Generate hash based on file URL and request parameters (provided by repository plugin), not content hash because we cannot send content hash to external repository for file information&lt;br /&gt;
* Cached files are stored using hash code as file name&lt;br /&gt;
&lt;br /&gt;
==== class repository_cache ====&lt;br /&gt;
&lt;br /&gt;
Returned stored_file instance or file path&lt;br /&gt;
&lt;br /&gt;
* store($url, $string_to_be_hashed)&lt;br /&gt;
* get($string_to_be_hashed)&lt;br /&gt;
&lt;br /&gt;
=== Filepicker Javascript API for customizing===&lt;br /&gt;
* File picker should be able to dislable file references by taking an option&lt;br /&gt;
* Support &amp;lt;object&amp;gt; tag in filepicker container&lt;br /&gt;
* Provide Javascript API to allow plugin communicate with filepicker&lt;br /&gt;
** Notify file picker to download file &lt;br /&gt;
** Notify file picker to pop up authentication page&lt;br /&gt;
&lt;br /&gt;
=== File manager to handle virtual files===&lt;br /&gt;
Not much trouble here, need to make sure draftfile.php can serve external resources, because all files managed by file manager is in draft area&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Project]]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=32155</id>
		<title>Improved support for external File content</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=32155"/>
		<updated>2012-02-13T05:31:08Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Work in progress}}&lt;br /&gt;
{{Infobox Project&lt;br /&gt;
|name = File synching&lt;br /&gt;
|state = Planning&lt;br /&gt;
|tracker =  MDL-28666&lt;br /&gt;
|discussion = TODO&lt;br /&gt;
|assignee = [[User:Martin Dougiamas|Martin Dougiamas]]&lt;br /&gt;
}}&lt;br /&gt;
{{Moodle 2.2}}&lt;br /&gt;
&lt;br /&gt;
==Problems with Files in 2.0==&lt;br /&gt;
&lt;br /&gt;
* It is not currently easy to use a file in multiple places throughout Moodle and update them all at once&lt;br /&gt;
* It is not currently easy to create a simple shared &amp;quot;course repository&amp;quot; for teachers to use&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Example use cases that will become possible==&lt;br /&gt;
&lt;br /&gt;
A teacher wants to upload a file once and use it in multiple courses.  When they update the file, it should be updated in all their courses automatically.&lt;br /&gt;
&lt;br /&gt;
# The teacher uploads the file to their private file area (or other repository).&lt;br /&gt;
# In each place they want to add the file, they use the file picker to select the file from their private files area (or other repo) and select &amp;quot;link to latest version&amp;quot;.&lt;br /&gt;
# Replacing the file will automatically mean the linked copies use it.&lt;br /&gt;
# Deleting the original file will force the linked copies to be static copies (the teacher will be informed of all the copies before proceeding with the delete).&lt;br /&gt;
&lt;br /&gt;
Several teachers want to create a shared repository of files together&lt;br /&gt;
# One teacher adds a course repository block to the course&lt;br /&gt;
# Using the block, the teacher creates an instance of a &amp;quot;?filesystem? repository&amp;quot; inside the course (essentially a folder).&lt;br /&gt;
# The content of the repository can be edited via the &amp;quot;Course repositories&amp;quot; block.&lt;br /&gt;
# All of the teachers in that course can now see that appear in their filepickers and select files &lt;br /&gt;
# Files can be &amp;quot;linked&amp;quot; as above&lt;br /&gt;
&lt;br /&gt;
A student wants to submit a linked file as an assignment, so they can continue updating it after the assignment due date.&lt;br /&gt;
# This will not be possible, because the assignment will not allow linking to files.&lt;br /&gt;
# The student is forced to upload a copy of a file and this is protected by the assignment module.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Solution summary==&lt;br /&gt;
&lt;br /&gt;
The basic idea is to allow the CONTENT of files to be stored outside of the filepool, while all the metadata and access is controlled by Moodle exactly the same as it is now.&lt;br /&gt;
&lt;br /&gt;
# Extend Files API with a new &amp;quot;reference&amp;quot; concept, with all files now having a &amp;quot;repositoryid&amp;quot; (the repository instance that the file came from) and a &amp;quot;reference&amp;quot; (the address in that repository of the content of the file).&lt;br /&gt;
# All files &#039;&#039;&#039;copied&#039;&#039;&#039; into Moodle don&#039;t have record in `files_reference` table, but others can specify a UUID (usually a URL with file-specific tokens in it, created by the original user who placed the file) in reference and repostiroyid columns.&lt;br /&gt;
# Improve the filesystem repository plugin to make it easier to create folder repositories on the fly, via a block.&lt;br /&gt;
# Add support to the filepicker UI to add &amp;quot;linking&amp;quot; to more repositories that support it, including the server files and filesystem repository.&lt;br /&gt;
# Virtual files are served via pluginfile.php URLs just like normal files:&lt;br /&gt;
## pluginfile.php uses module callback to determine access (as now), and if it passes then&lt;br /&gt;
## pluginfile.php calls a file logging subsystem to log the fact that this file is being served (useful for copyright reporting, for example)&lt;br /&gt;
## If the file is not &#039;local&#039;, then pluginfile.php uses the relevant repository callback to get the content of the file and streams it to the user with appropriate mimetypes etc&lt;br /&gt;
## The repositories have a way to cache this content in the normal Moodle filepool, to avoid repeated downloads.&lt;br /&gt;
## If an external repository is down or not configured, then the repository plugin can choose to just serve the local cached version (useful for restored backups and disaster tolerance)&lt;br /&gt;
# When a file is linked to another another file in the local location, then it has a location of &amp;quot;filepool&amp;quot;.  We need to pay attention to these during:&lt;br /&gt;
## Garbage cleanups &lt;br /&gt;
## Deleting the original file should create real copies of that file where required (user will be informed).&lt;br /&gt;
&lt;br /&gt;
Note that the original URL of files in external repositories are never revealed to users.&lt;br /&gt;
&lt;br /&gt;
==Details==&lt;br /&gt;
&lt;br /&gt;
=== File picker walk-through ===&lt;br /&gt;
[[File:File_picker_using_repository_reference_SD.png]]&lt;br /&gt;
# User clicks the &amp;quot;Insert image&amp;quot; button in TinyMCE, then launches our File Picker in the dialog&lt;br /&gt;
# User chooses a repository which supports UUID direct file references (eg Equella, Alfresco etc)&lt;br /&gt;
# A. File picker will ask repository plugin for customised repository UI if supported&lt;br /&gt;
# B. File Picker use repository UI and &amp;lt;object&amp;gt; type to display customised UI in file picker right pane&lt;br /&gt;
# User selects a file from the repository, if is customised UI, file picker Javascript API will be used to trigger file selection event (with all file related information), the repository plugin gives you two choices in the file picker interface  http://tracker.moodle.org/secure/attachment/25823/2011-11-09+15.23.png http://tracker.moodle.org/secure/attachment/25825/File+picker+options.png&lt;br /&gt;
#* C. Use the current version: this will copy the file to Moodle (as now).  No need to reference external content.&lt;br /&gt;
#* C. Use the latest version: this will use a file reference, so that the most recent version is always pulled from the repository&lt;br /&gt;
# D. If &amp;quot;Use the latest version&amp;quot; selected, Repository API will download the file as usual but store it for caching, we could locate this cached file when we need it, cron will take responsibility of invalidating/updating it&lt;br /&gt;
# E. Repository API ask File API to create a file with file parameters and reference.  File API stores the reference and repository instance id in `mdl_files_reference` table and returns the file URL which looks the same as any other Moodle file URL.&lt;br /&gt;
# File picker gives the file URL to TinyMCE&lt;br /&gt;
# When TinyMCE displays the resource, it will cause the browser to call the file URL, which contains pluginfile.php.&lt;br /&gt;
# Pluginfile.php uses File API to send file contents to browsers, if File API detects the requested file is not ordinary moodle files which is located at external repository, File API will ask Repository API for file content, Repository API will firstly look for the cached file, if file is too old or not found (removed  by cron checking), Repository API will fetch the resource (and cache it), then return to File API. Alternatively, Repository API could disable caching, asking for fresh content all the time.&lt;br /&gt;
&lt;br /&gt;
=== File request walk-through ===&lt;br /&gt;
[[File:File_Request_SD.png]]&lt;br /&gt;
&lt;br /&gt;
# A. User request a file&lt;br /&gt;
# B. File API detects if the request file is regular moodle files or located at external repository, if it&#039;s external files, File API will ask repository API to grab the file&lt;br /&gt;
# C. File API collects the file reference information from database, it could be stored in php serialised or JSON format&lt;br /&gt;
# D. File API passes raw file reference information to repository plugin&lt;br /&gt;
# E. Repository plugin will firstly check if file available locally&lt;br /&gt;
# F. If not repository plugin will use file reference information to grab the file&lt;br /&gt;
# G. Repository API returns file content to File API to serve the file&lt;br /&gt;
&lt;br /&gt;
=== Database changes ===&lt;br /&gt;
&lt;br /&gt;
We can create another table for left joining, this requires File API query this table when locating files:&lt;br /&gt;
* New file table `mdl_files_reference`&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `fileid` foreign key of `mdl_files`.`id`&lt;br /&gt;
** `repositoryid`&lt;br /&gt;
** `reference` - can be URL, UUID or other data format, repository plugin callbacks know the meaning of this field. File reference should be cached when adding to moodle, contenthash should be accurate.&lt;br /&gt;
&lt;br /&gt;
* `mdl_files_log` table for files access log, File API should have a new function to insert records to this table&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `userid` (0 if it&#039;s guest), &lt;br /&gt;
** `timeaccess`, &lt;br /&gt;
** `fileid`&lt;br /&gt;
&lt;br /&gt;
=== File API changes ===&lt;br /&gt;
* pluginfile.php and draftfile.php (it&#039;s actually send_stored_file()): when file isn&#039;t local file (`repositoryid` isn&#039;t zero), stored_file instance should ask repository plugin‘s send the file contents, Repository API decide return the cached copy or fresh contents&lt;br /&gt;
* Preserve file reference information when call file_storage::create_file_from_storedfile&lt;br /&gt;
* file_storage::get_area_files should retrieve file reference information, also file_storage::get_file_by_id, file_storage::get_file_by_hash&lt;br /&gt;
* stored_file::delete() should detect repository files, and process it properly, and implement a method to update file, not delete then create&lt;br /&gt;
* When call file_prepare_draft_area(), it should keep linking files&#039; repository information&lt;br /&gt;
* file_save_draft_files should preserve file reference information&lt;br /&gt;
* Adding callback to files API delete function, if the actual file get deleted, callback needs to check all linked files, and convert them to actually files&lt;br /&gt;
* new method: file_storage::create_file_from_reference&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function create_file_from_reference($file_record, $repoisitoryid, $reference, array $options = NULL)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
* Cron: we probably need a Repository API function to cache/update external files&lt;br /&gt;
&lt;br /&gt;
=== Repository API changes ===&lt;br /&gt;
&lt;br /&gt;
* Additions to FILE_INTERNAL and FILE_EXTERNAL, we need another type FILE_REFERENCE = 4 // 0100, repository plugin needs to declare what types of files are supported&lt;br /&gt;
&lt;br /&gt;
* When users ask to create a reference (instead of copying) in file picker, Repository API try to cache the file in filepool(special file area for repository), after file downloaded, repository API should ask Files API to create a virtual file in `mdl_files` table, the existing fields stay the same, extra file reference information is stored in `mdl_files_reference` table, link or other format that repository plugins know. File API return the stored_file object, repository API generate the moodle url for users just like other moodle files without revealing the internal reference&lt;br /&gt;
&lt;br /&gt;
* Making repository plugin upgrade and versioning possible, repository plugin may need to update `reference` in `mdl_files_reference` table if reference info changed. (we already have db/upgrade.php, make sure all new plugins have one)&lt;br /&gt;
&lt;br /&gt;
* When deleting repository instance, all files imported by this instance will have to be converted to actual files, this has to be done by a File API function&lt;br /&gt;
&lt;br /&gt;
* Cron will use repository plugin callbacks to clean up cache files, repoistory::cron($repositoryid)&lt;br /&gt;
&lt;br /&gt;
* repository::send_file(stored_file $stored_file)&lt;br /&gt;
&lt;br /&gt;
* repository::get_file_reference($ref)&lt;br /&gt;
&lt;br /&gt;
=== Repository plugins changes ===&lt;br /&gt;
* Server files: store file parameters in `reference` field&lt;br /&gt;
* Private files: same as server files plugin&lt;br /&gt;
* Alfresco: Store UUID in `reference` field, but alfresco will change UUID once tomcat restart, may need other information to locate files&lt;br /&gt;
* Flickr private: needs flickr secret and token and photo id to locate the files&lt;br /&gt;
* File system: store file path in `reference` field&lt;br /&gt;
* s3: store file path, s3 repository will use secret and token to fetch the file from s3 no matter files are public or not.&lt;br /&gt;
* EQUELLA&lt;br /&gt;
*# Admin installs EQUELLA and setup parameters for single sign on&lt;br /&gt;
*# Teacher clicks EQUELLA instance, EQUELLA will return an URL of repository UI (plugin code)&lt;br /&gt;
*# Teacher pick a file from EQUELLA, EQUELLA repository UI will revoke file picker JavaScript API to notify moodle download this resource (plugin code)&lt;br /&gt;
*# Repository API stores file UUID and SSO userid, creates file reference in moodle file pool&lt;br /&gt;
*# EQUELLA plugin implements method to download contents using stored UUID and SSO userid&lt;br /&gt;
*# EQUELLA plugin implements method to update/invalidate cached resources (by cron)&lt;br /&gt;
&lt;br /&gt;
=== Content caching ===&lt;br /&gt;
* Create moodledata/repostory/cache directory&lt;br /&gt;
* Generate hash based on file URL and request parameters (provided by repository plugin), not content hash because we cannot send content hash to external repository for file information&lt;br /&gt;
* Cached files are stored using hash code as file name&lt;br /&gt;
&lt;br /&gt;
==== class repository_cache =====&lt;br /&gt;
&lt;br /&gt;
Returned stored_file instance or file path&lt;br /&gt;
&lt;br /&gt;
* store($url, $string_to_be_hashed)&lt;br /&gt;
* get($string_to_be_hashed)&lt;br /&gt;
&lt;br /&gt;
=== Filepicker Javascript API for customizing===&lt;br /&gt;
* File picker should be able to dislable file references by taking an option&lt;br /&gt;
* Support &amp;lt;object&amp;gt; tag in filepicker container&lt;br /&gt;
* Provide Javascript API to allow plugin communicate with filepicker&lt;br /&gt;
** Notify file picker to download file &lt;br /&gt;
** Notify file picker to pop up authentication page&lt;br /&gt;
&lt;br /&gt;
=== File manager to handle virtual files===&lt;br /&gt;
Not much trouble here, need to make sure draftfile.php can serve external resources, because all files managed by file manager is in draft area&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Project]]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=32154</id>
		<title>Improved support for external File content</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=32154"/>
		<updated>2012-02-13T05:29:54Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Work in progress}}&lt;br /&gt;
{{Infobox Project&lt;br /&gt;
|name = File synching&lt;br /&gt;
|state = Planning&lt;br /&gt;
|tracker =  MDL-28666&lt;br /&gt;
|discussion = TODO&lt;br /&gt;
|assignee = [[User:Martin Dougiamas|Martin Dougiamas]]&lt;br /&gt;
}}&lt;br /&gt;
{{Moodle 2.2}}&lt;br /&gt;
&lt;br /&gt;
==Problems with Files in 2.0==&lt;br /&gt;
&lt;br /&gt;
* It is not currently easy to use a file in multiple places throughout Moodle and update them all at once&lt;br /&gt;
* It is not currently easy to create a simple shared &amp;quot;course repository&amp;quot; for teachers to use&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Example use cases that will become possible==&lt;br /&gt;
&lt;br /&gt;
A teacher wants to upload a file once and use it in multiple courses.  When they update the file, it should be updated in all their courses automatically.&lt;br /&gt;
&lt;br /&gt;
# The teacher uploads the file to their private file area (or other repository).&lt;br /&gt;
# In each place they want to add the file, they use the file picker to select the file from their private files area (or other repo) and select &amp;quot;link to latest version&amp;quot;.&lt;br /&gt;
# Replacing the file will automatically mean the linked copies use it.&lt;br /&gt;
# Deleting the original file will force the linked copies to be static copies (the teacher will be informed of all the copies before proceeding with the delete).&lt;br /&gt;
&lt;br /&gt;
Several teachers want to create a shared repository of files together&lt;br /&gt;
# One teacher adds a course repository block to the course&lt;br /&gt;
# Using the block, the teacher creates an instance of a &amp;quot;?filesystem? repository&amp;quot; inside the course (essentially a folder).&lt;br /&gt;
# The content of the repository can be edited via the &amp;quot;Course repositories&amp;quot; block.&lt;br /&gt;
# All of the teachers in that course can now see that appear in their filepickers and select files &lt;br /&gt;
# Files can be &amp;quot;linked&amp;quot; as above&lt;br /&gt;
&lt;br /&gt;
A student wants to submit a linked file as an assignment, so they can continue updating it after the assignment due date.&lt;br /&gt;
# This will not be possible, because the assignment will not allow linking to files.&lt;br /&gt;
# The student is forced to upload a copy of a file and this is protected by the assignment module.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Solution summary==&lt;br /&gt;
&lt;br /&gt;
The basic idea is to allow the CONTENT of files to be stored outside of the filepool, while all the metadata and access is controlled by Moodle exactly the same as it is now.&lt;br /&gt;
&lt;br /&gt;
# Extend Files API with a new &amp;quot;reference&amp;quot; concept, with all files now having a &amp;quot;repositoryid&amp;quot; (the repository instance that the file came from) and a &amp;quot;reference&amp;quot; (the address in that repository of the content of the file).&lt;br /&gt;
# All files &#039;&#039;&#039;copied&#039;&#039;&#039; into Moodle don&#039;t have record in `files_reference` table, but others can specify a UUID (usually a URL with file-specific tokens in it, created by the original user who placed the file) in reference and repostiroyid columns.&lt;br /&gt;
# Improve the filesystem repository plugin to make it easier to create folder repositories on the fly, via a block.&lt;br /&gt;
# Add support to the filepicker UI to add &amp;quot;linking&amp;quot; to more repositories that support it, including the server files and filesystem repository.&lt;br /&gt;
# Virtual files are served via pluginfile.php URLs just like normal files:&lt;br /&gt;
## pluginfile.php uses module callback to determine access (as now), and if it passes then&lt;br /&gt;
## pluginfile.php calls a file logging subsystem to log the fact that this file is being served (useful for copyright reporting, for example)&lt;br /&gt;
## If the file is not &#039;local&#039;, then pluginfile.php uses the relevant repository callback to get the content of the file and streams it to the user with appropriate mimetypes etc&lt;br /&gt;
## The repositories have a way to cache this content in the normal Moodle filepool, to avoid repeated downloads.&lt;br /&gt;
## If an external repository is down or not configured, then the repository plugin can choose to just serve the local cached version (useful for restored backups and disaster tolerance)&lt;br /&gt;
# When a file is linked to another another file in the local location, then it has a location of &amp;quot;filepool&amp;quot;.  We need to pay attention to these during:&lt;br /&gt;
## Garbage cleanups &lt;br /&gt;
## Deleting the original file should create real copies of that file where required (user will be informed).&lt;br /&gt;
&lt;br /&gt;
Note that the original URL of files in external repositories are never revealed to users.&lt;br /&gt;
&lt;br /&gt;
==Details==&lt;br /&gt;
&lt;br /&gt;
=== File picker walk-through ===&lt;br /&gt;
[[File:File_picker_using_repository_reference_SD.png]]&lt;br /&gt;
# User clicks the &amp;quot;Insert image&amp;quot; button in TinyMCE, then launches our File Picker in the dialog&lt;br /&gt;
# User chooses a repository which supports UUID direct file references (eg Equella, Alfresco etc)&lt;br /&gt;
# A. File picker will ask repository plugin for customised repository UI if supported&lt;br /&gt;
# B. File Picker use repository UI and &amp;lt;object&amp;gt; type to display customised UI in file picker right pane&lt;br /&gt;
# User selects a file from the repository, if is customised UI, file picker Javascript API will be used to trigger file selection event (with all file related information), the repository plugin gives you two choices in the file picker interface  http://tracker.moodle.org/secure/attachment/25823/2011-11-09+15.23.png http://tracker.moodle.org/secure/attachment/25825/File+picker+options.png&lt;br /&gt;
#* C. Use the current version: this will copy the file to Moodle (as now).  No need to reference external content.&lt;br /&gt;
#* C. Use the latest version: this will use a file reference, so that the most recent version is always pulled from the repository&lt;br /&gt;
# D. If &amp;quot;Use the latest version&amp;quot; selected, Repository API will download the file as usual but store it for caching, we could locate this cached file when we need it, cron will take responsibility of invalidating/updating it&lt;br /&gt;
# E. Repository API ask File API to create a file with file parameters and reference.  File API stores the reference and repository instance id in `mdl_files_reference` table and returns the file URL which looks the same as any other Moodle file URL.&lt;br /&gt;
# File picker gives the file URL to TinyMCE&lt;br /&gt;
# When TinyMCE displays the resource, it will cause the browser to call the file URL, which contains pluginfile.php.&lt;br /&gt;
# Pluginfile.php uses File API to send file contents to browsers, if File API detects the requested file is not ordinary moodle files which is located at external repository, File API will ask Repository API for file content, Repository API will firstly look for the cached file, if file is too old or not found (removed  by cron checking), Repository API will fetch the resource (and cache it), then return to File API. Alternatively, Repository API could disable caching, asking for fresh content all the time.&lt;br /&gt;
&lt;br /&gt;
=== File request walk-through ===&lt;br /&gt;
[[File:File_Request_SD.png]]&lt;br /&gt;
&lt;br /&gt;
# A. User request a file&lt;br /&gt;
# B. File API detects if the request file is regular moodle files or located at external repository, if it&#039;s external files, File API will ask repository API to grab the file&lt;br /&gt;
# C. File API collects the file reference information from database, it could be stored in php serialised or JSON format&lt;br /&gt;
# D. File API passes raw file reference information to repository plugin&lt;br /&gt;
# E. Repository plugin will firstly check if file available locally&lt;br /&gt;
# F. If not repository plugin will use file reference information to grab the file&lt;br /&gt;
# G. Repository API returns file content to File API to serve the file&lt;br /&gt;
&lt;br /&gt;
=== Database changes ===&lt;br /&gt;
&lt;br /&gt;
We can create another table for left joining, this requires File API query this table when locating files:&lt;br /&gt;
* New file table `mdl_files_reference`&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `fileid` foreign key of `mdl_files`.`id`&lt;br /&gt;
** `repositoryid`&lt;br /&gt;
** `reference` - can be URL, UUID or other data format, repository plugin callbacks know the meaning of this field. File reference should be cached when adding to moodle, contenthash should be accurate.&lt;br /&gt;
&lt;br /&gt;
* `mdl_files_log` table for files access log, File API should have a new function to insert records to this table&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `userid` (0 if it&#039;s guest), &lt;br /&gt;
** `timeaccess`, &lt;br /&gt;
** `fileid`&lt;br /&gt;
&lt;br /&gt;
=== File API changes ===&lt;br /&gt;
* pluginfile.php and draftfile.php (it&#039;s actually send_stored_file()): when file isn&#039;t local file (`repositoryid` isn&#039;t zero), stored_file instance should ask repository plugin‘s send the file contents, Repository API decide return the cached copy or fresh contents&lt;br /&gt;
* Preserve file reference information when call file_storage::create_file_from_storedfile&lt;br /&gt;
* file_storage::get_area_files should retrieve file reference information, also file_storage::get_file_by_id, file_storage::get_file_by_hash&lt;br /&gt;
* stored_file::delete() should detect repository files, and process it properly&lt;br /&gt;
* When call file_prepare_draft_area(), it should keep linking files&#039; repository information&lt;br /&gt;
* file_save_draft_files should preserve file reference information&lt;br /&gt;
* Adding callback to files API delete function, if the actual file get deleted, callback needs to check all linked files, and convert them to actually files&lt;br /&gt;
* new method: file_storage::create_file_from_reference&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function create_file_from_reference($file_record, $repoisitoryid, $reference, array $options = NULL)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
* Cron: we probably need a Repository API function to cache/update external files&lt;br /&gt;
&lt;br /&gt;
=== Repository API changes ===&lt;br /&gt;
&lt;br /&gt;
* Additions to FILE_INTERNAL and FILE_EXTERNAL, we need another type FILE_REFERENCE = 4 // 0100, repository plugin needs to declare what types of files are supported&lt;br /&gt;
&lt;br /&gt;
* When users ask to create a reference (instead of copying) in file picker, Repository API try to cache the file in filepool(special file area for repository), after file downloaded, repository API should ask Files API to create a virtual file in `mdl_files` table, the existing fields stay the same, extra file reference information is stored in `mdl_files_reference` table, link or other format that repository plugins know. File API return the stored_file object, repository API generate the moodle url for users just like other moodle files without revealing the internal reference&lt;br /&gt;
&lt;br /&gt;
* Making repository plugin upgrade and versioning possible, repository plugin may need to update `reference` in `mdl_files_reference` table if reference info changed. (we already have db/upgrade.php, make sure all new plugins have one)&lt;br /&gt;
&lt;br /&gt;
* When deleting repository instance, all files imported by this instance will have to be converted to actual files, this has to be done by a File API function&lt;br /&gt;
&lt;br /&gt;
* Cron will use repository plugin callbacks to clean up cache files, repoistory::cron($repositoryid)&lt;br /&gt;
&lt;br /&gt;
* repository::send_file(stored_file $stored_file)&lt;br /&gt;
&lt;br /&gt;
* repository::get_file_reference($ref)&lt;br /&gt;
&lt;br /&gt;
=== Repository plugins changes ===&lt;br /&gt;
* Server files: store file parameters in `reference` field&lt;br /&gt;
* Private files: same as server files plugin&lt;br /&gt;
* Alfresco: Store UUID in `reference` field, but alfresco will change UUID once tomcat restart, may need other information to locate files&lt;br /&gt;
* Flickr private: needs flickr secret and token and photo id to locate the files&lt;br /&gt;
* File system: store file path in `reference` field&lt;br /&gt;
* s3: store file path, s3 repository will use secret and token to fetch the file from s3 no matter files are public or not.&lt;br /&gt;
* EQUELLA&lt;br /&gt;
*# Admin installs EQUELLA and setup parameters for single sign on&lt;br /&gt;
*# Teacher clicks EQUELLA instance, EQUELLA will return an URL of repository UI (plugin code)&lt;br /&gt;
*# Teacher pick a file from EQUELLA, EQUELLA repository UI will revoke file picker JavaScript API to notify moodle download this resource (plugin code)&lt;br /&gt;
*# Repository API stores file UUID and SSO userid, creates file reference in moodle file pool&lt;br /&gt;
*# EQUELLA plugin implements method to download contents using stored UUID and SSO userid&lt;br /&gt;
*# EQUELLA plugin implements method to update/invalidate cached resources (by cron)&lt;br /&gt;
&lt;br /&gt;
=== Content caching ===&lt;br /&gt;
* Create moodledata/repostory/cache directory&lt;br /&gt;
* Generate hash based on file URL and request parameters (provided by repository plugin), not content hash because we cannot send content hash to external repository for file information&lt;br /&gt;
* Cached files are stored using hash code as file name&lt;br /&gt;
&lt;br /&gt;
==== class repository_cache =====&lt;br /&gt;
&lt;br /&gt;
Returned stored_file instance or file path&lt;br /&gt;
&lt;br /&gt;
* store($url, $string_to_be_hashed)&lt;br /&gt;
* get($string_to_be_hashed)&lt;br /&gt;
&lt;br /&gt;
=== Filepicker Javascript API for customizing===&lt;br /&gt;
* File picker should be able to dislable file references by taking an option&lt;br /&gt;
* Support &amp;lt;object&amp;gt; tag in filepicker container&lt;br /&gt;
* Provide Javascript API to allow plugin communicate with filepicker&lt;br /&gt;
** Notify file picker to download file &lt;br /&gt;
** Notify file picker to pop up authentication page&lt;br /&gt;
&lt;br /&gt;
=== File manager to handle virtual files===&lt;br /&gt;
Not much trouble here, need to make sure draftfile.php can serve external resources, because all files managed by file manager is in draft area&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Project]]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=32153</id>
		<title>Improved support for external File content</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=32153"/>
		<updated>2012-02-13T03:31:20Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Work in progress}}&lt;br /&gt;
{{Infobox Project&lt;br /&gt;
|name = File synching&lt;br /&gt;
|state = Planning&lt;br /&gt;
|tracker =  MDL-28666&lt;br /&gt;
|discussion = TODO&lt;br /&gt;
|assignee = [[User:Martin Dougiamas|Martin Dougiamas]]&lt;br /&gt;
}}&lt;br /&gt;
{{Moodle 2.2}}&lt;br /&gt;
&lt;br /&gt;
==Problems with Files in 2.0==&lt;br /&gt;
&lt;br /&gt;
* It is not currently easy to use a file in multiple places throughout Moodle and update them all at once&lt;br /&gt;
* It is not currently easy to create a simple shared &amp;quot;course repository&amp;quot; for teachers to use&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Example use cases that will become possible==&lt;br /&gt;
&lt;br /&gt;
A teacher wants to upload a file once and use it in multiple courses.  When they update the file, it should be updated in all their courses automatically.&lt;br /&gt;
&lt;br /&gt;
# The teacher uploads the file to their private file area (or other repository).&lt;br /&gt;
# In each place they want to add the file, they use the file picker to select the file from their private files area (or other repo) and select &amp;quot;link to latest version&amp;quot;.&lt;br /&gt;
# Replacing the file will automatically mean the linked copies use it.&lt;br /&gt;
# Deleting the original file will force the linked copies to be static copies (the teacher will be informed of all the copies before proceeding with the delete).&lt;br /&gt;
&lt;br /&gt;
Several teachers want to create a shared repository of files together&lt;br /&gt;
# One teacher adds a course repository block to the course&lt;br /&gt;
# Using the block, the teacher creates an instance of a &amp;quot;?filesystem? repository&amp;quot; inside the course (essentially a folder).&lt;br /&gt;
# The content of the repository can be edited via the &amp;quot;Course repositories&amp;quot; block.&lt;br /&gt;
# All of the teachers in that course can now see that appear in their filepickers and select files &lt;br /&gt;
# Files can be &amp;quot;linked&amp;quot; as above&lt;br /&gt;
&lt;br /&gt;
A student wants to submit a linked file as an assignment, so they can continue updating it after the assignment due date.&lt;br /&gt;
# This will not be possible, because the assignment will not allow linking to files.&lt;br /&gt;
# The student is forced to upload a copy of a file and this is protected by the assignment module.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Solution summary==&lt;br /&gt;
&lt;br /&gt;
The basic idea is to allow the CONTENT of files to be stored outside of the filepool, while all the metadata and access is controlled by Moodle exactly the same as it is now.&lt;br /&gt;
&lt;br /&gt;
# Extend Files API with a new &amp;quot;reference&amp;quot; concept, with all files now having a &amp;quot;repositoryid&amp;quot; (the repository instance that the file came from) and a &amp;quot;reference&amp;quot; (the address in that repository of the content of the file).&lt;br /&gt;
# All files &#039;&#039;&#039;copied&#039;&#039;&#039; into Moodle don&#039;t have record in `files_reference` table, but others can specify a UUID (usually a URL with file-specific tokens in it, created by the original user who placed the file) in reference and repostiroyid columns.&lt;br /&gt;
# Improve the filesystem repository plugin to make it easier to create folder repositories on the fly, via a block.&lt;br /&gt;
# Add support to the filepicker UI to add &amp;quot;linking&amp;quot; to more repositories that support it, including the server files and filesystem repository.&lt;br /&gt;
# Virtual files are served via pluginfile.php URLs just like normal files:&lt;br /&gt;
## pluginfile.php uses module callback to determine access (as now), and if it passes then&lt;br /&gt;
## pluginfile.php calls a file logging subsystem to log the fact that this file is being served (useful for copyright reporting, for example)&lt;br /&gt;
## If the file is not &#039;local&#039;, then pluginfile.php uses the relevant repository callback to get the content of the file and streams it to the user with appropriate mimetypes etc&lt;br /&gt;
## The repositories have a way to cache this content in the normal Moodle filepool, to avoid repeated downloads.&lt;br /&gt;
## If an external repository is down or not configured, then the repository plugin can choose to just serve the local cached version (useful for restored backups and disaster tolerance)&lt;br /&gt;
# When a file is linked to another another file in the local location, then it has a location of &amp;quot;filepool&amp;quot;.  We need to pay attention to these during:&lt;br /&gt;
## Garbage cleanups &lt;br /&gt;
## Deleting the original file should create real copies of that file where required (user will be informed).&lt;br /&gt;
&lt;br /&gt;
Note that the original URL of files in external repositories are never revealed to users.&lt;br /&gt;
&lt;br /&gt;
==Details==&lt;br /&gt;
&lt;br /&gt;
=== File picker walk-through ===&lt;br /&gt;
[[File:File_picker_using_repository_reference_SD.png]]&lt;br /&gt;
# User clicks the &amp;quot;Insert image&amp;quot; button in TinyMCE, then launches our File Picker in the dialog&lt;br /&gt;
# User chooses a repository which supports UUID direct file references (eg Equella, Alfresco etc)&lt;br /&gt;
# A. File picker will ask repository plugin for customised repository UI if supported&lt;br /&gt;
# B. File Picker use repository UI and &amp;lt;object&amp;gt; type to display customised UI in file picker right pane&lt;br /&gt;
# User selects a file from the repository, if is customised UI, file picker Javascript API will be used to trigger file selection event (with all file related information), the repository plugin gives you two choices in the file picker interface  http://tracker.moodle.org/secure/attachment/25823/2011-11-09+15.23.png http://tracker.moodle.org/secure/attachment/25825/File+picker+options.png&lt;br /&gt;
#* C. Use the current version: this will copy the file to Moodle (as now).  No need to reference external content.&lt;br /&gt;
#* C. Use the latest version: this will use a file reference, so that the most recent version is always pulled from the repository&lt;br /&gt;
# D. If &amp;quot;Use the latest version&amp;quot; selected, Repository API will download the file as usual but store it for caching, we could locate this cached file when we need it, cron will take responsibility of invalidating/updating it&lt;br /&gt;
# E. Repository API ask File API to create a file with file parameters and reference.  File API stores the reference and repository instance id in `mdl_files_reference` table and returns the file URL which looks the same as any other Moodle file URL.&lt;br /&gt;
# File picker gives the file URL to TinyMCE&lt;br /&gt;
# When TinyMCE displays the resource, it will cause the browser to call the file URL, which contains pluginfile.php.&lt;br /&gt;
# Pluginfile.php uses File API to send file contents to browsers, if File API detects the requested file is not ordinary moodle files which is located at external repository, File API will ask Repository API for file content, Repository API will firstly look for the cached file, if file is too old or not found (removed  by cron checking), Repository API will fetch the resource (and cache it), then return to File API. Alternatively, Repository API could disable caching, asking for fresh content all the time.&lt;br /&gt;
&lt;br /&gt;
=== File request walk-through ===&lt;br /&gt;
[[File:File_Request_SD.png]]&lt;br /&gt;
&lt;br /&gt;
# A. User request a file&lt;br /&gt;
# B. File API detects if the request file is regular moodle files or located at external repository, if it&#039;s external files, File API will ask repository API to grab the file&lt;br /&gt;
# C. File API collects the file reference information from database, it could be stored in php serialised or JSON format&lt;br /&gt;
# D. File API passes raw file reference information to repository plugin&lt;br /&gt;
# E. Repository plugin will firstly check if file available locally&lt;br /&gt;
# F. If not repository plugin will use file reference information to grab the file&lt;br /&gt;
# G. Repository API returns file content to File API to serve the file&lt;br /&gt;
&lt;br /&gt;
=== Database changes ===&lt;br /&gt;
&lt;br /&gt;
We can create another table for left joining, this requires File API query this table when locating files:&lt;br /&gt;
* New file table `mdl_files_reference`&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `fileid` foreign key of `mdl_files`.`id`&lt;br /&gt;
** `repositoryid`&lt;br /&gt;
** `reference` - can be URL, UUID or other data format, repository plugin callbacks know the meaning of this field. File reference should be cached when adding to moodle, contenthash should be accurate.&lt;br /&gt;
&lt;br /&gt;
* `mdl_files_log` table for files access log, File API should have a new function to insert records to this table&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `userid` (0 if it&#039;s guest), &lt;br /&gt;
** `timeaccess`, &lt;br /&gt;
** `fileid`&lt;br /&gt;
&lt;br /&gt;
=== File API changes ===&lt;br /&gt;
* pluginfile.php and draftfile.php (it&#039;s actually send_stored_file()): when file isn&#039;t local file (`repositoryid` isn&#039;t zero), stored_file instance should ask repository plugin‘s callback to fetch file contents, Repository API decide return the cached copy or fresh contents&lt;br /&gt;
* When call file_prepare_draft_area(), it should keep linking files&#039; repository information.&lt;br /&gt;
* Adding callback to files API delete function, if the actual file get deleted, callback needs to check all linked files, and convert them to actually files&lt;br /&gt;
* new method: file_storage::create_file_from_reference&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function create_file_from_reference($file_record, $repoisitoryid, $reference, array $options = NULL)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
* Cron: we probably need a Repository API function to cache/update external files&lt;br /&gt;
&lt;br /&gt;
=== Repository API changes ===&lt;br /&gt;
&lt;br /&gt;
* Addtions to FILE_INTERNAL and FILE_EXTERNAL, we need another type FILE_REFERENCE = 4 // 0100, repository plugin needs to declare what types of files are supported&lt;br /&gt;
&lt;br /&gt;
* When users ask to create a reference (instead of copying) in file picker, Repository API try to cache the file in filepool(special file area for repository), after file downloaded, repository API should ask Files API to create a virtual file in `mdl_files` table, the existing fields stay the same, extra file reference information is stored in `mdl_files_reference` table, link or other format that repository plugins know. File API return the stored_file object, repository API generate the moodle url for users just like other moodle files without revealing the internal reference&lt;br /&gt;
&lt;br /&gt;
* Making repository plugin upgrade and versioning possible, repository plugin may need to update `reference` in `mdl_files_reference` table if reference info changed. (we already have db/upgrade.php, make sure all new plugins have one)&lt;br /&gt;
&lt;br /&gt;
* When deleting repository instance, all files imported by this instance will have to be converted to actual files, this has to be done by a File API function&lt;br /&gt;
&lt;br /&gt;
* Cron will use repository plugin callbacks to clean up cache files, repoistory::cron($repositoryid)&lt;br /&gt;
&lt;br /&gt;
=== Repository plugins changes ===&lt;br /&gt;
* Server files: store file parameters in `reference` field&lt;br /&gt;
* Private files: same as server files plugin&lt;br /&gt;
* Alfresco: Store UUID in `reference` field, but alfresco will change UUID once tomcat restart, may need other information to locate files&lt;br /&gt;
* Flickr private: needs flickr secret and token and photo id to locate the files&lt;br /&gt;
* File system: store file path in `reference` field&lt;br /&gt;
* s3: store file path, s3 repository will use secret and token to fetch the file from s3 no matter files are public or not.&lt;br /&gt;
* EQUELLA&lt;br /&gt;
*# Admin installs EQUELLA and setup parameters for single sign on&lt;br /&gt;
*# Teacher clicks EQUELLA instance, EQUELLA will return an URL of repository UI (plugin code)&lt;br /&gt;
*# Teacher pick a file from EQUELLA, EQUELLA repository UI will revoke file picker JavaScript API to notify moodle download this resource (plugin code)&lt;br /&gt;
*# Repository API stores file UUID and SSO userid, creates file reference in moodle file pool&lt;br /&gt;
*# EQUELLA plugin implements method to download contents using stored UUID and SSO userid&lt;br /&gt;
*# EQUELLA plugin implements method to update/invalidate cached resources (by cron)&lt;br /&gt;
&lt;br /&gt;
=== Content caching ===&lt;br /&gt;
* Create moodledata/repostory/cache directory&lt;br /&gt;
* Generate hash based on file URL and request parameters (provided by repository plugin), not content hash because we cannot send content hash to external repository for file information&lt;br /&gt;
* Cached files are stored using hash code as file name&lt;br /&gt;
&lt;br /&gt;
==== class repository_cache =====&lt;br /&gt;
* store_file($url, $string_to_be_hashed)&lt;br /&gt;
* get_file($string_to_be_hashed)&lt;br /&gt;
&lt;br /&gt;
=== Filepicker Javascript API for customizing===&lt;br /&gt;
* File picker should be able to dislable file references by taking an option&lt;br /&gt;
* Support &amp;lt;object&amp;gt; tag in filepicker container&lt;br /&gt;
* Provide Javascript API to allow plugin communicate with filepicker&lt;br /&gt;
** Notify file picker to download file &lt;br /&gt;
** Notify file picker to pop up authentication page&lt;br /&gt;
&lt;br /&gt;
=== File manager to handle virtual files===&lt;br /&gt;
Not much trouble here, need to make sure draftfile.php can serve external resources, because all files managed by file manager is in draft area&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Project]]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=32023</id>
		<title>Improved support for external File content</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=32023"/>
		<updated>2012-02-03T03:37:10Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Work in progress}}&lt;br /&gt;
{{Infobox Project&lt;br /&gt;
|name = File synching&lt;br /&gt;
|state = Planning&lt;br /&gt;
|tracker =  MDL-28666&lt;br /&gt;
|discussion = TODO&lt;br /&gt;
|assignee = [[User:Martin Dougiamas|Martin Dougiamas]]&lt;br /&gt;
}}&lt;br /&gt;
{{Moodle 2.2}}&lt;br /&gt;
&lt;br /&gt;
==Problems with Files in 2.0==&lt;br /&gt;
&lt;br /&gt;
* It is not currently easy to use a file in multiple places throughout Moodle and update them all at once&lt;br /&gt;
* It is not currently easy to create a simple shared &amp;quot;course repository&amp;quot; for teachers to use&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Example use cases that will become possible==&lt;br /&gt;
&lt;br /&gt;
A teacher wants to upload a file once and use it in multiple courses.  When they update the file, it should be updated in all their courses automatically.&lt;br /&gt;
&lt;br /&gt;
# The teacher uploads the file to their private file area (or other repository).&lt;br /&gt;
# In each place they want to add the file, they use the file picker to select the file from their private files area (or other repo) and select &amp;quot;link to latest version&amp;quot;.&lt;br /&gt;
# Replacing the file will automatically mean the linked copies use it.&lt;br /&gt;
# Deleting the original file will force the linked copies to be static copies (the teacher will be informed of all the copies before proceeding with the delete).&lt;br /&gt;
&lt;br /&gt;
Several teachers want to create a shared repository of files together&lt;br /&gt;
# One teacher adds a course repository block to the course&lt;br /&gt;
# Using the block, the teacher creates an instance of a &amp;quot;?filesystem? repository&amp;quot; inside the course (essentially a folder).&lt;br /&gt;
# The content of the repository can be edited via the &amp;quot;Course repositories&amp;quot; block.&lt;br /&gt;
# All of the teachers in that course can now see that appear in their filepickers and select files &lt;br /&gt;
# Files can be &amp;quot;linked&amp;quot; as above&lt;br /&gt;
&lt;br /&gt;
A student wants to submit a linked file as an assignment, so they can continue updating it after the assignment due date.&lt;br /&gt;
# This will not be possible, because the assignment will not allow linking to files.&lt;br /&gt;
# The student is forced to upload a copy of a file and this is protected by the assignment module.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Solution summary==&lt;br /&gt;
&lt;br /&gt;
The basic idea is to allow the CONTENT of files to be stored outside of the filepool, while all the metadata and access is controlled by Moodle exactly the same as it is now.&lt;br /&gt;
&lt;br /&gt;
# Extend Files API with a new &amp;quot;source&amp;quot; concept, with all files now having a &amp;quot;sourcerepositoryid&amp;quot; (the repository instance that the file came from) and a &amp;quot;sourcereference&amp;quot; (the address in that repository of the content of the file).&lt;br /&gt;
# All files &#039;&#039;&#039;copied&#039;&#039;&#039; into Moodle have a &amp;quot;sourcerepositoryid&amp;quot; of &amp;quot;0&amp;quot;, but others can specify a UUID (usually a URL with file-specific tokens in it, created by the original user who placed the file).&lt;br /&gt;
# Improve the filesystem repository plugin to make it easier to create folder repositories on the fly, via a block.&lt;br /&gt;
# Add support to the filepicker UI to add &amp;quot;linking&amp;quot; to more repositories that support it, including the server files and filesystem browser.&lt;br /&gt;
# Virtual files are served via pluginfile.php URLs just like normal files:&lt;br /&gt;
## pluginfile.php uses module callback to determine access (as now), and if it passes then&lt;br /&gt;
## pluginfile.php calls a file logging subsystem to log the fact that this file is being served (useful for copyright reporting, for example)&lt;br /&gt;
## If the sourcerepository is not &#039;local&#039;, then pluginfile.php uses the relevant repository callback to get the content of the file and streams it to the user with appropriate mimetypes etc&lt;br /&gt;
## The repositories have a way to cache this content in the normal Moodle filepool, to avoid repeated downloads.&lt;br /&gt;
## If an external repository is down or not configured, then the repository plugin can choose to just serve the local cached version (useful for restored backups and disaster tolerance)&lt;br /&gt;
# When a file is linked to another another file in the local location, then it has a location of &amp;quot;filepool&amp;quot;.  We need to pay attention to these during:&lt;br /&gt;
## Garbage cleanups &lt;br /&gt;
## Deleting the original file should create real copies of that file where required (user will be informed).&lt;br /&gt;
&lt;br /&gt;
Note that the original URL of files in external repositories are never revealed to users.&lt;br /&gt;
&lt;br /&gt;
==Details==&lt;br /&gt;
&lt;br /&gt;
=== File picker walk-through ===&lt;br /&gt;
[[File:File_picker_using_repository_reference_SD.png]]&lt;br /&gt;
# User clicks the &amp;quot;Insert image&amp;quot; button in TinyMCE, then launches our File Picker in the dialog&lt;br /&gt;
# User chooses a repository which supports UUID direct file references (eg Equella, Alfresco etc)&lt;br /&gt;
# A. File picker will ask repository plugin for customised repository UI if supported&lt;br /&gt;
# B. File Picker use repository UI and &amp;lt;object&amp;gt; type to display customised UI in file picker right pane&lt;br /&gt;
# User selects a file from the repository, if is customised UI, file picker Javascript API will be used to trigger file selection event (with all file related information), the repository plugin gives you two choices in the file picker interface  http://tracker.moodle.org/secure/attachment/25823/2011-11-09+15.23.png http://tracker.moodle.org/secure/attachment/25825/File+picker+options.png&lt;br /&gt;
#* C. Use the current version: this will copy the file to Moodle (as now).  No need to reference external content.&lt;br /&gt;
#* C. Use the latest version: this will use a file reference, so that the most recent version is always pulled from the repository&lt;br /&gt;
# D. If &amp;quot;Use the latest version&amp;quot; selected, Repository API will download the file as usual but store it for caching, we could locate this cached file when we need it, cron will take responsibility of invalidating/updating it&lt;br /&gt;
# E. Repository API ask File API to create a file with file parameters and reference.  File API stores the reference and repository instance id in `mdl_files_reference` table and returns the file URL which looks the same as any other Moodle file URL.&lt;br /&gt;
# File picker gives the file URL to TinyMCE&lt;br /&gt;
# When TinyMCE displays the resource, it will cause the browser to call the file URL, which contains pluginfile.php.&lt;br /&gt;
# Pluginfile.php uses File API to send file contents to browsers, if File API detects the requested file is not ordinary moodle files which is located at external repository, File API will ask Repository API for file content, Repository API will firstly look for the cached file, if file is too old or not found (removed  by cron checking), Repository API will fetch the resource (and cache it), then return to File API. Alternatively, Repository API could disable caching, asking for fresh content all the time.&lt;br /&gt;
&lt;br /&gt;
=== File request walk-through ===&lt;br /&gt;
[[File:File_Request_SD.png]]&lt;br /&gt;
&lt;br /&gt;
# A. User request a file&lt;br /&gt;
# B. File API detects if the request file is regular moodle files or located at external repository, if it&#039;s external files, File API will ask repository API to grab the file&lt;br /&gt;
# C. File API collects the file reference information from database, it could be stored in php serialised or JSON format&lt;br /&gt;
# D. File API passes raw file reference information to repository plugin&lt;br /&gt;
# E. Repository plugin will firstly check if file available locally&lt;br /&gt;
# F. If not repository plugin will use file reference information to grab the file&lt;br /&gt;
# G. Repository API returns file content to File API to serve the file&lt;br /&gt;
&lt;br /&gt;
=== Database changes ===&lt;br /&gt;
&lt;br /&gt;
We can create another table for left joining, this requires File API query this table when locating files:&lt;br /&gt;
* New file table `mdl_files_reference`&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `fileid` foreign key of `mdl_files`.`id`&lt;br /&gt;
** `repositoryid`&lt;br /&gt;
** `reference` - can be URL, UUID or other data format, repository plugin callbacks know the meaning of this field. File reference should be cached when adding to moodle, contenthash should be accurate.&lt;br /&gt;
&lt;br /&gt;
* `mdl_files_log` table for files access log, File API should have a new function to insert records to this table&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `userid` (0 if it&#039;s guest), &lt;br /&gt;
** `timeaccess`, &lt;br /&gt;
** `fileid`&lt;br /&gt;
&lt;br /&gt;
=== File API changes ===&lt;br /&gt;
* pluginfile.php and draftfile.php (it&#039;s actually send_stored_file()): when file isn&#039;t local file (`repositoryid` isn&#039;t zero), stored_file instance should ask repository plugin‘s callback to fetch file contents, Repository API decide return the cached copy or fresh contents&lt;br /&gt;
* When call file_prepare_draft_area(), it should keep linking files&#039; repository information.&lt;br /&gt;
* Adding callback to files API delete function, if the actual file get deleted, callback needs to check all linked files, and convert them to actually files&lt;br /&gt;
* new method: file_storage::create_file_from_reference&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function create_file_from_reference($file_record, $repoisitoryid, $reference, array $options = NULL)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
* Cron: we probably need a Repository API function to cache/update external files&lt;br /&gt;
&lt;br /&gt;
=== Repository API changes ===&lt;br /&gt;
&lt;br /&gt;
* Addtions to FILE_INTERNAL and FILE_EXTERNAL, we need another type FILE_REFERENCE = 4 // 0100, repository plugin needs to declare what types of files are supported&lt;br /&gt;
&lt;br /&gt;
* When users ask to create a reference (instead of copying) in file picker, Repository API try to cache the file in filepool(special file area for repository), after file downloaded, repository API should ask Files API to create a virtual file in `mdl_files` table, the existing fields stay the same, extra file reference information is stored in `mdl_files_reference` table, link or other format that repository plugins know. File API return the stored_file object, repository API generate the moodle url for users just like other moodle files without revealing the internal reference&lt;br /&gt;
&lt;br /&gt;
* Making repository plugin upgrade and versioning possible, repository plugin may need to update `reference` in `mdl_files_reference` table if reference info changed. (we already have db/upgrade.php, make sure all new plugins have one)&lt;br /&gt;
&lt;br /&gt;
* When deleting repository instance, all files imported by this instance will have to be converted to actual files, this has to be done by a File API function&lt;br /&gt;
&lt;br /&gt;
* Cron will use repository plugin callbacks to clean up cache files, repoistory::cron($repositoryid)&lt;br /&gt;
&lt;br /&gt;
=== Repository plugins changes ===&lt;br /&gt;
* Server files: store file parameters in `reference` field&lt;br /&gt;
* Private files: same as server files plugin&lt;br /&gt;
* Alfresco: Store UUID in `reference` field, but alfresco will change UUID once tomcat restart, may need other information to locate files&lt;br /&gt;
* Flickr private: needs flickr secret and token and photo id to locate the files&lt;br /&gt;
* File system: store file path in `reference` field&lt;br /&gt;
* s3: store file path, s3 repository will use secret and token to fetch the file from s3 no matter files are public or not.&lt;br /&gt;
* EQUELLA&lt;br /&gt;
*# Admin installs EQUELLA and setup parameters for single sign on&lt;br /&gt;
*# Teacher clicks EQUELLA instance, EQUELLA will return an URL of repository UI (plugin code)&lt;br /&gt;
*# Teacher pick a file from EQUELLA, EQUELLA repository UI will revoke file picker JavaScript API to notify moodle download this resource (plugin code)&lt;br /&gt;
*# Repository API stores file UUID and SSO userid, creates file reference in moodle file pool&lt;br /&gt;
*# EQUELLA plugin implements method to download contents using stored UUID and SSO userid&lt;br /&gt;
*# EQUELLA plugin implements method to update/invalidate cached resources (by cron)&lt;br /&gt;
&lt;br /&gt;
=== Content caching ===&lt;br /&gt;
* Create moodledata/repostory/cache directory&lt;br /&gt;
* Generate hash based on file URL and request parameters (provided by repository plugin), not content hash because we cannot send content hash to external repository for file information&lt;br /&gt;
* Cached files are stored using hash code as file name&lt;br /&gt;
&lt;br /&gt;
==== class repository_cache =====&lt;br /&gt;
* store_file($url, $string_to_be_hashed)&lt;br /&gt;
* get_file($string_to_be_hashed)&lt;br /&gt;
&lt;br /&gt;
=== Filepicker Javascript API for customizing===&lt;br /&gt;
* File picker should be able to dislable file references by taking an option&lt;br /&gt;
* Support &amp;lt;object&amp;gt; tag in filepicker container&lt;br /&gt;
* Provide Javascript API to allow plugin communicate with filepicker&lt;br /&gt;
** Notify file picker to download file &lt;br /&gt;
** Notify file picker to pop up authentication page&lt;br /&gt;
&lt;br /&gt;
=== File manager to handle virtual files===&lt;br /&gt;
Not much trouble here, need to make sure draftfile.php can serve external resources, because all files managed by file manager is in draft area&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Project]]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=32017</id>
		<title>Improved support for external File content</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=32017"/>
		<updated>2012-02-02T08:32:56Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Work in progress}}&lt;br /&gt;
{{Infobox Project&lt;br /&gt;
|name = File synching&lt;br /&gt;
|state = Planning&lt;br /&gt;
|tracker =  MDL-28666&lt;br /&gt;
|discussion = TODO&lt;br /&gt;
|assignee = [[User:Martin Dougiamas|Martin Dougiamas]]&lt;br /&gt;
}}&lt;br /&gt;
{{Moodle 2.2}}&lt;br /&gt;
&lt;br /&gt;
==Problems with Files in 2.0==&lt;br /&gt;
&lt;br /&gt;
* It is not currently easy to use a file in multiple places throughout Moodle and update them all at once&lt;br /&gt;
* It is not currently easy to create a simple shared &amp;quot;course repository&amp;quot; for teachers to use&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Example use cases that will become possible==&lt;br /&gt;
&lt;br /&gt;
A teacher wants to upload a file once and use it in multiple courses.  When they update the file, it should be updated in all their courses automatically.&lt;br /&gt;
&lt;br /&gt;
# The teacher uploads the file to their private file area (or other repository).&lt;br /&gt;
# In each place they want to add the file, they use the file picker to select the file from their private files area (or other repo) and select &amp;quot;link to latest version&amp;quot;.&lt;br /&gt;
# Replacing the file will automatically mean the linked copies use it.&lt;br /&gt;
# Deleting the original file will force the linked copies to be static copies (the teacher will be informed of all the copies before proceeding with the delete).&lt;br /&gt;
&lt;br /&gt;
Several teachers want to create a shared repository of files together&lt;br /&gt;
# One teacher adds a course repository block to the course&lt;br /&gt;
# Using the block, the teacher creates an instance of a &amp;quot;?filesystem? repository&amp;quot; inside the course (essentially a folder).&lt;br /&gt;
# The content of the repository can be edited via the &amp;quot;Course repositories&amp;quot; block.&lt;br /&gt;
# All of the teachers in that course can now see that appear in their filepickers and select files &lt;br /&gt;
# Files can be &amp;quot;linked&amp;quot; as above&lt;br /&gt;
&lt;br /&gt;
A student wants to submit a linked file as an assignment, so they can continue updating it after the assignment due date.&lt;br /&gt;
# This will not be possible, because the assignment will not allow linking to files.&lt;br /&gt;
# The student is forced to upload a copy of a file and this is protected by the assignment module.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Solution summary==&lt;br /&gt;
&lt;br /&gt;
The basic idea is to allow the CONTENT of files to be stored outside of the filepool, while all the metadata and access is controlled by Moodle exactly the same as it is now.&lt;br /&gt;
&lt;br /&gt;
# Extend Files API with a new &amp;quot;source&amp;quot; concept, with all files now having a &amp;quot;sourcerepositoryid&amp;quot; (the repository instance that the file came from) and a &amp;quot;sourcereference&amp;quot; (the address in that repository of the content of the file).&lt;br /&gt;
# All files &#039;&#039;&#039;copied&#039;&#039;&#039; into Moodle have a &amp;quot;sourcerepositoryid&amp;quot; of &amp;quot;0&amp;quot;, but others can specify a UUID (usually a URL with file-specific tokens in it, created by the original user who placed the file).&lt;br /&gt;
# Improve the filesystem repository plugin to make it easier to create folder repositories on the fly, via a block.&lt;br /&gt;
# Add support to the filepicker UI to add &amp;quot;linking&amp;quot; to more repositories that support it, including the server files and filesystem browser.&lt;br /&gt;
# Virtual files are served via pluginfile.php URLs just like normal files:&lt;br /&gt;
## pluginfile.php uses module callback to determine access (as now), and if it passes then&lt;br /&gt;
## pluginfile.php calls a file logging subsystem to log the fact that this file is being served (useful for copyright reporting, for example)&lt;br /&gt;
## If the sourcerepository is not &#039;local&#039;, then pluginfile.php uses the relevant repository callback to get the content of the file and streams it to the user with appropriate mimetypes etc&lt;br /&gt;
## The repositories have a way to cache this content in the normal Moodle filepool, to avoid repeated downloads.&lt;br /&gt;
## If an external repository is down or not configured, then the repository plugin can choose to just serve the local cached version (useful for restored backups and disaster tolerance)&lt;br /&gt;
# When a file is linked to another another file in the local location, then it has a location of &amp;quot;filepool&amp;quot;.  We need to pay attention to these during:&lt;br /&gt;
## Garbage cleanups &lt;br /&gt;
## Deleting the original file should create real copies of that file where required (user will be informed).&lt;br /&gt;
&lt;br /&gt;
Note that the original URL of files in external repositories are never revealed to users.&lt;br /&gt;
&lt;br /&gt;
==Details==&lt;br /&gt;
&lt;br /&gt;
=== File picker walk-through ===&lt;br /&gt;
[[File:File_picker_using_repository_reference_SD.png]]&lt;br /&gt;
# User clicks the &amp;quot;Insert image&amp;quot; button in TinyMCE, then launches our File Picker in the dialog&lt;br /&gt;
# User chooses a repository which supports UUID direct file references (eg Equella, Alfresco etc)&lt;br /&gt;
# A. File picker will ask repository plugin for customised repository UI if supported&lt;br /&gt;
# B. File Picker use repository UI and &amp;lt;object&amp;gt; type to display customised UI in file picker right pane&lt;br /&gt;
# User selects a file from the repository, if is customised UI, file picker Javascript API will be used to trigger file selection event (with all file related information), the repository plugin gives you two choices in the file picker interface  http://tracker.moodle.org/secure/attachment/25823/2011-11-09+15.23.png http://tracker.moodle.org/secure/attachment/25825/File+picker+options.png&lt;br /&gt;
#* C. Use the current version: this will copy the file to Moodle (as now).  No need to reference external content.&lt;br /&gt;
#* C. Use the latest version: this will use a file reference, so that the most recent version is always pulled from the repository&lt;br /&gt;
# D. If &amp;quot;Use the latest version&amp;quot; selected, Repository API will download the file as usual but store it in special area in file pool for caching, we could locate this cached file when we need it, cron will take responsibility of invalidating/updating it&lt;br /&gt;
# E. Repository API ask File API to create a file with content hash and UUID.  File API stores the UUID and repository instance id in `mdl_files_reference` table and returns the file URL which looks the same as any other Moodle file URL.&lt;br /&gt;
# File picker gives the file URL to TinyMCE&lt;br /&gt;
# When TinyMCE displays the resource, it will cause the browser to call the file URL, which contains pluginfile.php.&lt;br /&gt;
# Pluginfile.php uses File API to send file contents to browsers, if File API detects the requested file is not ordinary moodle files which is located at external repository, File API will ask Repository API for file content, Repository API will firstly look for the cached file, if file is too old or not found (removed  by cron checking), Repository API will fetch the resource (and cache it), then return to File API. Alternatively, Repository API could disable caching, asking for fresh content all the time.&lt;br /&gt;
&lt;br /&gt;
=== File request walk-through ===&lt;br /&gt;
[[File:File_Request_SD.png]]&lt;br /&gt;
&lt;br /&gt;
# A. User request a file&lt;br /&gt;
# B. File API detects if the request file is regular moodle files or located at external repository, if it&#039;s external files, File API will ask repository API to grab the file&lt;br /&gt;
# C. File API collects the file reference information from database, it could be stored in php serialised or JSON format&lt;br /&gt;
# D. File API passes raw file reference information to repository plugin&lt;br /&gt;
# E. Repository plugin will firstly check if file available locally&lt;br /&gt;
# F. If not repository plugin will use file reference information to grab the file&lt;br /&gt;
# G. Repository API returns file content to File API to serve the file&lt;br /&gt;
&lt;br /&gt;
=== Database changes ===&lt;br /&gt;
&lt;br /&gt;
We can create another table for left joining, this requires File API query this table when locating files:&lt;br /&gt;
* New file table `mdl_files_reference`&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `fileid` foreign key of `mdl_files`.`id`&lt;br /&gt;
** `repositoryid`&lt;br /&gt;
** `reference` - can be URL, UUID or other data format, repository plugin callbacks know the meaning of this field. File reference should be cached when adding to moodle, contenthash should be accurate.&lt;br /&gt;
&lt;br /&gt;
* `mdl_files_log` table for files access log, File API should have a new function to insert records to this table&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `userid` (0 if it&#039;s guest), &lt;br /&gt;
** `timeaccess`, &lt;br /&gt;
** `fileid`&lt;br /&gt;
&lt;br /&gt;
=== File API changes ===&lt;br /&gt;
* pluginfile.php and draftfile.php (it&#039;s actually send_stored_file()): when file isn&#039;t local file (`repositoryid` isn&#039;t zero), stored_file instance should ask repository plugin‘s callback to fetch file contents, Repository API decide return the cached copy or fresh contents&lt;br /&gt;
* When call file_prepare_draft_area(), it should keep linking files&#039; repository information.&lt;br /&gt;
* Adding callback to files API delete function, if the actual file get deleted, callback needs to check all linked files, and convert them to actually files&lt;br /&gt;
* new method: file_storage::create_file_from_reference&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function create_file_from_reference($file_record, $repoisitoryid, $reference, array $options = NULL)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
* Cron: we probably need a Repository API function to cache/update external files&lt;br /&gt;
&lt;br /&gt;
=== Repository API changes ===&lt;br /&gt;
&lt;br /&gt;
* Addtions to FILE_INTERNAL and FILE_EXTERNAL, we need another type FILE_REFERENCE = 4 // 0100, repository plugin needs to declare what types of files are supported&lt;br /&gt;
&lt;br /&gt;
* When users ask to create a reference (instead of copying) in file picker, Repository API try to cache the file in filepool(special file area for repository), after file downloaded, repository API should ask Files API to create a virtual file in `mdl_files` table, the existing fields stay the same, extra file reference information is stored in `mdl_files_reference` table, link or other format that repository plugins know. File API return the stored_file object, repository API generate the moodle url for users just like other moodle files without revealing the internal reference&lt;br /&gt;
&lt;br /&gt;
* Making repository plugin upgrade and versioning possible, repository plugin may need to update `reference` in `mdl_files_reference` table if reference info changed. (we already have db/upgrade.php, make sure all new plugins have one)&lt;br /&gt;
&lt;br /&gt;
* When deleting repository instance, all files imported by this instance will have to be converted to actual files, this has to be done by a File API function&lt;br /&gt;
&lt;br /&gt;
* Cron will use repository plugin callbacks to clean up cache files, repoistory::cron($repositoryid)&lt;br /&gt;
&lt;br /&gt;
=== Repository plugins changes ===&lt;br /&gt;
* Server files: store file parameters in `reference` field&lt;br /&gt;
* Private files: same as server files plugin&lt;br /&gt;
* Alfresco: Store UUID in `reference` field, but alfresco will change UUID once tomcat restart, may need other information to locate files&lt;br /&gt;
* Flickr private: needs flickr secret and token and photo id to locate the files&lt;br /&gt;
* File system: store file path in `reference` field&lt;br /&gt;
* s3: store file path, s3 repository will use secret and token to fetch the file from s3 no matter files are public or not.&lt;br /&gt;
* EQUELLA&lt;br /&gt;
*# Admin installs EQUELLA and setup parameters for single sign on&lt;br /&gt;
*# Teacher clicks EQUELLA instance, EQUELLA will return an URL of repository UI (plugin code)&lt;br /&gt;
*# Teacher pick a file from EQUELLA, EQUELLA repository UI will revoke file picker JavaScript API to notify moodle download this resource (plugin code)&lt;br /&gt;
*# Repository API stores file UUID and SSO userid, creates file reference in moodle file pool&lt;br /&gt;
*# EQUELLA plugin implements method to download contents using stored UUID and SSO userid&lt;br /&gt;
*# EQUELLA plugin implements method to update/invalidate cached resources (by cron)&lt;br /&gt;
&lt;br /&gt;
=== Content caching ===&lt;br /&gt;
* Create moodledata/repostory/cache directory&lt;br /&gt;
* Generate hash based on file URL and request parameters (provided by repository plugin), not content hash because we cannot send content hash to external repository for file information&lt;br /&gt;
* Cached files are stored using hash code as file name&lt;br /&gt;
&lt;br /&gt;
==== class repository_cache =====&lt;br /&gt;
* store_file($url, $string_to_be_hashed)&lt;br /&gt;
* get_file($string_to_be_hashed)&lt;br /&gt;
&lt;br /&gt;
=== Filepicker Javascript API for customizing===&lt;br /&gt;
* File picker should be able to dislable file references by taking an option&lt;br /&gt;
* Support &amp;lt;object&amp;gt; tag in filepicker container&lt;br /&gt;
* Provide Javascript API to allow plugin communicate with filepicker&lt;br /&gt;
** Notify file picker to download file &lt;br /&gt;
** Notify file picker to pop up authentication page&lt;br /&gt;
&lt;br /&gt;
=== File manager to handle virtual files===&lt;br /&gt;
Not much trouble here, need to make sure draftfile.php can serve external resources, because all files managed by file manager is in draft area&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Project]]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=32007</id>
		<title>Improved support for external File content</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Improved_support_for_external_File_content&amp;diff=32007"/>
		<updated>2012-01-31T06:56:08Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Work in progress}}&lt;br /&gt;
{{Infobox Project&lt;br /&gt;
|name = File synching&lt;br /&gt;
|state = Planning&lt;br /&gt;
|tracker =  MDL-28666&lt;br /&gt;
|discussion = TODO&lt;br /&gt;
|assignee = [[User:Martin Dougiamas|Martin Dougiamas]]&lt;br /&gt;
}}&lt;br /&gt;
{{Moodle 2.2}}&lt;br /&gt;
&lt;br /&gt;
==Problems with Files in 2.0==&lt;br /&gt;
&lt;br /&gt;
* It is not currently easy to use a file in multiple places throughout Moodle and update them all at once&lt;br /&gt;
* It is not currently easy to create a simple shared &amp;quot;course repository&amp;quot; for teachers to use&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Example use cases that will become possible==&lt;br /&gt;
&lt;br /&gt;
A teacher wants to upload a file once and use it in multiple courses.  When they update the file, it should be updated in all their courses automatically.&lt;br /&gt;
&lt;br /&gt;
# The teacher uploads the file to their private file area (or other repository).&lt;br /&gt;
# In each place they want to add the file, they use the file picker to select the file from their private files area (or other repo) and select &amp;quot;link to latest version&amp;quot;.&lt;br /&gt;
# Replacing the file will automatically mean the linked copies use it.&lt;br /&gt;
# Deleting the original file will force the linked copies to be static copies (the teacher will be informed of all the copies before proceeding with the delete).&lt;br /&gt;
&lt;br /&gt;
Several teachers want to create a shared repository of files together&lt;br /&gt;
# One teacher adds a course repository block to the course&lt;br /&gt;
# Using the block, the teacher creates an instance of a &amp;quot;?filesystem? repository&amp;quot; inside the course (essentially a folder).&lt;br /&gt;
# The content of the repository can be edited via the &amp;quot;Course repositories&amp;quot; block.&lt;br /&gt;
# All of the teachers in that course can now see that appear in their filepickers and select files &lt;br /&gt;
# Files can be &amp;quot;linked&amp;quot; as above&lt;br /&gt;
&lt;br /&gt;
A student wants to submit a linked file as an assignment, so they can continue updating it after the assignment due date.&lt;br /&gt;
# This will not be possible, because the assignment will not allow linking to files.&lt;br /&gt;
# The student is forced to upload a copy of a file and this is protected by the assignment module.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==Solution summary==&lt;br /&gt;
&lt;br /&gt;
The basic idea is to allow the CONTENT of files to be stored outside of the filepool, while all the metadata and access is controlled by Moodle exactly the same as it is now.&lt;br /&gt;
&lt;br /&gt;
# Extend Files API with a new &amp;quot;source&amp;quot; concept, with all files now having a &amp;quot;sourcerepositoryid&amp;quot; (the repository instance that the file came from) and a &amp;quot;sourcereference&amp;quot; (the address in that repository of the content of the file).&lt;br /&gt;
# All files &#039;&#039;&#039;copied&#039;&#039;&#039; into Moodle have a &amp;quot;sourcerepositoryid&amp;quot; of &amp;quot;0&amp;quot;, but others can specify a UUID (usually a URL with file-specific tokens in it, created by the original user who placed the file).&lt;br /&gt;
# Improve the filesystem repository plugin to make it easier to create folder repositories on the fly, via a block.&lt;br /&gt;
# Add support to the filepicker UI to add &amp;quot;linking&amp;quot; to more repositories that support it, including the server files and filesystem browser.&lt;br /&gt;
# Virtual files are served via pluginfile.php URLs just like normal files:&lt;br /&gt;
## pluginfile.php uses module callback to determine access (as now), and if it passes then&lt;br /&gt;
## pluginfile.php calls a file logging subsystem to log the fact that this file is being served (useful for copyright reporting, for example)&lt;br /&gt;
## If the sourcerepository is not &#039;local&#039;, then pluginfile.php uses the relevant repository callback to get the content of the file and streams it to the user with appropriate mimetypes etc&lt;br /&gt;
## The repositories have a way to cache this content in the normal Moodle filepool, to avoid repeated downloads.&lt;br /&gt;
## If an external repository is down or not configured, then the repository plugin can choose to just serve the local cached version (useful for restored backups and disaster tolerance)&lt;br /&gt;
# When a file is linked to another another file in the local location, then it has a location of &amp;quot;filepool&amp;quot;.  We need to pay attention to these during:&lt;br /&gt;
## Garbage cleanups &lt;br /&gt;
## Deleting the original file should create real copies of that file where required (user will be informed).&lt;br /&gt;
&lt;br /&gt;
Note that the original URL of files in external repositories are never revealed to users.&lt;br /&gt;
&lt;br /&gt;
==Details==&lt;br /&gt;
&lt;br /&gt;
=== File picker walk-through ===&lt;br /&gt;
[[File:File_picker_using_repository_reference_SD.png]]&lt;br /&gt;
# User clicks the &amp;quot;Insert image&amp;quot; button in TinyMCE, then launches our File Picker in the dialog&lt;br /&gt;
# User chooses a repository which supports UUID direct file references (eg Equella, Alfresco etc)&lt;br /&gt;
# A. File picker will ask repository plugin for customised repository UI if supported&lt;br /&gt;
# B. File Picker use repository UI and &amp;lt;object&amp;gt; type to display customised UI in file picker right pane&lt;br /&gt;
# User selects a file from the repository, if is customised UI, file picker Javascript API will be used to trigger file selection event (with all file related information), the repository plugin gives you two choices in the file picker interface  http://tracker.moodle.org/secure/attachment/25823/2011-11-09+15.23.png http://tracker.moodle.org/secure/attachment/25825/File+picker+options.png&lt;br /&gt;
#* C. Use the current version: this will copy the file to Moodle (as now).  No need to reference external content.&lt;br /&gt;
#* C. Use the latest version: this will use a file reference, so that the most recent version is always pulled from the repository&lt;br /&gt;
# D. If &amp;quot;Use the latest version&amp;quot; selected, Repository API will download the file as usual but store it in special area in file pool for caching, we could locate this cached file when we need it, cron will take responsibility of invalidating/updating it&lt;br /&gt;
# E. Repository API ask File API to create a file with content hash and UUID.  File API stores the UUID and repository instance id in `mdl_files_source` table and returns the file URL which looks the same as any other Moodle file URL.&lt;br /&gt;
# File picker gives the file URL to TinyMCE&lt;br /&gt;
# When TinyMCE displays the resource, it will cause the browser to call the file URL, which contains pluginfile.php.&lt;br /&gt;
# Pluginfile.php uses File API to send file contents to browsers, if File API detects the requested file is not ordinary moodle files which is located at external repository, File API will ask Repository API for file content, Repository API will firstly look for the cached file, if file is too old or not found (removed  by cron checking), Repository API will fetch the resource (and cache it), then return to File API. Alternatively, Repository API could disable caching, asking for fresh content all the time.&lt;br /&gt;
&lt;br /&gt;
=== File request walk-through ===&lt;br /&gt;
[[File:File_Request_SD.png]]&lt;br /&gt;
&lt;br /&gt;
# A. User request a file&lt;br /&gt;
# B. File API detects if the request file is regular moodle files or located at external repository, if it&#039;s external files, File API will ask repository API to grab the file&lt;br /&gt;
# C. File API collects the file reference information from database, it could be stored in php serialised or JSON format&lt;br /&gt;
# D. File API passes raw file reference information to repository plugin&lt;br /&gt;
# E. Repository plugin will firstly check if file available locally&lt;br /&gt;
# F. If not repository plugin will use file reference information to grab the file&lt;br /&gt;
# G. Repository API returns file content to File API to serve the file&lt;br /&gt;
&lt;br /&gt;
=== Database changes ===&lt;br /&gt;
&lt;br /&gt;
We can create another table for left joining, this requires File API query this table when locating files:&lt;br /&gt;
* New file table `mdl_files_source`&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `fileid` foreign key of `mdl_files`.`id`&lt;br /&gt;
** `repositoryid`&lt;br /&gt;
** `reference` - can be URL, UUID or other data format, repository plugin callbacks know the meaning of this field. File reference should be cached when adding to moodle, contenthash should be accurate.&lt;br /&gt;
&lt;br /&gt;
* `mdl_files_log` table for files access log, File API should have a new function to insert records to this table&lt;br /&gt;
** `id` primary key&lt;br /&gt;
** `userid` (0 if it&#039;s guest), &lt;br /&gt;
** `timeaccess`, &lt;br /&gt;
** `fileid`&lt;br /&gt;
&lt;br /&gt;
=== File API changes ===&lt;br /&gt;
* pluginfile.php: when file isn&#039;t local file (`sourcerepositoryid` isn&#039;t zero), pluginfile.php should ask repository plugin‘s callback to fetch file contents, Repository API decide return the cached copy or fresh contents&lt;br /&gt;
* draftfile.php should do the same as pluginfile.php&lt;br /&gt;
* When call file_prepare_draft_area(), it should keep linking files&#039; repository information.&lt;br /&gt;
* Adding callback to files API delete function, if the actual file get deleted, callback needs to check all linked files, and convert them to actually files&lt;br /&gt;
* new method: file_storage::create_file_from_reference&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function create_file_from_reference($file_record, $repoisitoryid, $reference, array $options = NULL)&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
* Cron: we probably need a Repository API function to cache/update external files&lt;br /&gt;
&lt;br /&gt;
=== Repository API changes ===&lt;br /&gt;
&lt;br /&gt;
* Addtions to FILE_INTERNAL and FILE_EXTERNAL, we need another type FILE_REFERENCE, repository plugin needs to declare what types of files are supported&lt;br /&gt;
&lt;br /&gt;
* When users ask to create a reference (instead of copying) in file picker, Repository API try to cache the file in filepool(special file area for repository), after file downloaded, repository API should ask Files API to create a virtual file in `mdl_files` table, the existing fields stay the same, `sourcerepositoryid` will be repoistory instance id, `sourcereference` will be a reference, link or other format that repository plugins know. File API return the stored_file object, repository API generate the moodle url for users just like other moodle files without revealing the internal reference&lt;br /&gt;
&lt;br /&gt;
* Making repository plugin upgrade and versioning possible, repository plugin may need to update `sourcereference` in `mdl_files` table if reference info changed. (we already have db/upgrade.php, make sure all new plugins have one)&lt;br /&gt;
&lt;br /&gt;
* When deleting repository instance, all files imported by this instance will have to be converted to actual files, this has to be done by a File API function&lt;br /&gt;
&lt;br /&gt;
* Instead of using get_file() to download file content, we need one new method repository::get_reference_info($file) to get file reference information&lt;br /&gt;
&lt;br /&gt;
* Cron will use repository plugin callbacks to clean up cache files, repoistory::cron($repositoryid)&lt;br /&gt;
&lt;br /&gt;
=== Repository plugins changes ===&lt;br /&gt;
* Server files: store file parameters in `reference` field&lt;br /&gt;
* Private files: same as server files plugin&lt;br /&gt;
* Alfresco: Store UUID in `reference` field, but alfresco will change UUID once tomcat restart, may need other information to locate files&lt;br /&gt;
* Flickr private: needs flickr secret and token and photo id to locate the files&lt;br /&gt;
* File system: store file path in `reference` field&lt;br /&gt;
* s3: store file path, s3 repository will use secret and token to fetch the file from s3 no matter files are public or not.&lt;br /&gt;
* EQUELLA&lt;br /&gt;
*# Admin installs EQUELLA and setup parameters for single sign on&lt;br /&gt;
*# Teacher clicks EQUELLA instance, EQUELLA will return an URL of repository UI (plugin code)&lt;br /&gt;
*# Teacher pick a file from EQUELLA, EQUELLA repository UI will revoke file picker JavaScript API to notify moodle download this resource (plugin code)&lt;br /&gt;
*# Repository API stores file UUID and SSO userid, creates file reference in moodle file pool&lt;br /&gt;
*# EQUELLA plugin implements method to download contents using stored UUID and SSO userid&lt;br /&gt;
*# EQUELLA plugin implements method to update/invalidate cached resources (by cron)&lt;br /&gt;
&lt;br /&gt;
=== Content caching ===&lt;br /&gt;
* Create moodledata/repostory/cache directory&lt;br /&gt;
* Generate hash based on file URL and request parameters (provided by repository plugin), not content hash because we cannot send content hash to external repository for file information&lt;br /&gt;
* Cached files are stored using hash code as file name&lt;br /&gt;
&lt;br /&gt;
==== class repository_cache =====&lt;br /&gt;
* store_file($url, $string_to_be_hashed)&lt;br /&gt;
* get_file($string_to_be_hashed)&lt;br /&gt;
&lt;br /&gt;
=== Filepicker Javascript API for customizing===&lt;br /&gt;
* File picker should be able to dislable file references by taking an option&lt;br /&gt;
* Support &amp;lt;object&amp;gt; tag in filepicker container&lt;br /&gt;
* Provide Javascript API to allow plugin communicate with filepicker&lt;br /&gt;
** Notify file picker to download file &lt;br /&gt;
** Notify file picker to pop up authentication page&lt;br /&gt;
&lt;br /&gt;
=== File manager to handle virtual files===&lt;br /&gt;
Not much trouble here, need to make sure draftfile.php can serve external resources, because all files managed by file manager is in draft area&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Project]]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Authentication_plugins&amp;diff=31784</id>
		<title>Authentication plugins</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Authentication_plugins&amp;diff=31784"/>
		<updated>2012-01-25T09:14:10Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: /* Template */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction==&lt;br /&gt;
&lt;br /&gt;
This page first gives an &#039;&#039;&#039;overview&#039;&#039;&#039; of the &#039;&#039;authentication process&#039;&#039; and then explains how &#039;&#039;authentication modules&#039;&#039; can be &#039;&#039;&#039;created&#039;&#039;&#039; using hooks to take over from the native authentication in Moodle.&lt;br /&gt;
=== Overview of Moodle authentication process ===&lt;br /&gt;
[[File:1.9.11_login_element.png|203px||thumb|right|The login UI element in Moodle 1.9]]The authentication use case in Moodle starts when a user clicks on the Login link in the UI. Then the following happens (skipping some minor details and rarer scenarios):&lt;br /&gt;
&lt;br /&gt;
# The default login page (&amp;lt;tt&amp;gt;/login/index.php&amp;lt;/tt&amp;gt;) is displayed. OR, if a system administrator has set the Alternate Login URL on the &amp;quot;Manage authentication&amp;quot; page, that URL will be displayed.&lt;br /&gt;
# A user enters their credentials and submits the form.&lt;br /&gt;
# The handler code in &amp;lt;tt&amp;gt;/login/index.php&amp;lt;/tt&amp;gt; runs:&lt;br /&gt;
## Gets a list of enabled authentication plugins.&lt;br /&gt;
## Runs &amp;lt;tt&amp;gt;loginpage_hook()&amp;lt;/tt&amp;gt; for each plugin, in case any of them needs to intercept the login request.&lt;br /&gt;
## Checks to make sure that the username meets Moodle&#039;s criteria (alphanumeric, with periods and hyphens allowed).&lt;br /&gt;
## Calls &amp;lt;tt&amp;gt;authenticate_user_login()&amp;lt;/tt&amp;gt; in &amp;lt;tt&amp;gt;/lib/moodlelib.php&amp;lt;/tt&amp;gt;, which returns a &amp;lt;code&amp;gt;$user&amp;lt;/code&amp;gt; object. (Details of this code follow this main outline.)&lt;br /&gt;
## Determines whether authentication was successful (by checking whether &amp;lt;code&amp;gt;$user&amp;lt;/code&amp;gt; is a valid object) and, if not, sends them back to the login page with an error message. Otherwise, it figures out where to send the user based on their original page request, whether their password is expired, etc., and redirects them there.&lt;br /&gt;
&lt;br /&gt;
==History==&lt;br /&gt;
Authentication plugins exist from 1.9&lt;br /&gt;
==Example==&lt;br /&gt;
[http://moodle.org/plugins/view.php?plugin=auth_googleoauth2 Google / Facebook / Messenger Oauth2 Authentication plugin]&lt;br /&gt;
&lt;br /&gt;
==Template==&lt;br /&gt;
Please see Moodle &#039;&#039;&#039;none&#039;&#039;&#039; authentication plugin (auth/none), it&#039;s the perfect plugin template to start with.&lt;br /&gt;
&lt;br /&gt;
==Naming convention==&lt;br /&gt;
==File structure==&lt;br /&gt;
# Choose a name for your plugin.  We&#039;ll use &#039;sentry&#039; as an example below; change it to whatever name you have chosen.&lt;br /&gt;
# Under your Moodle installation root, create the directory &amp;lt;tt&amp;gt;/auth/sentry&amp;lt;/tt&amp;gt;.  It should be sibling to existing auth plugin directories: &#039;db&#039;, &#039;nologin&#039;, &#039;none&#039;, etc.&lt;br /&gt;
# Create the file &amp;lt;tt&amp;gt;/auth/sentry/auth.php&amp;lt;/tt&amp;gt;.  Within the file, create a class &amp;lt;tt&amp;gt;auth_plugin_sentry&amp;lt;/tt&amp;gt; that extends &amp;lt;tt&amp;gt;auth_plugin_base&amp;lt;/tt&amp;gt; from &amp;lt;tt&amp;gt;/lib/authlib.php&amp;lt;/tt&amp;gt;.  (You will need to &amp;lt;code&amp;gt;require_once&amp;lt;/code&amp;gt; the authlib file.)&lt;br /&gt;
# Implement the &amp;lt;tt&amp;gt;user_login()&amp;lt;/tt&amp;gt; function in your &amp;lt;tt&amp;gt;auth.php&amp;lt;/tt&amp;gt; file, and create or override additional functions based on your plugin&#039;s requirements.&lt;br /&gt;
# Log in to your Moodle installation as a site administrator and find, in the site administrator block, the page &amp;quot;Users -&amp;gt; Authentication -&amp;gt; Manage authentication&amp;quot;.  You will see your plugin in the list, appearing as &amp;lt;nowiki&amp;gt;[[auth_sentrytitle]]&amp;lt;/nowiki&amp;gt;.  You can enable it and move it up and down in the order.  &#039;&#039;&#039;At this point, with the plugin enabled, your plugin is registered and will be used by Moodle in its authentication process.&#039;&#039;&#039;&lt;br /&gt;
# If you don&#039;t like seeing &amp;lt;nowiki&amp;gt;[[auth_sentrytitle]]&amp;lt;/nowiki&amp;gt; as the name of your plugin in the Moodle UI, you&#039;ll need to create language files for your plugin.  Do this by creating the directory &amp;lt;tt&amp;gt;/auth/sentry/lang&amp;lt;/tt&amp;gt;, and under it, a directory for each language that your installation needs to support.  (Example: &amp;lt;tt&amp;gt;/auth/sentry/lang/en&amp;lt;/tt&amp;gt;.)  Within each of these language directories, create a file called &amp;lt;tt&amp;gt;auth_sentry.php&amp;lt;/tt&amp;gt;.  That file should set the desired value for &amp;lt;code&amp;gt;$string[&#039;auth_sentrytitle&#039;] for that language (use $string[&#039;pluginname&#039;] for Moodle2)&amp;lt;/code&amp;gt;.  You can also set the plugin description by setting &amp;lt;code&amp;gt;$string[&#039;auth_sentrydescription&#039;]&amp;lt;/code&amp;gt;, and you can also assign other translatable strings that your plugin uses, in these files.  &#039;&#039;&#039;Note:&#039;&#039;&#039; A folder named like &#039;&#039;&#039;en&#039;&#039;&#039; may not work for everyone. Please refer to the lang folder under your moodle installation directory to get an idea about the language/locale codes used in your moodle installation. For example, the lang folder in own users system contained folders named &#039;&#039;&#039;en&#039;&#039;&#039; and &#039;&#039;&#039;zh-cn&#039;&#039;&#039;. He emulated the same in the lang folder of the auth plugin and the plugin picked up the title and description strings immediately. ([[Places_to_search_for_lang_strings|More info on how Moodle handles language]]).&lt;br /&gt;
# If you want to configure your plugin through the Moodle UI, implement &amp;lt;tt&amp;gt;config_form()&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;process_config()&amp;lt;/tt&amp;gt; in the plugin class.  You might find it convenient to use the &#039;db&#039; plugin as a model for this.  The plugin&#039;s config settings can then be managed through the Manage authentication page by clicking on the Settings link for that plugin, and the values will be stored in the &amp;lt;tt&amp;gt;mdl_config_pluginstable&amp;lt;/tt&amp;gt; in the database.&lt;br /&gt;
&lt;br /&gt;
==Interfacing to API&#039;s==&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;authenticate_user_login()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
That&#039;s the main outline, but a lot of interesting stuff happens in &amp;lt;tt&amp;gt;authenticate_user_login()&amp;lt;/tt&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
# It gets a list of enabled authentication plugins.&lt;br /&gt;
# It looks up the username in the mdl_user table to see if they are allowed to log in, and which authentication plugin handles their login requests. (This will be the plugin that handled their first-ever login request.)&lt;br /&gt;
# It creates a user object, which will contain the data from &amp;lt;tt&amp;gt;mdl_user&amp;lt;/tt&amp;gt; if the username is known; if not, it will be an empty object.&lt;br /&gt;
# It does the following with the authentication plugin (note that for a username unknown to Moodle, it will do these steps for each authenticated plugin until one succeeds or it has tried them all):&lt;br /&gt;
## Calls the &amp;lt;tt&amp;gt;user_login()&amp;lt;/tt&amp;gt; function provided by that plugin, which returns a boolean value based on whether the credentials authenticate or not. If the result is false (not authenticated), skips the rest of the steps below and continues to the next plugin.&lt;br /&gt;
## If the plugin authenticates against an external system (not Moodle&#039;s user database), its &amp;lt;tt&amp;gt;update_user_record()&amp;lt;/tt&amp;gt; function is called to get the user&#039;s name, contact info, etc.&lt;br /&gt;
## Creates the Moodle user record if it doesn&#039;t already exist.&lt;br /&gt;
## Calls the plugin&#039;s &amp;lt;tt&amp;gt;sync_roles()&amp;lt;/tt&amp;gt; function.&lt;br /&gt;
## Notifies each enabled authentication plugin that the user successfully authenticated, by calling each one&#039;s &amp;lt;tt&amp;gt;user_authenticated_hook()&amp;lt;/tt&amp;gt; function.&lt;br /&gt;
# It returns the user object if everything was successful, or false if no plugin was able to successfully authenticate the credentials.&lt;br /&gt;
=== &amp;lt;tt&amp;gt;user_login($username, $password)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
This must be rewritten by plugin to return boolean value, returns true if the username and password work and false if they are wrong or don&#039;t exist.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;can_change_password()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Returns true if this authentication plugin can change users&#039; password.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;change_password_url()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Returns the URL for changing the users&#039; passwords, or empty if the default URL can be used.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;can_edit_profile()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Returns true if this authentication plugin can edit the users&#039; profile.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;edit_profile_url()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Returns the URL for editing users&#039; profile, or empty if the defaults URL can be used.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;prevent_local_passwords()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Indicates if password hashes should be stored in local moodle database.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;user_update_password($user, $newpassword)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Update the user&#039;s password.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;user_update($olduser, $newuser)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Called when the user record is updated. It will modify the user information in external database.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;user_delete($olduser)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
User delete requested. Internal user record had been deleted.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;can_reset_password()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Returns true if plugin allows resetting of internal password.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;user_signup($user, $notify=true)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Sign up a new user ready for confirmation, password is passed in plaintext.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;can_confirm()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Returns true if plugin allows confirming of new users.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;user_confirm($username, $confirmsecret)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Confirm the new user as registered.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;user_exists($username)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Checks if user exists in external db.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;password_expire($username)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Returns number of days to user password expires.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;sync_roles()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Sync roles for this user - usually creator&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;get_userinfo($username)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Read user information from external database and returns it as array.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;config_form($config, $err, $user_fields)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Prints a form for configuring this authentication plugin. It&#039;s called from admin/auth.php, and outputs a full page with a form for configuring this plugin.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;validate_form($form, $err)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Validate form data.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;process_config($config)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Processes and stores configuration data for this authentication plugin.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;loginpage_hook()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Hook for overriding behaviour of login page.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;user_authenticated_hook($user, $username, $password)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Post authentication hook. This method is called from authenticate_user_login() for all enabled auth plugins.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;prelogout_hook()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Pre logout hook.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;logoutpage_hook()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Hook for overriding behaviour of logout page.&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
* [[Authentication API]]&lt;br /&gt;
* Using Moodle [http://moodle.org/mod/forum/discuss.php?d=102070 Overview of entire authentication code flow] forum discussion&lt;br /&gt;
* Using Moodle [http://moodle.org/mod/forum/view.php?id=42 User authentication forum]&lt;br /&gt;
* Moodle Docs [[:en:Manage authentication|Manage authentication]]&lt;br /&gt;
* [[:en:Authentication FAQ|Authentication FAQ]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Authentication]]&lt;br /&gt;
[[Category:Plugins]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[fr:Méthodes_d&#039;authentification]]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Authentication_plugins&amp;diff=31779</id>
		<title>Authentication plugins</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Authentication_plugins&amp;diff=31779"/>
		<updated>2012-01-25T09:05:46Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: /* File structure */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction==&lt;br /&gt;
&lt;br /&gt;
This page first gives an &#039;&#039;&#039;overview&#039;&#039;&#039; of the &#039;&#039;authentication process&#039;&#039; and then explains how &#039;&#039;authentication modules&#039;&#039; can be &#039;&#039;&#039;created&#039;&#039;&#039; using hooks to take over from the native authentication in Moodle.&lt;br /&gt;
=== Overview of Moodle authentication process ===&lt;br /&gt;
[[File:1.9.11_login_element.png|203px||thumb|right|The login UI element in Moodle 1.9]]The authentication use case in Moodle starts when a user clicks on the Login link in the UI. Then the following happens (skipping some minor details and rarer scenarios):&lt;br /&gt;
&lt;br /&gt;
# The default login page (&amp;lt;tt&amp;gt;/login/index.php&amp;lt;/tt&amp;gt;) is displayed. OR, if a system administrator has set the Alternate Login URL on the &amp;quot;Manage authentication&amp;quot; page, that URL will be displayed.&lt;br /&gt;
# A user enters their credentials and submits the form.&lt;br /&gt;
# The handler code in &amp;lt;tt&amp;gt;/login/index.php&amp;lt;/tt&amp;gt; runs:&lt;br /&gt;
## Gets a list of enabled authentication plugins.&lt;br /&gt;
## Runs &amp;lt;tt&amp;gt;loginpage_hook()&amp;lt;/tt&amp;gt; for each plugin, in case any of them needs to intercept the login request.&lt;br /&gt;
## Checks to make sure that the username meets Moodle&#039;s criteria (alphanumeric, with periods and hyphens allowed).&lt;br /&gt;
## Calls &amp;lt;tt&amp;gt;authenticate_user_login()&amp;lt;/tt&amp;gt; in &amp;lt;tt&amp;gt;/lib/moodlelib.php&amp;lt;/tt&amp;gt;, which returns a &amp;lt;code&amp;gt;$user&amp;lt;/code&amp;gt; object. (Details of this code follow this main outline.)&lt;br /&gt;
## Determines whether authentication was successful (by checking whether &amp;lt;code&amp;gt;$user&amp;lt;/code&amp;gt; is a valid object) and, if not, sends them back to the login page with an error message. Otherwise, it figures out where to send the user based on their original page request, whether their password is expired, etc., and redirects them there.&lt;br /&gt;
&lt;br /&gt;
==History==&lt;br /&gt;
Authentication plugins exist from 1.9&lt;br /&gt;
==Example==&lt;br /&gt;
[http://moodle.org/plugins/view.php?plugin=auth_googleoauth2 Google / Facebook / Messenger Oauth2 Authentication plugin]&lt;br /&gt;
&lt;br /&gt;
==Template==&lt;br /&gt;
==Naming convention==&lt;br /&gt;
==File structure==&lt;br /&gt;
# Choose a name for your plugin.  We&#039;ll use &#039;sentry&#039; as an example below; change it to whatever name you have chosen.&lt;br /&gt;
# Under your Moodle installation root, create the directory &amp;lt;tt&amp;gt;/auth/sentry&amp;lt;/tt&amp;gt;.  It should be sibling to existing auth plugin directories: &#039;db&#039;, &#039;nologin&#039;, &#039;none&#039;, etc.&lt;br /&gt;
# Create the file &amp;lt;tt&amp;gt;/auth/sentry/auth.php&amp;lt;/tt&amp;gt;.  Within the file, create a class &amp;lt;tt&amp;gt;auth_plugin_sentry&amp;lt;/tt&amp;gt; that extends &amp;lt;tt&amp;gt;auth_plugin_base&amp;lt;/tt&amp;gt; from &amp;lt;tt&amp;gt;/lib/authlib.php&amp;lt;/tt&amp;gt;.  (You will need to &amp;lt;code&amp;gt;require_once&amp;lt;/code&amp;gt; the authlib file.)&lt;br /&gt;
# Implement the &amp;lt;tt&amp;gt;user_login()&amp;lt;/tt&amp;gt; function in your &amp;lt;tt&amp;gt;auth.php&amp;lt;/tt&amp;gt; file, and create or override additional functions based on your plugin&#039;s requirements.&lt;br /&gt;
# Log in to your Moodle installation as a site administrator and find, in the site administrator block, the page &amp;quot;Users -&amp;gt; Authentication -&amp;gt; Manage authentication&amp;quot;.  You will see your plugin in the list, appearing as &amp;lt;nowiki&amp;gt;[[auth_sentrytitle]]&amp;lt;/nowiki&amp;gt;.  You can enable it and move it up and down in the order.  &#039;&#039;&#039;At this point, with the plugin enabled, your plugin is registered and will be used by Moodle in its authentication process.&#039;&#039;&#039;&lt;br /&gt;
# If you don&#039;t like seeing &amp;lt;nowiki&amp;gt;[[auth_sentrytitle]]&amp;lt;/nowiki&amp;gt; as the name of your plugin in the Moodle UI, you&#039;ll need to create language files for your plugin.  Do this by creating the directory &amp;lt;tt&amp;gt;/auth/sentry/lang&amp;lt;/tt&amp;gt;, and under it, a directory for each language that your installation needs to support.  (Example: &amp;lt;tt&amp;gt;/auth/sentry/lang/en&amp;lt;/tt&amp;gt;.)  Within each of these language directories, create a file called &amp;lt;tt&amp;gt;auth_sentry.php&amp;lt;/tt&amp;gt;.  That file should set the desired value for &amp;lt;code&amp;gt;$string[&#039;auth_sentrytitle&#039;] for that language (use $string[&#039;pluginname&#039;] for Moodle2)&amp;lt;/code&amp;gt;.  You can also set the plugin description by setting &amp;lt;code&amp;gt;$string[&#039;auth_sentrydescription&#039;]&amp;lt;/code&amp;gt;, and you can also assign other translatable strings that your plugin uses, in these files.  &#039;&#039;&#039;Note:&#039;&#039;&#039; A folder named like &#039;&#039;&#039;en&#039;&#039;&#039; may not work for everyone. Please refer to the lang folder under your moodle installation directory to get an idea about the language/locale codes used in your moodle installation. For example, the lang folder in own users system contained folders named &#039;&#039;&#039;en&#039;&#039;&#039; and &#039;&#039;&#039;zh-cn&#039;&#039;&#039;. He emulated the same in the lang folder of the auth plugin and the plugin picked up the title and description strings immediately. ([[Places_to_search_for_lang_strings|More info on how Moodle handles language]]).&lt;br /&gt;
# If you want to configure your plugin through the Moodle UI, implement &amp;lt;tt&amp;gt;config_form()&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;process_config()&amp;lt;/tt&amp;gt; in the plugin class.  You might find it convenient to use the &#039;db&#039; plugin as a model for this.  The plugin&#039;s config settings can then be managed through the Manage authentication page by clicking on the Settings link for that plugin, and the values will be stored in the &amp;lt;tt&amp;gt;mdl_config_pluginstable&amp;lt;/tt&amp;gt; in the database.&lt;br /&gt;
&lt;br /&gt;
==Interfacing to API&#039;s==&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;authenticate_user_login()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
That&#039;s the main outline, but a lot of interesting stuff happens in &amp;lt;tt&amp;gt;authenticate_user_login()&amp;lt;/tt&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
# It gets a list of enabled authentication plugins.&lt;br /&gt;
# It looks up the username in the mdl_user table to see if they are allowed to log in, and which authentication plugin handles their login requests. (This will be the plugin that handled their first-ever login request.)&lt;br /&gt;
# It creates a user object, which will contain the data from &amp;lt;tt&amp;gt;mdl_user&amp;lt;/tt&amp;gt; if the username is known; if not, it will be an empty object.&lt;br /&gt;
# It does the following with the authentication plugin (note that for a username unknown to Moodle, it will do these steps for each authenticated plugin until one succeeds or it has tried them all):&lt;br /&gt;
## Calls the &amp;lt;tt&amp;gt;user_login()&amp;lt;/tt&amp;gt; function provided by that plugin, which returns a boolean value based on whether the credentials authenticate or not. If the result is false (not authenticated), skips the rest of the steps below and continues to the next plugin.&lt;br /&gt;
## If the plugin authenticates against an external system (not Moodle&#039;s user database), its &amp;lt;tt&amp;gt;update_user_record()&amp;lt;/tt&amp;gt; function is called to get the user&#039;s name, contact info, etc.&lt;br /&gt;
## Creates the Moodle user record if it doesn&#039;t already exist.&lt;br /&gt;
## Calls the plugin&#039;s &amp;lt;tt&amp;gt;sync_roles()&amp;lt;/tt&amp;gt; function.&lt;br /&gt;
## Notifies each enabled authentication plugin that the user successfully authenticated, by calling each one&#039;s &amp;lt;tt&amp;gt;user_authenticated_hook()&amp;lt;/tt&amp;gt; function.&lt;br /&gt;
# It returns the user object if everything was successful, or false if no plugin was able to successfully authenticate the credentials.&lt;br /&gt;
=== &amp;lt;tt&amp;gt;user_login($username, $password)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
This must be rewritten by plugin to return boolean value, returns true if the username and password work and false if they are wrong or don&#039;t exist.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;can_change_password()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Returns true if this authentication plugin can change users&#039; password.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;change_password_url()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Returns the URL for changing the users&#039; passwords, or empty if the default URL can be used.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;can_edit_profile()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Returns true if this authentication plugin can edit the users&#039; profile.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;edit_profile_url()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Returns the URL for editing users&#039; profile, or empty if the defaults URL can be used.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;prevent_local_passwords()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Indicates if password hashes should be stored in local moodle database.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;user_update_password($user, $newpassword)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Update the user&#039;s password.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;user_update($olduser, $newuser)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Called when the user record is updated. It will modify the user information in external database.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;user_delete($olduser)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
User delete requested. Internal user record had been deleted.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;can_reset_password()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Returns true if plugin allows resetting of internal password.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;user_signup($user, $notify=true)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Sign up a new user ready for confirmation, password is passed in plaintext.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;can_confirm()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Returns true if plugin allows confirming of new users.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;user_confirm($username, $confirmsecret)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Confirm the new user as registered.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;user_exists($username)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Checks if user exists in external db.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;password_expire($username)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Returns number of days to user password expires.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;sync_roles()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Sync roles for this user - usually creator&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;get_userinfo($username)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Read user information from external database and returns it as array.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;config_form($config, $err, $user_fields)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Prints a form for configuring this authentication plugin. It&#039;s called from admin/auth.php, and outputs a full page with a form for configuring this plugin.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;validate_form($form, $err)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Validate form data.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;process_config($config)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Processes and stores configuration data for this authentication plugin.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;loginpage_hook()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Hook for overriding behaviour of login page.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;user_authenticated_hook($user, $username, $password)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Post authentication hook. This method is called from authenticate_user_login() for all enabled auth plugins.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;prelogout_hook()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Pre logout hook.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;logoutpage_hook()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Hook for overriding behaviour of logout page.&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
* [[Authentication API]]&lt;br /&gt;
* Using Moodle [http://moodle.org/mod/forum/discuss.php?d=102070 Overview of entire authentication code flow] forum discussion&lt;br /&gt;
* Using Moodle [http://moodle.org/mod/forum/view.php?id=42 User authentication forum]&lt;br /&gt;
* Moodle Docs [[:en:Manage authentication|Manage authentication]]&lt;br /&gt;
* [[:en:Authentication FAQ|Authentication FAQ]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Authentication]]&lt;br /&gt;
[[Category:Plugins]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[fr:Méthodes_d&#039;authentification]]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Authentication_plugins&amp;diff=31777</id>
		<title>Authentication plugins</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Authentication_plugins&amp;diff=31777"/>
		<updated>2012-01-25T09:02:35Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: /* Example */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction==&lt;br /&gt;
&lt;br /&gt;
This page first gives an &#039;&#039;&#039;overview&#039;&#039;&#039; of the &#039;&#039;authentication process&#039;&#039; and then explains how &#039;&#039;authentication modules&#039;&#039; can be &#039;&#039;&#039;created&#039;&#039;&#039; using hooks to take over from the native authentication in Moodle.&lt;br /&gt;
=== Overview of Moodle authentication process ===&lt;br /&gt;
[[File:1.9.11_login_element.png|203px||thumb|right|The login UI element in Moodle 1.9]]The authentication use case in Moodle starts when a user clicks on the Login link in the UI. Then the following happens (skipping some minor details and rarer scenarios):&lt;br /&gt;
&lt;br /&gt;
# The default login page (&amp;lt;tt&amp;gt;/login/index.php&amp;lt;/tt&amp;gt;) is displayed. OR, if a system administrator has set the Alternate Login URL on the &amp;quot;Manage authentication&amp;quot; page, that URL will be displayed.&lt;br /&gt;
# A user enters their credentials and submits the form.&lt;br /&gt;
# The handler code in &amp;lt;tt&amp;gt;/login/index.php&amp;lt;/tt&amp;gt; runs:&lt;br /&gt;
## Gets a list of enabled authentication plugins.&lt;br /&gt;
## Runs &amp;lt;tt&amp;gt;loginpage_hook()&amp;lt;/tt&amp;gt; for each plugin, in case any of them needs to intercept the login request.&lt;br /&gt;
## Checks to make sure that the username meets Moodle&#039;s criteria (alphanumeric, with periods and hyphens allowed).&lt;br /&gt;
## Calls &amp;lt;tt&amp;gt;authenticate_user_login()&amp;lt;/tt&amp;gt; in &amp;lt;tt&amp;gt;/lib/moodlelib.php&amp;lt;/tt&amp;gt;, which returns a &amp;lt;code&amp;gt;$user&amp;lt;/code&amp;gt; object. (Details of this code follow this main outline.)&lt;br /&gt;
## Determines whether authentication was successful (by checking whether &amp;lt;code&amp;gt;$user&amp;lt;/code&amp;gt; is a valid object) and, if not, sends them back to the login page with an error message. Otherwise, it figures out where to send the user based on their original page request, whether their password is expired, etc., and redirects them there.&lt;br /&gt;
&lt;br /&gt;
==History==&lt;br /&gt;
Authentication plugins exist from 1.9&lt;br /&gt;
==Example==&lt;br /&gt;
[http://moodle.org/plugins/view.php?plugin=auth_googleoauth2 Google / Facebook / Messenger Oauth2 Authentication plugin]&lt;br /&gt;
&lt;br /&gt;
==Template==&lt;br /&gt;
==Naming convention==&lt;br /&gt;
==File structure==&lt;br /&gt;
# Choose a name for your plugin.  We&#039;ll use &#039;sentry&#039; as an example below; change it to whatever name you have chosen.&lt;br /&gt;
&lt;br /&gt;
# Under your Moodle installation root, create the directory &amp;lt;tt&amp;gt;/auth/sentry&amp;lt;/tt&amp;gt;.  It should be sibling to existing auth plugin directories: &#039;db&#039;, &#039;nologin&#039;, &#039;none&#039;, etc.&lt;br /&gt;
&lt;br /&gt;
# Create the file &amp;lt;tt&amp;gt;/auth/sentry/auth.php&amp;lt;/tt&amp;gt;.  Within the file, create a class &amp;lt;tt&amp;gt;auth_plugin_sentry&amp;lt;/tt&amp;gt; that extends &amp;lt;tt&amp;gt;auth_plugin_base&amp;lt;/tt&amp;gt; from &amp;lt;tt&amp;gt;/lib/authlib.php&amp;lt;/tt&amp;gt;.  (You will need to &amp;lt;code&amp;gt;require_once&amp;lt;/code&amp;gt; the authlib file.)&lt;br /&gt;
&lt;br /&gt;
# Implement the &amp;lt;tt&amp;gt;user_login()&amp;lt;/tt&amp;gt; function in your &amp;lt;tt&amp;gt;auth.php&amp;lt;/tt&amp;gt; file, and create or override additional functions based on your plugin&#039;s requirements.&lt;br /&gt;
&lt;br /&gt;
# Log in to your Moodle installation as a site administrator and find, in the site administrator block, the page &amp;quot;Users -&amp;gt; Authentication -&amp;gt; Manage authentication&amp;quot;.  You will see your plugin in the list, appearing as &amp;lt;nowiki&amp;gt;[[auth_sentrytitle]]&amp;lt;/nowiki&amp;gt;.  You can enable it and move it up and down in the order.  &#039;&#039;&#039;At this point, with the plugin enabled, your plugin is registered and will be used by Moodle in its authentication process.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
# If you don&#039;t like seeing &amp;lt;nowiki&amp;gt;[[auth_sentrytitle]]&amp;lt;/nowiki&amp;gt; as the name of your plugin in the Moodle UI, you&#039;ll need to create language files for your plugin.  Do this by creating the directory &amp;lt;tt&amp;gt;/auth/sentry/lang&amp;lt;/tt&amp;gt;, and under it, a directory for each language that your installation needs to support.  (Example: &amp;lt;tt&amp;gt;/auth/sentry/lang/en&amp;lt;/tt&amp;gt;.)  Within each of these language directories, create a file called &amp;lt;tt&amp;gt;auth_sentry.php&amp;lt;/tt&amp;gt;.  That file should set the desired value for &amp;lt;code&amp;gt;$string[&#039;auth_sentrytitle&#039;] for that language (use $string[&#039;pluginname&#039;] for Moodle2)&amp;lt;/code&amp;gt;.  You can also set the plugin description by setting &amp;lt;code&amp;gt;$string[&#039;auth_sentrydescription&#039;]&amp;lt;/code&amp;gt;, and you can also assign other translatable strings that your plugin uses, in these files. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; A folder named like &#039;&#039;&#039;en&#039;&#039;&#039; may not work for everyone. Please refer to the lang folder under your moodle installation directory to get an idea about the language/locale codes used in your moodle installation. For example, the lang folder in own users system contained folders named &#039;&#039;&#039;en&#039;&#039;&#039; and &#039;&#039;&#039;zh-cn&#039;&#039;&#039;. He emulated the same in the lang folder of the auth plugin and the plugin picked up the title and description strings immediately. ([[Places_to_search_for_lang_strings|More info on how Moodle handles language]]).&lt;br /&gt;
&lt;br /&gt;
# If you want to configure your plugin through the Moodle UI, implement &amp;lt;tt&amp;gt;config_form()&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;process_config()&amp;lt;/tt&amp;gt; in the plugin class.  You might find it convenient to use the &#039;db&#039; plugin as a model for this.  The plugin&#039;s config settings can then be managed through the Manage authentication page by clicking on the Settings link for that plugin, and the values will be stored in the &amp;lt;tt&amp;gt;mdl_config_pluginstable&amp;lt;/tt&amp;gt; in the database.&lt;br /&gt;
&lt;br /&gt;
==Interfacing to API&#039;s==&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;authenticate_user_login()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
That&#039;s the main outline, but a lot of interesting stuff happens in &amp;lt;tt&amp;gt;authenticate_user_login()&amp;lt;/tt&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
# It gets a list of enabled authentication plugins.&lt;br /&gt;
# It looks up the username in the mdl_user table to see if they are allowed to log in, and which authentication plugin handles their login requests. (This will be the plugin that handled their first-ever login request.)&lt;br /&gt;
# It creates a user object, which will contain the data from &amp;lt;tt&amp;gt;mdl_user&amp;lt;/tt&amp;gt; if the username is known; if not, it will be an empty object.&lt;br /&gt;
# It does the following with the authentication plugin (note that for a username unknown to Moodle, it will do these steps for each authenticated plugin until one succeeds or it has tried them all):&lt;br /&gt;
## Calls the &amp;lt;tt&amp;gt;user_login()&amp;lt;/tt&amp;gt; function provided by that plugin, which returns a boolean value based on whether the credentials authenticate or not. If the result is false (not authenticated), skips the rest of the steps below and continues to the next plugin.&lt;br /&gt;
## If the plugin authenticates against an external system (not Moodle&#039;s user database), its &amp;lt;tt&amp;gt;update_user_record()&amp;lt;/tt&amp;gt; function is called to get the user&#039;s name, contact info, etc.&lt;br /&gt;
## Creates the Moodle user record if it doesn&#039;t already exist.&lt;br /&gt;
## Calls the plugin&#039;s &amp;lt;tt&amp;gt;sync_roles()&amp;lt;/tt&amp;gt; function.&lt;br /&gt;
## Notifies each enabled authentication plugin that the user successfully authenticated, by calling each one&#039;s &amp;lt;tt&amp;gt;user_authenticated_hook()&amp;lt;/tt&amp;gt; function.&lt;br /&gt;
# It returns the user object if everything was successful, or false if no plugin was able to successfully authenticate the credentials.&lt;br /&gt;
=== &amp;lt;tt&amp;gt;user_login($username, $password)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
This must be rewritten by plugin to return boolean value, returns true if the username and password work and false if they are wrong or don&#039;t exist.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;can_change_password()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Returns true if this authentication plugin can change users&#039; password.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;change_password_url()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Returns the URL for changing the users&#039; passwords, or empty if the default URL can be used.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;can_edit_profile()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Returns true if this authentication plugin can edit the users&#039; profile.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;edit_profile_url()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Returns the URL for editing users&#039; profile, or empty if the defaults URL can be used.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;prevent_local_passwords()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Indicates if password hashes should be stored in local moodle database.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;user_update_password($user, $newpassword)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Update the user&#039;s password.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;user_update($olduser, $newuser)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Called when the user record is updated. It will modify the user information in external database.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;user_delete($olduser)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
User delete requested. Internal user record had been deleted.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;can_reset_password()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Returns true if plugin allows resetting of internal password.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;user_signup($user, $notify=true)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Sign up a new user ready for confirmation, password is passed in plaintext.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;can_confirm()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Returns true if plugin allows confirming of new users.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;user_confirm($username, $confirmsecret)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Confirm the new user as registered.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;user_exists($username)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Checks if user exists in external db.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;password_expire($username)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Returns number of days to user password expires.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;sync_roles()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Sync roles for this user - usually creator&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;get_userinfo($username)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Read user information from external database and returns it as array.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;config_form($config, $err, $user_fields)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Prints a form for configuring this authentication plugin. It&#039;s called from admin/auth.php, and outputs a full page with a form for configuring this plugin.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;validate_form($form, $err)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Validate form data.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;process_config($config)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Processes and stores configuration data for this authentication plugin.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;loginpage_hook()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Hook for overriding behaviour of login page.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;user_authenticated_hook($user, $username, $password)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Post authentication hook. This method is called from authenticate_user_login() for all enabled auth plugins.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;prelogout_hook()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Pre logout hook.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;logoutpage_hook()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Hook for overriding behaviour of logout page.&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
* [[Authentication API]]&lt;br /&gt;
* Using Moodle [http://moodle.org/mod/forum/discuss.php?d=102070 Overview of entire authentication code flow] forum discussion&lt;br /&gt;
* Using Moodle [http://moodle.org/mod/forum/view.php?id=42 User authentication forum]&lt;br /&gt;
* Moodle Docs [[:en:Manage authentication|Manage authentication]]&lt;br /&gt;
* [[:en:Authentication FAQ|Authentication FAQ]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Authentication]]&lt;br /&gt;
[[Category:Plugins]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[fr:Méthodes_d&#039;authentification]]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Repository_plugins&amp;diff=31762</id>
		<title>Repository plugins</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Repository_plugins&amp;diff=31762"/>
		<updated>2012-01-25T08:48:42Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Repository plugin allow Moodle to bring contents into Moodle from external repositories.&lt;br /&gt;
&lt;br /&gt;
===Prerequisites===&lt;br /&gt;
Before starting coding, it is necessary to know how to use repository administration pages and how to use the file picker.&lt;br /&gt;
&lt;br /&gt;
===Overview===&lt;br /&gt;
&lt;br /&gt;
The 3 different parts to write&lt;br /&gt;
# Administration - You can customise the way administrators and users can configure their repositories. &lt;br /&gt;
# File picker integration - The core of your plugin, it will manage communication between Moodle and the repository service, and also the file picker display.&lt;br /&gt;
# I18n - Internationalization should be done at the same time as you&#039;re writing the other parts.&lt;br /&gt;
&lt;br /&gt;
== History ==&lt;br /&gt;
&lt;br /&gt;
Repository plugins exists from 2.0&lt;br /&gt;
&lt;br /&gt;
== Example ==&lt;br /&gt;
*[[Box.net Repository Plugin|Box.net Repository Plugin]]&lt;br /&gt;
*[[Flickr Repository Plugin|Flickr Repository Plugin]]&lt;br /&gt;
*[[Moodle Repository Plugin|Remote Moodle Repository Plugin]]&lt;br /&gt;
&lt;br /&gt;
== Template ==&lt;br /&gt;
There is a template available in [https://github.com/dongsheng/moodle-repository_demo Dongsheng Cai&#039;s github]&lt;br /&gt;
&lt;br /&gt;
==File structure==&lt;br /&gt;
# Create a folder for your plugin in &#039;&#039;moodle/repository/&#039;&#039; e.g. &#039;&#039;moodle/repository/myplugin&#039;&#039;&lt;br /&gt;
# Create the following files and add them to the plugin folder:&lt;br /&gt;
#* &#039;&#039;lib.php&#039;&#039;, the main class will be named &amp;quot;repository_myplugin&amp;quot;&lt;br /&gt;
#* &#039;&#039;pix/icon.png&#039;&#039; (the icon displayed in the file picker)&lt;br /&gt;
#* &#039;&#039;[[version.php]]&#039;&#039;&lt;br /&gt;
# Create the language file &#039;&#039;repository_myplugin.php&#039;&#039; and add it to the plugin folder, keeping the following folder structure:&lt;br /&gt;
#*&#039;&#039;moodle/repository/myplugin/lang/en/repository_myplugin.php&#039;&#039; or &#039;&#039;moodle/lang/en/repository_myplugin.php&#039;&#039;&lt;br /&gt;
# Create access.php and upgrade scripts (optional)&lt;br /&gt;
&lt;br /&gt;
==Administration APIs==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Functions===&lt;br /&gt;
All of the following functions are optional. If they&#039;re not implemented, your plugin will not have manual settings and will have only one instance displayed in the File Picker (The repository API creates this unique instance when the administrator add the plugin).&lt;br /&gt;
&lt;br /&gt;
==== get_instance_option_names====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Return an array of strings. These strings are setting names. These settings are specific to an instance.&lt;br /&gt;
If the function returns an empty array, the API will consider that the plugin displays only one repository in the file picker.&lt;br /&gt;
Parent function returns an empty array.&lt;br /&gt;
&lt;br /&gt;
====instance_config_form(&amp;amp;$mform)====&lt;br /&gt;
This is for modifying the Moodle form displaying the settings specific to an instance.&lt;br /&gt;
&lt;br /&gt;
For example, to add a required text box called email_address:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;text&#039;, &#039;email_address&#039;, get_string(&#039;emailaddress&#039;, &#039;repository_flickr_public&#039;));&lt;br /&gt;
$mform-&amp;gt;addRule(&#039;email_address&#039;, $strrequired, &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
   &lt;br /&gt;
&#039;&#039;Note: &#039;&#039;mform&#039;&#039; has by default a name text box (cannot be removed).&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Parent function does nothing.&lt;br /&gt;
&lt;br /&gt;
====instance_form_validation($mform, $data, $errors)====&lt;br /&gt;
This allows us to validate what has been submitted in the instance configuration form. For instance:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function instance_form_validation($mform, $data, $errors) {&lt;br /&gt;
        if (empty($data[&#039;email_address&#039;])) {&lt;br /&gt;
            $errors[&#039;email_address&#039;] = get_string(&#039;invalidemailsettingname&#039;, &#039;repository_flickr_public&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        return $errors;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_type_option_names====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Return an array of string. These strings are setting names. These settings are shared by all instances.&lt;br /&gt;
Parent function return an empty array.&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_type_option_names() {&lt;br /&gt;
        return array_merge(parent::get_type_option_names(), array(&#039;rootpath&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====type_config_form(&amp;amp;$mform)====&lt;br /&gt;
This is for modifying the Moodle form displaying the plugin settings.&lt;br /&gt;
&lt;br /&gt;
For example, to display the standard repository plugin settings along with the custom ones use:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function type_config_form($mform) {&lt;br /&gt;
        parent::type_config_form($mform);&lt;br /&gt;
&lt;br /&gt;
        $rootpath = get_config(&#039;repository_someplugin&#039;, &#039;rootpath&#039;);&lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;text&#039;, &#039;rootpath&#039;, get_string(&#039;rootpath&#039;, &#039;repository_someplugin&#039;), array(&#039;size&#039; =&amp;gt; &#039;40&#039;));&lt;br /&gt;
        $mform-&amp;gt;setDefault(&#039;rootpath&#039;, $rootpath);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====type_form_validation($mform, $data, $errors)====&lt;br /&gt;
Use this function if you need to validate some variables submitted by plugin settings form. Push the items to $error array in the format (&amp;quot;fieldname&amp;quot; =&amp;gt; &amp;quot;errormessage&amp;quot;) to have them highlighted in the form.&lt;br /&gt;
&lt;br /&gt;
With the example above, this function may look like:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function type_form_validation($mform, $data, $errors) {&lt;br /&gt;
        if (!is_dir($data[&#039;rootpath&#039;])) {&lt;br /&gt;
            $errors[&#039;rootpath&#039;] = get_string(&#039;invalidrootpath&#039;, &#039;repository_someplugin&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        return $errors;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====plugin_init()====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
This function is called when the administrator adds the plugin. So unless the administrator deletes the plugin and re-adds it, it should be called only once.&lt;br /&gt;
Parent function does nothing.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
As an example, let&#039;s create a Flickr plugin for accessing a public flickr account. The plugin will be called &amp;quot;Flickr Public&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Firstly the skeleton:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    &amp;lt;?php&lt;br /&gt;
    /**&lt;br /&gt;
     * repository_flickr_public class&lt;br /&gt;
     * Moodle user can access public flickr account&lt;br /&gt;
     *&lt;br /&gt;
     * @license http://www.gnu.org/copyleft/gpl.html GNU Public License&lt;br /&gt;
    */&lt;br /&gt;
    class repository_flickr_public extends repository {&lt;br /&gt;
    }&lt;br /&gt;
?&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then consider the question &amp;quot;What does my plugin do?&amp;quot;&lt;br /&gt;
&lt;br /&gt;
In the Moodle file picker, we want to display some flickr public repositories directly linked to a flickr public account. For example &#039;&#039;My Public Flickr Pictures&#039;&#039;, and also &#039;&#039;My Friend&#039;s Flickr Pictures&#039;&#039;. When the user clicks on one of these repositories, the public pictures are displayed in the file picker.&lt;br /&gt;
&lt;br /&gt;
In order to access to a flickr public account, the plugin needs to know the email address of the Flickr public account owner. So the administrator will need to set an email address for every repository. Let&#039;s add an &amp;quot;email address&amp;quot; setting to every repository.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
//We tell the API that the repositories have specific settings: &amp;quot;email address&amp;quot;&lt;br /&gt;
    public static function get_instance_option_names() {&lt;br /&gt;
        return array(&#039;email_address&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
//We add an &amp;quot;email address&amp;quot; text box to the create/edit repository instance Moodle form&lt;br /&gt;
    public function instance_config_form(&amp;amp;$mform) {&lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;text&#039;, &#039;email_address&#039;, get_string(&#039;emailaddress&#039;, &#039;repository_flickr_public&#039;));&lt;br /&gt;
        $mform-&amp;gt;addRule(&#039;email_address&#039;, get_string(&#039;required&#039;), &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So at this moment all our Flickr Public Repositories will have a specific email address. However this is not enough. In order to communicate with Flickr, Moodle needs to know a Flickr API key (http://www.flickr.com/services/api/). This API key is the same for any repository. We could add it with the email address setting but the administrator would have to enter the same API key for every repository. Hopefully the administrator can add settings to the plugin level, impacting all repositories. The code is similar the repository instance settings:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
//We tell the API that the repositories have general settings: &amp;quot;api_key&amp;quot;&lt;br /&gt;
    public static function get_type_option_names() {&lt;br /&gt;
        return array(&#039;api_key&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
//We add an &amp;quot;api key&amp;quot; text box to the create/edit repository plugin Moodle form (also called a Repository type Moodle form)&lt;br /&gt;
    public function type_config_form(&amp;amp;$mform) {&lt;br /&gt;
        //the following line is needed in order to retrieve the API key value from the database when Moodle displays the edit form&lt;br /&gt;
        $api_key = get_config(&#039;flickr_public&#039;, &#039;api_key&#039;);&lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;text&#039;, &#039;api_key&#039;, get_string(&#039;apikey&#039;, &#039;repository_flickr_public&#039;), &lt;br /&gt;
                           array(&#039;value&#039;=&amp;gt;$api_key,&#039;size&#039; =&amp;gt; &#039;40&#039;));&lt;br /&gt;
        $mform-&amp;gt;addRule(&#039;api_key&#039;, get_string(&#039;required&#039;), &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Have we finished yet?&lt;br /&gt;
&lt;br /&gt;
Yes! We have created everything necessary for the administration pages. But let&#039;s go further. It would be good if the user can enter any &amp;quot;Flickr public account email address&amp;quot; in the file picker. In fact we want to display in the file picker a Flickr Public repository that the Moodle administrator can never delete. Let&#039;s add:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
     //this function is only called one time, when the Moodle administrator add the Flickr Public Plugin into the Moodle site.&lt;br /&gt;
     public static function plugin_init() {&lt;br /&gt;
        //here we create a default repository instance. The last parameter is 1 in order to set the instance as readonly.&lt;br /&gt;
        repository_static_function(&#039;flickr_public&#039;,&#039;create&#039;, &#039;flickr_public&#039;, 0, get_system_context(), &lt;br /&gt;
                                    array(&#039;name&#039; =&amp;gt; &#039;default instance&#039;,&#039;email_address&#039; =&amp;gt; null),1);&lt;br /&gt;
     }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That&#039;s all - the administration part of our Flickr Public plugin is done. For your information, Box.net, Flickr, and Flickr Public all have similar administration APIs.&lt;br /&gt;
&lt;br /&gt;
==Repository APIs==&lt;br /&gt;
=== Quick Start ===&lt;br /&gt;
First of all, the File Picker using intensively Ajax you will need a easy way to debug. Install FirePHP (MDL-16371) and make it works. It will save you a lot of time. (You might give the [http://moodle.org/mod/forum/discuss.php?d=119961 FirePHP plugin for Moodle] a try, it&#039;s still work in progress, though.)&lt;br /&gt;
&lt;br /&gt;
* Your first question when you write your plugin specification is &#039;Does the user need to log-in&#039;? If they do, in your plugin you have to detect user session in constructor() function, and use print_login() if required, see more details below.&lt;br /&gt;
* For most of plugins, you need to establish a connection with the remote repository. This connection can be done into the get_listing(), constructor() function, see more details below.&lt;br /&gt;
* You wanna retrieve the file that the user selected, rewrite get_file() if required, see more details below.&lt;br /&gt;
* Optional question that you should ask yourself is &#039;Does the user can execute a search&#039;, if they do, you will have to rewrite search() method, see more details below.&lt;br /&gt;
&lt;br /&gt;
===Functions you *MUST* override===&lt;br /&gt;
====__construct====&lt;br /&gt;
You may initialize your plugin here, such as:&lt;br /&gt;
# Get options from database&lt;br /&gt;
# Get user name and password from HTTP POST&lt;br /&gt;
&lt;br /&gt;
====get_listing($path=&amp;quot;&amp;quot;, $page=&amp;quot;&amp;quot;)====&lt;br /&gt;
This function will return a list of files, the list must be a array like this:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$list = array(&lt;br /&gt;
 //this will be used to build navigation bar&lt;br /&gt;
&#039;path&#039;=&amp;gt;array(array(&#039;name&#039;=&amp;gt;&#039;root&#039;,&#039;path&#039;=&amp;gt;&#039;/&#039;), array(&#039;name&#039;=&amp;gt;&#039;subfolder&#039;, &#039;path&#039;=&amp;gt;&#039;/subfolder&#039;)),&lt;br /&gt;
&#039;manage&#039;=&amp;gt;&#039;http://webmgr.moodle.com&#039;,&lt;br /&gt;
&#039;list&#039;=&amp;gt; array(&lt;br /&gt;
    array(&#039;title&#039;=&amp;gt;&#039;filename1&#039;, &#039;date&#039;=&amp;gt;&#039;01/01/2009&#039;, &#039;size&#039;=&amp;gt;&#039;10MB&#039;, &#039;source&#039;=&amp;gt;&#039;http://www.moodle.com/dl.rar&#039;),&lt;br /&gt;
    array(&#039;title&#039;=&amp;gt;&#039;folder&#039;, &#039;date&#039;=&amp;gt;&#039;01/01/2009&#039;, &#039;size&#039;=&amp;gt;&#039;0&#039;, &#039;children&#039;=&amp;gt;array())&lt;br /&gt;
)&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;The full specification of list element:&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 array(&lt;br /&gt;
   // Used to build navegation bar, so you need to include all parents folders&lt;br /&gt;
   // array(array(&#039;name&#039;=&amp;gt;&#039;root&#039;,&#039;path&#039;=&amp;gt;&#039;/&#039;), array(&#039;name&#039;=&amp;gt;&#039;subfolder&#039;, &#039;path&#039;=&amp;gt;&#039;/subfolder&#039;))&lt;br /&gt;
   &#039;path&#039; =&amp;gt; (array) this will be used to build navigation bar&lt;br /&gt;
   // dynload tells file picker to fetch list dynamically, when user click&lt;br /&gt;
   // the folder, it will send a ajax request to server side.&lt;br /&gt;
   // if you are using pagination, &#039;page&#039; and &#039;pages&#039; parameters should be used&lt;br /&gt;
   // and note, you&#039;d better don&#039;t use pagination and page at the same time&lt;br /&gt;
   &#039;page&#039; =&amp;gt; (int) which page is this list&lt;br /&gt;
   &#039;pages&#039; =&amp;gt; (pages) how many pages&lt;br /&gt;
   &#039;dynload&#039; =&amp;gt; (bool) use dynamic loading,&lt;br /&gt;
   // will display a link in file picker&lt;br /&gt;
   &#039;manage&#039; =&amp;gt; (string) url of the file manager,&lt;br /&gt;
   // set to true, the login link will be removed from file picker&lt;br /&gt;
   &#039;nologin&#039; =&amp;gt; (bool) requires login,&lt;br /&gt;
   // set to true, the search link will be removed from file picker&lt;br /&gt;
   &#039;nosearch&#039; =&amp;gt; (bool) no search link,&lt;br /&gt;
   // set this option will display a upload form in file picker&lt;br /&gt;
   // only used in upload plugin currently&lt;br /&gt;
   &#039;upload&#039; =&amp;gt; array( // upload manager&lt;br /&gt;
     &#039;label&#039; =&amp;gt; (string) label of the form element,&lt;br /&gt;
     &#039;id&#039; =&amp;gt; (string) id of the form element&lt;br /&gt;
   ),&lt;br /&gt;
   // file picker will build a file tree according this &lt;br /&gt;
   // list&lt;br /&gt;
   &#039;list&#039; =&amp;gt; array(&lt;br /&gt;
     array( // file&lt;br /&gt;
       &#039;title&#039; =&amp;gt; (string) file name,&lt;br /&gt;
       &#039;shorttitle&#039; =&amp;gt; (string) optional, if you prefer to display a short title&lt;br /&gt;
       &#039;date&#039; =&amp;gt; (string) file last modification time, usually userdate(...),&lt;br /&gt;
       &#039;size&#039; =&amp;gt; (int) file size,&lt;br /&gt;
       &#039;thumbnail&#039; =&amp;gt; (string) url to thumbnail for the file,&lt;br /&gt;
       &#039;thumbnail_width&#039; =&amp;gt; (int) the width of the thumbnail image,&lt;br /&gt;
       &#039;source&#039; =&amp;gt; plugin-dependent unique path to the file (id, url, path, etc.),&lt;br /&gt;
       &#039;url&#039;=&amp;gt; the accessible url of file&lt;br /&gt;
     ),&lt;br /&gt;
     array( // folder - same as file, but no &#039;source&#039;.&lt;br /&gt;
       &#039;title&#039; =&amp;gt; (string) folder name,&lt;br /&gt;
       &#039;shorttitle&#039; =&amp;gt; (string) optional, if you prefer to display a short title&lt;br /&gt;
       &#039;path&#039; =&amp;gt; (string) path to this folder&lt;br /&gt;
       &#039;date&#039; =&amp;gt; (string) folder last modification time, usually userdate(...),&lt;br /&gt;
       &#039;size&#039; =&amp;gt; 0,&lt;br /&gt;
       &#039;thumbnail&#039; =&amp;gt; (string) url to thumbnail for the folder,&lt;br /&gt;
       &#039;children&#039; =&amp;gt; array( // an empty folder needs to have &#039;children&#039; defined, but empty.&lt;br /&gt;
         // content (files and folders)&lt;br /&gt;
       )&lt;br /&gt;
     ),&lt;br /&gt;
   )&lt;br /&gt;
 )&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Dynamically loading&lt;br /&gt;
Some repositories contain many files which cannot load in one time, in this case, we need dynamically loading to fetch them step by step, files in subfolder won&#039;t be listed until user click the folder in file picker treeview.&lt;br /&gt;
&lt;br /&gt;
As a plug-in developer, if you set dynload flag as &#039;&#039;&#039;true&#039;&#039;&#039;, you should return files and folders (set children as a null array) in current path instead of building the whole file tree.&lt;br /&gt;
&lt;br /&gt;
Example of dynamically loading&lt;br /&gt;
See [http://cvs.moodle.org/moodle/repository/alfresco/lib.php?view=log Alfresco] plug-in&lt;br /&gt;
&lt;br /&gt;
===Functions you can override===&lt;br /&gt;
====print_login====&lt;br /&gt;
This function will help to print a login form, for the Ajax file picker, this function will return a&lt;br /&gt;
PHP array to define this form.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    public function print_login(){&lt;br /&gt;
        if ($this-&amp;gt;options[&#039;ajax&#039;]) {&lt;br /&gt;
            $user_field-&amp;gt;label = get_string(&#039;username&#039;, &#039;repository_boxnet&#039;).&#039;: &#039;;&lt;br /&gt;
            $user_field-&amp;gt;id    = &#039;box_username&#039;;&lt;br /&gt;
            $user_field-&amp;gt;type  = &#039;text&#039;;&lt;br /&gt;
            $user_field-&amp;gt;name  = &#039;boxusername&#039;;&lt;br /&gt;
            $user_field-&amp;gt;value = $ret-&amp;gt;username;&lt;br /&gt;
            &lt;br /&gt;
            $passwd_field-&amp;gt;label = get_string(&#039;password&#039;, &#039;repository_boxnet&#039;).&#039;: &#039;;&lt;br /&gt;
            $passwd_field-&amp;gt;id    = &#039;box_password&#039;;&lt;br /&gt;
            $passwd_field-&amp;gt;type  = &#039;password&#039;;&lt;br /&gt;
            $passwd_field-&amp;gt;name  = &#039;boxpassword&#039;;&lt;br /&gt;
&lt;br /&gt;
            $ret = array();&lt;br /&gt;
            $ret[&#039;login&#039;] = array($user_field, $passwd_field);&lt;br /&gt;
            // if you are going to rename submit button label, use this option&lt;br /&gt;
            //$ret[&#039;login_btn_label&#039;] = get_string(&#039;submit_btn_label&#039;);&lt;br /&gt;
            // if you are going to display a search form instead of login form&lt;br /&gt;
            // set this option to true&lt;br /&gt;
            //$ret[&#039;login_search_form&#039;] = true;&lt;br /&gt;
            return $ret;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
This will help to generate a form by file picker which contains user name and password input elements.&lt;br /&gt;
&lt;br /&gt;
If your plugin don&#039;t require logging in, you don&#039;t need to override it, it will call get_listing to list files automatically by default.&lt;br /&gt;
====check_login====&lt;br /&gt;
This function will return a boolean value to tell Moodle whether the user has logged in.&lt;br /&gt;
By default, this function will return true.&lt;br /&gt;
====logout====&lt;br /&gt;
When a user clicks the logout button in file picker, this function will be called. You may clean up the session or disconnect the connection with remote server here.&lt;br /&gt;
====print_search====&lt;br /&gt;
When a user clicks the search button on file picker, this function will be called to return a search form. By default, it will create a form with single search bar - you can override it to create a advanced search form.&lt;br /&gt;
====search====&lt;br /&gt;
This function will do the searching job. You can obtain the POST parameters from the from the form you created in print_search function&lt;br /&gt;
This function will return a file list exactly like the one from get_listing.&lt;br /&gt;
====get_file====&lt;br /&gt;
When a user clicks the &amp;quot;Get&amp;quot; button to transfer the file, this function will be called. Basically, it will download a file to Moodle - you can override it to modify the file then move it to a better location.&lt;br /&gt;
====get_name====&lt;br /&gt;
This function will return the name of the repository instance.&lt;br /&gt;
&lt;br /&gt;
== I18n - Internationalization ==&lt;br /&gt;
These following strings are required in &#039;&#039;moodle/repository/myplugin/lang/en/repository_myplugin.php&#039;&#039; or &#039;&#039;moodle/lang/en/repository_myplugin.php&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$string[&#039;pluginname&#039;] = &#039;Flickr Public&#039;;&lt;br /&gt;
$string[&#039;configplugin&#039;] = &#039;Flickr Public configuration&#039;;&lt;br /&gt;
$string[&#039;pluginname_help&#039;] = &#039;A Flickr public repository&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Database Tables==&lt;br /&gt;
&lt;br /&gt;
=== repository ===&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;Default&#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;id&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|autoincrementing &lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;type&#039;&#039;&#039;&lt;br /&gt;
|varchar(255)&lt;br /&gt;
|&lt;br /&gt;
|The type of the repository &lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;visible&#039;&#039;&#039;&lt;br /&gt;
|tinyint(1)&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|sortorder&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== repository_instances ===&lt;br /&gt;
&lt;br /&gt;
This table contains one entry for every configured external repository instance.&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;Default&#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;id&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|autoincrementing &lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|name&lt;br /&gt;
|varchar 255&lt;br /&gt;
|&lt;br /&gt;
|A custom name for this repository (non-unique)&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;typeid&#039;&#039;&#039; &lt;br /&gt;
|int(10)&lt;br /&gt;
| &lt;br /&gt;
|The id of repository type&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 person who created this repository instance&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;contextid&#039;&#039;&#039; &lt;br /&gt;
|int(10)&lt;br /&gt;
| &lt;br /&gt;
|The context that this repository is available to ( = system context for site-wide ones)&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|username&lt;br /&gt;
|varchar(255)&lt;br /&gt;
| &lt;br /&gt;
|username to log in with, if required (almost never!)&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|password&lt;br /&gt;
|varchar(255)&lt;br /&gt;
| &lt;br /&gt;
|password to log in with, if required (almost never!)&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|timecreated&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|The time this repository was created&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|timemodified&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|The last time the repository was modified&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== repository_instance_config ===&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;Default&#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;id&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|autoincrementing &lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;instanceid&#039;&#039;&#039;&lt;br /&gt;
|int(int)&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;name&#039;&#039;&#039;&lt;br /&gt;
|varchar(255)&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|value&lt;br /&gt;
|Text&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
*[[Repository API| Repository API]]&lt;br /&gt;
*[[Repository_Interface_for_Moodle/Course/User| Repository Interface for Moodle/Course/User]]&lt;br /&gt;
*[[QA:Use Case Number Attribution| Use Case Number Attribution]]&lt;br /&gt;
* MDL-16543 - A list of officially supported repository plugins&lt;br /&gt;
* MDL-16543 - Template plugin for developers&lt;br /&gt;
[[Category:Repositories]]&lt;br /&gt;
[[Category:Plugins]]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Authentication_plugins&amp;diff=31761</id>
		<title>Authentication plugins</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Authentication_plugins&amp;diff=31761"/>
		<updated>2012-01-25T08:48:12Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Introduction==&lt;br /&gt;
&lt;br /&gt;
This page first gives an &#039;&#039;&#039;overview&#039;&#039;&#039; of the &#039;&#039;authentication process&#039;&#039; and then explains how &#039;&#039;authentication modules&#039;&#039; can be &#039;&#039;&#039;created&#039;&#039;&#039; using hooks to take over from the native authentication in Moodle.&lt;br /&gt;
=== Overview of Moodle authentication process ===&lt;br /&gt;
[[File:1.9.11_login_element.png|203px||thumb|right|The login UI element in Moodle 1.9]]The authentication use case in Moodle starts when a user clicks on the Login link in the UI. Then the following happens (skipping some minor details and rarer scenarios):&lt;br /&gt;
&lt;br /&gt;
# The default login page (&amp;lt;tt&amp;gt;/login/index.php&amp;lt;/tt&amp;gt;) is displayed. OR, if a system administrator has set the Alternate Login URL on the &amp;quot;Manage authentication&amp;quot; page, that URL will be displayed.&lt;br /&gt;
# A user enters their credentials and submits the form.&lt;br /&gt;
# The handler code in &amp;lt;tt&amp;gt;/login/index.php&amp;lt;/tt&amp;gt; runs:&lt;br /&gt;
## Gets a list of enabled authentication plugins.&lt;br /&gt;
## Runs &amp;lt;tt&amp;gt;loginpage_hook()&amp;lt;/tt&amp;gt; for each plugin, in case any of them needs to intercept the login request.&lt;br /&gt;
## Checks to make sure that the username meets Moodle&#039;s criteria (alphanumeric, with periods and hyphens allowed).&lt;br /&gt;
## Calls &amp;lt;tt&amp;gt;authenticate_user_login()&amp;lt;/tt&amp;gt; in &amp;lt;tt&amp;gt;/lib/moodlelib.php&amp;lt;/tt&amp;gt;, which returns a &amp;lt;code&amp;gt;$user&amp;lt;/code&amp;gt; object. (Details of this code follow this main outline.)&lt;br /&gt;
## Determines whether authentication was successful (by checking whether &amp;lt;code&amp;gt;$user&amp;lt;/code&amp;gt; is a valid object) and, if not, sends them back to the login page with an error message. Otherwise, it figures out where to send the user based on their original page request, whether their password is expired, etc., and redirects them there.&lt;br /&gt;
&lt;br /&gt;
==History==&lt;br /&gt;
Authentication plugins exist from 1.9&lt;br /&gt;
==Example==&lt;br /&gt;
==Template==&lt;br /&gt;
==Naming convention==&lt;br /&gt;
==File structure==&lt;br /&gt;
# Choose a name for your plugin.  We&#039;ll use &#039;sentry&#039; as an example below; change it to whatever name you have chosen.&lt;br /&gt;
&lt;br /&gt;
# Under your Moodle installation root, create the directory &amp;lt;tt&amp;gt;/auth/sentry&amp;lt;/tt&amp;gt;.  It should be sibling to existing auth plugin directories: &#039;db&#039;, &#039;nologin&#039;, &#039;none&#039;, etc.&lt;br /&gt;
&lt;br /&gt;
# Create the file &amp;lt;tt&amp;gt;/auth/sentry/auth.php&amp;lt;/tt&amp;gt;.  Within the file, create a class &amp;lt;tt&amp;gt;auth_plugin_sentry&amp;lt;/tt&amp;gt; that extends &amp;lt;tt&amp;gt;auth_plugin_base&amp;lt;/tt&amp;gt; from &amp;lt;tt&amp;gt;/lib/authlib.php&amp;lt;/tt&amp;gt;.  (You will need to &amp;lt;code&amp;gt;require_once&amp;lt;/code&amp;gt; the authlib file.)&lt;br /&gt;
&lt;br /&gt;
# Implement the &amp;lt;tt&amp;gt;user_login()&amp;lt;/tt&amp;gt; function in your &amp;lt;tt&amp;gt;auth.php&amp;lt;/tt&amp;gt; file, and create or override additional functions based on your plugin&#039;s requirements.&lt;br /&gt;
&lt;br /&gt;
# Log in to your Moodle installation as a site administrator and find, in the site administrator block, the page &amp;quot;Users -&amp;gt; Authentication -&amp;gt; Manage authentication&amp;quot;.  You will see your plugin in the list, appearing as &amp;lt;nowiki&amp;gt;[[auth_sentrytitle]]&amp;lt;/nowiki&amp;gt;.  You can enable it and move it up and down in the order.  &#039;&#039;&#039;At this point, with the plugin enabled, your plugin is registered and will be used by Moodle in its authentication process.&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
# If you don&#039;t like seeing &amp;lt;nowiki&amp;gt;[[auth_sentrytitle]]&amp;lt;/nowiki&amp;gt; as the name of your plugin in the Moodle UI, you&#039;ll need to create language files for your plugin.  Do this by creating the directory &amp;lt;tt&amp;gt;/auth/sentry/lang&amp;lt;/tt&amp;gt;, and under it, a directory for each language that your installation needs to support.  (Example: &amp;lt;tt&amp;gt;/auth/sentry/lang/en&amp;lt;/tt&amp;gt;.)  Within each of these language directories, create a file called &amp;lt;tt&amp;gt;auth_sentry.php&amp;lt;/tt&amp;gt;.  That file should set the desired value for &amp;lt;code&amp;gt;$string[&#039;auth_sentrytitle&#039;] for that language (use $string[&#039;pluginname&#039;] for Moodle2)&amp;lt;/code&amp;gt;.  You can also set the plugin description by setting &amp;lt;code&amp;gt;$string[&#039;auth_sentrydescription&#039;]&amp;lt;/code&amp;gt;, and you can also assign other translatable strings that your plugin uses, in these files. &lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; A folder named like &#039;&#039;&#039;en&#039;&#039;&#039; may not work for everyone. Please refer to the lang folder under your moodle installation directory to get an idea about the language/locale codes used in your moodle installation. For example, the lang folder in own users system contained folders named &#039;&#039;&#039;en&#039;&#039;&#039; and &#039;&#039;&#039;zh-cn&#039;&#039;&#039;. He emulated the same in the lang folder of the auth plugin and the plugin picked up the title and description strings immediately. ([[Places_to_search_for_lang_strings|More info on how Moodle handles language]]).&lt;br /&gt;
&lt;br /&gt;
# If you want to configure your plugin through the Moodle UI, implement &amp;lt;tt&amp;gt;config_form()&amp;lt;/tt&amp;gt; and &amp;lt;tt&amp;gt;process_config()&amp;lt;/tt&amp;gt; in the plugin class.  You might find it convenient to use the &#039;db&#039; plugin as a model for this.  The plugin&#039;s config settings can then be managed through the Manage authentication page by clicking on the Settings link for that plugin, and the values will be stored in the &amp;lt;tt&amp;gt;mdl_config_pluginstable&amp;lt;/tt&amp;gt; in the database.&lt;br /&gt;
&lt;br /&gt;
==Interfacing to API&#039;s==&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;authenticate_user_login()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
That&#039;s the main outline, but a lot of interesting stuff happens in &amp;lt;tt&amp;gt;authenticate_user_login()&amp;lt;/tt&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
# It gets a list of enabled authentication plugins.&lt;br /&gt;
# It looks up the username in the mdl_user table to see if they are allowed to log in, and which authentication plugin handles their login requests. (This will be the plugin that handled their first-ever login request.)&lt;br /&gt;
# It creates a user object, which will contain the data from &amp;lt;tt&amp;gt;mdl_user&amp;lt;/tt&amp;gt; if the username is known; if not, it will be an empty object.&lt;br /&gt;
# It does the following with the authentication plugin (note that for a username unknown to Moodle, it will do these steps for each authenticated plugin until one succeeds or it has tried them all):&lt;br /&gt;
## Calls the &amp;lt;tt&amp;gt;user_login()&amp;lt;/tt&amp;gt; function provided by that plugin, which returns a boolean value based on whether the credentials authenticate or not. If the result is false (not authenticated), skips the rest of the steps below and continues to the next plugin.&lt;br /&gt;
## If the plugin authenticates against an external system (not Moodle&#039;s user database), its &amp;lt;tt&amp;gt;update_user_record()&amp;lt;/tt&amp;gt; function is called to get the user&#039;s name, contact info, etc.&lt;br /&gt;
## Creates the Moodle user record if it doesn&#039;t already exist.&lt;br /&gt;
## Calls the plugin&#039;s &amp;lt;tt&amp;gt;sync_roles()&amp;lt;/tt&amp;gt; function.&lt;br /&gt;
## Notifies each enabled authentication plugin that the user successfully authenticated, by calling each one&#039;s &amp;lt;tt&amp;gt;user_authenticated_hook()&amp;lt;/tt&amp;gt; function.&lt;br /&gt;
# It returns the user object if everything was successful, or false if no plugin was able to successfully authenticate the credentials.&lt;br /&gt;
=== &amp;lt;tt&amp;gt;user_login($username, $password)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
This must be rewritten by plugin to return boolean value, returns true if the username and password work and false if they are wrong or don&#039;t exist.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;can_change_password()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Returns true if this authentication plugin can change users&#039; password.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;change_password_url()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Returns the URL for changing the users&#039; passwords, or empty if the default URL can be used.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;can_edit_profile()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Returns true if this authentication plugin can edit the users&#039; profile.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;edit_profile_url()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Returns the URL for editing users&#039; profile, or empty if the defaults URL can be used.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;prevent_local_passwords()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Indicates if password hashes should be stored in local moodle database.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;user_update_password($user, $newpassword)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Update the user&#039;s password.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;user_update($olduser, $newuser)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Called when the user record is updated. It will modify the user information in external database.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;user_delete($olduser)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
User delete requested. Internal user record had been deleted.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;can_reset_password()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Returns true if plugin allows resetting of internal password.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;user_signup($user, $notify=true)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Sign up a new user ready for confirmation, password is passed in plaintext.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;can_confirm()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Returns true if plugin allows confirming of new users.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;user_confirm($username, $confirmsecret)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Confirm the new user as registered.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;user_exists($username)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Checks if user exists in external db.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;password_expire($username)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Returns number of days to user password expires.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;sync_roles()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Sync roles for this user - usually creator&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;get_userinfo($username)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Read user information from external database and returns it as array.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;config_form($config, $err, $user_fields)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Prints a form for configuring this authentication plugin. It&#039;s called from admin/auth.php, and outputs a full page with a form for configuring this plugin.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;validate_form($form, $err)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Validate form data.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;process_config($config)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Processes and stores configuration data for this authentication plugin.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;loginpage_hook()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Hook for overriding behaviour of login page.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;user_authenticated_hook($user, $username, $password)&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Post authentication hook. This method is called from authenticate_user_login() for all enabled auth plugins.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;prelogout_hook()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Pre logout hook.&lt;br /&gt;
&lt;br /&gt;
=== &amp;lt;tt&amp;gt;logoutpage_hook()&amp;lt;/tt&amp;gt;===&lt;br /&gt;
Hook for overriding behaviour of logout page.&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
* [[Authentication API]]&lt;br /&gt;
* Using Moodle [http://moodle.org/mod/forum/discuss.php?d=102070 Overview of entire authentication code flow] forum discussion&lt;br /&gt;
* Using Moodle [http://moodle.org/mod/forum/view.php?id=42 User authentication forum]&lt;br /&gt;
* Moodle Docs [[:en:Manage authentication|Manage authentication]]&lt;br /&gt;
* [[:en:Authentication FAQ|Authentication FAQ]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Authentication]]&lt;br /&gt;
[[Category:Plugins]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[fr:Méthodes_d&#039;authentification]]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Repository_plugins&amp;diff=31732</id>
		<title>Repository plugins</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Repository_plugins&amp;diff=31732"/>
		<updated>2012-01-25T06:14:22Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Repository plugin allow Moodle to bring contents into Moodle from external repositories.&lt;br /&gt;
&lt;br /&gt;
===Prerequisites===&lt;br /&gt;
Before starting coding, it is necessary to know how to use repository administration pages and how to use the file picker.&lt;br /&gt;
&lt;br /&gt;
===Overview===&lt;br /&gt;
&lt;br /&gt;
The 3 different parts to write&lt;br /&gt;
# Administration - You can customise the way administrators and users can configure their repositories. &lt;br /&gt;
# File picker integration - The core of your plugin, it will manage communication between Moodle and the repository service, and also the file picker display.&lt;br /&gt;
# I18n - Internationalization should be done at the same time as you&#039;re writing the other parts.&lt;br /&gt;
&lt;br /&gt;
== History ==&lt;br /&gt;
&lt;br /&gt;
Repository plugins exists from 2.0&lt;br /&gt;
&lt;br /&gt;
== Example ==&lt;br /&gt;
*[[Box.net Repository Plugin|Box.net Repository Plugin]]&lt;br /&gt;
*[[Flickr Repository Plugin|Flickr Repository Plugin]]&lt;br /&gt;
*[[Moodle Repository Plugin|Remote Moodle Repository Plugin]]&lt;br /&gt;
&lt;br /&gt;
== Template ==&lt;br /&gt;
There is a template available in [https://github.com/dongsheng/moodle-repository_demo Dongsheng Cai&#039;s github]&lt;br /&gt;
&lt;br /&gt;
== Naming convention== &lt;br /&gt;
&lt;br /&gt;
# Create a folder for your plugin in &#039;&#039;moodle/repository/&#039;&#039; e.g. &#039;&#039;moodle/repository/myplugin&#039;&#039;&lt;br /&gt;
# Create the following files and add them to the plugin folder:&lt;br /&gt;
#* &#039;&#039;lib.php&#039;&#039;, the main class will be named &amp;quot;repository_myplugin&amp;quot;&lt;br /&gt;
#* &#039;&#039;pix/icon.png&#039;&#039; (the icon displayed in the file picker)&lt;br /&gt;
#* &#039;&#039;[[version.php]]&#039;&#039;&lt;br /&gt;
# Create the language file &#039;&#039;repository_myplugin.php&#039;&#039; and add it to the plugin folder, keeping the following folder structure:&lt;br /&gt;
#*&#039;&#039;moodle/repository/myplugin/lang/en/repository_myplugin.php&#039;&#039; or &#039;&#039;moodle/lang/en/repository_myplugin.php&#039;&#039;&lt;br /&gt;
# Create access.php and upgrade scripts (optional)&lt;br /&gt;
&lt;br /&gt;
==Administration APIs==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Functions===&lt;br /&gt;
All of the following functions are optional. If they&#039;re not implemented, your plugin will not have manual settings and will have only one instance displayed in the File Picker (The repository API creates this unique instance when the administrator add the plugin).&lt;br /&gt;
&lt;br /&gt;
==== get_instance_option_names====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Return an array of strings. These strings are setting names. These settings are specific to an instance.&lt;br /&gt;
If the function returns an empty array, the API will consider that the plugin displays only one repository in the file picker.&lt;br /&gt;
Parent function returns an empty array.&lt;br /&gt;
&lt;br /&gt;
====instance_config_form(&amp;amp;$mform)====&lt;br /&gt;
This is for modifying the Moodle form displaying the settings specific to an instance.&lt;br /&gt;
&lt;br /&gt;
For example, to add a required text box called email_address:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;text&#039;, &#039;email_address&#039;, get_string(&#039;emailaddress&#039;, &#039;repository_flickr_public&#039;));&lt;br /&gt;
$mform-&amp;gt;addRule(&#039;email_address&#039;, $strrequired, &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
   &lt;br /&gt;
&#039;&#039;Note: &#039;&#039;mform&#039;&#039; has by default a name text box (cannot be removed).&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Parent function does nothing.&lt;br /&gt;
&lt;br /&gt;
====instance_form_validation($mform, $data, $errors)====&lt;br /&gt;
This allows us to validate what has been submitted in the instance configuration form. For instance:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function instance_form_validation($mform, $data, $errors) {&lt;br /&gt;
        if (empty($data[&#039;email_address&#039;])) {&lt;br /&gt;
            $errors[&#039;email_address&#039;] = get_string(&#039;invalidemailsettingname&#039;, &#039;repository_flickr_public&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        return $errors;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_type_option_names====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Return an array of string. These strings are setting names. These settings are shared by all instances.&lt;br /&gt;
Parent function return an empty array.&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_type_option_names() {&lt;br /&gt;
        return array_merge(parent::get_type_option_names(), array(&#039;rootpath&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====type_config_form(&amp;amp;$mform)====&lt;br /&gt;
This is for modifying the Moodle form displaying the plugin settings.&lt;br /&gt;
&lt;br /&gt;
For example, to display the standard repository plugin settings along with the custom ones use:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function type_config_form($mform) {&lt;br /&gt;
        parent::type_config_form($mform);&lt;br /&gt;
&lt;br /&gt;
        $rootpath = get_config(&#039;repository_someplugin&#039;, &#039;rootpath&#039;);&lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;text&#039;, &#039;rootpath&#039;, get_string(&#039;rootpath&#039;, &#039;repository_someplugin&#039;), array(&#039;size&#039; =&amp;gt; &#039;40&#039;));&lt;br /&gt;
        $mform-&amp;gt;setDefault(&#039;rootpath&#039;, $rootpath);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====type_form_validation($mform, $data, $errors)====&lt;br /&gt;
Use this function if you need to validate some variables submitted by plugin settings form. Push the items to $error array in the format (&amp;quot;fieldname&amp;quot; =&amp;gt; &amp;quot;errormessage&amp;quot;) to have them highlighted in the form.&lt;br /&gt;
&lt;br /&gt;
With the example above, this function may look like:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function type_form_validation($mform, $data, $errors) {&lt;br /&gt;
        if (!is_dir($data[&#039;rootpath&#039;])) {&lt;br /&gt;
            $errors[&#039;rootpath&#039;] = get_string(&#039;invalidrootpath&#039;, &#039;repository_someplugin&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        return $errors;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====plugin_init()====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
This function is called when the administrator adds the plugin. So unless the administrator deletes the plugin and re-adds it, it should be called only once.&lt;br /&gt;
Parent function does nothing.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
As an example, let&#039;s create a Flickr plugin for accessing a public flickr account. The plugin will be called &amp;quot;Flickr Public&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Firstly the skeleton:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    &amp;lt;?php&lt;br /&gt;
    /**&lt;br /&gt;
     * repository_flickr_public class&lt;br /&gt;
     * Moodle user can access public flickr account&lt;br /&gt;
     *&lt;br /&gt;
     * @license http://www.gnu.org/copyleft/gpl.html GNU Public License&lt;br /&gt;
    */&lt;br /&gt;
    class repository_flickr_public extends repository {&lt;br /&gt;
    }&lt;br /&gt;
?&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then consider the question &amp;quot;What does my plugin do?&amp;quot;&lt;br /&gt;
&lt;br /&gt;
In the Moodle file picker, we want to display some flickr public repositories directly linked to a flickr public account. For example &#039;&#039;My Public Flickr Pictures&#039;&#039;, and also &#039;&#039;My Friend&#039;s Flickr Pictures&#039;&#039;. When the user clicks on one of these repositories, the public pictures are displayed in the file picker.&lt;br /&gt;
&lt;br /&gt;
In order to access to a flickr public account, the plugin needs to know the email address of the Flickr public account owner. So the administrator will need to set an email address for every repository. Let&#039;s add an &amp;quot;email address&amp;quot; setting to every repository.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
//We tell the API that the repositories have specific settings: &amp;quot;email address&amp;quot;&lt;br /&gt;
    public static function get_instance_option_names() {&lt;br /&gt;
        return array(&#039;email_address&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
//We add an &amp;quot;email address&amp;quot; text box to the create/edit repository instance Moodle form&lt;br /&gt;
    public function instance_config_form(&amp;amp;$mform) {&lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;text&#039;, &#039;email_address&#039;, get_string(&#039;emailaddress&#039;, &#039;repository_flickr_public&#039;));&lt;br /&gt;
        $mform-&amp;gt;addRule(&#039;email_address&#039;, get_string(&#039;required&#039;), &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So at this moment all our Flickr Public Repositories will have a specific email address. However this is not enough. In order to communicate with Flickr, Moodle needs to know a Flickr API key (http://www.flickr.com/services/api/). This API key is the same for any repository. We could add it with the email address setting but the administrator would have to enter the same API key for every repository. Hopefully the administrator can add settings to the plugin level, impacting all repositories. The code is similar the repository instance settings:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
//We tell the API that the repositories have general settings: &amp;quot;api_key&amp;quot;&lt;br /&gt;
    public static function get_type_option_names() {&lt;br /&gt;
        return array(&#039;api_key&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
//We add an &amp;quot;api key&amp;quot; text box to the create/edit repository plugin Moodle form (also called a Repository type Moodle form)&lt;br /&gt;
    public function type_config_form(&amp;amp;$mform) {&lt;br /&gt;
        //the following line is needed in order to retrieve the API key value from the database when Moodle displays the edit form&lt;br /&gt;
        $api_key = get_config(&#039;flickr_public&#039;, &#039;api_key&#039;);&lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;text&#039;, &#039;api_key&#039;, get_string(&#039;apikey&#039;, &#039;repository_flickr_public&#039;), &lt;br /&gt;
                           array(&#039;value&#039;=&amp;gt;$api_key,&#039;size&#039; =&amp;gt; &#039;40&#039;));&lt;br /&gt;
        $mform-&amp;gt;addRule(&#039;api_key&#039;, get_string(&#039;required&#039;), &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Have we finished yet?&lt;br /&gt;
&lt;br /&gt;
Yes! We have created everything necessary for the administration pages. But let&#039;s go further. It would be good if the user can enter any &amp;quot;Flickr public account email address&amp;quot; in the file picker. In fact we want to display in the file picker a Flickr Public repository that the Moodle administrator can never delete. Let&#039;s add:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
     //this function is only called one time, when the Moodle administrator add the Flickr Public Plugin into the Moodle site.&lt;br /&gt;
     public static function plugin_init() {&lt;br /&gt;
        //here we create a default repository instance. The last parameter is 1 in order to set the instance as readonly.&lt;br /&gt;
        repository_static_function(&#039;flickr_public&#039;,&#039;create&#039;, &#039;flickr_public&#039;, 0, get_system_context(), &lt;br /&gt;
                                    array(&#039;name&#039; =&amp;gt; &#039;default instance&#039;,&#039;email_address&#039; =&amp;gt; null),1);&lt;br /&gt;
     }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That&#039;s all - the administration part of our Flickr Public plugin is done. For your information, Box.net, Flickr, and Flickr Public all have similar administration APIs.&lt;br /&gt;
&lt;br /&gt;
==Repository APIs==&lt;br /&gt;
=== Quick Start ===&lt;br /&gt;
First of all, the File Picker using intensively Ajax you will need a easy way to debug. Install FirePHP (MDL-16371) and make it works. It will save you a lot of time. (You might give the [http://moodle.org/mod/forum/discuss.php?d=119961 FirePHP plugin for Moodle] a try, it&#039;s still work in progress, though.)&lt;br /&gt;
&lt;br /&gt;
* Your first question when you write your plugin specification is &#039;Does the user need to log-in&#039;? If they do, in your plugin you have to detect user session in constructor() function, and use print_login() if required, see more details below.&lt;br /&gt;
* For most of plugins, you need to establish a connection with the remote repository. This connection can be done into the get_listing(), constructor() function, see more details below.&lt;br /&gt;
* You wanna retrieve the file that the user selected, rewrite get_file() if required, see more details below.&lt;br /&gt;
* Optional question that you should ask yourself is &#039;Does the user can execute a search&#039;, if they do, you will have to rewrite search() method, see more details below.&lt;br /&gt;
&lt;br /&gt;
===Functions you *MUST* override===&lt;br /&gt;
====__construct====&lt;br /&gt;
You may initialize your plugin here, such as:&lt;br /&gt;
# Get options from database&lt;br /&gt;
# Get user name and password from HTTP POST&lt;br /&gt;
&lt;br /&gt;
====get_listing($path=&amp;quot;&amp;quot;, $page=&amp;quot;&amp;quot;)====&lt;br /&gt;
This function will return a list of files, the list must be a array like this:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$list = array(&lt;br /&gt;
 //this will be used to build navigation bar&lt;br /&gt;
&#039;path&#039;=&amp;gt;array(array(&#039;name&#039;=&amp;gt;&#039;root&#039;,&#039;path&#039;=&amp;gt;&#039;/&#039;), array(&#039;name&#039;=&amp;gt;&#039;subfolder&#039;, &#039;path&#039;=&amp;gt;&#039;/subfolder&#039;)),&lt;br /&gt;
&#039;manage&#039;=&amp;gt;&#039;http://webmgr.moodle.com&#039;,&lt;br /&gt;
&#039;list&#039;=&amp;gt; array(&lt;br /&gt;
    array(&#039;title&#039;=&amp;gt;&#039;filename1&#039;, &#039;date&#039;=&amp;gt;&#039;01/01/2009&#039;, &#039;size&#039;=&amp;gt;&#039;10MB&#039;, &#039;source&#039;=&amp;gt;&#039;http://www.moodle.com/dl.rar&#039;),&lt;br /&gt;
    array(&#039;title&#039;=&amp;gt;&#039;folder&#039;, &#039;date&#039;=&amp;gt;&#039;01/01/2009&#039;, &#039;size&#039;=&amp;gt;&#039;0&#039;, &#039;children&#039;=&amp;gt;array())&lt;br /&gt;
)&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;The full specification of list element:&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 array(&lt;br /&gt;
   // Used to build navegation bar, so you need to include all parents folders&lt;br /&gt;
   // array(array(&#039;name&#039;=&amp;gt;&#039;root&#039;,&#039;path&#039;=&amp;gt;&#039;/&#039;), array(&#039;name&#039;=&amp;gt;&#039;subfolder&#039;, &#039;path&#039;=&amp;gt;&#039;/subfolder&#039;))&lt;br /&gt;
   &#039;path&#039; =&amp;gt; (array) this will be used to build navigation bar&lt;br /&gt;
   // dynload tells file picker to fetch list dynamically, when user click&lt;br /&gt;
   // the folder, it will send a ajax request to server side.&lt;br /&gt;
   // if you are using pagination, &#039;page&#039; and &#039;pages&#039; parameters should be used&lt;br /&gt;
   // and note, you&#039;d better don&#039;t use pagination and page at the same time&lt;br /&gt;
   &#039;page&#039; =&amp;gt; (int) which page is this list&lt;br /&gt;
   &#039;pages&#039; =&amp;gt; (pages) how many pages&lt;br /&gt;
   &#039;dynload&#039; =&amp;gt; (bool) use dynamic loading,&lt;br /&gt;
   // will display a link in file picker&lt;br /&gt;
   &#039;manage&#039; =&amp;gt; (string) url of the file manager,&lt;br /&gt;
   // set to true, the login link will be removed from file picker&lt;br /&gt;
   &#039;nologin&#039; =&amp;gt; (bool) requires login,&lt;br /&gt;
   // set to true, the search link will be removed from file picker&lt;br /&gt;
   &#039;nosearch&#039; =&amp;gt; (bool) no search link,&lt;br /&gt;
   // set this option will display a upload form in file picker&lt;br /&gt;
   // only used in upload plugin currently&lt;br /&gt;
   &#039;upload&#039; =&amp;gt; array( // upload manager&lt;br /&gt;
     &#039;label&#039; =&amp;gt; (string) label of the form element,&lt;br /&gt;
     &#039;id&#039; =&amp;gt; (string) id of the form element&lt;br /&gt;
   ),&lt;br /&gt;
   // file picker will build a file tree according this &lt;br /&gt;
   // list&lt;br /&gt;
   &#039;list&#039; =&amp;gt; array(&lt;br /&gt;
     array( // file&lt;br /&gt;
       &#039;title&#039; =&amp;gt; (string) file name,&lt;br /&gt;
       &#039;shorttitle&#039; =&amp;gt; (string) optional, if you prefer to display a short title&lt;br /&gt;
       &#039;date&#039; =&amp;gt; (string) file last modification time, usually userdate(...),&lt;br /&gt;
       &#039;size&#039; =&amp;gt; (int) file size,&lt;br /&gt;
       &#039;thumbnail&#039; =&amp;gt; (string) url to thumbnail for the file,&lt;br /&gt;
       &#039;thumbnail_width&#039; =&amp;gt; (int) the width of the thumbnail image,&lt;br /&gt;
       &#039;source&#039; =&amp;gt; plugin-dependent unique path to the file (id, url, path, etc.),&lt;br /&gt;
       &#039;url&#039;=&amp;gt; the accessible url of file&lt;br /&gt;
     ),&lt;br /&gt;
     array( // folder - same as file, but no &#039;source&#039;.&lt;br /&gt;
       &#039;title&#039; =&amp;gt; (string) folder name,&lt;br /&gt;
       &#039;shorttitle&#039; =&amp;gt; (string) optional, if you prefer to display a short title&lt;br /&gt;
       &#039;path&#039; =&amp;gt; (string) path to this folder&lt;br /&gt;
       &#039;date&#039; =&amp;gt; (string) folder last modification time, usually userdate(...),&lt;br /&gt;
       &#039;size&#039; =&amp;gt; 0,&lt;br /&gt;
       &#039;thumbnail&#039; =&amp;gt; (string) url to thumbnail for the folder,&lt;br /&gt;
       &#039;children&#039; =&amp;gt; array( // an empty folder needs to have &#039;children&#039; defined, but empty.&lt;br /&gt;
         // content (files and folders)&lt;br /&gt;
       )&lt;br /&gt;
     ),&lt;br /&gt;
   )&lt;br /&gt;
 )&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Dynamically loading&lt;br /&gt;
Some repositories contain many files which cannot load in one time, in this case, we need dynamically loading to fetch them step by step, files in subfolder won&#039;t be listed until user click the folder in file picker treeview.&lt;br /&gt;
&lt;br /&gt;
As a plug-in developer, if you set dynload flag as &#039;&#039;&#039;true&#039;&#039;&#039;, you should return files and folders (set children as a null array) in current path instead of building the whole file tree.&lt;br /&gt;
&lt;br /&gt;
Example of dynamically loading&lt;br /&gt;
See [http://cvs.moodle.org/moodle/repository/alfresco/lib.php?view=log Alfresco] plug-in&lt;br /&gt;
&lt;br /&gt;
===Functions you can override===&lt;br /&gt;
====print_login====&lt;br /&gt;
This function will help to print a login form, for the Ajax file picker, this function will return a&lt;br /&gt;
PHP array to define this form.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    public function print_login(){&lt;br /&gt;
        if ($this-&amp;gt;options[&#039;ajax&#039;]) {&lt;br /&gt;
            $user_field-&amp;gt;label = get_string(&#039;username&#039;, &#039;repository_boxnet&#039;).&#039;: &#039;;&lt;br /&gt;
            $user_field-&amp;gt;id    = &#039;box_username&#039;;&lt;br /&gt;
            $user_field-&amp;gt;type  = &#039;text&#039;;&lt;br /&gt;
            $user_field-&amp;gt;name  = &#039;boxusername&#039;;&lt;br /&gt;
            $user_field-&amp;gt;value = $ret-&amp;gt;username;&lt;br /&gt;
            &lt;br /&gt;
            $passwd_field-&amp;gt;label = get_string(&#039;password&#039;, &#039;repository_boxnet&#039;).&#039;: &#039;;&lt;br /&gt;
            $passwd_field-&amp;gt;id    = &#039;box_password&#039;;&lt;br /&gt;
            $passwd_field-&amp;gt;type  = &#039;password&#039;;&lt;br /&gt;
            $passwd_field-&amp;gt;name  = &#039;boxpassword&#039;;&lt;br /&gt;
&lt;br /&gt;
            $ret = array();&lt;br /&gt;
            $ret[&#039;login&#039;] = array($user_field, $passwd_field);&lt;br /&gt;
            // if you are going to rename submit button label, use this option&lt;br /&gt;
            //$ret[&#039;login_btn_label&#039;] = get_string(&#039;submit_btn_label&#039;);&lt;br /&gt;
            // if you are going to display a search form instead of login form&lt;br /&gt;
            // set this option to true&lt;br /&gt;
            //$ret[&#039;login_search_form&#039;] = true;&lt;br /&gt;
            return $ret;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
This will help to generate a form by file picker which contains user name and password input elements.&lt;br /&gt;
&lt;br /&gt;
If your plugin don&#039;t require logging in, you don&#039;t need to override it, it will call get_listing to list files automatically by default.&lt;br /&gt;
====check_login====&lt;br /&gt;
This function will return a boolean value to tell Moodle whether the user has logged in.&lt;br /&gt;
By default, this function will return true.&lt;br /&gt;
====logout====&lt;br /&gt;
When a user clicks the logout button in file picker, this function will be called. You may clean up the session or disconnect the connection with remote server here.&lt;br /&gt;
====print_search====&lt;br /&gt;
When a user clicks the search button on file picker, this function will be called to return a search form. By default, it will create a form with single search bar - you can override it to create a advanced search form.&lt;br /&gt;
====search====&lt;br /&gt;
This function will do the searching job. You can obtain the POST parameters from the from the form you created in print_search function&lt;br /&gt;
This function will return a file list exactly like the one from get_listing.&lt;br /&gt;
====get_file====&lt;br /&gt;
When a user clicks the &amp;quot;Get&amp;quot; button to transfer the file, this function will be called. Basically, it will download a file to Moodle - you can override it to modify the file then move it to a better location.&lt;br /&gt;
====get_name====&lt;br /&gt;
This function will return the name of the repository instance.&lt;br /&gt;
&lt;br /&gt;
== I18n - Internationalization ==&lt;br /&gt;
These following strings are required in &#039;&#039;moodle/repository/myplugin/lang/en/repository_myplugin.php&#039;&#039; or &#039;&#039;moodle/lang/en/repository_myplugin.php&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$string[&#039;pluginname&#039;] = &#039;Flickr Public&#039;;&lt;br /&gt;
$string[&#039;configplugin&#039;] = &#039;Flickr Public configuration&#039;;&lt;br /&gt;
$string[&#039;pluginname_help&#039;] = &#039;A Flickr public repository&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Database Tables==&lt;br /&gt;
&lt;br /&gt;
=== repository ===&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;Default&#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;id&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|autoincrementing &lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;type&#039;&#039;&#039;&lt;br /&gt;
|varchar(255)&lt;br /&gt;
|&lt;br /&gt;
|The type of the repository &lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;visible&#039;&#039;&#039;&lt;br /&gt;
|tinyint(1)&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|sortorder&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== repository_instances ===&lt;br /&gt;
&lt;br /&gt;
This table contains one entry for every configured external repository instance.&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;Default&#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;id&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|autoincrementing &lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|name&lt;br /&gt;
|varchar 255&lt;br /&gt;
|&lt;br /&gt;
|A custom name for this repository (non-unique)&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;typeid&#039;&#039;&#039; &lt;br /&gt;
|int(10)&lt;br /&gt;
| &lt;br /&gt;
|The id of repository type&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 person who created this repository instance&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;contextid&#039;&#039;&#039; &lt;br /&gt;
|int(10)&lt;br /&gt;
| &lt;br /&gt;
|The context that this repository is available to ( = system context for site-wide ones)&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|username&lt;br /&gt;
|varchar(255)&lt;br /&gt;
| &lt;br /&gt;
|username to log in with, if required (almost never!)&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|password&lt;br /&gt;
|varchar(255)&lt;br /&gt;
| &lt;br /&gt;
|password to log in with, if required (almost never!)&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|timecreated&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|The time this repository was created&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|timemodified&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|The last time the repository was modified&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== repository_instance_config ===&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;Default&#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;id&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|autoincrementing &lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;instanceid&#039;&#039;&#039;&lt;br /&gt;
|int(int)&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;name&#039;&#039;&#039;&lt;br /&gt;
|varchar(255)&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|value&lt;br /&gt;
|Text&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
*[[Repository API| Repository API]]&lt;br /&gt;
*[[Repository_Interface_for_Moodle/Course/User| Repository Interface for Moodle/Course/User]]&lt;br /&gt;
*[[QA:Use Case Number Attribution| Use Case Number Attribution]]&lt;br /&gt;
* MDL-16543 - A list of officially supported repository plugins&lt;br /&gt;
* MDL-16543 - Template plugin for developers&lt;br /&gt;
[[Category:Repositories]]&lt;br /&gt;
[[Category:Plugins]]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Repository_plugins&amp;diff=31731</id>
		<title>Repository plugins</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Repository_plugins&amp;diff=31731"/>
		<updated>2012-01-25T06:13:26Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: /* I18n - Internationalization */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Repository plugin allow Moodle to bring contents into Moodle from external repositories.&lt;br /&gt;
&lt;br /&gt;
===Prerequisites===&lt;br /&gt;
Before starting coding, it is necessary to know how to use repository administration pages and how to use the file picker.&lt;br /&gt;
&lt;br /&gt;
===Overview===&lt;br /&gt;
&lt;br /&gt;
The 3 different parts to write&lt;br /&gt;
# Administration - You can customise the way administrators and users can configure their repositories. &lt;br /&gt;
# File picker integration - The core of your plugin, it will manage communication between Moodle and the repository service, and also the file picker display.&lt;br /&gt;
# I18n - Internationalization should be done at the same time as you&#039;re writing the other parts.&lt;br /&gt;
&lt;br /&gt;
== History ==&lt;br /&gt;
&lt;br /&gt;
Repository plugins exists from 2.0&lt;br /&gt;
&lt;br /&gt;
== Example ==&lt;br /&gt;
*[[Box.net Repository Plugin|Box.net Repository Plugin]]&lt;br /&gt;
*[[Flickr Repository Plugin|Flickr Repository Plugin]]&lt;br /&gt;
*[[Moodle Repository Plugin|Remote Moodle Repository Plugin]]&lt;br /&gt;
&lt;br /&gt;
== Template ==&lt;br /&gt;
There is a template available in [https://github.com/dongsheng/moodle-repository_demo Dongsheng Cai&#039;s github]&lt;br /&gt;
&lt;br /&gt;
== Naming convention== &lt;br /&gt;
&lt;br /&gt;
# Create a folder for your plugin in &#039;&#039;moodle/repository/&#039;&#039; e.g. &#039;&#039;moodle/repository/myplugin&#039;&#039;&lt;br /&gt;
# Create the following files and add them to the plugin folder:&lt;br /&gt;
#* &#039;&#039;lib.php&#039;&#039;, the main class will be named &amp;quot;repository_myplugin&amp;quot;&lt;br /&gt;
#* &#039;&#039;pix/icon.png&#039;&#039; (the icon displayed in the file picker)&lt;br /&gt;
#* &#039;&#039;[[version.php]]&#039;&#039;&lt;br /&gt;
# Create the language file &#039;&#039;repository_myplugin.php&#039;&#039; and add it to the plugin folder, keeping the following folder structure:&lt;br /&gt;
#*&#039;&#039;moodle/repository/myplugin/lang/en/repository_myplugin.php&#039;&#039; or &#039;&#039;moodle/lang/en/repository_myplugin.php&#039;&#039;&lt;br /&gt;
# Create access.php and upgrade scripts (optional)&lt;br /&gt;
&lt;br /&gt;
==Administration APIs==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Functions===&lt;br /&gt;
All of the following functions are optional. If they&#039;re not implemented, your plugin will not have manual settings and will have only one instance displayed in the File Picker (The repository API creates this unique instance when the administrator add the plugin).&lt;br /&gt;
&lt;br /&gt;
==== get_instance_option_names====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Return an array of strings. These strings are setting names. These settings are specific to an instance.&lt;br /&gt;
If the function returns an empty array, the API will consider that the plugin displays only one repository in the file picker.&lt;br /&gt;
Parent function returns an empty array.&lt;br /&gt;
&lt;br /&gt;
====instance_config_form(&amp;amp;$mform)====&lt;br /&gt;
This is for modifying the Moodle form displaying the settings specific to an instance.&lt;br /&gt;
&lt;br /&gt;
For example, to add a required text box called email_address:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;text&#039;, &#039;email_address&#039;, get_string(&#039;emailaddress&#039;, &#039;repository_flickr_public&#039;));&lt;br /&gt;
$mform-&amp;gt;addRule(&#039;email_address&#039;, $strrequired, &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
   &lt;br /&gt;
&#039;&#039;Note: &#039;&#039;mform&#039;&#039; has by default a name text box (cannot be removed).&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Parent function does nothing.&lt;br /&gt;
&lt;br /&gt;
====instance_form_validation($mform, $data, $errors)====&lt;br /&gt;
This allows us to validate what has been submitted in the instance configuration form. For instance:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function instance_form_validation($mform, $data, $errors) {&lt;br /&gt;
        if (empty($data[&#039;email_address&#039;])) {&lt;br /&gt;
            $errors[&#039;email_address&#039;] = get_string(&#039;invalidemailsettingname&#039;, &#039;repository_flickr_public&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        return $errors;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_type_option_names====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Return an array of string. These strings are setting names. These settings are shared by all instances.&lt;br /&gt;
Parent function return an empty array.&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_type_option_names() {&lt;br /&gt;
        return array_merge(parent::get_type_option_names(), array(&#039;rootpath&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====type_config_form(&amp;amp;$mform)====&lt;br /&gt;
This is for modifying the Moodle form displaying the plugin settings.&lt;br /&gt;
&lt;br /&gt;
For example, to display the standard repository plugin settings along with the custom ones use:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function type_config_form($mform) {&lt;br /&gt;
        parent::type_config_form($mform);&lt;br /&gt;
&lt;br /&gt;
        $rootpath = get_config(&#039;repository_someplugin&#039;, &#039;rootpath&#039;);&lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;text&#039;, &#039;rootpath&#039;, get_string(&#039;rootpath&#039;, &#039;repository_someplugin&#039;), array(&#039;size&#039; =&amp;gt; &#039;40&#039;));&lt;br /&gt;
        $mform-&amp;gt;setDefault(&#039;rootpath&#039;, $rootpath);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====type_form_validation($mform, $data, $errors)====&lt;br /&gt;
Use this function if you need to validate some variables submitted by plugin settings form. Push the items to $error array in the format (&amp;quot;fieldname&amp;quot; =&amp;gt; &amp;quot;errormessage&amp;quot;) to have them highlighted in the form.&lt;br /&gt;
&lt;br /&gt;
With the example above, this function may look like:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function type_form_validation($mform, $data, $errors) {&lt;br /&gt;
        if (!is_dir($data[&#039;rootpath&#039;])) {&lt;br /&gt;
            $errors[&#039;rootpath&#039;] = get_string(&#039;invalidrootpath&#039;, &#039;repository_someplugin&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        return $errors;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====plugin_init()====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
This function is called when the administrator adds the plugin. So unless the administrator deletes the plugin and re-adds it, it should be called only once.&lt;br /&gt;
Parent function does nothing.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
As an example, let&#039;s create a Flickr plugin for accessing a public flickr account. The plugin will be called &amp;quot;Flickr Public&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Firstly the skeleton:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    &amp;lt;?php&lt;br /&gt;
    /**&lt;br /&gt;
     * repository_flickr_public class&lt;br /&gt;
     * Moodle user can access public flickr account&lt;br /&gt;
     *&lt;br /&gt;
     * @license http://www.gnu.org/copyleft/gpl.html GNU Public License&lt;br /&gt;
    */&lt;br /&gt;
    class repository_flickr_public extends repository {&lt;br /&gt;
    }&lt;br /&gt;
?&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then consider the question &amp;quot;What does my plugin do?&amp;quot;&lt;br /&gt;
&lt;br /&gt;
In the Moodle file picker, we want to display some flickr public repositories directly linked to a flickr public account. For example &#039;&#039;My Public Flickr Pictures&#039;&#039;, and also &#039;&#039;My Friend&#039;s Flickr Pictures&#039;&#039;. When the user clicks on one of these repositories, the public pictures are displayed in the file picker.&lt;br /&gt;
&lt;br /&gt;
In order to access to a flickr public account, the plugin needs to know the email address of the Flickr public account owner. So the administrator will need to set an email address for every repository. Let&#039;s add an &amp;quot;email address&amp;quot; setting to every repository.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
//We tell the API that the repositories have specific settings: &amp;quot;email address&amp;quot;&lt;br /&gt;
    public static function get_instance_option_names() {&lt;br /&gt;
        return array(&#039;email_address&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
//We add an &amp;quot;email address&amp;quot; text box to the create/edit repository instance Moodle form&lt;br /&gt;
    public function instance_config_form(&amp;amp;$mform) {&lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;text&#039;, &#039;email_address&#039;, get_string(&#039;emailaddress&#039;, &#039;repository_flickr_public&#039;));&lt;br /&gt;
        $mform-&amp;gt;addRule(&#039;email_address&#039;, get_string(&#039;required&#039;), &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So at this moment all our Flickr Public Repositories will have a specific email address. However this is not enough. In order to communicate with Flickr, Moodle needs to know a Flickr API key (http://www.flickr.com/services/api/). This API key is the same for any repository. We could add it with the email address setting but the administrator would have to enter the same API key for every repository. Hopefully the administrator can add settings to the plugin level, impacting all repositories. The code is similar the repository instance settings:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
//We tell the API that the repositories have general settings: &amp;quot;api_key&amp;quot;&lt;br /&gt;
    public static function get_type_option_names() {&lt;br /&gt;
        return array(&#039;api_key&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
//We add an &amp;quot;api key&amp;quot; text box to the create/edit repository plugin Moodle form (also called a Repository type Moodle form)&lt;br /&gt;
    public function type_config_form(&amp;amp;$mform) {&lt;br /&gt;
        //the following line is needed in order to retrieve the API key value from the database when Moodle displays the edit form&lt;br /&gt;
        $api_key = get_config(&#039;flickr_public&#039;, &#039;api_key&#039;);&lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;text&#039;, &#039;api_key&#039;, get_string(&#039;apikey&#039;, &#039;repository_flickr_public&#039;), &lt;br /&gt;
                           array(&#039;value&#039;=&amp;gt;$api_key,&#039;size&#039; =&amp;gt; &#039;40&#039;));&lt;br /&gt;
        $mform-&amp;gt;addRule(&#039;api_key&#039;, get_string(&#039;required&#039;), &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Have we finished yet?&lt;br /&gt;
&lt;br /&gt;
Yes! We have created everything necessary for the administration pages. But let&#039;s go further. It would be good if the user can enter any &amp;quot;Flickr public account email address&amp;quot; in the file picker. In fact we want to display in the file picker a Flickr Public repository that the Moodle administrator can never delete. Let&#039;s add:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
     //this function is only called one time, when the Moodle administrator add the Flickr Public Plugin into the Moodle site.&lt;br /&gt;
     public static function plugin_init() {&lt;br /&gt;
        //here we create a default repository instance. The last parameter is 1 in order to set the instance as readonly.&lt;br /&gt;
        repository_static_function(&#039;flickr_public&#039;,&#039;create&#039;, &#039;flickr_public&#039;, 0, get_system_context(), &lt;br /&gt;
                                    array(&#039;name&#039; =&amp;gt; &#039;default instance&#039;,&#039;email_address&#039; =&amp;gt; null),1);&lt;br /&gt;
     }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That&#039;s all - the administration part of our Flickr Public plugin is done. For your information, Box.net, Flickr, and Flickr Public all have similar administration APIs.&lt;br /&gt;
&lt;br /&gt;
==Repository APIs==&lt;br /&gt;
=== Quick Start ===&lt;br /&gt;
First of all, the File Picker using intensively Ajax you will need a easy way to debug. Install FirePHP (MDL-16371) and make it works. It will save you a lot of time. (You might give the [http://moodle.org/mod/forum/discuss.php?d=119961 FirePHP plugin for Moodle] a try, it&#039;s still work in progress, though.)&lt;br /&gt;
&lt;br /&gt;
* Your first question when you write your plugin specification is &#039;Does the user need to log-in&#039;? If they do, in your plugin you have to detect user session in constructor() function, and use print_login() if required, see more details below.&lt;br /&gt;
* For most of plugins, you need to establish a connection with the remote repository. This connection can be done into the get_listing(), constructor() function, see more details below.&lt;br /&gt;
* You wanna retrieve the file that the user selected, rewrite get_file() if required, see more details below.&lt;br /&gt;
* Optional question that you should ask yourself is &#039;Does the user can execute a search&#039;, if they do, you will have to rewrite search() method, see more details below.&lt;br /&gt;
&lt;br /&gt;
===Functions you *MUST* override===&lt;br /&gt;
====__construct====&lt;br /&gt;
You may initialize your plugin here, such as:&lt;br /&gt;
# Get options from database&lt;br /&gt;
# Get user name and password from HTTP POST&lt;br /&gt;
&lt;br /&gt;
====get_listing($path=&amp;quot;&amp;quot;, $page=&amp;quot;&amp;quot;)====&lt;br /&gt;
This function will return a list of files, the list must be a array like this:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$list = array(&lt;br /&gt;
 //this will be used to build navigation bar&lt;br /&gt;
&#039;path&#039;=&amp;gt;array(array(&#039;name&#039;=&amp;gt;&#039;root&#039;,&#039;path&#039;=&amp;gt;&#039;/&#039;), array(&#039;name&#039;=&amp;gt;&#039;subfolder&#039;, &#039;path&#039;=&amp;gt;&#039;/subfolder&#039;)),&lt;br /&gt;
&#039;manage&#039;=&amp;gt;&#039;http://webmgr.moodle.com&#039;,&lt;br /&gt;
&#039;list&#039;=&amp;gt; array(&lt;br /&gt;
    array(&#039;title&#039;=&amp;gt;&#039;filename1&#039;, &#039;date&#039;=&amp;gt;&#039;01/01/2009&#039;, &#039;size&#039;=&amp;gt;&#039;10MB&#039;, &#039;source&#039;=&amp;gt;&#039;http://www.moodle.com/dl.rar&#039;),&lt;br /&gt;
    array(&#039;title&#039;=&amp;gt;&#039;folder&#039;, &#039;date&#039;=&amp;gt;&#039;01/01/2009&#039;, &#039;size&#039;=&amp;gt;&#039;0&#039;, &#039;children&#039;=&amp;gt;array())&lt;br /&gt;
)&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;The full specification of list element:&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 array(&lt;br /&gt;
   // Used to build navegation bar, so you need to include all parents folders&lt;br /&gt;
   // array(array(&#039;name&#039;=&amp;gt;&#039;root&#039;,&#039;path&#039;=&amp;gt;&#039;/&#039;), array(&#039;name&#039;=&amp;gt;&#039;subfolder&#039;, &#039;path&#039;=&amp;gt;&#039;/subfolder&#039;))&lt;br /&gt;
   &#039;path&#039; =&amp;gt; (array) this will be used to build navigation bar&lt;br /&gt;
   // dynload tells file picker to fetch list dynamically, when user click&lt;br /&gt;
   // the folder, it will send a ajax request to server side.&lt;br /&gt;
   // if you are using pagination, &#039;page&#039; and &#039;pages&#039; parameters should be used&lt;br /&gt;
   // and note, you&#039;d better don&#039;t use pagination and page at the same time&lt;br /&gt;
   &#039;page&#039; =&amp;gt; (int) which page is this list&lt;br /&gt;
   &#039;pages&#039; =&amp;gt; (pages) how many pages&lt;br /&gt;
   &#039;dynload&#039; =&amp;gt; (bool) use dynamic loading,&lt;br /&gt;
   // will display a link in file picker&lt;br /&gt;
   &#039;manage&#039; =&amp;gt; (string) url of the file manager,&lt;br /&gt;
   // set to true, the login link will be removed from file picker&lt;br /&gt;
   &#039;nologin&#039; =&amp;gt; (bool) requires login,&lt;br /&gt;
   // set to true, the search link will be removed from file picker&lt;br /&gt;
   &#039;nosearch&#039; =&amp;gt; (bool) no search link,&lt;br /&gt;
   // set this option will display a upload form in file picker&lt;br /&gt;
   // only used in upload plugin currently&lt;br /&gt;
   &#039;upload&#039; =&amp;gt; array( // upload manager&lt;br /&gt;
     &#039;label&#039; =&amp;gt; (string) label of the form element,&lt;br /&gt;
     &#039;id&#039; =&amp;gt; (string) id of the form element&lt;br /&gt;
   ),&lt;br /&gt;
   // file picker will build a file tree according this &lt;br /&gt;
   // list&lt;br /&gt;
   &#039;list&#039; =&amp;gt; array(&lt;br /&gt;
     array( // file&lt;br /&gt;
       &#039;title&#039; =&amp;gt; (string) file name,&lt;br /&gt;
       &#039;shorttitle&#039; =&amp;gt; (string) optional, if you prefer to display a short title&lt;br /&gt;
       &#039;date&#039; =&amp;gt; (string) file last modification time, usually userdate(...),&lt;br /&gt;
       &#039;size&#039; =&amp;gt; (int) file size,&lt;br /&gt;
       &#039;thumbnail&#039; =&amp;gt; (string) url to thumbnail for the file,&lt;br /&gt;
       &#039;thumbnail_width&#039; =&amp;gt; (int) the width of the thumbnail image,&lt;br /&gt;
       &#039;source&#039; =&amp;gt; plugin-dependent unique path to the file (id, url, path, etc.),&lt;br /&gt;
       &#039;url&#039;=&amp;gt; the accessible url of file&lt;br /&gt;
     ),&lt;br /&gt;
     array( // folder - same as file, but no &#039;source&#039;.&lt;br /&gt;
       &#039;title&#039; =&amp;gt; (string) folder name,&lt;br /&gt;
       &#039;shorttitle&#039; =&amp;gt; (string) optional, if you prefer to display a short title&lt;br /&gt;
       &#039;path&#039; =&amp;gt; (string) path to this folder&lt;br /&gt;
       &#039;date&#039; =&amp;gt; (string) folder last modification time, usually userdate(...),&lt;br /&gt;
       &#039;size&#039; =&amp;gt; 0,&lt;br /&gt;
       &#039;thumbnail&#039; =&amp;gt; (string) url to thumbnail for the folder,&lt;br /&gt;
       &#039;children&#039; =&amp;gt; array( // an empty folder needs to have &#039;children&#039; defined, but empty.&lt;br /&gt;
         // content (files and folders)&lt;br /&gt;
       )&lt;br /&gt;
     ),&lt;br /&gt;
   )&lt;br /&gt;
 )&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Dynamically loading&lt;br /&gt;
Some repositories contain many files which cannot load in one time, in this case, we need dynamically loading to fetch them step by step, files in subfolder won&#039;t be listed until user click the folder in file picker treeview.&lt;br /&gt;
&lt;br /&gt;
As a plug-in developer, if you set dynload flag as &#039;&#039;&#039;true&#039;&#039;&#039;, you should return files and folders (set children as a null array) in current path instead of building the whole file tree.&lt;br /&gt;
&lt;br /&gt;
Example of dynamically loading&lt;br /&gt;
See [http://cvs.moodle.org/moodle/repository/alfresco/lib.php?view=log Alfresco] plug-in&lt;br /&gt;
&lt;br /&gt;
===Functions you can override===&lt;br /&gt;
====print_login====&lt;br /&gt;
This function will help to print a login form, for the Ajax file picker, this function will return a&lt;br /&gt;
PHP array to define this form.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    public function print_login(){&lt;br /&gt;
        if ($this-&amp;gt;options[&#039;ajax&#039;]) {&lt;br /&gt;
            $user_field-&amp;gt;label = get_string(&#039;username&#039;, &#039;repository_boxnet&#039;).&#039;: &#039;;&lt;br /&gt;
            $user_field-&amp;gt;id    = &#039;box_username&#039;;&lt;br /&gt;
            $user_field-&amp;gt;type  = &#039;text&#039;;&lt;br /&gt;
            $user_field-&amp;gt;name  = &#039;boxusername&#039;;&lt;br /&gt;
            $user_field-&amp;gt;value = $ret-&amp;gt;username;&lt;br /&gt;
            &lt;br /&gt;
            $passwd_field-&amp;gt;label = get_string(&#039;password&#039;, &#039;repository_boxnet&#039;).&#039;: &#039;;&lt;br /&gt;
            $passwd_field-&amp;gt;id    = &#039;box_password&#039;;&lt;br /&gt;
            $passwd_field-&amp;gt;type  = &#039;password&#039;;&lt;br /&gt;
            $passwd_field-&amp;gt;name  = &#039;boxpassword&#039;;&lt;br /&gt;
&lt;br /&gt;
            $ret = array();&lt;br /&gt;
            $ret[&#039;login&#039;] = array($user_field, $passwd_field);&lt;br /&gt;
            // if you are going to rename submit button label, use this option&lt;br /&gt;
            //$ret[&#039;login_btn_label&#039;] = get_string(&#039;submit_btn_label&#039;);&lt;br /&gt;
            // if you are going to display a search form instead of login form&lt;br /&gt;
            // set this option to true&lt;br /&gt;
            //$ret[&#039;login_search_form&#039;] = true;&lt;br /&gt;
            return $ret;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
This will help to generate a form by file picker which contains user name and password input elements.&lt;br /&gt;
&lt;br /&gt;
If your plugin don&#039;t require logging in, you don&#039;t need to override it, it will call get_listing to list files automatically by default.&lt;br /&gt;
====check_login====&lt;br /&gt;
This function will return a boolean value to tell Moodle whether the user has logged in.&lt;br /&gt;
By default, this function will return true.&lt;br /&gt;
====logout====&lt;br /&gt;
When a user clicks the logout button in file picker, this function will be called. You may clean up the session or disconnect the connection with remote server here.&lt;br /&gt;
====print_search====&lt;br /&gt;
When a user clicks the search button on file picker, this function will be called to return a search form. By default, it will create a form with single search bar - you can override it to create a advanced search form.&lt;br /&gt;
====search====&lt;br /&gt;
This function will do the searching job. You can obtain the POST parameters from the from the form you created in print_search function&lt;br /&gt;
This function will return a file list exactly like the one from get_listing.&lt;br /&gt;
====get_file====&lt;br /&gt;
When a user clicks the &amp;quot;Get&amp;quot; button to transfer the file, this function will be called. Basically, it will download a file to Moodle - you can override it to modify the file then move it to a better location.&lt;br /&gt;
====get_name====&lt;br /&gt;
This function will return the name of the repository instance.&lt;br /&gt;
&lt;br /&gt;
== I18n - Internationalization ==&lt;br /&gt;
These following strings are required in &#039;&#039;moodle/repository/myplugin/lang/en/repository_myplugin.php&#039;&#039; or &#039;&#039;moodle/lang/en/repository_myplugin.php&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$string[&#039;pluginname&#039;] = &#039;Flickr Public&#039;;&lt;br /&gt;
$string[&#039;configplugin&#039;] = &#039;Flickr Public configuration&#039;;&lt;br /&gt;
$string[&#039;pluginname_help&#039;] = &#039;A Flickr public repository&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Database Tables==&lt;br /&gt;
&lt;br /&gt;
=== repository ===&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;Default&#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;id&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|autoincrementing &lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;type&#039;&#039;&#039;&lt;br /&gt;
|varchar(255)&lt;br /&gt;
|&lt;br /&gt;
|The type of the repository &lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;visible&#039;&#039;&#039;&lt;br /&gt;
|tinyint(1)&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|sortorder&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== repository_instances ===&lt;br /&gt;
&lt;br /&gt;
This table contains one entry for every configured external repository instance.&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;Default&#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;id&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|autoincrementing &lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|name&lt;br /&gt;
|varchar 255&lt;br /&gt;
|&lt;br /&gt;
|A custom name for this repository (non-unique)&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;typeid&#039;&#039;&#039; &lt;br /&gt;
|int(10)&lt;br /&gt;
| &lt;br /&gt;
|The id of repository type&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 person who created this repository instance&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;contextid&#039;&#039;&#039; &lt;br /&gt;
|int(10)&lt;br /&gt;
| &lt;br /&gt;
|The context that this repository is available to ( = system context for site-wide ones)&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|username&lt;br /&gt;
|varchar(255)&lt;br /&gt;
| &lt;br /&gt;
|username to log in with, if required (almost never!)&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|password&lt;br /&gt;
|varchar(255)&lt;br /&gt;
| &lt;br /&gt;
|password to log in with, if required (almost never!)&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|timecreated&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|The time this repository was created&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|timemodified&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|The last time the repository was modified&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== repository_instance_config ===&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;Default&#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;id&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|autoincrementing &lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;instanceid&#039;&#039;&#039;&lt;br /&gt;
|int(int)&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;name&#039;&#039;&#039;&lt;br /&gt;
|varchar(255)&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|value&lt;br /&gt;
|Text&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Standard repository plugins ==&lt;br /&gt;
This is the functional specification list of the officially supported repository plugins.&lt;br /&gt;
For each plugins, the two mains part we are interested in are:&lt;br /&gt;
* How do I administer the plugin? See [[Repository_Administration_Specification| Repository Administration Specification - UC001-3]]&lt;br /&gt;
* How do I set up an account for this repository? See [[Repository_Interface_for_Moodle/Course/User| Repository Interface for Moodle/Course/User]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
*[[Repository API| Repository API]]&lt;br /&gt;
*[[Repository_Interface_for_Moodle/Course/User| Repository Interface for Moodle/Course/User]]&lt;br /&gt;
*[[QA:Use Case Number Attribution| Use Case Number Attribution]]&lt;br /&gt;
* MDL-16543 - A list of officially supported repository plugins&lt;br /&gt;
* MDL-16543 - Template plugin for developers&lt;br /&gt;
[[Category:Repositories]]&lt;br /&gt;
[[Category:Plugins]]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Repository_plugins&amp;diff=31730</id>
		<title>Repository plugins</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=Repository_plugins&amp;diff=31730"/>
		<updated>2012-01-25T06:07:20Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Repository plugin allow Moodle to bring contents into Moodle from external repositories.&lt;br /&gt;
&lt;br /&gt;
===Prerequisites===&lt;br /&gt;
Before starting coding, it is necessary to know how to use repository administration pages and how to use the file picker.&lt;br /&gt;
&lt;br /&gt;
===Overview===&lt;br /&gt;
&lt;br /&gt;
The 3 different parts to write&lt;br /&gt;
# Administration - You can customise the way administrators and users can configure their repositories. &lt;br /&gt;
# File picker integration - The core of your plugin, it will manage communication between Moodle and the repository service, and also the file picker display.&lt;br /&gt;
# I18n - Internationalization should be done at the same time as you&#039;re writing the other parts.&lt;br /&gt;
&lt;br /&gt;
== History ==&lt;br /&gt;
&lt;br /&gt;
Repository plugins exists from 2.0&lt;br /&gt;
&lt;br /&gt;
== Example ==&lt;br /&gt;
*[[Box.net Repository Plugin|Box.net Repository Plugin]]&lt;br /&gt;
*[[Flickr Repository Plugin|Flickr Repository Plugin]]&lt;br /&gt;
*[[Moodle Repository Plugin|Remote Moodle Repository Plugin]]&lt;br /&gt;
&lt;br /&gt;
== Template ==&lt;br /&gt;
There is a template available in [https://github.com/dongsheng/moodle-repository_demo Dongsheng Cai&#039;s github]&lt;br /&gt;
&lt;br /&gt;
== Naming convention== &lt;br /&gt;
&lt;br /&gt;
# Create a folder for your plugin in &#039;&#039;moodle/repository/&#039;&#039; e.g. &#039;&#039;moodle/repository/myplugin&#039;&#039;&lt;br /&gt;
# Create the following files and add them to the plugin folder:&lt;br /&gt;
#* &#039;&#039;lib.php&#039;&#039;, the main class will be named &amp;quot;repository_myplugin&amp;quot;&lt;br /&gt;
#* &#039;&#039;pix/icon.png&#039;&#039; (the icon displayed in the file picker)&lt;br /&gt;
#* &#039;&#039;[[version.php]]&#039;&#039;&lt;br /&gt;
# Create the language file &#039;&#039;repository_myplugin.php&#039;&#039; and add it to the plugin folder, keeping the following folder structure:&lt;br /&gt;
#*&#039;&#039;moodle/repository/myplugin/lang/en/repository_myplugin.php&#039;&#039; or &#039;&#039;moodle/lang/en/repository_myplugin.php&#039;&#039;&lt;br /&gt;
# Create access.php and upgrade scripts (optional)&lt;br /&gt;
&lt;br /&gt;
==Administration APIs==&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
===Functions===&lt;br /&gt;
All of the following functions are optional. If they&#039;re not implemented, your plugin will not have manual settings and will have only one instance displayed in the File Picker (The repository API creates this unique instance when the administrator add the plugin).&lt;br /&gt;
&lt;br /&gt;
==== get_instance_option_names====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Return an array of strings. These strings are setting names. These settings are specific to an instance.&lt;br /&gt;
If the function returns an empty array, the API will consider that the plugin displays only one repository in the file picker.&lt;br /&gt;
Parent function returns an empty array.&lt;br /&gt;
&lt;br /&gt;
====instance_config_form(&amp;amp;$mform)====&lt;br /&gt;
This is for modifying the Moodle form displaying the settings specific to an instance.&lt;br /&gt;
&lt;br /&gt;
For example, to add a required text box called email_address:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$mform-&amp;gt;addElement(&#039;text&#039;, &#039;email_address&#039;, get_string(&#039;emailaddress&#039;, &#039;repository_flickr_public&#039;));&lt;br /&gt;
$mform-&amp;gt;addRule(&#039;email_address&#039;, $strrequired, &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
   &lt;br /&gt;
&#039;&#039;Note: &#039;&#039;mform&#039;&#039; has by default a name text box (cannot be removed).&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Parent function does nothing.&lt;br /&gt;
&lt;br /&gt;
====instance_form_validation($mform, $data, $errors)====&lt;br /&gt;
This allows us to validate what has been submitted in the instance configuration form. For instance:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function instance_form_validation($mform, $data, $errors) {&lt;br /&gt;
        if (empty($data[&#039;email_address&#039;])) {&lt;br /&gt;
            $errors[&#039;email_address&#039;] = get_string(&#039;invalidemailsettingname&#039;, &#039;repository_flickr_public&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        return $errors;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====get_type_option_names====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
Return an array of string. These strings are setting names. These settings are shared by all instances.&lt;br /&gt;
Parent function return an empty array.&lt;br /&gt;
&lt;br /&gt;
For example:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function get_type_option_names() {&lt;br /&gt;
        return array_merge(parent::get_type_option_names(), array(&#039;rootpath&#039;));&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====type_config_form(&amp;amp;$mform)====&lt;br /&gt;
This is for modifying the Moodle form displaying the plugin settings.&lt;br /&gt;
&lt;br /&gt;
For example, to display the standard repository plugin settings along with the custom ones use:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public function type_config_form($mform) {&lt;br /&gt;
        parent::type_config_form($mform);&lt;br /&gt;
&lt;br /&gt;
        $rootpath = get_config(&#039;repository_someplugin&#039;, &#039;rootpath&#039;);&lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;text&#039;, &#039;rootpath&#039;, get_string(&#039;rootpath&#039;, &#039;repository_someplugin&#039;), array(&#039;size&#039; =&amp;gt; &#039;40&#039;));&lt;br /&gt;
        $mform-&amp;gt;setDefault(&#039;rootpath&#039;, $rootpath);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====type_form_validation($mform, $data, $errors)====&lt;br /&gt;
Use this function if you need to validate some variables submitted by plugin settings form. Push the items to $error array in the format (&amp;quot;fieldname&amp;quot; =&amp;gt; &amp;quot;errormessage&amp;quot;) to have them highlighted in the form.&lt;br /&gt;
&lt;br /&gt;
With the example above, this function may look like:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
public static function type_form_validation($mform, $data, $errors) {&lt;br /&gt;
        if (!is_dir($data[&#039;rootpath&#039;])) {&lt;br /&gt;
            $errors[&#039;rootpath&#039;] = get_string(&#039;invalidrootpath&#039;, &#039;repository_someplugin&#039;);&lt;br /&gt;
        }&lt;br /&gt;
        return $errors;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====plugin_init()====&lt;br /&gt;
&#039;&#039;This function must be declared static&#039;&#039;&amp;lt;br&amp;gt;&lt;br /&gt;
This function is called when the administrator adds the plugin. So unless the administrator deletes the plugin and re-adds it, it should be called only once.&lt;br /&gt;
Parent function does nothing.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
As an example, let&#039;s create a Flickr plugin for accessing a public flickr account. The plugin will be called &amp;quot;Flickr Public&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Firstly the skeleton:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    &amp;lt;?php&lt;br /&gt;
    /**&lt;br /&gt;
     * repository_flickr_public class&lt;br /&gt;
     * Moodle user can access public flickr account&lt;br /&gt;
     *&lt;br /&gt;
     * @license http://www.gnu.org/copyleft/gpl.html GNU Public License&lt;br /&gt;
    */&lt;br /&gt;
    class repository_flickr_public extends repository {&lt;br /&gt;
    }&lt;br /&gt;
?&amp;gt;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Then consider the question &amp;quot;What does my plugin do?&amp;quot;&lt;br /&gt;
&lt;br /&gt;
In the Moodle file picker, we want to display some flickr public repositories directly linked to a flickr public account. For example &#039;&#039;My Public Flickr Pictures&#039;&#039;, and also &#039;&#039;My Friend&#039;s Flickr Pictures&#039;&#039;. When the user clicks on one of these repositories, the public pictures are displayed in the file picker.&lt;br /&gt;
&lt;br /&gt;
In order to access to a flickr public account, the plugin needs to know the email address of the Flickr public account owner. So the administrator will need to set an email address for every repository. Let&#039;s add an &amp;quot;email address&amp;quot; setting to every repository.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
//We tell the API that the repositories have specific settings: &amp;quot;email address&amp;quot;&lt;br /&gt;
    public static function get_instance_option_names() {&lt;br /&gt;
        return array(&#039;email_address&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
//We add an &amp;quot;email address&amp;quot; text box to the create/edit repository instance Moodle form&lt;br /&gt;
    public function instance_config_form(&amp;amp;$mform) {&lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;text&#039;, &#039;email_address&#039;, get_string(&#039;emailaddress&#039;, &#039;repository_flickr_public&#039;));&lt;br /&gt;
        $mform-&amp;gt;addRule(&#039;email_address&#039;, get_string(&#039;required&#039;), &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
So at this moment all our Flickr Public Repositories will have a specific email address. However this is not enough. In order to communicate with Flickr, Moodle needs to know a Flickr API key (http://www.flickr.com/services/api/). This API key is the same for any repository. We could add it with the email address setting but the administrator would have to enter the same API key for every repository. Hopefully the administrator can add settings to the plugin level, impacting all repositories. The code is similar the repository instance settings:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
//We tell the API that the repositories have general settings: &amp;quot;api_key&amp;quot;&lt;br /&gt;
    public static function get_type_option_names() {&lt;br /&gt;
        return array(&#039;api_key&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
//We add an &amp;quot;api key&amp;quot; text box to the create/edit repository plugin Moodle form (also called a Repository type Moodle form)&lt;br /&gt;
    public function type_config_form(&amp;amp;$mform) {&lt;br /&gt;
        //the following line is needed in order to retrieve the API key value from the database when Moodle displays the edit form&lt;br /&gt;
        $api_key = get_config(&#039;flickr_public&#039;, &#039;api_key&#039;);&lt;br /&gt;
        $mform-&amp;gt;addElement(&#039;text&#039;, &#039;api_key&#039;, get_string(&#039;apikey&#039;, &#039;repository_flickr_public&#039;), &lt;br /&gt;
                           array(&#039;value&#039;=&amp;gt;$api_key,&#039;size&#039; =&amp;gt; &#039;40&#039;));&lt;br /&gt;
        $mform-&amp;gt;addRule(&#039;api_key&#039;, get_string(&#039;required&#039;), &#039;required&#039;, null, &#039;client&#039;);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Have we finished yet?&lt;br /&gt;
&lt;br /&gt;
Yes! We have created everything necessary for the administration pages. But let&#039;s go further. It would be good if the user can enter any &amp;quot;Flickr public account email address&amp;quot; in the file picker. In fact we want to display in the file picker a Flickr Public repository that the Moodle administrator can never delete. Let&#039;s add:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
     //this function is only called one time, when the Moodle administrator add the Flickr Public Plugin into the Moodle site.&lt;br /&gt;
     public static function plugin_init() {&lt;br /&gt;
        //here we create a default repository instance. The last parameter is 1 in order to set the instance as readonly.&lt;br /&gt;
        repository_static_function(&#039;flickr_public&#039;,&#039;create&#039;, &#039;flickr_public&#039;, 0, get_system_context(), &lt;br /&gt;
                                    array(&#039;name&#039; =&amp;gt; &#039;default instance&#039;,&#039;email_address&#039; =&amp;gt; null),1);&lt;br /&gt;
     }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
That&#039;s all - the administration part of our Flickr Public plugin is done. For your information, Box.net, Flickr, and Flickr Public all have similar administration APIs.&lt;br /&gt;
&lt;br /&gt;
==Repository APIs==&lt;br /&gt;
=== Quick Start ===&lt;br /&gt;
First of all, the File Picker using intensively Ajax you will need a easy way to debug. Install FirePHP (MDL-16371) and make it works. It will save you a lot of time. (You might give the [http://moodle.org/mod/forum/discuss.php?d=119961 FirePHP plugin for Moodle] a try, it&#039;s still work in progress, though.)&lt;br /&gt;
&lt;br /&gt;
* Your first question when you write your plugin specification is &#039;Does the user need to log-in&#039;? If they do, in your plugin you have to detect user session in constructor() function, and use print_login() if required, see more details below.&lt;br /&gt;
* For most of plugins, you need to establish a connection with the remote repository. This connection can be done into the get_listing(), constructor() function, see more details below.&lt;br /&gt;
* You wanna retrieve the file that the user selected, rewrite get_file() if required, see more details below.&lt;br /&gt;
* Optional question that you should ask yourself is &#039;Does the user can execute a search&#039;, if they do, you will have to rewrite search() method, see more details below.&lt;br /&gt;
&lt;br /&gt;
===Functions you *MUST* override===&lt;br /&gt;
====__construct====&lt;br /&gt;
You may initialize your plugin here, such as:&lt;br /&gt;
# Get options from database&lt;br /&gt;
# Get user name and password from HTTP POST&lt;br /&gt;
&lt;br /&gt;
====get_listing($path=&amp;quot;&amp;quot;, $page=&amp;quot;&amp;quot;)====&lt;br /&gt;
This function will return a list of files, the list must be a array like this:&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$list = array(&lt;br /&gt;
 //this will be used to build navigation bar&lt;br /&gt;
&#039;path&#039;=&amp;gt;array(array(&#039;name&#039;=&amp;gt;&#039;root&#039;,&#039;path&#039;=&amp;gt;&#039;/&#039;), array(&#039;name&#039;=&amp;gt;&#039;subfolder&#039;, &#039;path&#039;=&amp;gt;&#039;/subfolder&#039;)),&lt;br /&gt;
&#039;manage&#039;=&amp;gt;&#039;http://webmgr.moodle.com&#039;,&lt;br /&gt;
&#039;list&#039;=&amp;gt; array(&lt;br /&gt;
    array(&#039;title&#039;=&amp;gt;&#039;filename1&#039;, &#039;date&#039;=&amp;gt;&#039;01/01/2009&#039;, &#039;size&#039;=&amp;gt;&#039;10MB&#039;, &#039;source&#039;=&amp;gt;&#039;http://www.moodle.com/dl.rar&#039;),&lt;br /&gt;
    array(&#039;title&#039;=&amp;gt;&#039;folder&#039;, &#039;date&#039;=&amp;gt;&#039;01/01/2009&#039;, &#039;size&#039;=&amp;gt;&#039;0&#039;, &#039;children&#039;=&amp;gt;array())&lt;br /&gt;
)&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;The full specification of list element:&#039;&#039;&#039;&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 array(&lt;br /&gt;
   // Used to build navegation bar, so you need to include all parents folders&lt;br /&gt;
   // array(array(&#039;name&#039;=&amp;gt;&#039;root&#039;,&#039;path&#039;=&amp;gt;&#039;/&#039;), array(&#039;name&#039;=&amp;gt;&#039;subfolder&#039;, &#039;path&#039;=&amp;gt;&#039;/subfolder&#039;))&lt;br /&gt;
   &#039;path&#039; =&amp;gt; (array) this will be used to build navigation bar&lt;br /&gt;
   // dynload tells file picker to fetch list dynamically, when user click&lt;br /&gt;
   // the folder, it will send a ajax request to server side.&lt;br /&gt;
   // if you are using pagination, &#039;page&#039; and &#039;pages&#039; parameters should be used&lt;br /&gt;
   // and note, you&#039;d better don&#039;t use pagination and page at the same time&lt;br /&gt;
   &#039;page&#039; =&amp;gt; (int) which page is this list&lt;br /&gt;
   &#039;pages&#039; =&amp;gt; (pages) how many pages&lt;br /&gt;
   &#039;dynload&#039; =&amp;gt; (bool) use dynamic loading,&lt;br /&gt;
   // will display a link in file picker&lt;br /&gt;
   &#039;manage&#039; =&amp;gt; (string) url of the file manager,&lt;br /&gt;
   // set to true, the login link will be removed from file picker&lt;br /&gt;
   &#039;nologin&#039; =&amp;gt; (bool) requires login,&lt;br /&gt;
   // set to true, the search link will be removed from file picker&lt;br /&gt;
   &#039;nosearch&#039; =&amp;gt; (bool) no search link,&lt;br /&gt;
   // set this option will display a upload form in file picker&lt;br /&gt;
   // only used in upload plugin currently&lt;br /&gt;
   &#039;upload&#039; =&amp;gt; array( // upload manager&lt;br /&gt;
     &#039;label&#039; =&amp;gt; (string) label of the form element,&lt;br /&gt;
     &#039;id&#039; =&amp;gt; (string) id of the form element&lt;br /&gt;
   ),&lt;br /&gt;
   // file picker will build a file tree according this &lt;br /&gt;
   // list&lt;br /&gt;
   &#039;list&#039; =&amp;gt; array(&lt;br /&gt;
     array( // file&lt;br /&gt;
       &#039;title&#039; =&amp;gt; (string) file name,&lt;br /&gt;
       &#039;shorttitle&#039; =&amp;gt; (string) optional, if you prefer to display a short title&lt;br /&gt;
       &#039;date&#039; =&amp;gt; (string) file last modification time, usually userdate(...),&lt;br /&gt;
       &#039;size&#039; =&amp;gt; (int) file size,&lt;br /&gt;
       &#039;thumbnail&#039; =&amp;gt; (string) url to thumbnail for the file,&lt;br /&gt;
       &#039;thumbnail_width&#039; =&amp;gt; (int) the width of the thumbnail image,&lt;br /&gt;
       &#039;source&#039; =&amp;gt; plugin-dependent unique path to the file (id, url, path, etc.),&lt;br /&gt;
       &#039;url&#039;=&amp;gt; the accessible url of file&lt;br /&gt;
     ),&lt;br /&gt;
     array( // folder - same as file, but no &#039;source&#039;.&lt;br /&gt;
       &#039;title&#039; =&amp;gt; (string) folder name,&lt;br /&gt;
       &#039;shorttitle&#039; =&amp;gt; (string) optional, if you prefer to display a short title&lt;br /&gt;
       &#039;path&#039; =&amp;gt; (string) path to this folder&lt;br /&gt;
       &#039;date&#039; =&amp;gt; (string) folder last modification time, usually userdate(...),&lt;br /&gt;
       &#039;size&#039; =&amp;gt; 0,&lt;br /&gt;
       &#039;thumbnail&#039; =&amp;gt; (string) url to thumbnail for the folder,&lt;br /&gt;
       &#039;children&#039; =&amp;gt; array( // an empty folder needs to have &#039;children&#039; defined, but empty.&lt;br /&gt;
         // content (files and folders)&lt;br /&gt;
       )&lt;br /&gt;
     ),&lt;br /&gt;
   )&lt;br /&gt;
 )&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
Dynamically loading&lt;br /&gt;
Some repositories contain many files which cannot load in one time, in this case, we need dynamically loading to fetch them step by step, files in subfolder won&#039;t be listed until user click the folder in file picker treeview.&lt;br /&gt;
&lt;br /&gt;
As a plug-in developer, if you set dynload flag as &#039;&#039;&#039;true&#039;&#039;&#039;, you should return files and folders (set children as a null array) in current path instead of building the whole file tree.&lt;br /&gt;
&lt;br /&gt;
Example of dynamically loading&lt;br /&gt;
See [http://cvs.moodle.org/moodle/repository/alfresco/lib.php?view=log Alfresco] plug-in&lt;br /&gt;
&lt;br /&gt;
===Functions you can override===&lt;br /&gt;
====print_login====&lt;br /&gt;
This function will help to print a login form, for the Ajax file picker, this function will return a&lt;br /&gt;
PHP array to define this form.&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
    public function print_login(){&lt;br /&gt;
        if ($this-&amp;gt;options[&#039;ajax&#039;]) {&lt;br /&gt;
            $user_field-&amp;gt;label = get_string(&#039;username&#039;, &#039;repository_boxnet&#039;).&#039;: &#039;;&lt;br /&gt;
            $user_field-&amp;gt;id    = &#039;box_username&#039;;&lt;br /&gt;
            $user_field-&amp;gt;type  = &#039;text&#039;;&lt;br /&gt;
            $user_field-&amp;gt;name  = &#039;boxusername&#039;;&lt;br /&gt;
            $user_field-&amp;gt;value = $ret-&amp;gt;username;&lt;br /&gt;
            &lt;br /&gt;
            $passwd_field-&amp;gt;label = get_string(&#039;password&#039;, &#039;repository_boxnet&#039;).&#039;: &#039;;&lt;br /&gt;
            $passwd_field-&amp;gt;id    = &#039;box_password&#039;;&lt;br /&gt;
            $passwd_field-&amp;gt;type  = &#039;password&#039;;&lt;br /&gt;
            $passwd_field-&amp;gt;name  = &#039;boxpassword&#039;;&lt;br /&gt;
&lt;br /&gt;
            $ret = array();&lt;br /&gt;
            $ret[&#039;login&#039;] = array($user_field, $passwd_field);&lt;br /&gt;
            // if you are going to rename submit button label, use this option&lt;br /&gt;
            //$ret[&#039;login_btn_label&#039;] = get_string(&#039;submit_btn_label&#039;);&lt;br /&gt;
            // if you are going to display a search form instead of login form&lt;br /&gt;
            // set this option to true&lt;br /&gt;
            //$ret[&#039;login_search_form&#039;] = true;&lt;br /&gt;
            return $ret;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
This will help to generate a form by file picker which contains user name and password input elements.&lt;br /&gt;
&lt;br /&gt;
If your plugin don&#039;t require logging in, you don&#039;t need to override it, it will call get_listing to list files automatically by default.&lt;br /&gt;
====check_login====&lt;br /&gt;
This function will return a boolean value to tell Moodle whether the user has logged in.&lt;br /&gt;
By default, this function will return true.&lt;br /&gt;
====logout====&lt;br /&gt;
When a user clicks the logout button in file picker, this function will be called. You may clean up the session or disconnect the connection with remote server here.&lt;br /&gt;
====print_search====&lt;br /&gt;
When a user clicks the search button on file picker, this function will be called to return a search form. By default, it will create a form with single search bar - you can override it to create a advanced search form.&lt;br /&gt;
====search====&lt;br /&gt;
This function will do the searching job. You can obtain the POST parameters from the from the form you created in print_search function&lt;br /&gt;
This function will return a file list exactly like the one from get_listing.&lt;br /&gt;
====get_file====&lt;br /&gt;
When a user clicks the &amp;quot;Get&amp;quot; button to transfer the file, this function will be called. Basically, it will download a file to Moodle - you can override it to modify the file then move it to a better location.&lt;br /&gt;
====get_name====&lt;br /&gt;
This function will return the name of the repository instance.&lt;br /&gt;
&lt;br /&gt;
== I18n - Internationalization ==&lt;br /&gt;
These following strings are required in &#039;&#039;moodle/repository/myplugin/lang/en_utf8/repository_myplugin.php&#039;&#039; or &#039;&#039;moodle/lang/en_utf8/repository_myplugin.php&#039;&#039;:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$string[&#039;configplugin&#039;] = &#039;Flickr Public configuration&#039;;&lt;br /&gt;
$string[&#039;repositorydesc&#039;] = &#039;A Flickr public repository&#039;;&lt;br /&gt;
$string[&#039;repositoryname&#039;] = &#039;Flickr Public&#039;;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Database Tables==&lt;br /&gt;
&lt;br /&gt;
=== repository ===&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;Default&#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;id&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|autoincrementing &lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;type&#039;&#039;&#039;&lt;br /&gt;
|varchar(255)&lt;br /&gt;
|&lt;br /&gt;
|The type of the repository &lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;visible&#039;&#039;&#039;&lt;br /&gt;
|tinyint(1)&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|sortorder&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== repository_instances ===&lt;br /&gt;
&lt;br /&gt;
This table contains one entry for every configured external repository instance.&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;Default&#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;id&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|autoincrementing &lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|name&lt;br /&gt;
|varchar 255&lt;br /&gt;
|&lt;br /&gt;
|A custom name for this repository (non-unique)&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;typeid&#039;&#039;&#039; &lt;br /&gt;
|int(10)&lt;br /&gt;
| &lt;br /&gt;
|The id of repository type&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 person who created this repository instance&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;contextid&#039;&#039;&#039; &lt;br /&gt;
|int(10)&lt;br /&gt;
| &lt;br /&gt;
|The context that this repository is available to ( = system context for site-wide ones)&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|username&lt;br /&gt;
|varchar(255)&lt;br /&gt;
| &lt;br /&gt;
|username to log in with, if required (almost never!)&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|password&lt;br /&gt;
|varchar(255)&lt;br /&gt;
| &lt;br /&gt;
|password to log in with, if required (almost never!)&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|timecreated&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|The time this repository was created&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|timemodified&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|The last time the repository was modified&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== repository_instance_config ===&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;Default&#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;id&#039;&#039;&#039;&lt;br /&gt;
|int(10)&lt;br /&gt;
|&lt;br /&gt;
|autoincrementing &lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;instanceid&#039;&#039;&#039;&lt;br /&gt;
|int(int)&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|&#039;&#039;&#039;name&#039;&#039;&#039;&lt;br /&gt;
|varchar(255)&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
&lt;br /&gt;
|-&lt;br /&gt;
|value&lt;br /&gt;
|Text&lt;br /&gt;
|&lt;br /&gt;
|&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== Standard repository plugins ==&lt;br /&gt;
This is the functional specification list of the officially supported repository plugins.&lt;br /&gt;
For each plugins, the two mains part we are interested in are:&lt;br /&gt;
* How do I administer the plugin? See [[Repository_Administration_Specification| Repository Administration Specification - UC001-3]]&lt;br /&gt;
* How do I set up an account for this repository? See [[Repository_Interface_for_Moodle/Course/User| Repository Interface for Moodle/Course/User]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
*[[Repository API| Repository API]]&lt;br /&gt;
*[[Repository_Interface_for_Moodle/Course/User| Repository Interface for Moodle/Course/User]]&lt;br /&gt;
*[[QA:Use Case Number Attribution| Use Case Number Attribution]]&lt;br /&gt;
* MDL-16543 - A list of officially supported repository plugins&lt;br /&gt;
* MDL-16543 - Template plugin for developers&lt;br /&gt;
[[Category:Repositories]]&lt;br /&gt;
[[Category:Plugins]]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=File_API&amp;diff=31460</id>
		<title>File API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=File_API&amp;diff=31460"/>
		<updated>2012-01-16T05:48:08Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: /* See also */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.0}}&lt;br /&gt;
==Overview==&lt;br /&gt;
&lt;br /&gt;
The File API is for managing all the files stored by Moodle. If you are interested in how the file API works internally, see [[File API internals]]. The page is just about what you need to know to use the file API. Related is the [[Repository API]], which lets users get files into Moodle.&lt;br /&gt;
&lt;br /&gt;
If you are looking for an explanation on how to manage moodle files in moodle forms, you most likely need to read [[Using_the_File_API_in_Moodle_forms|Using the File API in Moodle forms]].&lt;br /&gt;
&lt;br /&gt;
==File areas==&lt;br /&gt;
&lt;br /&gt;
Files are conceptually stored in &#039;&#039;&#039;file areas&#039;&#039;&#039;. A file area is uniquely identified by:&lt;br /&gt;
* A context id.&lt;br /&gt;
* full component name (using [[Frankenstyle]]), for example &#039;course&#039;, &#039;mod_forum&#039;, &#039;mod_glossary&#039;, &#039;block_html&#039;.&lt;br /&gt;
* A file area type, for example &#039;intro&#039; or &#039;post&#039;.&lt;br /&gt;
* A unique itemid. Normally, the itemid relates to something depending on the file area type. For example, for a &#039;course&#039;, &#039;intro&#039; file area, the itemid is 0. For forum post, it is the post id.&lt;br /&gt;
&lt;br /&gt;
File areas are not listed separately anywhere, they are stored implicitly in the files table. Please note that each subsystem is allowed to access only own file areas, for example only code in /mod/assignment/* may access files with component &#039;mod_assignment&#039;.&lt;br /&gt;
&lt;br /&gt;
===Naming file areas===&lt;br /&gt;
&lt;br /&gt;
The names of the file areas are not strictly defined, but it is strongly recommended to use singulars and common names of areas if possible (intro, post, attachment, description, ...).&lt;br /&gt;
&lt;br /&gt;
==Serving files to users==&lt;br /&gt;
&lt;br /&gt;
You must refer to the file with a URL that includes a file-serving script, often pluginfile.php. For example&lt;br /&gt;
&lt;br /&gt;
The general form of the URL is something like&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$url = $CFG-&amp;gt;wwwroot/pluginfile.php/$contextid/$component/$filearea/arbitrary/extra/infomation.ext&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A specific example might be&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$url = $CFG-&amp;gt;wwwroot/pluginfile.php/$forumcontextid/mod_forum/post/$postid/image.jpg&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The file serving script then looks at the context id, and component name, and the file area name, and based on that arranges for the file to be served, following appropriate security checks.&lt;br /&gt;
&lt;br /&gt;
In most cases this is done by calling a callback function in the appropriate plugin, these functions are stored in lib.php files and named component_name_pluginfile() . The arbitrary/extra/infomation.ext is passed to the callback. For example, files in the mod_forum+post file area end up being served by the mod_forum_pluginfile function in mod/forum/lib.php.&lt;br /&gt;
&lt;br /&gt;
You normally use an API function to generate these URL automatically, most often the &amp;lt;tt&amp;gt;file_rewrite_pluginfile_urls&amp;lt;/tt&amp;gt; function.&lt;br /&gt;
&lt;br /&gt;
==Getting files from the user==&lt;br /&gt;
&lt;br /&gt;
* See [[Using_the_File_API_in_Moodle_forms|Using the File API in Moodle forms]]&lt;br /&gt;
&lt;br /&gt;
==Examples==&lt;br /&gt;
Please note that in reality developers outside of core will not deal with file api directly in majority of cases, instead use formslib elements which are doing all this automatically.&lt;br /&gt;
&lt;br /&gt;
===Browsing files===&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$browser = get_file_browser();&lt;br /&gt;
$context = get_system_context();&lt;br /&gt;
&lt;br /&gt;
$filearea = null;&lt;br /&gt;
$itemid   = null;&lt;br /&gt;
$filename = null;&lt;br /&gt;
if ($fileinfo = $browser-&amp;gt;get_file_info($context, $component, $filearea, $itemid, &#039;/&#039;, $filename)) {&lt;br /&gt;
    // build a Breadcrumb trail&lt;br /&gt;
    $level = $fileinfo-&amp;gt;get_parent();&lt;br /&gt;
    while ($level) {&lt;br /&gt;
        $path[] = array(&#039;name&#039;=&amp;gt;$level-&amp;gt;get_visible_name());&lt;br /&gt;
        $level = $level-&amp;gt;get_parent();&lt;br /&gt;
    }&lt;br /&gt;
    $path = array_reverse($path);&lt;br /&gt;
    $children = $fileinfo-&amp;gt;get_children();&lt;br /&gt;
    foreach ($children as $child) {&lt;br /&gt;
        if ($child-&amp;gt;is_directory()) {&lt;br /&gt;
            echo $child-&amp;gt;get_visible_name();&lt;br /&gt;
            // display contextid, itemid, component, filepath and filename&lt;br /&gt;
            var_dump($child-&amp;gt;get_params());&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;
===Moving files around===&lt;br /&gt;
&lt;br /&gt;
For example, if you have just built a file at the path&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $from_zip_file = $CFG-&amp;gt;dataroot . &#039;/temp/backup/&#039; . $preferences-&amp;gt;backup_unique_code .&lt;br /&gt;
         &#039;/&#039; . $preferences-&amp;gt;backup_name;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
And you want to move it into the course_backup file area, do&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $context = get_context_instance(CONTEXT_COURSE, $preferences-&amp;gt;backup_course);&lt;br /&gt;
 $fs = get_file_storage();&lt;br /&gt;
 $file_record = array(&#039;contextid&#039;=&amp;gt;$context-&amp;gt;id, &#039;component&#039;=&amp;gt;&#039;course&#039;, &#039;filearea&#039;=&amp;gt;&#039;backup&#039;,&lt;br /&gt;
         &#039;itemid&#039;=&amp;gt;0, &#039;filepath&#039;=&amp;gt;&#039;/&#039;, &#039;filename&#039;=&amp;gt;$preferences-&amp;gt;backup_name,&lt;br /&gt;
         &#039;timecreated&#039;=&amp;gt;time(), &#039;timemodified&#039;=&amp;gt;time());&lt;br /&gt;
 $fs-&amp;gt;create_file_from_pathname($file_record, $from_zip_file);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== List area files ===&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$fs = get_file_storage();&lt;br /&gt;
$files = $fs-&amp;gt;get_area_files($contextid, &#039;mod_assignment&#039;, &#039;submission&#039;, $submission-&amp;gt;id);&lt;br /&gt;
foreach ($files as $f) {&lt;br /&gt;
    // $f is an instance of stored_file&lt;br /&gt;
    echo $f-&amp;gt;get_filename();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or as links...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$out = array();&lt;br /&gt;
        &lt;br /&gt;
$fs = get_file_storage();&lt;br /&gt;
$files = $fs-&amp;gt;get_area_files($contextid, &#039;mod_assignment&#039;, &#039;submission&#039;, $submission-&amp;gt;id);&lt;br /&gt;
            &lt;br /&gt;
foreach ($files as $file) {&lt;br /&gt;
    $url = &amp;quot;{$CFG-&amp;gt;wwwroot}/pluginfile.php/{$file-&amp;gt;get_contextid()}/mod_assignment/submission}&amp;quot;;&lt;br /&gt;
    $filename = $file-&amp;gt;get_filename();&lt;br /&gt;
    $fileurl = $url.$file-&amp;gt;get_filepath().$file-&amp;gt;get_itemid().&#039;/&#039;.$filename;&lt;br /&gt;
    $out[] = html_writer::link($fileurl, $filename);&lt;br /&gt;
}&lt;br /&gt;
      &lt;br /&gt;
$br = html_writer::empty_tag(&#039;br&#039;);&lt;br /&gt;
        &lt;br /&gt;
return implode($br, $out);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create file ===&lt;br /&gt;
&lt;br /&gt;
Here&#039;s how to create a file whose contents will be a text string. This is the equivalent of the PHP function &amp;lt;tt&amp;gt;file_put_contents&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$fs = get_file_storage();&lt;br /&gt;
&lt;br /&gt;
// Prepare file record object&lt;br /&gt;
$fileinfo = array(&lt;br /&gt;
    &#039;contextid&#039; =&amp;gt; $context-&amp;gt;id, // ID of context&lt;br /&gt;
    &#039;component&#039; =&amp;gt; &#039;mod_mymodule&#039;,     // usually = table name&lt;br /&gt;
    &#039;filearea&#039; =&amp;gt; &#039;myarea&#039;,     // usually = table name&lt;br /&gt;
    &#039;itemid&#039; =&amp;gt; 0,               // usually = ID of row in table&lt;br /&gt;
    &#039;filepath&#039; =&amp;gt; &#039;/&#039;,           // any path beginning and ending in /&lt;br /&gt;
    &#039;filename&#039; =&amp;gt; &#039;myfile.txt&#039;); // any filename&lt;br /&gt;
&lt;br /&gt;
// Create file containing text &#039;hello world&#039;&lt;br /&gt;
$fs-&amp;gt;create_file_from_string($fileinfo, &#039;hello world&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you want to create a file in the Moodle file area based on a &#039;real&#039; file e.g. in a temporary folder, you can use &amp;lt;tt&amp;gt;create_file_from_pathname&amp;lt;/tt&amp;gt; instead.&lt;br /&gt;
&lt;br /&gt;
Unlike with ordinary files, this method will not automatically overwrite an existing file. If you wish to overwrite a file, you must first get the file and (if it exists) delete it, and only then create it again.&lt;br /&gt;
&lt;br /&gt;
=== Read file ===&lt;br /&gt;
&lt;br /&gt;
This is a way to read a file, equivalent to &amp;lt;tt&amp;gt;file_get_contents&amp;lt;/tt&amp;gt;. &#039;&#039;&#039;Please note your are allowed to do this ONLY from mod/mymodule/* code, it is not acceptable to do this anywhere else.&#039;&#039;&#039; Other code has to use file_browser interface instead.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$fs = get_file_storage();&lt;br /&gt;
&lt;br /&gt;
// Prepare file record object&lt;br /&gt;
$fileinfo = array(&lt;br /&gt;
    &#039;component&#039; =&amp;gt; &#039;mod_mymodule&#039;,     // usually = table name&lt;br /&gt;
    &#039;filearea&#039; =&amp;gt; &#039;myarea&#039;,     // usually = table name&lt;br /&gt;
    &#039;itemid&#039; =&amp;gt; 0,               // usually = ID of row in table&lt;br /&gt;
    &#039;contextid&#039; =&amp;gt; $context-&amp;gt;id, // ID of context&lt;br /&gt;
    &#039;filepath&#039; =&amp;gt; &#039;/&#039;,           // any path beginning and ending in /&lt;br /&gt;
    &#039;filename&#039; =&amp;gt; &#039;myfile.txt&#039;); // any filename&lt;br /&gt;
&lt;br /&gt;
// Get file&lt;br /&gt;
$file = $fs-&amp;gt;get_file($fileinfo-&amp;gt;contextid, $fileinfo-&amp;gt;component, $fileinfo-&amp;gt;filearea, &lt;br /&gt;
        $fileinfo-&amp;gt;itemid, $fileinfo-&amp;gt;filepath, $fileinfo-&amp;gt;filename);&lt;br /&gt;
&lt;br /&gt;
// Read contents&lt;br /&gt;
if ($file) {&lt;br /&gt;
    $contents = $file-&amp;gt;get_content();&lt;br /&gt;
} else {&lt;br /&gt;
    // file doesn&#039;t exist - do something&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you want to access the file directly on disk, this is not permitted. Instead, you need to make a copy of the file in a temporary area and use that. You can do this with &amp;lt;tt&amp;gt;$file-&amp;gt;copy_content_to($pathname)&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Delete file ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$fs = get_file_storage();&lt;br /&gt;
&lt;br /&gt;
// Prepare file record object&lt;br /&gt;
$fileinfo = array(&lt;br /&gt;
    &#039;component&#039; =&amp;gt; &#039;mod_mymodule&#039;,&lt;br /&gt;
    &#039;filearea&#039; =&amp;gt; &#039;myarea&#039;,     // usually = table name&lt;br /&gt;
    &#039;itemid&#039; =&amp;gt; 0,               // usually = ID of row in table&lt;br /&gt;
    &#039;contextid&#039; =&amp;gt; $context-&amp;gt;id, // ID of context&lt;br /&gt;
    &#039;filepath&#039; =&amp;gt; &#039;/&#039;,           // any path beginning and ending in /&lt;br /&gt;
    &#039;filename&#039; =&amp;gt; &#039;myfile.txt&#039;); // any filename&lt;br /&gt;
&lt;br /&gt;
// Get file&lt;br /&gt;
$file = $fs-&amp;gt;get_file($fileinfo-&amp;gt;contextid, $fileinfo-&amp;gt;component, $fileinfo-&amp;gt;filearea, &lt;br /&gt;
        $fileinfo-&amp;gt;itemid, $fileinfo-&amp;gt;filepath, $fileinfo-&amp;gt;filename);&lt;br /&gt;
&lt;br /&gt;
// Delete it if it exists&lt;br /&gt;
if ($file) {&lt;br /&gt;
    $file-&amp;gt;delete();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
* [[File API internals]] how the File API works internally.&lt;br /&gt;
* [[Using the File API in Moodle forms]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Files]]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Using_the_File_API_in_Moodle_forms&amp;diff=31456</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=31456"/>
		<updated>2012-01-16T05:31:25Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: &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, 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, &#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.  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;
file_prepare_draft_area($draftitemid, $context-&amp;gt;id, &#039;mod_glossary&#039;, &#039;attachment&#039;, $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;
$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;, $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 way for using of 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 &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, &#039;maxbytes&#039;=&amp;gt;$maxbytes);&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;), 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, &#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, &#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;, $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;, $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; // Could also use $CFG-&amp;gt;maxbytes if you are not coding within a course context&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, &#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(&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;
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, &#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, &#039;mod_glossary&#039;, &#039;attachment&#039;, $entry-&amp;gt;id);&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;
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;), 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, 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;), &#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, $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;), null, $attachmentoptions);&lt;br /&gt;
$mform-&amp;gt;setHelpButton(&#039;attachment_filemanager&#039;, array(&#039;attachment2&#039;, 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, &#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, &#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>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=File_API&amp;diff=31454</id>
		<title>File API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=File_API&amp;diff=31454"/>
		<updated>2012-01-16T04:00:57Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.0}}&lt;br /&gt;
==Overview==&lt;br /&gt;
&lt;br /&gt;
The File API is for managing all the files stored by Moodle. If you are interested in how the file API works internally, see [[File API internals]]. The page is just about what you need to know to use the file API. Related is the [[Repository API]], which lets users get files into Moodle.&lt;br /&gt;
&lt;br /&gt;
If you are looking for an explanation on how to manage moodle files in moodle forms, you most likely need to read [[Using_the_File_API_in_Moodle_forms|Using the File API in Moodle forms]].&lt;br /&gt;
&lt;br /&gt;
==File areas==&lt;br /&gt;
&lt;br /&gt;
Files are conceptually stored in &#039;&#039;&#039;file areas&#039;&#039;&#039;. A file area is uniquely identified by:&lt;br /&gt;
* A context id.&lt;br /&gt;
* full component name (using [[Frankenstyle]]), for example &#039;course&#039;, &#039;mod_forum&#039;, &#039;mod_glossary&#039;, &#039;block_html&#039;.&lt;br /&gt;
* A file area type, for example &#039;intro&#039; or &#039;post&#039;.&lt;br /&gt;
* A unique itemid. Normally, the itemid relates to something depending on the file area type. For example, for a &#039;course&#039;, &#039;intro&#039; file area, the itemid is 0. For forum post, it is the post id.&lt;br /&gt;
&lt;br /&gt;
File areas are not listed separately anywhere, they are stored implicitly in the files table. Please note that each subsystem is allowed to access only own file areas, for example only code in /mod/assignment/* may access files with component &#039;mod_assignment&#039;.&lt;br /&gt;
&lt;br /&gt;
===Naming file areas===&lt;br /&gt;
&lt;br /&gt;
The names of the file areas are not strictly defined, but it is strongly recommended to use singulars and common names of areas if possible (intro, post, attachment, description, ...).&lt;br /&gt;
&lt;br /&gt;
==Serving files to users==&lt;br /&gt;
&lt;br /&gt;
You must refer to the file with a URL that includes a file-serving script, often pluginfile.php. For example&lt;br /&gt;
&lt;br /&gt;
The general form of the URL is something like&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$url = $CFG-&amp;gt;wwwroot/pluginfile.php/$contextid/$component/$filearea/arbitrary/extra/infomation.ext&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A specific example might be&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$url = $CFG-&amp;gt;wwwroot/pluginfile.php/$forumcontextid/mod_forum/post/$postid/image.jpg&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The file serving script then looks at the context id, and component name, and the file area name, and based on that arranges for the file to be served, following appropriate security checks.&lt;br /&gt;
&lt;br /&gt;
In most cases this is done by calling a callback function in the appropriate plugin, these functions are stored in lib.php files and named component_name_pluginfile() . The arbitrary/extra/infomation.ext is passed to the callback. For example, files in the mod_forum+post file area end up being served by the mod_forum_pluginfile function in mod/forum/lib.php.&lt;br /&gt;
&lt;br /&gt;
You normally use an API function to generate these URL automatically, most often the &amp;lt;tt&amp;gt;file_rewrite_pluginfile_urls&amp;lt;/tt&amp;gt; function.&lt;br /&gt;
&lt;br /&gt;
==Getting files from the user==&lt;br /&gt;
&lt;br /&gt;
* See [[Using_the_File_API_in_Moodle_forms|Using the File API in Moodle forms]]&lt;br /&gt;
&lt;br /&gt;
==Examples==&lt;br /&gt;
Please note that in reality developers outside of core will not deal with file api directly in majority of cases, instead use formslib elements which are doing all this automatically.&lt;br /&gt;
&lt;br /&gt;
===Browsing files===&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$browser = get_file_browser();&lt;br /&gt;
$context = get_system_context();&lt;br /&gt;
&lt;br /&gt;
$filearea = null;&lt;br /&gt;
$itemid   = null;&lt;br /&gt;
$filename = null;&lt;br /&gt;
if ($fileinfo = $browser-&amp;gt;get_file_info($context, $component, $filearea, $itemid, &#039;/&#039;, $filename)) {&lt;br /&gt;
    // build a Breadcrumb trail&lt;br /&gt;
    $level = $fileinfo-&amp;gt;get_parent();&lt;br /&gt;
    while ($level) {&lt;br /&gt;
        $path[] = array(&#039;name&#039;=&amp;gt;$level-&amp;gt;get_visible_name());&lt;br /&gt;
        $level = $level-&amp;gt;get_parent();&lt;br /&gt;
    }&lt;br /&gt;
    $path = array_reverse($path);&lt;br /&gt;
    $children = $fileinfo-&amp;gt;get_children();&lt;br /&gt;
    foreach ($children as $child) {&lt;br /&gt;
        if ($child-&amp;gt;is_directory()) {&lt;br /&gt;
            echo $child-&amp;gt;get_visible_name();&lt;br /&gt;
            // display contextid, itemid, component, filepath and filename&lt;br /&gt;
            var_dump($child-&amp;gt;get_params());&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;
===Moving files around===&lt;br /&gt;
&lt;br /&gt;
For example, if you have just built a file at the path&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $from_zip_file = $CFG-&amp;gt;dataroot . &#039;/temp/backup/&#039; . $preferences-&amp;gt;backup_unique_code .&lt;br /&gt;
         &#039;/&#039; . $preferences-&amp;gt;backup_name;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
And you want to move it into the course_backup file area, do&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $context = get_context_instance(CONTEXT_COURSE, $preferences-&amp;gt;backup_course);&lt;br /&gt;
 $fs = get_file_storage();&lt;br /&gt;
 $file_record = array(&#039;contextid&#039;=&amp;gt;$context-&amp;gt;id, &#039;component&#039;=&amp;gt;&#039;course&#039;, &#039;filearea&#039;=&amp;gt;&#039;backup&#039;,&lt;br /&gt;
         &#039;itemid&#039;=&amp;gt;0, &#039;filepath&#039;=&amp;gt;&#039;/&#039;, &#039;filename&#039;=&amp;gt;$preferences-&amp;gt;backup_name,&lt;br /&gt;
         &#039;timecreated&#039;=&amp;gt;time(), &#039;timemodified&#039;=&amp;gt;time());&lt;br /&gt;
 $fs-&amp;gt;create_file_from_pathname($file_record, $from_zip_file);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== List area files ===&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$fs = get_file_storage();&lt;br /&gt;
$files = $fs-&amp;gt;get_area_files($contextid, &#039;mod_assignment&#039;, &#039;submission&#039;, $submission-&amp;gt;id);&lt;br /&gt;
foreach ($files as $f) {&lt;br /&gt;
    // $f is an instance of stored_file&lt;br /&gt;
    echo $f-&amp;gt;get_filename();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or as links...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$out = array();&lt;br /&gt;
        &lt;br /&gt;
$fs = get_file_storage();&lt;br /&gt;
$files = $fs-&amp;gt;get_area_files($contextid, &#039;mod_assignment&#039;, &#039;submission&#039;, $submission-&amp;gt;id);&lt;br /&gt;
            &lt;br /&gt;
foreach ($files as $file) {&lt;br /&gt;
    $url = &amp;quot;{$CFG-&amp;gt;wwwroot}/pluginfile.php/{$file-&amp;gt;get_contextid()}/mod_assignment/submission}&amp;quot;;&lt;br /&gt;
    $filename = $file-&amp;gt;get_filename();&lt;br /&gt;
    $fileurl = $url.$file-&amp;gt;get_filepath().$file-&amp;gt;get_itemid().&#039;/&#039;.$filename;&lt;br /&gt;
    $out[] = html_writer::link($fileurl, $filename);&lt;br /&gt;
}&lt;br /&gt;
      &lt;br /&gt;
$br = html_writer::empty_tag(&#039;br&#039;);&lt;br /&gt;
        &lt;br /&gt;
return implode($br, $out);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create file ===&lt;br /&gt;
&lt;br /&gt;
Here&#039;s how to create a file whose contents will be a text string. This is the equivalent of the PHP function &amp;lt;tt&amp;gt;file_put_contents&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$fs = get_file_storage();&lt;br /&gt;
&lt;br /&gt;
// Prepare file record object&lt;br /&gt;
$fileinfo = array(&lt;br /&gt;
    &#039;contextid&#039; =&amp;gt; $context-&amp;gt;id, // ID of context&lt;br /&gt;
    &#039;component&#039; =&amp;gt; &#039;mod_mymodule&#039;,     // usually = table name&lt;br /&gt;
    &#039;filearea&#039; =&amp;gt; &#039;myarea&#039;,     // usually = table name&lt;br /&gt;
    &#039;itemid&#039; =&amp;gt; 0,               // usually = ID of row in table&lt;br /&gt;
    &#039;filepath&#039; =&amp;gt; &#039;/&#039;,           // any path beginning and ending in /&lt;br /&gt;
    &#039;filename&#039; =&amp;gt; &#039;myfile.txt&#039;); // any filename&lt;br /&gt;
&lt;br /&gt;
// Create file containing text &#039;hello world&#039;&lt;br /&gt;
$fs-&amp;gt;create_file_from_string($fileinfo, &#039;hello world&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you want to create a file in the Moodle file area based on a &#039;real&#039; file e.g. in a temporary folder, you can use &amp;lt;tt&amp;gt;create_file_from_pathname&amp;lt;/tt&amp;gt; instead.&lt;br /&gt;
&lt;br /&gt;
Unlike with ordinary files, this method will not automatically overwrite an existing file. If you wish to overwrite a file, you must first get the file and (if it exists) delete it, and only then create it again.&lt;br /&gt;
&lt;br /&gt;
=== Read file ===&lt;br /&gt;
&lt;br /&gt;
This is a way to read a file, equivalent to &amp;lt;tt&amp;gt;file_get_contents&amp;lt;/tt&amp;gt;. &#039;&#039;&#039;Please note your are allowed to do this ONLY from mod/mymodule/* code, it is not acceptable to do this anywhere else.&#039;&#039;&#039; Other code has to use file_browser interface instead.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$fs = get_file_storage();&lt;br /&gt;
&lt;br /&gt;
// Prepare file record object&lt;br /&gt;
$fileinfo = array(&lt;br /&gt;
    &#039;component&#039; =&amp;gt; &#039;mod_mymodule&#039;,     // usually = table name&lt;br /&gt;
    &#039;filearea&#039; =&amp;gt; &#039;myarea&#039;,     // usually = table name&lt;br /&gt;
    &#039;itemid&#039; =&amp;gt; 0,               // usually = ID of row in table&lt;br /&gt;
    &#039;contextid&#039; =&amp;gt; $context-&amp;gt;id, // ID of context&lt;br /&gt;
    &#039;filepath&#039; =&amp;gt; &#039;/&#039;,           // any path beginning and ending in /&lt;br /&gt;
    &#039;filename&#039; =&amp;gt; &#039;myfile.txt&#039;); // any filename&lt;br /&gt;
&lt;br /&gt;
// Get file&lt;br /&gt;
$file = $fs-&amp;gt;get_file($fileinfo-&amp;gt;contextid, $fileinfo-&amp;gt;component, $fileinfo-&amp;gt;filearea, &lt;br /&gt;
        $fileinfo-&amp;gt;itemid, $fileinfo-&amp;gt;filepath, $fileinfo-&amp;gt;filename);&lt;br /&gt;
&lt;br /&gt;
// Read contents&lt;br /&gt;
if ($file) {&lt;br /&gt;
    $contents = $file-&amp;gt;get_content();&lt;br /&gt;
} else {&lt;br /&gt;
    // file doesn&#039;t exist - do something&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you want to access the file directly on disk, this is not permitted. Instead, you need to make a copy of the file in a temporary area and use that. You can do this with &amp;lt;tt&amp;gt;$file-&amp;gt;copy_content_to($pathname)&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Delete file ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$fs = get_file_storage();&lt;br /&gt;
&lt;br /&gt;
// Prepare file record object&lt;br /&gt;
$fileinfo = array(&lt;br /&gt;
    &#039;component&#039; =&amp;gt; &#039;mod_mymodule&#039;,&lt;br /&gt;
    &#039;filearea&#039; =&amp;gt; &#039;myarea&#039;,     // usually = table name&lt;br /&gt;
    &#039;itemid&#039; =&amp;gt; 0,               // usually = ID of row in table&lt;br /&gt;
    &#039;contextid&#039; =&amp;gt; $context-&amp;gt;id, // ID of context&lt;br /&gt;
    &#039;filepath&#039; =&amp;gt; &#039;/&#039;,           // any path beginning and ending in /&lt;br /&gt;
    &#039;filename&#039; =&amp;gt; &#039;myfile.txt&#039;); // any filename&lt;br /&gt;
&lt;br /&gt;
// Get file&lt;br /&gt;
$file = $fs-&amp;gt;get_file($fileinfo-&amp;gt;contextid, $fileinfo-&amp;gt;component, $fileinfo-&amp;gt;filearea, &lt;br /&gt;
        $fileinfo-&amp;gt;itemid, $fileinfo-&amp;gt;filepath, $fileinfo-&amp;gt;filename);&lt;br /&gt;
&lt;br /&gt;
// Delete it if it exists&lt;br /&gt;
if ($file) {&lt;br /&gt;
    $file-&amp;gt;delete();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
* [[File API internals]] how the File API works internally.&lt;br /&gt;
* [[Using_the_File_API_in_Moodle_forms]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Files]]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=Using_the_File_API_in_Moodle_forms&amp;diff=31444</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=31444"/>
		<updated>2012-01-16T03:20:10Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: &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 [[Using_the_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, 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, &#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.  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;
file_prepare_draft_area($draftitemid, $context-&amp;gt;id, &#039;mod_glossary&#039;, &#039;attachment&#039;, $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;
$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;, $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 way for using of 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 &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, &#039;maxbytes&#039;=&amp;gt;$maxbytes);&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;), 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, &#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, &#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;, $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;, $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; // Could also use $CFG-&amp;gt;maxbytes if you are not coding within a course context&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, &#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(&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;
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, &#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, &#039;mod_glossary&#039;, &#039;attachment&#039;, $entry-&amp;gt;id);&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;
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;), 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, 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;), &#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, $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;), null, $attachmentoptions);&lt;br /&gt;
$mform-&amp;gt;setHelpButton(&#039;attachment_filemanager&#039;, array(&#039;attachment2&#039;, 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, &#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, &#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>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=File_API&amp;diff=31441</id>
		<title>File API</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=File_API&amp;diff=31441"/>
		<updated>2012-01-16T03:16:23Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Moodle 2.0}}&lt;br /&gt;
==Overview==&lt;br /&gt;
&lt;br /&gt;
The File API is for managing all the files stored by Moodle. If you are interested in how the file API works internally, see [[File API]]. The page is just about what you need to know to use the file API. Related is the [[Repository API]], which lets users get files into Moodle.&lt;br /&gt;
&lt;br /&gt;
If you are looking for an explanation on how to manage moodle files in moodle forms, you most likely need to read [[Using_the_File_API_in_Moodle_forms|Using the File API in Moodle forms]].&lt;br /&gt;
&lt;br /&gt;
==File areas==&lt;br /&gt;
&lt;br /&gt;
Files are conceptually stored in &#039;&#039;&#039;file areas&#039;&#039;&#039;. A file area is uniquely identified by:&lt;br /&gt;
* A context id.&lt;br /&gt;
* full component name, for example &#039;course&#039;, &#039;mod_forum&#039;, &#039;mod_glossary&#039;, &#039;block_html&#039;.&lt;br /&gt;
* A file area type, for example &#039;intro&#039; or &#039;post&#039;.&lt;br /&gt;
* A unique itemid. Normally, the itemid relates to something depending on the file area type. For example, for a &#039;course&#039;, &#039;intro&#039; file area, the itemid is 0. For forum post, it is the post id.&lt;br /&gt;
&lt;br /&gt;
File areas are not listed separately anywhere, they are stored implicitly in the files table. Please note that each subsystem is allowed to access only own file areas, for example only code in /mod/assignment/* may access files with component &#039;mod_assignment&#039;.&lt;br /&gt;
&lt;br /&gt;
===Naming file areas===&lt;br /&gt;
&lt;br /&gt;
The names of the file areas are not strictly defined, but it is strongly recommended to use singulars and common names of areas if possible (intro, post, attachment, description, ...).&lt;br /&gt;
&lt;br /&gt;
==Serving files to users==&lt;br /&gt;
&lt;br /&gt;
You must refer to the file with a URL that includes a file-serving script, often pluginfile.php. For example&lt;br /&gt;
&lt;br /&gt;
The general form of the URL is something like&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$url = $CFG-&amp;gt;wwwroot/pluginfile.php/$contextid/$component/$filearea/arbitrary/extra/infomation.ext&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
A specific example might be&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$url = $CFG-&amp;gt;wwwroot/pluginfile.php/$forumcontextid/mod_forum/post/$postid/image.jpg&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The file serving script then looks at the context id, and component name, and the file area name, and based on that arranges for the file to be served, following appropriate security checks.&lt;br /&gt;
&lt;br /&gt;
In most cases this is done by calling a callback function in the appropriate plugin, these functions are stored in lib.php files and named component_name_pluginfile() . The arbitrary/extra/infomation.ext is passed to the callback. For example, files in the mod_forum+post file area end up being served by the mod_forum_pluginfile function in mod/forum/lib.php.&lt;br /&gt;
&lt;br /&gt;
You normally use an API function to generate these URL automatically, most often the file_rewrite_pluginfile_urls function.&lt;br /&gt;
&lt;br /&gt;
==Getting files from the user==&lt;br /&gt;
&lt;br /&gt;
* See [[Using_the_File_API_in_Moodle_forms|Using the File API in Moodle forms]]&lt;br /&gt;
==Examples==&lt;br /&gt;
Please note that in reality developers outside of core will not deal with file api directly in majority of cases, instead use formslib elements which are doing all this automatically.&lt;br /&gt;
&lt;br /&gt;
===Browsing files===&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$browser = get_file_browser();&lt;br /&gt;
$context = get_system_context();&lt;br /&gt;
&lt;br /&gt;
$filearea = null;&lt;br /&gt;
$itemid   = null;&lt;br /&gt;
$filename = null;&lt;br /&gt;
if ($fileinfo = $browser-&amp;gt;get_file_info($context, $component, $filearea, $itemid, &#039;/&#039;, $filename)) {&lt;br /&gt;
    // build a Breadcrumb trail&lt;br /&gt;
    $level = $fileinfo-&amp;gt;get_parent();&lt;br /&gt;
    while ($level) {&lt;br /&gt;
        $params = base64_encode(serialize($level-&amp;gt;get_params()));&lt;br /&gt;
        $path[] = array(&#039;name&#039;=&amp;gt;$level-&amp;gt;get_visible_name(), &#039;path&#039;=&amp;gt;$params);&lt;br /&gt;
        $level = $level-&amp;gt;get_parent();&lt;br /&gt;
    }&lt;br /&gt;
    $path = array_reverse($path);&lt;br /&gt;
    $children = $fileinfo-&amp;gt;get_children();&lt;br /&gt;
    foreach ($children as $child) {&lt;br /&gt;
        if ($child-&amp;gt;is_directory()) {&lt;br /&gt;
            echo $child-&amp;gt;get_visible_name();&lt;br /&gt;
            // display contextid, itemid, component, filepath and filename&lt;br /&gt;
            var_dump($child-&amp;gt;get_params());&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;
===Moving files around===&lt;br /&gt;
&lt;br /&gt;
For example, if you have just built a file at the path&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $from_zip_file = $CFG-&amp;gt;dataroot . &#039;/temp/backup/&#039; . $preferences-&amp;gt;backup_unique_code .&lt;br /&gt;
         &#039;/&#039; . $preferences-&amp;gt;backup_name;&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
And you want to move it into the course_backup file area, do&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
 $context = get_context_instance(CONTEXT_COURSE, $preferences-&amp;gt;backup_course);&lt;br /&gt;
 $fs = get_file_storage();&lt;br /&gt;
 $file_record = array(&#039;contextid&#039;=&amp;gt;$context-&amp;gt;id, &#039;component&#039;=&amp;gt;&#039;course&#039;, &#039;filearea&#039;=&amp;gt;&#039;backup&#039;,&lt;br /&gt;
         &#039;itemid&#039;=&amp;gt;0, &#039;filepath&#039;=&amp;gt;&#039;/&#039;, &#039;filename&#039;=&amp;gt;$preferences-&amp;gt;backup_name,&lt;br /&gt;
         &#039;timecreated&#039;=&amp;gt;time(), &#039;timemodified&#039;=&amp;gt;time());&lt;br /&gt;
 $fs-&amp;gt;create_file_from_pathname($file_record, $from_zip_file);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===Create a copy of stored file===&lt;br /&gt;
 TODO: this example is bogus, nobody should ever write code like this (skodak)&lt;br /&gt;
If you need to create a copy of stored file (actually, it add a new record in database):&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$context  = get_context_instance_by_id($contextid);&lt;br /&gt;
$file_info = $browser-&amp;gt;get_file_info($context, $component, $filearea, $fileitemid, $filepath, $filename);&lt;br /&gt;
// copy this file to draft area&lt;br /&gt;
$file_info-&amp;gt;copy_to_storage($user_context-&amp;gt;id, &#039;user&#039;, &#039;draft&#039;, $newitemid, &#039;/&#039;, $title);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The above code is intended for user-interface behaviour which respects the current user&#039;s permissions. If you need to write code which copies files at the back-end in areas which are not directly editable by users (for example, when copying a glossary entry to another glossary, and you want to copy its attachments), you do not need to use the user-level $browser at all. All operations are carried out with the file_storage object (usually $fs). The method to use is create_file_from_storedfile.&lt;br /&gt;
&lt;br /&gt;
For example, the glossary stores per-entry files in two areas (&#039;mod_glossary&#039;, &#039;attachment&#039; which stores attachments and &#039;mod_glossary&#039;, &#039;entry&#039; which stores files directly related to the HTML definition, such as embedded images). The following code copies all the files relating to a specific glossary entry from one such area to a different glossary item in a different glossary. Note that it changes the context ID and item ID of each file to represent the new context (for the different glossary module) and the new glossary entry (a new row was added to glossary_entries).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$fs = get_file_storage();&lt;br /&gt;
if ($files = $fs-&amp;gt;get_area_files($oldcontextid, &#039;mod_glossary&#039;, &#039;attachment&#039;, $oldentryid)) {&lt;br /&gt;
    foreach ($files as $file) {&lt;br /&gt;
        $fs-&amp;gt;create_file_from_storedfile(array(&lt;br /&gt;
                &#039;contextid&#039; =&amp;gt; $newcontextid,&lt;br /&gt;
                &#039;itemid&#039; =&amp;gt; $newentryid), $file);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== List area files ===&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$fs = get_file_storage();&lt;br /&gt;
$files = $fs-&amp;gt;get_area_files($contextid, &#039;mod_assignment&#039;, &#039;submission&#039;, $submission-&amp;gt;id);&lt;br /&gt;
foreach ($files as $f) {&lt;br /&gt;
    // $f is an instance of stored_file&lt;br /&gt;
    echo $f-&amp;gt;get_filename();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Or as links...&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$out = array();&lt;br /&gt;
        &lt;br /&gt;
$fs = get_file_storage();&lt;br /&gt;
$files = $fs-&amp;gt;get_area_files($contextid, &#039;mod_assignment&#039;, &#039;submission&#039;, $submission-&amp;gt;id);&lt;br /&gt;
            &lt;br /&gt;
foreach ($files as $file) {&lt;br /&gt;
    $url = &amp;quot;{$CFG-&amp;gt;wwwroot}/pluginfile.php/{$file-&amp;gt;get_contextid()}/mod_assignment/submission}&amp;quot;;&lt;br /&gt;
    $filename = $file-&amp;gt;get_filename();&lt;br /&gt;
    $fileurl = $url.$file-&amp;gt;get_filepath().$file-&amp;gt;get_itemid().&#039;/&#039;.$filename;&lt;br /&gt;
    $out[] = html_writer::link($fileurl, $filename);&lt;br /&gt;
}&lt;br /&gt;
      &lt;br /&gt;
$br = html_writer::empty_tag(&#039;br&#039;);&lt;br /&gt;
        &lt;br /&gt;
return implode($br, $out);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Create file ===&lt;br /&gt;
&lt;br /&gt;
Here&#039;s how to create a file whose contents will be a text string. This is the equivalent of the PHP function &amp;lt;tt&amp;gt;file_put_contents&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$fs = get_file_storage();&lt;br /&gt;
&lt;br /&gt;
// Prepare file record object&lt;br /&gt;
$fileinfo = array(&lt;br /&gt;
    &#039;contextid&#039; =&amp;gt; $context-&amp;gt;id, // ID of context&lt;br /&gt;
    &#039;component&#039; =&amp;gt; &#039;mod_mymodule&#039;,     // usually = table name&lt;br /&gt;
    &#039;filearea&#039; =&amp;gt; &#039;myarea&#039;,     // usually = table name&lt;br /&gt;
    &#039;itemid&#039; =&amp;gt; 0,               // usually = ID of row in table&lt;br /&gt;
    &#039;filepath&#039; =&amp;gt; &#039;/&#039;,           // any path beginning and ending in /&lt;br /&gt;
    &#039;filename&#039; =&amp;gt; &#039;myfile.txt&#039;); // any filename&lt;br /&gt;
&lt;br /&gt;
// Create file containing text &#039;hello world&#039;&lt;br /&gt;
$fs-&amp;gt;create_file_from_string($fileinfo, &#039;hello world&#039;);&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you want to create a file in the Moodle file area based on a &#039;real&#039; file e.g. in a temporary folder, you can use &amp;lt;tt&amp;gt;create_file_from_pathname&amp;lt;/tt&amp;gt; instead.&lt;br /&gt;
&lt;br /&gt;
Unlike with ordinary files, this method will not automatically overwrite an existing file. If you wish to overwrite a file, you must first get the file and (if it exists) delete it, and only then create it again.&lt;br /&gt;
&lt;br /&gt;
=== Read file ===&lt;br /&gt;
&lt;br /&gt;
This is a way to read a file, equivalent to &amp;lt;tt&amp;gt;file_get_contents&amp;lt;/tt&amp;gt;. &#039;&#039;&#039;Please note your are allowed to do this ONLY from mod/mymodule/* code, it is not acceptable to do this anywhere else.&#039;&#039;&#039; Other code has to use file_browser interface instead.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$fs = get_file_storage();&lt;br /&gt;
&lt;br /&gt;
// Prepare file record object&lt;br /&gt;
$fileinfo = array(&lt;br /&gt;
    &#039;component&#039; =&amp;gt; &#039;mod_mymodule&#039;,     // usually = table name&lt;br /&gt;
    &#039;filearea&#039; =&amp;gt; &#039;myarea&#039;,     // usually = table name&lt;br /&gt;
    &#039;itemid&#039; =&amp;gt; 0,               // usually = ID of row in table&lt;br /&gt;
    &#039;contextid&#039; =&amp;gt; $context-&amp;gt;id, // ID of context&lt;br /&gt;
    &#039;filepath&#039; =&amp;gt; &#039;/&#039;,           // any path beginning and ending in /&lt;br /&gt;
    &#039;filename&#039; =&amp;gt; &#039;myfile.txt&#039;); // any filename&lt;br /&gt;
&lt;br /&gt;
// Get file&lt;br /&gt;
$file = $fs-&amp;gt;get_file($fileinfo-&amp;gt;contextid, $fileinfo-&amp;gt;component, $fileinfo-&amp;gt;filearea, &lt;br /&gt;
        $fileinfo-&amp;gt;itemid, $fileinfo-&amp;gt;filepath, $fileinfo-&amp;gt;filename);&lt;br /&gt;
&lt;br /&gt;
// Read contents&lt;br /&gt;
if ($file) {&lt;br /&gt;
    $contents = $file-&amp;gt;get_content();&lt;br /&gt;
} else {&lt;br /&gt;
    // file doesn&#039;t exist - do something&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
If you want to access the file directly on disk, this is not permitted. Instead, you need to make a copy of the file in a temporary area and use that. You can do this with &amp;lt;tt&amp;gt;$file-&amp;gt;copy_content_to($pathname)&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Delete file ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code php&amp;gt;&lt;br /&gt;
$fs = get_file_storage();&lt;br /&gt;
&lt;br /&gt;
// Prepare file record object&lt;br /&gt;
$fileinfo = array(&lt;br /&gt;
    &#039;component&#039; =&amp;gt; &#039;mod_mymodule&#039;,&lt;br /&gt;
    &#039;filearea&#039; =&amp;gt; &#039;myarea&#039;,     // usually = table name&lt;br /&gt;
    &#039;itemid&#039; =&amp;gt; 0,               // usually = ID of row in table&lt;br /&gt;
    &#039;contextid&#039; =&amp;gt; $context-&amp;gt;id, // ID of context&lt;br /&gt;
    &#039;filepath&#039; =&amp;gt; &#039;/&#039;,           // any path beginning and ending in /&lt;br /&gt;
    &#039;filename&#039; =&amp;gt; &#039;myfile.txt&#039;); // any filename&lt;br /&gt;
&lt;br /&gt;
// Get file&lt;br /&gt;
$file = $fs-&amp;gt;get_file($fileinfo-&amp;gt;contextid, $fileinfo-&amp;gt;component, $fileinfo-&amp;gt;filearea, &lt;br /&gt;
        $fileinfo-&amp;gt;itemid, $fileinfo-&amp;gt;filepath, $fileinfo-&amp;gt;filename);&lt;br /&gt;
&lt;br /&gt;
// Delete it if it exists&lt;br /&gt;
if ($file) {&lt;br /&gt;
    $file-&amp;gt;delete();&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
&lt;br /&gt;
* [[File API]] how the File API works internally.&lt;br /&gt;
* [[Roadmap|Moodle 2.0 roadmap]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Files]]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=File_API_internals&amp;diff=31439</id>
		<title>File API internals</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=File_API_internals&amp;diff=31439"/>
		<updated>2012-01-16T03:11:22Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Infobox Project&lt;br /&gt;
|name = File API&lt;br /&gt;
|state = Implemented&lt;br /&gt;
|tracker = MDL-14589&lt;br /&gt;
|discussion = n/a&lt;br /&gt;
|assignee = [[User:Petr Škoda (škoďák)|Petr Škoda (škoďák)]]&lt;br /&gt;
}}&lt;br /&gt;
{{Moodle 2.0}}&lt;br /&gt;
&lt;br /&gt;
==Objectives==&lt;br /&gt;
&lt;br /&gt;
The goals of the new File API are: &lt;br /&gt;
&lt;br /&gt;
* allow files to be stored within Moodle, as part of the content (as we do now).&lt;br /&gt;
* use a consistent and flexible approach for all file handling throughout Moodle.&lt;br /&gt;
* give modules control over which users can access a file, using capabilities and other local rules.&lt;br /&gt;
* make it easy to determine which parts of Moodle use which files, to simplify operations like backup and restore.&lt;br /&gt;
* track where files originally came from.&lt;br /&gt;
* avoid redundant storage, when the same file is used twice.&lt;br /&gt;
* fully support Unicode file names, irrespective of the capabilities of the underlying file system.&lt;br /&gt;
&lt;br /&gt;
==Overview==&lt;br /&gt;
&lt;br /&gt;
The File API is a set of core interfaces to allow the rest of Moodle to store, serve and manage files. It applies only to files that are part of the Moodle site&#039;s content. It is not used for internal files, such as those in the following subdirectories of dataroot: temp, lang, cache, environment, filter, search, sessions, upgradelogs, ...&lt;br /&gt;
&lt;br /&gt;
To learn how to use File API, please visit [[Using the File API]].&lt;br /&gt;
&lt;br /&gt;
The API can be subdivided into the following parts:&lt;br /&gt;
; File storage&lt;br /&gt;
: Low level file storage without access control information. Stores the content of files on disc, with metadata in associated database tables. &lt;br /&gt;
; File serving&lt;br /&gt;
: Lets users accessing a Moodle site get the files (file.php, draftfile.php, pluginfile.php, userfile.php, etc.)&lt;br /&gt;
:* Serve the files on request&lt;br /&gt;
:* with appropriate security checks&lt;br /&gt;
; File related user interfaces&lt;br /&gt;
: Provides the interface for (lib/form/file.php, filemanager.php, filepicker.php and files/index.php, draftfiles.php)&lt;br /&gt;
:* Form elements allowing users to select a file using the file picker, and have it stored within Moodle.&lt;br /&gt;
:* UI for users to manage their files, replacing the old course files UI&lt;br /&gt;
; File browsing API&lt;br /&gt;
: Allows code to browse and optionally manipulate the file areas&lt;br /&gt;
:* find information about available files in each area.&lt;br /&gt;
:* print links to files.&lt;br /&gt;
:* optionally move/rename/copy/delete/etc.&lt;br /&gt;
&lt;br /&gt;
== File API internals ==&lt;br /&gt;
&lt;br /&gt;
=== File storage on disk ===&lt;br /&gt;
&lt;br /&gt;
Files are stored in $CFG-&amp;gt;dataroot (also known as moodledata) in the filedir subfolder.&lt;br /&gt;
&lt;br /&gt;
Files are stored according to the SHA1 hash of their content. This means each file with particular contents is stored once, irrespective of how many times it is included in different places, even if it is referred to by different names. (This idea comes from the git version control system.) To relate a file on disc to a user-comprehensible path or filename, you need to use the &#039;&#039;files&#039;&#039; database table. See the next section.&lt;br /&gt;
&lt;br /&gt;
Suppose a file has SHA1 hash 081371cb102fa559e81993fddc230c79205232ce. Then it will be stored in on disc as moodledata/filedir/08/13/081371cb102fa559e81993fddc230c79205232ce.&lt;br /&gt;
&lt;br /&gt;
This means Moodle can not store two files with the same SHA1 hash, luckily it is extremely unlikely that this would ever happen. Technically it is also possible to implement reliable collision tests (with some performance cost), for now we just test file lengths in addition to SHA1 hash.&lt;br /&gt;
&lt;br /&gt;
=== Files table ===&lt;br /&gt;
&lt;br /&gt;
This table contains one entry for each usage of a file. Enough information is kept here so that the file can be fully identified and retrieved again if necessary. It is necessary because some databases have hard limit on index size.&lt;br /&gt;
&lt;br /&gt;
If, for example, the same image is used in a user&#039;s profile, and a forum post, then there will be two rows in this table, one for each use of the file, and Moodle will treat the two as separate files, even though the file is only stored once on disc.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
! Field&lt;br /&gt;
! Type&lt;br /&gt;
! Default&lt;br /&gt;
! Info&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;id&#039;&#039;&#039; &lt;br /&gt;
| int(10)  &lt;br /&gt;
| auto-incrementing&lt;br /&gt;
| The unique ID for this file.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;contenthash&#039;&#039;&#039;&lt;br /&gt;
| varchar(40)&lt;br /&gt;
|  &lt;br /&gt;
| The sha1 hash of content.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;pathnamehash&#039;&#039;&#039;&lt;br /&gt;
| varchar(40)&lt;br /&gt;
| &lt;br /&gt;
| The sha1 hash of &amp;quot;/contextid/component/filearea/itemid/filepath/filename.ext&amp;quot; - prevents file duplicates and allows fast lookup.  It is necessary because some databases have hard limit on index size.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;contextid&#039;&#039;&#039; &lt;br /&gt;
| int(10)&lt;br /&gt;
| &lt;br /&gt;
| The context id defined in context table - identifies the instance of plugin owning the file.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;component&#039;&#039;&#039;&lt;br /&gt;
| varchar(50)&lt;br /&gt;
|&lt;br /&gt;
| Like &amp;quot;mod_forum&amp;quot;, &amp;quot;course&amp;quot;, &amp;quot;mod_assignment&amp;quot;, &amp;quot;backup&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;filearea&#039;&#039;&#039;&lt;br /&gt;
| varchar(50)&lt;br /&gt;
|&lt;br /&gt;
| Like &amp;quot;submissions&amp;quot;, &amp;quot;intro&amp;quot; and &amp;quot;content&amp;quot; (images and swf linked from summaries), etc.; &amp;quot;blogs&amp;quot; and &amp;quot;userfiles&amp;quot; are special case that live at the system context.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;itemid&#039;&#039;&#039;&lt;br /&gt;
| int(10)&lt;br /&gt;
| &lt;br /&gt;
| Some plugin specific item id (eg. forum post, blog entry or assignment submission or user id for user files)&lt;br /&gt;
|-&lt;br /&gt;
| filepath&lt;br /&gt;
| text&lt;br /&gt;
| &lt;br /&gt;
| relative path to file from module content root, useful in Scorm and Resource mod - most of the mods do not need this&lt;br /&gt;
|-&lt;br /&gt;
| filename&lt;br /&gt;
| varchar(255)&lt;br /&gt;
| &lt;br /&gt;
| The full Unicode name of this file (case sensitive)&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;userid&#039;&#039;&#039;&lt;br /&gt;
| int(10)  &lt;br /&gt;
| NULL&lt;br /&gt;
| Optional - general user id field - meaning depending on plugin&lt;br /&gt;
|-&lt;br /&gt;
| filesize&lt;br /&gt;
| int(10)&lt;br /&gt;
| &lt;br /&gt;
| size of file - bytes&lt;br /&gt;
|-&lt;br /&gt;
| mimetype&lt;br /&gt;
| varchar(100)&lt;br /&gt;
| NULL&lt;br /&gt;
| type of file&lt;br /&gt;
|-&lt;br /&gt;
| status&lt;br /&gt;
| int(10)&lt;br /&gt;
| &lt;br /&gt;
| general file status flag - will be used for lost or infected files&lt;br /&gt;
|-&lt;br /&gt;
| source&lt;br /&gt;
| text&lt;br /&gt;
| &lt;br /&gt;
| file source - usually url&lt;br /&gt;
|-&lt;br /&gt;
| author&lt;br /&gt;
| varchar(255)&lt;br /&gt;
|&lt;br /&gt;
| original author of file, used when importing from other systems&lt;br /&gt;
|-&lt;br /&gt;
| license&lt;br /&gt;
| varchar(255)&lt;br /&gt;
|&lt;br /&gt;
| license type, empty means site default&lt;br /&gt;
|-&lt;br /&gt;
| timecreated&lt;br /&gt;
| int(10)&lt;br /&gt;
| &lt;br /&gt;
| The time this file was created&lt;br /&gt;
|-&lt;br /&gt;
| timemodified&lt;br /&gt;
| int(10)&lt;br /&gt;
| &lt;br /&gt;
| The last time the file was last modified&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Indexes:&lt;br /&gt;
* non-unique index on (contextid, component, filearea, itemid)&lt;br /&gt;
* non-unique index on (contenthash)&lt;br /&gt;
* unique index on (pathnamehash).&lt;br /&gt;
&lt;br /&gt;
The plugin type does not need to be specified because it can be derived from the context. Items like blog that do not have their own context will use their own file area inside a suitable context. In this case, the user context.&lt;br /&gt;
&lt;br /&gt;
Entries with filename = &#039;.&#039; represent directories. Directory entries like this are created automatically when a file is added within them.&lt;br /&gt;
&lt;br /&gt;
Note: &#039;files&#039; plural is used even thought that goes against the [[Database|coding guidelines]] because &#039;file&#039; is a reserved word in some SQL dialects.&lt;br /&gt;
&lt;br /&gt;
===Implementation of basic operations===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Each plugin may directly access only files in own context and areas!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Low level access API is defined in &#039;&#039;file_storage&#039;&#039; class which is obtained from &amp;lt;code&amp;gt;get_file_storage()&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
====Storing a file====&lt;br /&gt;
&lt;br /&gt;
# Calculate the SHA1 hash of the file contents.&lt;br /&gt;
# Check if a file with this SHA1 hash already exists on disc in file directory or file trash. If not, store the file there.&lt;br /&gt;
# Add the record for this file to the files table using the low level address&lt;br /&gt;
&lt;br /&gt;
====Reading a file====&lt;br /&gt;
&lt;br /&gt;
# Fetch the record (which includes the SHA1 hash) for the file you want from the files table. You can fetch either all area files or quickly get one file with a specific contenthash.&lt;br /&gt;
# Retrieve the contents using the SHA1 hash from the file directory.&lt;br /&gt;
&lt;br /&gt;
====Deleting a file====&lt;br /&gt;
&lt;br /&gt;
# Delete the record from the files table.&lt;br /&gt;
# Verify if some other file is still needing the content, if not move the content file into file trash&lt;br /&gt;
# Later, admin/cron.php deletes content files from trash directory&lt;br /&gt;
&lt;br /&gt;
== File serving ==&lt;br /&gt;
&lt;br /&gt;
Deals with serving of files - browser requests file, Moodle sends it back. We have three main files. It is important to setup slasharguments on server properly (file.php/some/thing/xxx.jpg), any content that relies on relative links can not work without it (scorm, uploaded html pages, etc.).&lt;br /&gt;
&lt;br /&gt;
=== legacy file.php ===&lt;br /&gt;
&lt;br /&gt;
Serves legacy course files, the file name and parameter structure is critical for backwards compatibility of existing course content.&lt;br /&gt;
&lt;br /&gt;
 /file.php/courseid/dir/dir/filename.ext&lt;br /&gt;
&lt;br /&gt;
Internally the files are stored in &amp;lt;code&amp;gt;array(&#039;contextid&#039;=&amp;gt;$coursecontextid, &#039;component;=&amp;gt;&#039;course&#039;, &#039;filearea&#039;=&amp;gt;&#039;legacy&#039;, &#039;itemid&#039;=&amp;gt;0)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The legacy course files are completely disabled in all new courses created in 2.0. The major problem here is to how to educate our users that they can not make huge piles of files in each course any more.&lt;br /&gt;
&lt;br /&gt;
=== pluginfile.php ===&lt;br /&gt;
All plugins should use this script to serve all files.&lt;br /&gt;
* plugins decide about access control&lt;br /&gt;
* optional XSS protection - student submitted files must not be served with normal headers, we have to force download instead; ideally there should be second wwwroot for serving of untrusted files&lt;br /&gt;
* links to these files are constructed on the fly from the relative links stored in database, this means that plugin may link only own files&lt;br /&gt;
&lt;br /&gt;
Absolute file links need to be rewritten if html editing allowed in plugin. The links are stored internally as relative links. Before editing or display the internal link representation is converted to absolute links using simple str_replace() @@thipluginlink/summary@@/image.jpg --&amp;gt; /pluginfile.php/assignmentcontextid/intro/image.jpg, it is converted back to internal links before saving.&lt;br /&gt;
&lt;br /&gt;
Script parameters are virtual file names, in most cases the parameters match the low level file storage, but they do not have to:&lt;br /&gt;
&lt;br /&gt;
 /pluginfile.php/contextid/areaname/arbitrary/params/or/dirs/filename.ext&lt;br /&gt;
&lt;br /&gt;
pluginfile.php detects the type of plugin from context table, fetches basic info (like $course or $cm if appropriate) and calls plugin function (or later method) which does the access control and finally sends the file to user. &#039;&#039;areaname&#039;&#039; separates files by type and divides the context into several subtrees - for example &#039;&#039;summary&#039;&#039; files (images used in module intros), post attachments, etc.&lt;br /&gt;
&lt;br /&gt;
==== Assignment example ====&lt;br /&gt;
&lt;br /&gt;
 /pluginfile.php/assignmentcontextid/mod_assignment/intro/someimage.jpg&lt;br /&gt;
 /pluginfile.php/assignmentcontextid/mod_assignment/submission/submissionid/attachmentname.ext&lt;br /&gt;
 /pluginfile.php/assignmentcontextid/mod_assignment/allsubmissions/groupid/allsubmissionfiles.zip&lt;br /&gt;
&lt;br /&gt;
The last line example of virtual file that should created on the fly, it is not implemented yet.&lt;br /&gt;
&lt;br /&gt;
====scorm example====&lt;br /&gt;
&lt;br /&gt;
 /pluginfile.php/scormcontextid/mod_scorm/intro/someimage.jpg&lt;br /&gt;
 /pluginfile.php/scormcontextid/mod_scorm/content/revisionnumber/dir/somescormfile.js&lt;br /&gt;
&lt;br /&gt;
The revision counter is incremented when any file changes in order to prevent caching problems.&lt;br /&gt;
&lt;br /&gt;
====quiz example====&lt;br /&gt;
&lt;br /&gt;
 pluginfile.php/quizcontextid/mod_quiz/intro/niceimage.jpg&lt;br /&gt;
&lt;br /&gt;
====questions example====&lt;br /&gt;
&lt;br /&gt;
This section was out of date. See [[File_storage_conversion_Quiz_and_Questions]] for the latest thinking.&lt;br /&gt;
&lt;br /&gt;
====blog example====&lt;br /&gt;
Blog entries or notes in general do not have context id (because they live in system context, SYSCONTEXTID below is the id of system context).&lt;br /&gt;
The note attachments are always served with XSS protection on, ideally we should use separate wwwroot for this. Access control can be hardcoded.&lt;br /&gt;
&lt;br /&gt;
 /pluginfile.php/SYSCONTEXTID/blog/attachment/blogentryid/attachmentname.ext&lt;br /&gt;
&lt;br /&gt;
Internally stored in &amp;lt;code&amp;gt;array(&#039;contextid&#039;=&amp;gt;SYSCONTEXTID, &#039;component&#039;=&amp;gt;&#039;blog&#039;, &#039;filearea&#039;=&amp;gt;&#039;attachment&#039;, &#039;itemid&#039;=&amp;gt;$blogentryid)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 /pluginfile.php/SYSCONTEXTID/blog/post/blogentryid/embeddedimage.ext&lt;br /&gt;
&lt;br /&gt;
Internally stored in &amp;lt;code&amp;gt;array(&#039;contextid&#039;=&amp;gt;SYSCONTEXTID, &#039;component&#039;=&amp;gt;&#039;blog&#039;, &#039;filearea&#039;=&amp;gt;&#039;post&#039;, &#039;itemid&#039;=&amp;gt;$blogentryid)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Temporary files ===&lt;br /&gt;
Temporary files are usually used during the lifetime of one script only.&lt;br /&gt;
uses:&lt;br /&gt;
* exports&lt;br /&gt;
* imports&lt;br /&gt;
* processing by executable files (latex, mimetex)&lt;br /&gt;
&lt;br /&gt;
These files should never use utf-8 file names.&lt;br /&gt;
&lt;br /&gt;
=== Legacy file storage and serving ===&lt;br /&gt;
Going to use good-old separate directories in $CFG-&amp;gt;dataroot.&lt;br /&gt;
&lt;br /&gt;
file serving and storage:&lt;br /&gt;
# user avatars - user/pix.php&lt;br /&gt;
# group avatars - user/pixgroup.php&lt;br /&gt;
# tex, algebra - filter/tex/* and filter/algebra/*&lt;br /&gt;
# rss cache (?full rss rewrite soon?) - backwards compatibility only rss/file.php&lt;br /&gt;
&lt;br /&gt;
only storage:&lt;br /&gt;
#sessions&lt;br /&gt;
&lt;br /&gt;
== File browsing API ==&lt;br /&gt;
&lt;br /&gt;
This is what other parts of Moodle use to access files that they do not own.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Class: file_browser ===&lt;br /&gt;
&lt;br /&gt;
=== Class: file_info and subclasses ===&lt;br /&gt;
&lt;br /&gt;
== File related user interfaces ==&lt;br /&gt;
&lt;br /&gt;
All files are obtained through from the file repositories.&lt;br /&gt;
&lt;br /&gt;
=== Formslib fields ===&lt;br /&gt;
* file picker&lt;br /&gt;
* file manager&lt;br /&gt;
* file upload (obsolete, do not use)&lt;br /&gt;
&lt;br /&gt;
=== Integration with the HTML editor ===&lt;br /&gt;
&lt;br /&gt;
Each instance of the HTML editor can be told to store related files in a particular file area.&lt;br /&gt;
&lt;br /&gt;
During editing, files are stored in a draft files area. Then when the form is submitted they are moved into the real file area.&lt;br /&gt;
&lt;br /&gt;
Files are selected using the repository file picker.&lt;br /&gt;
&lt;br /&gt;
=== Legacy file manager ===&lt;br /&gt;
&lt;br /&gt;
Available only for legacy reasons. It is not supposed to be used.&lt;br /&gt;
&lt;br /&gt;
All the contexts, file areas and files now form a single huge tree structure, although each user only has access to certain parts of that tree. The file manager (files/index.php) allow users to browse this tree, and manage files within it, according to the level of permissions they have.&lt;br /&gt;
&lt;br /&gt;
Single pane file manager is hard to implement without drag &amp;amp; drop which is notoriously problematic in web based applications. I propose to implement a two pane commander-style file manager. Two pane manager allows you to easily copy/move files between two different contexts (ex: courses).&lt;br /&gt;
&lt;br /&gt;
File manager must not interact directly with filesystem API, instead each module should return traversable tree of files and directories with both real and localised names (localised names are needed for dirs like backupdata).&lt;br /&gt;
&lt;br /&gt;
== Backwards compatibility ==&lt;br /&gt;
&lt;br /&gt;
=== Content backwards compatibility ===&lt;br /&gt;
&lt;br /&gt;
This should be preserved as much as possible. This will involve rewriting links in content during the upgrade to 2.0. &lt;br /&gt;
&lt;br /&gt;
Some new features (like resource sharing - if implemented) may not work with existing data that still uses files from course files area.&lt;br /&gt;
&lt;br /&gt;
There might be a breakage of links due to special characters stripping in uploaded files which will not match the links in uploaded html files any more. This should not be very common I hope.&lt;br /&gt;
&lt;br /&gt;
===Code backwards compatibility===&lt;br /&gt;
&lt;br /&gt;
Other Moodle code (for example plugins) will have to be converted to the new APIs. See [[Using_the_file_API]] for guidance.&lt;br /&gt;
&lt;br /&gt;
It is not possible to provide backwards-compatibility here. For example, the old $CFG-&amp;gt;dataroot/$courseid/ will no longer exist, and there is no way to emulate that, so we won&#039;t try.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Upgrade and migration ==&lt;br /&gt;
&lt;br /&gt;
When a site is upgraded to Moodle 2.0, all the files in moodledata will have to be migrated. This is going to be a pain, like DML/DDL was :-(&lt;br /&gt;
&lt;br /&gt;
The upgrade process should be interruptible (like the Unicode upgrade was) so it can be stopped/restarted any time.&lt;br /&gt;
&lt;br /&gt;
=== Migration of content ===&lt;br /&gt;
&lt;br /&gt;
* resources - move files to new resource content file area; can be done automatically for pdf, image resources; definitely not accurate for uploaded web pages&lt;br /&gt;
* questions - image file moved to new area, image tag appended to questions&lt;br /&gt;
* moddata files - the easiest part, just move to new storage&lt;br /&gt;
* coursefiles - there might be many outdated files :-( :-(&lt;br /&gt;
* rss feeds links in readers - will be broken, the new security related code would break it anyway&lt;br /&gt;
&lt;br /&gt;
=== Moving files to files table and file pool ===&lt;br /&gt;
&lt;br /&gt;
The migration process must be interruptable because it might take a very long time. The files would be moved from old location, the restarting would be straightforward.&lt;br /&gt;
&lt;br /&gt;
Proposed stages:&lt;br /&gt;
#migration of all course files except moddata - finish marked by some $CFG-&amp;gt;files_migrated=true; - this step breaks the old file manager and html editor integration&lt;br /&gt;
#migration of blog attachments&lt;br /&gt;
#migration of question files&lt;br /&gt;
#migration of moddata files - each module is responsible to copy data from converted coursefiles or directly from moddata which is not converted automatically&lt;br /&gt;
&lt;br /&gt;
Some people use symbolic links in coursefiles - we must make sure that those will be copied to new storage in both places, though they can not be linked any more - anybody wanting to have content synced will need to move the files to some repository and set up the sync again.&lt;br /&gt;
&lt;br /&gt;
::Talked about a double task here, when migrating course files to module areas:&lt;br /&gt;
::# Parse html files to detect all the dependencies and move them together.&lt;br /&gt;
::# Fallback in pluginfile.php so, if something isn&#039;t found in module filearea, search for it in course filearea, copying it and finally, serving it.&lt;br /&gt;
&lt;br /&gt;
:: Also we talked about the possibility of add a new setting to resource in order to define if it should work against old coursefiles or new autocontained file areas. Migrated resources will point to old coursefiles while new ones will enforce autocontained file areas.&lt;br /&gt;
&lt;br /&gt;
:: it seems that only resource files will be really complex (because allow arbitrary HTML inclusion). The rest (labels, intros... doesn&#039;t) and should be easier to parse.&lt;br /&gt;
&lt;br /&gt;
::[[User:Eloy Lafuente (stronk7)|Eloy Lafuente (stronk7)]] 19:00, 29 June 2008 (CDT)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Other issues ==&lt;br /&gt;
&lt;br /&gt;
=== Unicode support in zip format ===&lt;br /&gt;
&lt;br /&gt;
Zip format is an old standard for compressing files. It was created long before Unicode existed, and Unicode support was only recently added. There are several ways used for encoding of non-ASCII characters in path names, but unfortunately it is not very standardised. Most Windows packers use DOS encoding.&lt;br /&gt;
&lt;br /&gt;
Client software:&lt;br /&gt;
* Windows built-in compression - bundled with Windows, non-standard DOS encoding only&lt;br /&gt;
* WinZip - shareware, Unicode option (since v11.2)&lt;br /&gt;
* TotalCommander - shareware, single byte(DOS) encoding only&lt;br /&gt;
* 7-Zip - free, Unicode or DOS encoding depending on characters used in file name (since v4.58beta)&lt;br /&gt;
* Info-ZIP - free, uses some weird character set conversions&lt;br /&gt;
&lt;br /&gt;
PHP extraction:&lt;br /&gt;
* Info-ZIP binary execution - no Unicode support at all, mangles character sets in file names (depends on OS, see docs), files must be copied to temp directory before compression and after extraction&lt;br /&gt;
* PclZip PHP library - reads single byte encoded names only, problems with random problems and higher memory usage.&lt;br /&gt;
* Zip PHP extension - kind of works in latest PHP versions&lt;br /&gt;
&lt;br /&gt;
Large file support:&lt;br /&gt;
PHP running under 32bit operating systems does not support files &amp;gt;2GB (do not expect fix before PHP 6). This might be a potential problem for larger backups.&lt;br /&gt;
&lt;br /&gt;
Tar Alternative:&lt;br /&gt;
* tar with gzip compression - easy to implement in PHP + zlib extension (PclTar, Tar from PEAR or custom code)&lt;br /&gt;
* no problem with unicode in *nix, Windows again expects DOS encoding :-(&lt;br /&gt;
* seems suitable for backup/restore - yay!&lt;br /&gt;
&lt;br /&gt;
Summary:&lt;br /&gt;
# added zip processing class that fully hides the underlying library&lt;br /&gt;
# using single byte encoding &amp;quot;garbage in/garbage out&amp;quot; approach for encoding of files in zip archives; add new &#039;zipencoding&#039; string into lang packs (ex: cp852 DOS charset for Czech locale) and use it during extraction (we might support true unicode later when PHP Zip extension does that)&lt;br /&gt;
&lt;br /&gt;
== Not implemented yet ==&lt;br /&gt;
* antivirus scanning - this needs a different api because the upload of files is now handled via repository plugins&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[Using the File API]]&lt;br /&gt;
* [[Repository API]]&lt;br /&gt;
* [[Portfolio API]]&lt;br /&gt;
* [[Resource module file API migration]]&lt;br /&gt;
* MDL-14589 - File API Meta issue&lt;br /&gt;
&lt;br /&gt;
[[Category:Files]]&lt;br /&gt;
[[Category:Interfaces]]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
	<entry>
		<id>https://docs.moodle.org/dev/index.php?title=File_API_internals&amp;diff=31436</id>
		<title>File API internals</title>
		<link rel="alternate" type="text/html" href="https://docs.moodle.org/dev/index.php?title=File_API_internals&amp;diff=31436"/>
		<updated>2012-01-16T03:04:57Z</updated>

		<summary type="html">&lt;p&gt;Dongsheng: /* See also */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;{{Work in progress}}&lt;br /&gt;
{{Infobox Project&lt;br /&gt;
|name = File API&lt;br /&gt;
|state = Implemented&lt;br /&gt;
|tracker = MDL-14589&lt;br /&gt;
|discussion = n/a&lt;br /&gt;
|assignee = [[User:Petr Škoda (škoďák)|Petr Škoda (škoďák)]]&lt;br /&gt;
}}&lt;br /&gt;
{{Moodle 2.0}}&lt;br /&gt;
&lt;br /&gt;
==Objectives==&lt;br /&gt;
&lt;br /&gt;
The goals of the new File API are: &lt;br /&gt;
&lt;br /&gt;
* allow files to be stored within Moodle, as part of the content (as we do now).&lt;br /&gt;
* use a consistent and flexible approach for all file handling throughout Moodle.&lt;br /&gt;
* give modules control over which users can access a file, using capabilities and other local rules.&lt;br /&gt;
* make it easy to determine which parts of Moodle use which files, to simplify operations like backup and restore.&lt;br /&gt;
* track where files originally came from.&lt;br /&gt;
* avoid redundant storage, when the same file is used twice.&lt;br /&gt;
* fully support Unicode file names, irrespective of the capabilities of the underlying file system.&lt;br /&gt;
&lt;br /&gt;
==Overview==&lt;br /&gt;
&lt;br /&gt;
The File API is a set of core interfaces to allow the rest of Moodle to store, serve and manage files. It applies only to files that are part of the Moodle site&#039;s content. It is not used for internal files, such as those in the following subdirectories of dataroot: temp, lang, cache, environment, filter, search, sessions, upgradelogs, ...&lt;br /&gt;
&lt;br /&gt;
The API can be subdivided into the following parts:&lt;br /&gt;
; File storage&lt;br /&gt;
: Low level file storage without access control information. Stores the content of files on disc, with metadata in associated database tables. &lt;br /&gt;
; File serving&lt;br /&gt;
: Lets users accessing a Moodle site get the files (file.php, draftfile.php, pluginfile.php, userfile.php, etc.)&lt;br /&gt;
:* Serve the files on request&lt;br /&gt;
:* with appropriate security checks&lt;br /&gt;
; File related user interfaces&lt;br /&gt;
: Provides the interface for (lib/form/file.php, filemanager.php, filepicker.php and files/index.php, draftfiles.php)&lt;br /&gt;
:* Form elements allowing users to select a file using the file picker, and have it stored within Moodle.&lt;br /&gt;
:* UI for users to manage their files, replacing the old course files UI&lt;br /&gt;
; File browsing API&lt;br /&gt;
: Allows code to browse and optionally manipulate the file areas&lt;br /&gt;
:* find information about available files in each area.&lt;br /&gt;
:* print links to files.&lt;br /&gt;
:* optionally move/rename/copy/delete/etc.&lt;br /&gt;
&lt;br /&gt;
== File API internals ==&lt;br /&gt;
&lt;br /&gt;
=== File storage on disk ===&lt;br /&gt;
&lt;br /&gt;
Files are stored in $CFG-&amp;gt;dataroot (also known as moodledata) in the filedir subfolder.&lt;br /&gt;
&lt;br /&gt;
Files are stored according to the SHA1 hash of their content. This means each file with particular contents is stored once, irrespective of how many times it is included in different places, even if it is referred to by different names. (This idea comes from the git version control system.) To relate a file on disc to a user-comprehensible path or filename, you need to use the &#039;&#039;files&#039;&#039; database table. See the next section.&lt;br /&gt;
&lt;br /&gt;
Suppose a file has SHA1 hash 081371cb102fa559e81993fddc230c79205232ce. Then it will be stored in on disc as moodledata/filedir/08/13/081371cb102fa559e81993fddc230c79205232ce.&lt;br /&gt;
&lt;br /&gt;
This means Moodle can not store two files with the same SHA1 hash, luckily it is extremely unlikely that this would ever happen. Technically it is also possible to implement reliable collision tests (with some performance cost), for now we just test file lengths in addition to SHA1 hash.&lt;br /&gt;
&lt;br /&gt;
=== Files table ===&lt;br /&gt;
&lt;br /&gt;
This table contains one entry for each usage of a file. Enough information is kept here so that the file can be fully identified and retrieved again if necessary. It is necessary because some databases have hard limit on index size.&lt;br /&gt;
&lt;br /&gt;
If, for example, the same image is used in a user&#039;s profile, and a forum post, then there will be two rows in this table, one for each use of the file, and Moodle will treat the two as separate files, even though the file is only stored once on disc.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;nicetable&amp;quot;&lt;br /&gt;
! Field&lt;br /&gt;
! Type&lt;br /&gt;
! Default&lt;br /&gt;
! Info&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;id&#039;&#039;&#039; &lt;br /&gt;
| int(10)  &lt;br /&gt;
| auto-incrementing&lt;br /&gt;
| The unique ID for this file.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;contenthash&#039;&#039;&#039;&lt;br /&gt;
| varchar(40)&lt;br /&gt;
|  &lt;br /&gt;
| The sha1 hash of content.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;pathnamehash&#039;&#039;&#039;&lt;br /&gt;
| varchar(40)&lt;br /&gt;
| &lt;br /&gt;
| The sha1 hash of &amp;quot;/contextid/component/filearea/itemid/filepath/filename.ext&amp;quot; - prevents file duplicates and allows fast lookup.  It is necessary because some databases have hard limit on index size.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;contextid&#039;&#039;&#039; &lt;br /&gt;
| int(10)&lt;br /&gt;
| &lt;br /&gt;
| The context id defined in context table - identifies the instance of plugin owning the file.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;component&#039;&#039;&#039;&lt;br /&gt;
| varchar(50)&lt;br /&gt;
|&lt;br /&gt;
| Like &amp;quot;mod_forum&amp;quot;, &amp;quot;course&amp;quot;, &amp;quot;mod_assignment&amp;quot;, &amp;quot;backup&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;filearea&#039;&#039;&#039;&lt;br /&gt;
| varchar(50)&lt;br /&gt;
|&lt;br /&gt;
| Like &amp;quot;submissions&amp;quot;, &amp;quot;intro&amp;quot; and &amp;quot;content&amp;quot; (images and swf linked from summaries), etc.; &amp;quot;blogs&amp;quot; and &amp;quot;userfiles&amp;quot; are special case that live at the system context.&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;itemid&#039;&#039;&#039;&lt;br /&gt;
| int(10)&lt;br /&gt;
| &lt;br /&gt;
| Some plugin specific item id (eg. forum post, blog entry or assignment submission or user id for user files)&lt;br /&gt;
|-&lt;br /&gt;
| filepath&lt;br /&gt;
| text&lt;br /&gt;
| &lt;br /&gt;
| relative path to file from module content root, useful in Scorm and Resource mod - most of the mods do not need this&lt;br /&gt;
|-&lt;br /&gt;
| filename&lt;br /&gt;
| varchar(255)&lt;br /&gt;
| &lt;br /&gt;
| The full Unicode name of this file (case sensitive)&lt;br /&gt;
|-&lt;br /&gt;
| &#039;&#039;&#039;userid&#039;&#039;&#039;&lt;br /&gt;
| int(10)  &lt;br /&gt;
| NULL&lt;br /&gt;
| Optional - general user id field - meaning depending on plugin&lt;br /&gt;
|-&lt;br /&gt;
| filesize&lt;br /&gt;
| int(10)&lt;br /&gt;
| &lt;br /&gt;
| size of file - bytes&lt;br /&gt;
|-&lt;br /&gt;
| mimetype&lt;br /&gt;
| varchar(100)&lt;br /&gt;
| NULL&lt;br /&gt;
| type of file&lt;br /&gt;
|-&lt;br /&gt;
| status&lt;br /&gt;
| int(10)&lt;br /&gt;
| &lt;br /&gt;
| general file status flag - will be used for lost or infected files&lt;br /&gt;
|-&lt;br /&gt;
| source&lt;br /&gt;
| text&lt;br /&gt;
| &lt;br /&gt;
| file source - usually url&lt;br /&gt;
|-&lt;br /&gt;
| author&lt;br /&gt;
| varchar(255)&lt;br /&gt;
|&lt;br /&gt;
| original author of file, used when importing from other systems&lt;br /&gt;
|-&lt;br /&gt;
| license&lt;br /&gt;
| varchar(255)&lt;br /&gt;
|&lt;br /&gt;
| license type, empty means site default&lt;br /&gt;
|-&lt;br /&gt;
| timecreated&lt;br /&gt;
| int(10)&lt;br /&gt;
| &lt;br /&gt;
| The time this file was created&lt;br /&gt;
|-&lt;br /&gt;
| timemodified&lt;br /&gt;
| int(10)&lt;br /&gt;
| &lt;br /&gt;
| The last time the file was last modified&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Indexes:&lt;br /&gt;
* non-unique index on (contextid, component, filearea, itemid)&lt;br /&gt;
* non-unique index on (contenthash)&lt;br /&gt;
* unique index on (pathnamehash).&lt;br /&gt;
&lt;br /&gt;
The plugin type does not need to be specified because it can be derived from the context. Items like blog that do not have their own context will use their own file area inside a suitable context. In this case, the user context.&lt;br /&gt;
&lt;br /&gt;
Entries with filename = &#039;.&#039; represent directories. Directory entries like this are created automatically when a file is added within them.&lt;br /&gt;
&lt;br /&gt;
Note: &#039;files&#039; plural is used even thought that goes against the [[Database|coding guidelines]] because &#039;file&#039; is a reserved word in some SQL dialects.&lt;br /&gt;
&lt;br /&gt;
===Implementation of basic operations===&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Each plugin may directly access only files in own context and areas!&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
Low level access API is defined in &#039;&#039;file_storage&#039;&#039; class which is obtained from &amp;lt;code&amp;gt;get_file_storage()&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
====Storing a file====&lt;br /&gt;
&lt;br /&gt;
# Calculate the SHA1 hash of the file contents.&lt;br /&gt;
# Check if a file with this SHA1 hash already exists on disc in file directory or file trash. If not, store the file there.&lt;br /&gt;
# Add the record for this file to the files table using the low level address&lt;br /&gt;
&lt;br /&gt;
====Reading a file====&lt;br /&gt;
&lt;br /&gt;
# Fetch the record (which includes the SHA1 hash) for the file you want from the files table. You can fetch either all area files or quickly get one file with a specific contenthash.&lt;br /&gt;
# Retrieve the contents using the SHA1 hash from the file directory.&lt;br /&gt;
&lt;br /&gt;
====Deleting a file====&lt;br /&gt;
&lt;br /&gt;
# Delete the record from the files table.&lt;br /&gt;
# Verify if some other file is still needing the content, if not move the content file into file trash&lt;br /&gt;
# Later, admin/cron.php deletes content files from trash directory&lt;br /&gt;
&lt;br /&gt;
== File serving ==&lt;br /&gt;
&lt;br /&gt;
Deals with serving of files - browser requests file, Moodle sends it back. We have three main files. It is important to setup slasharguments on server properly (file.php/some/thing/xxx.jpg), any content that relies on relative links can not work without it (scorm, uploaded html pages, etc.).&lt;br /&gt;
&lt;br /&gt;
=== legacy file.php ===&lt;br /&gt;
&lt;br /&gt;
Serves legacy course files, the file name and parameter structure is critical for backwards compatibility of existing course content.&lt;br /&gt;
&lt;br /&gt;
 /file.php/courseid/dir/dir/filename.ext&lt;br /&gt;
&lt;br /&gt;
Internally the files are stored in &amp;lt;code&amp;gt;array(&#039;contextid&#039;=&amp;gt;$coursecontextid, &#039;component;=&amp;gt;&#039;course&#039;, &#039;filearea&#039;=&amp;gt;&#039;legacy&#039;, &#039;itemid&#039;=&amp;gt;0)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The legacy course files are completely disabled in all new courses created in 2.0. The major problem here is to how to educate our users that they can not make huge piles of files in each course any more.&lt;br /&gt;
&lt;br /&gt;
=== pluginfile.php ===&lt;br /&gt;
All plugins should use this script to serve all files.&lt;br /&gt;
* plugins decide about access control&lt;br /&gt;
* optional XSS protection - student submitted files must not be served with normal headers, we have to force download instead; ideally there should be second wwwroot for serving of untrusted files&lt;br /&gt;
* links to these files are constructed on the fly from the relative links stored in database, this means that plugin may link only own files&lt;br /&gt;
&lt;br /&gt;
Absolute file links need to be rewritten if html editing allowed in plugin. The links are stored internally as relative links. Before editing or display the internal link representation is converted to absolute links using simple str_replace() @@thipluginlink/summary@@/image.jpg --&amp;gt; /pluginfile.php/assignmentcontextid/intro/image.jpg, it is converted back to internal links before saving.&lt;br /&gt;
&lt;br /&gt;
Script parameters are virtual file names, in most cases the parameters match the low level file storage, but they do not have to:&lt;br /&gt;
&lt;br /&gt;
 /pluginfile.php/contextid/areaname/arbitrary/params/or/dirs/filename.ext&lt;br /&gt;
&lt;br /&gt;
pluginfile.php detects the type of plugin from context table, fetches basic info (like $course or $cm if appropriate) and calls plugin function (or later method) which does the access control and finally sends the file to user. &#039;&#039;areaname&#039;&#039; separates files by type and divides the context into several subtrees - for example &#039;&#039;summary&#039;&#039; files (images used in module intros), post attachments, etc.&lt;br /&gt;
&lt;br /&gt;
==== Assignment example ====&lt;br /&gt;
&lt;br /&gt;
 /pluginfile.php/assignmentcontextid/mod_assignment/intro/someimage.jpg&lt;br /&gt;
 /pluginfile.php/assignmentcontextid/mod_assignment/submission/submissionid/attachmentname.ext&lt;br /&gt;
 /pluginfile.php/assignmentcontextid/mod_assignment/allsubmissions/groupid/allsubmissionfiles.zip&lt;br /&gt;
&lt;br /&gt;
The last line example of virtual file that should created on the fly, it is not implemented yet.&lt;br /&gt;
&lt;br /&gt;
====scorm example====&lt;br /&gt;
&lt;br /&gt;
 /pluginfile.php/scormcontextid/mod_scorm/intro/someimage.jpg&lt;br /&gt;
 /pluginfile.php/scormcontextid/mod_scorm/content/revisionnumber/dir/somescormfile.js&lt;br /&gt;
&lt;br /&gt;
The revision counter is incremented when any file changes in order to prevent caching problems.&lt;br /&gt;
&lt;br /&gt;
====quiz example====&lt;br /&gt;
&lt;br /&gt;
 pluginfile.php/quizcontextid/mod_quiz/intro/niceimage.jpg&lt;br /&gt;
&lt;br /&gt;
====questions example====&lt;br /&gt;
&lt;br /&gt;
This section was out of date. See [[File_storage_conversion_Quiz_and_Questions]] for the latest thinking.&lt;br /&gt;
&lt;br /&gt;
====blog example====&lt;br /&gt;
Blog entries or notes in general do not have context id (because they live in system context, SYSCONTEXTID below is the id of system context).&lt;br /&gt;
The note attachments are always served with XSS protection on, ideally we should use separate wwwroot for this. Access control can be hardcoded.&lt;br /&gt;
&lt;br /&gt;
 /pluginfile.php/SYSCONTEXTID/blog/attachment/blogentryid/attachmentname.ext&lt;br /&gt;
&lt;br /&gt;
Internally stored in &amp;lt;code&amp;gt;array(&#039;contextid&#039;=&amp;gt;SYSCONTEXTID, &#039;component&#039;=&amp;gt;&#039;blog&#039;, &#039;filearea&#039;=&amp;gt;&#039;attachment&#039;, &#039;itemid&#039;=&amp;gt;$blogentryid)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
 /pluginfile.php/SYSCONTEXTID/blog/post/blogentryid/embeddedimage.ext&lt;br /&gt;
&lt;br /&gt;
Internally stored in &amp;lt;code&amp;gt;array(&#039;contextid&#039;=&amp;gt;SYSCONTEXTID, &#039;component&#039;=&amp;gt;&#039;blog&#039;, &#039;filearea&#039;=&amp;gt;&#039;post&#039;, &#039;itemid&#039;=&amp;gt;$blogentryid)&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Temporary files ===&lt;br /&gt;
Temporary files are usually used during the lifetime of one script only.&lt;br /&gt;
uses:&lt;br /&gt;
* exports&lt;br /&gt;
* imports&lt;br /&gt;
* processing by executable files (latex, mimetex)&lt;br /&gt;
&lt;br /&gt;
These files should never use utf-8 file names.&lt;br /&gt;
&lt;br /&gt;
=== Legacy file storage and serving ===&lt;br /&gt;
Going to use good-old separate directories in $CFG-&amp;gt;dataroot.&lt;br /&gt;
&lt;br /&gt;
file serving and storage:&lt;br /&gt;
# user avatars - user/pix.php&lt;br /&gt;
# group avatars - user/pixgroup.php&lt;br /&gt;
# tex, algebra - filter/tex/* and filter/algebra/*&lt;br /&gt;
# rss cache (?full rss rewrite soon?) - backwards compatibility only rss/file.php&lt;br /&gt;
&lt;br /&gt;
only storage:&lt;br /&gt;
#sessions&lt;br /&gt;
&lt;br /&gt;
== File browsing API ==&lt;br /&gt;
&lt;br /&gt;
This is what other parts of Moodle use to access files that they do not own.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== Class: file_browser ===&lt;br /&gt;
&lt;br /&gt;
=== Class: file_info and subclasses ===&lt;br /&gt;
&lt;br /&gt;
== File related user interfaces ==&lt;br /&gt;
&lt;br /&gt;
All files are obtained through from the file repositories.&lt;br /&gt;
&lt;br /&gt;
=== Formslib fields ===&lt;br /&gt;
* file picker&lt;br /&gt;
* file manager&lt;br /&gt;
* file upload (obsolete, do not use)&lt;br /&gt;
&lt;br /&gt;
=== Integration with the HTML editor ===&lt;br /&gt;
&lt;br /&gt;
Each instance of the HTML editor can be told to store related files in a particular file area.&lt;br /&gt;
&lt;br /&gt;
During editing, files are stored in a draft files area. Then when the form is submitted they are moved into the real file area.&lt;br /&gt;
&lt;br /&gt;
Files are selected using the repository file picker.&lt;br /&gt;
&lt;br /&gt;
=== Legacy file manager ===&lt;br /&gt;
&lt;br /&gt;
Available only for legacy reasons. It is not supposed to be used.&lt;br /&gt;
&lt;br /&gt;
All the contexts, file areas and files now form a single huge tree structure, although each user only has access to certain parts of that tree. The file manager (files/index.php) allow users to browse this tree, and manage files within it, according to the level of permissions they have.&lt;br /&gt;
&lt;br /&gt;
Single pane file manager is hard to implement without drag &amp;amp; drop which is notoriously problematic in web based applications. I propose to implement a two pane commander-style file manager. Two pane manager allows you to easily copy/move files between two different contexts (ex: courses).&lt;br /&gt;
&lt;br /&gt;
File manager must not interact directly with filesystem API, instead each module should return traversable tree of files and directories with both real and localised names (localised names are needed for dirs like backupdata).&lt;br /&gt;
&lt;br /&gt;
== Backwards compatibility ==&lt;br /&gt;
&lt;br /&gt;
=== Content backwards compatibility ===&lt;br /&gt;
&lt;br /&gt;
This should be preserved as much as possible. This will involve rewriting links in content during the upgrade to 2.0. &lt;br /&gt;
&lt;br /&gt;
Some new features (like resource sharing - if implemented) may not work with existing data that still uses files from course files area.&lt;br /&gt;
&lt;br /&gt;
There might be a breakage of links due to special characters stripping in uploaded files which will not match the links in uploaded html files any more. This should not be very common I hope.&lt;br /&gt;
&lt;br /&gt;
===Code backwards compatibility===&lt;br /&gt;
&lt;br /&gt;
Other Moodle code (for example plugins) will have to be converted to the new APIs. See [[Using_the_file_API]] for guidance.&lt;br /&gt;
&lt;br /&gt;
It is not possible to provide backwards-compatibility here. For example, the old $CFG-&amp;gt;dataroot/$courseid/ will no longer exist, and there is no way to emulate that, so we won&#039;t try.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Upgrade and migration ==&lt;br /&gt;
&lt;br /&gt;
When a site is upgraded to Moodle 2.0, all the files in moodledata will have to be migrated. This is going to be a pain, like DML/DDL was :-(&lt;br /&gt;
&lt;br /&gt;
The upgrade process should be interruptible (like the Unicode upgrade was) so it can be stopped/restarted any time.&lt;br /&gt;
&lt;br /&gt;
=== Migration of content ===&lt;br /&gt;
&lt;br /&gt;
* resources - move files to new resource content file area; can be done automatically for pdf, image resources; definitely not accurate for uploaded web pages&lt;br /&gt;
* questions - image file moved to new area, image tag appended to questions&lt;br /&gt;
* moddata files - the easiest part, just move to new storage&lt;br /&gt;
* coursefiles - there might be many outdated files :-( :-(&lt;br /&gt;
* rss feeds links in readers - will be broken, the new security related code would break it anyway&lt;br /&gt;
&lt;br /&gt;
=== Moving files to files table and file pool ===&lt;br /&gt;
&lt;br /&gt;
The migration process must be interruptable because it might take a very long time. The files would be moved from old location, the restarting would be straightforward.&lt;br /&gt;
&lt;br /&gt;
Proposed stages:&lt;br /&gt;
#migration of all course files except moddata - finish marked by some $CFG-&amp;gt;files_migrated=true; - this step breaks the old file manager and html editor integration&lt;br /&gt;
#migration of blog attachments&lt;br /&gt;
#migration of question files&lt;br /&gt;
#migration of moddata files - each module is responsible to copy data from converted coursefiles or directly from moddata which is not converted automatically&lt;br /&gt;
&lt;br /&gt;
Some people use symbolic links in coursefiles - we must make sure that those will be copied to new storage in both places, though they can not be linked any more - anybody wanting to have content synced will need to move the files to some repository and set up the sync again.&lt;br /&gt;
&lt;br /&gt;
::Talked about a double task here, when migrating course files to module areas:&lt;br /&gt;
::# Parse html files to detect all the dependencies and move them together.&lt;br /&gt;
::# Fallback in pluginfile.php so, if something isn&#039;t found in module filearea, search for it in course filearea, copying it and finally, serving it.&lt;br /&gt;
&lt;br /&gt;
:: Also we talked about the possibility of add a new setting to resource in order to define if it should work against old coursefiles or new autocontained file areas. Migrated resources will point to old coursefiles while new ones will enforce autocontained file areas.&lt;br /&gt;
&lt;br /&gt;
:: it seems that only resource files will be really complex (because allow arbitrary HTML inclusion). The rest (labels, intros... doesn&#039;t) and should be easier to parse.&lt;br /&gt;
&lt;br /&gt;
::[[User:Eloy Lafuente (stronk7)|Eloy Lafuente (stronk7)]] 19:00, 29 June 2008 (CDT)&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
== Other issues ==&lt;br /&gt;
&lt;br /&gt;
=== Unicode support in zip format ===&lt;br /&gt;
&lt;br /&gt;
Zip format is an old standard for compressing files. It was created long before Unicode existed, and Unicode support was only recently added. There are several ways used for encoding of non-ASCII characters in path names, but unfortunately it is not very standardised. Most Windows packers use DOS encoding.&lt;br /&gt;
&lt;br /&gt;
Client software:&lt;br /&gt;
* Windows built-in compression - bundled with Windows, non-standard DOS encoding only&lt;br /&gt;
* WinZip - shareware, Unicode option (since v11.2)&lt;br /&gt;
* TotalCommander - shareware, single byte(DOS) encoding only&lt;br /&gt;
* 7-Zip - free, Unicode or DOS encoding depending on characters used in file name (since v4.58beta)&lt;br /&gt;
* Info-ZIP - free, uses some weird character set conversions&lt;br /&gt;
&lt;br /&gt;
PHP extraction:&lt;br /&gt;
* Info-ZIP binary execution - no Unicode support at all, mangles character sets in file names (depends on OS, see docs), files must be copied to temp directory before compression and after extraction&lt;br /&gt;
* PclZip PHP library - reads single byte encoded names only, problems with random problems and higher memory usage.&lt;br /&gt;
* Zip PHP extension - kind of works in latest PHP versions&lt;br /&gt;
&lt;br /&gt;
Large file support:&lt;br /&gt;
PHP running under 32bit operating systems does not support files &amp;gt;2GB (do not expect fix before PHP 6). This might be a potential problem for larger backups.&lt;br /&gt;
&lt;br /&gt;
Tar Alternative:&lt;br /&gt;
* tar with gzip compression - easy to implement in PHP + zlib extension (PclTar, Tar from PEAR or custom code)&lt;br /&gt;
* no problem with unicode in *nix, Windows again expects DOS encoding :-(&lt;br /&gt;
* seems suitable for backup/restore - yay!&lt;br /&gt;
&lt;br /&gt;
Summary:&lt;br /&gt;
# added zip processing class that fully hides the underlying library&lt;br /&gt;
# using single byte encoding &amp;quot;garbage in/garbage out&amp;quot; approach for encoding of files in zip archives; add new &#039;zipencoding&#039; string into lang packs (ex: cp852 DOS charset for Czech locale) and use it during extraction (we might support true unicode later when PHP Zip extension does that)&lt;br /&gt;
&lt;br /&gt;
== Not implemented yet ==&lt;br /&gt;
* antivirus scanning - this needs a different api because the upload of files is now handled via repository plugins&lt;br /&gt;
&lt;br /&gt;
== See also ==&lt;br /&gt;
&lt;br /&gt;
* [[Using the File API]]&lt;br /&gt;
* [[Repository API]]&lt;br /&gt;
* [[Portfolio API]]&lt;br /&gt;
* [[Resource module file API migration]]&lt;br /&gt;
* MDL-14589 - File API Meta issue&lt;br /&gt;
&lt;br /&gt;
[[Category:Files]]&lt;br /&gt;
[[Category:Interfaces]]&lt;/div&gt;</summary>
		<author><name>Dongsheng</name></author>
	</entry>
</feed>