Note:

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

Filter enable/disable by context

From MoodleDocs

Moodle 2.0


We want to make configuration of filters more flexible, so, for example, you can do things like

  • Disable the glossary auto-linking filter in a quiz.
  • Enable the TeX filter only in the Maths course category.

Overview

Most filter settings will still be controlled at the system level. Administrators will still choose which filters enabled for the whole site, and, more importantly, which ones are not available at all. Also, any filter settings (for example which media types for the multimedia filter, and the paths for the TeX filter) will also still be set globally for the whole site by the administrator.

However, in addition to the overall on/off switch for each filter, we will store a separate active/inactive state for each filter in each context. (Don't worry, we don't actually store a row in the database for each context, mostly we use inheritance.)

This proposal relies on some of the proposed navigation changes for Moodle 2.0, to give us a place to add a new 'Text filtering' setting link for each context.

Detailed design

Changes to the filter admin screen

On Administration ► Plugins ► Filters ► Manage filters, the existing Disable/Enable column will be split into two.

  • Enabled? will control whether this filter can be used at all on this site.
  • Active? controls whether this filter is in use globally. (If the plugin in not enabled, you cannot activate it.)

(A filter may need to be enabled, but not active globally, for example in the "TeX filter only in the maths course" use case.)

We will also add a Delete column to this table, to let administrators delete all the related configuration from the database if they uninstall a plugin.

New settings page in each context

There will be a "Text filtering" settings page for each context (apart from block contexts), just like there are Assign roles and Override permissions pages.

The page will have a title something like "Text filtering in Course: Maths 101". It will contains a table with one row for each filter that the administrator has enabled. For each filter, there will be a drop-down with three choices: Inherit, Active and Inactive. For the inherit choice, it will display the value that would be inherited in brackets, for example "Inherit (Active)". (Would these be better as radio buttons?) There will be a Save changes button at the bottom.

File:Filters example 1.png Filters example 2.png

New capability moodle/filter:manage

This controls access to the Text filtering settings page.

By default Administrators, Course creators and Teachers will have this capability.

New database table filter_active

This table replaces $CFG->textfilters.

Column Type Comment
id INT(10) AUTO-INCREMENT Unique id
filter VARCHAR(32) e.g. 'filter/tex' or 'mod/glossary'
contextid INT(10) Foreign key references context.id
active INT(4) 1 = active, -1 = inactive. As a special case, if contextid == $systemcontext->id, then -9999 is used to mean that the administrator has disabled this filter.
sortorder INT(10) This is only used when contextid == $systemcontext->id. It stores the filter sort-order, numbering from 1. In other contexts, this column should contain 0.

Whenever a context is deleted, we must delete the corresponding rows from this table.

We will not delete data from here when a filter is disabled globally, so that if the administrator disables then re-enables the filter, the setting in all the different contexts are not lost.

Retrieving the active filters for a page

One of the Navigation 2.0 changes is that each page will be linked to a specific context. (That is, $PAGE->context will always to set to something sensible.)

Text will be filtered according to the page it appears on, not the context it belongs to. This is normally fine. For example, we want question text to be filtered according to the quiz that the question appears in.

With that in mind, suppose $contextids is the list of ids of this context and all its parents. Then the list of active filters is:

SELECT f.filter FROM filter_active f JOIN context ctx ON f.contextid = cxt.id WHERE ctx.id IN ($contextids) GROUP BY filter HAVING MAX(f.active * ctx.depth) > -MIN(f.active * ctx.depth) ORDER BY MAX(f.sortorder)

Why does this work?

To understand what that query is doing, you need to look at the results of

SELECT f.filter, f.contextid, ctx.depth, f.active, ctx.depth * f.active AS active_x_depth, f.sortorder FROM filter_active f JOIN context ctx ON f.contextid = cxt.id WHERE ctx.id IN ($contextids) ORDER BY f.filter, ctx.depth

That shows the data that is being aggregated by the GROUP BY and HAVING clauses. I will explain with reference to a specific example:

Suppose we are in a Quiz in the Maths 101 course in the Maths category in the system. $contextids = 1,3,10,22 (order reversed relative to the previous sentence - system context first). Then suppose that the above query returns

filter contextid depth active active_x_depth f.sortorder
filter/mediaplugin 1 1 1 1 1
filter/multilang 1 1 -1 -1 1
filter/tex 1 1 -1 -1 3
filter/tex 3 2 1 2 0
filter/tidy 1 1 -9999 -9999 4
filter/tidy 22 4 1 4 0
mod/glossary 1 1 1 1 2
mod/glossary 3 2 -1 -2 0
mod/glossary 10 3 1 3 0
mod/glossary 22 4 -1 -4 0
filter/mediaplugin
This is enabled, and active globally, and there are no overrides.
  • MAX(f.active * ctx.depth) = 1
  • MIN(f.active * ctx.depth) = 1
1 > -1, so this filter will be returned by the get active filters query.
filter/multilang
This is enabled, but not active globally, and there are no overrides.
  • MAX(f.active * ctx.depth) = -1
  • MIN(f.active * ctx.depth) = -1
-1 ≯ 1, so this filter will not be returned by the get active filters query.
filter/tex
This is enabled, but not active globally. However, it has been activated in the Maths category.
  • MAX(f.active * ctx.depth) = 2
  • MIN(f.active * ctx.depth) = -1
2 > 1, so this filter will be returned by the get active filters query.
filter/tidy
This is not enabled, but there is an old override in the database
  • MAX(f.active * ctx.depth) = 4
  • MIN(f.active * ctx.depth) = -9999
4 ≯ 9999, so this filter will not be returned by the get active filters query.
filter/glossary
A slightly silly example with the maximum number of overrides possible. The most specific override is the one that says that the filter is not active in the quiz.
  • MAX(f.active * ctx.depth) = 3
  • MIN(f.active * ctx.depth) = -4
3 ≯ 4, so this filter will not be returned by the get active filters query.
filter/myfilter
What! there is no filter/myfilter in the data above. What is going on? This is the situation when the administrator has just installed the myfilter plugin, but has not yet been to the manage filters administration page to update the filter settings. What happens?
Since there is nothing in the database, this filter is not returned by the get active filters query. That is, a newly installed filter is not active until the administrator explicitly activates it.

Sort order: Note that sortorder is zero except when contextid = 1. So MAX(f.sortorder) is just a clever trick to pull out the sortorder from the system context, ignoring all the zeros. There are other tricks that could be used instead, but this one is probably quite efficient.

Changes to text caching

The md5key used in the cache_text table will be changed to user the contextid, rather than the courseid, in $hashstr. See format_text in weblib.php.

Possible problems

Is filtering by page context really the right thing

Filtering according to page context might cause strange results on pages that aggregate content from different contexts.

For example, we normally think that the user's profile page is in the user context. That would mean that the Recent activity report there will be filtered according to the filter settings for the user context. (These will almost certainly inherit unmodified from the System context.) That would be strange if the TeX filter was only enabled in the Maths course. However, it may work better (both here and elsewhere) if we set $PAGE->context to the course context for profile pages like this.

On the other hand, if the parent looking at their child's recent activity does not have access to the course, it is good that they do not see the effects of the glossary auto-linking filter, which would like them to glossary entries they do not have permission to see.

The real problem page, would, of course, be the My Moodle page. That could aggregate content from any number of different contexts.

Or, we may find that we have to add a $contextid parameter to the format_text function, and find some way to avoid the performance problems that causes. (It would be really bad to query the database with a query like the one above for each call to format_text. Or even for each context that contributes content to the My Moodle page.)

See also