Note:

If you want to create a new page for developers, you should create it on the Moodle Developer Resource site.

Filters

From MoodleDocs
Important:

This content of this page has been updated and migrated to the new Moodle Developer Resources. The information contained on the page should no longer be seen up-to-date.

Why not view this page on the new site and help us to migrate more content to the new site!

Please note: This page contains information for developers. You may prefer to read the information about filters for teachers and administrators.

Moodle 2.0



Filters are a way to automatically transform content before it is output. For example

  • render embedded equations to images (the TeX filter)
  • Links to media files can be automatically converted to an embedded applet for playing the media.
  • Mentions of glossary terms can be automatically converted to links.

The possibilities are endless. There are a number of standard filters included with Moodle, or you can create your own. Filters are one of the easiest types of plugin to create. This page explains how.

Creating a basic filter

During this tutorial, we will build a simple example filter. We will make one that adds the word 'hello' before every occurrence of the word 'world'.

1. Since our filter is not part of a module, we should put it inside the 'filter' folder. Therefore, we create a directory called 'filter/helloworld'.

2. Inside that folder, we create a file called 'filter.php'.

3. Inside that PHP file, we define a class called filter_helloworld, that extends the moodle_text_filter class. (Note that the file doesn't end by closing the php section with the '?>' tag. This is standard for Moodle and is used to avoid problems with trailing whitespace.)

<?php
class filter_helloworld extends moodle_text_filter {
    // ...
}

4. Inside that class, we have to define one method, called 'filter'. This takes the HTML to be filtered as an argument. The method should then transform that, and return the processed text. Replace the '// ...' above with

class filter_helloworld extends moodle_text_filter {
    public function filter($text, array $options = array()) {
        return str_replace('world', 'hello world!', $text);
    }
}

5. version.php The version.php file keeps track of the version of your module, and other attributes, and is required for newer moodle versions. For a full list of the attributes please see version.php. Place the version.php in your 'filter/helloworld' directory.

defined('MOODLE_INTERNAL') || die();
$plugin->version   = 2016052300;        // The current plugin version (Date: YYYYMMDDXX)
$plugin->requires  = 2016051900;        // Requires this Moodle version
$plugin->component = 'filter_helloworld'; // Full name of the plugin (used for diagnostics)


That is basically all there is to it!

Giving your filter a name

To try the new filter, you first have to log in as Administrator and enable it by going to the page Administration ► Plugins ► Filters ► Manage filters.

When you do, you will find that your plugin does not have a name. We missed a step:

6. Inside the 'filter/helloworld' folder, create a folder called 'lang', and in there, create a folder called 'en'.

7. Inside there, create a file called 'filter_helloworld.php'. That is, you have just created the file 'filter/helloworld/lang/en/filter_helloworld.php'.

8. In that file, put

<?php

$string['filtername'] = 'Hello world!';

See String API for details.

Trying out your filter

We had just got to the filters administration screen. If you reload that page now, it should now show your filter with its proper name. Turn your filter on now.

Filters are applied to all text that is printed with the output functions format_text(), and, if you have turned on that option, format_string(). So, to see your filter in action, add some content containing the word 'world' somewhere, for example, create a test course, and use the word in the course description. When you look at that course in the course listing, you should see that your filter has transformed it.

Adding a global settings screen

Some filters can benefit from some settings to let the administrator control how they work. Suppose we want to greet something other than 'world'. To add global settings to the filter you need to:

9. Create a file called 'filtersettings.php' inside the 'filter/helloworld' folder. Use standard 'settings.php' file in Moodle 2.6 and later.

10. In the 'filtersettings.php' file, put something like:

$settings->add(new admin_setting_configtext('filter_helloworld/word',
        get_string('word', 'filter_helloworld'),
        get_string('word_desc', 'filter_helloworld'), 'world', PARAM_NOTAGS));

11. In the language file 'filter/helloworld/lang/en/filter_helloworld.php' add the necessary strings:

$string['word'] = 'The thing to greet';
$string['word_desc'] = 'The hello world filter will add the word \'hello\' in front of every occurrence of this word in any content.';

12. Change the filter to use the new setting:

class filter_helloworld extends moodle_text_filter {
    public function filter($text, array $options = array()) {
        $word = get_config('filter_helloworld', 'word');
        return str_replace($word, "hello $word!", $text);
    }
}

In standard Moodle, the censor, mediaplugin and tex filters all provide good examples of of how filters use global configuration like this.

A note about performance

One important thing to remember when creating a filter is that the filter will be called to transform every bit of text output using format_text(), and possibly also format_string(). That means that you have to be careful, or you could cause big performance problems. If you have to get data out of the database, try to cache it so that you only do a fixed number of database queries per page load. The Glossary filter is an example of this. (I am not sure how good an example ;-))

If your filter uses a special syntax or it is based on an appearance of a substring in the text, it is recommend to perform a quick and cheap strpos() search first prior to executing the full regex-based search and replace.

/**
 * Example of a filter that uses <a> links in some way.
 */
public function filter($text, array $options = array()) {

    if (!is_string($text) or empty($text)) {
        // Non-string data can not be filtered anyway.
        return $text;
    }

    if (stripos($text, '</a>') === false) {
        // Performance shortcut - if there is no </a> tag, nothing can match.
        return $text;
    }

    // Here we can perform some more complex operations with the <a>
    // links in the text.
}

Local configuration

In addition, in Moodle 2.0, filters can also have different configuration in each context. For example, the glossary filter could be changed so that in Forum A, you can choose to only link words from a particular glossary, say Glossary A, while in Forum B you choose to link words from Glossary B.

To do that sort of thing, you need to add a file called filterlocalsettings.php. In it, you must define a Moodle form that is a subclass of filter_local_settings_form. In addition to the standard formslib methods, you also need to define a save_changes method. There is not a good example of this in the standard Moodle install yet. To continue our example:

13. Create a file called 'filterlocalsettings.php' inside the 'filter/helloworld' folder.

14. In the 'filterlocalsettings.php' file, put:

class helloworld_filter_local_settings_form extends filter_local_settings_form {
    protected function definition_inner($mform) {
        $mform->addElement('text', 'word', get_string('word', 'filter_helloworld'), array('size' => 20));
        $mform->setType('word', PARAM_NOTAGS);
    }
}

15. Extend the filter to use the new setting, if it is present. The filter must be able to work if the setting is not set, for example by falling back to the global or default setting in this case

<?php
class filter_helloworld extends moodle_text_filter {
    public function filter($text, array $options = array()) {
        global $CFG;
        if (isset($this->localconfig['word'])) {
            $word = $this->localconfig['word'];
        } else {
            $word = $CFG->filter_helloworld/word;
        }
        return str_replace($word, "hello $word!", $text);
    }
}

Two types of filter

In the past, Moodle supported two different types of filter:

  • Stand-alone filters like the one we created above. These live in a folder inside the 'filter' folder. For example, in 'filter/myfilter'. 'filter/tex' is an example of a core filter of this type.
  • Filters that were part of an activity module. In this case, the filter code lives inside the 'mod/mymod' folder. 'mod/glossary' used to be an example of a core module with a filter.

The second option no longer exists in Moodle 2.5 and later. All filters live in the filter folder. Of course, a filter may depend on an associated other plugin, like mod_glossary. If so, you should declare that in the version.php file.

Dynamic content

From Moodle 2.7: On (very few) pages - it is possible that page content is loaded by ajax *after* the page is loaded (e.g. equations in a glossary popup). In certain filter types (e.g. MathJax) javascript is required to be run on the output of the filter in order to do the final markup. For these types of filters, a javascript event is triggered when new content is added to the page (the content will have already been processed by the filter in php). The javascript for a filter can listen for these event notifications and reprocess the affected dom nodes.

To subscribe to the event:

       // Listen for events triggered when new text is added to a page that needs                                                  
       // processing by a filter.                                                                                                  
       Y.on(M.core.event.FILTER_CONTENT_UPDATED, this.contentUpdated, this);


To handle the event:

   /**                                                                                                                             
    * Handle content updated events - typeset the new content.                                                                     
    * @method contentUpdated                                                                                                       
    * @param Y.Event - Custom event with "nodes" indicating the root of the updated nodes.                                         
    */                                                                                                                             
   contentUpdated: function(event) {                                                                                               
       var self = this;                                                                                                            
       Y.use('mathjax', function() {                                                                                               
           self._setLocale();                                                                                                      
           event.nodes.each(function (node) {                                                                                      
               node.all('.filter_mathjaxloader_equation').each(function(node) {                                                    
                   MathJax.Hub.Queue(["Typeset", MathJax.Hub, node.getDOMNode()]);                                                 
               });                                                                                                                 
           });                                                                                                                     
       });                                                                                                                         
   }                      

See: filter/mathjaxloader/yui/src/loader/js/loader.js

See also