Development:Module visibility and display: Difference between revisions

From MoodleDocs
 
(20 intermediate revisions by 2 users not shown)
Line 1: Line 1:
This document is '''only a proposal'''. The functionality described doesn't exist yet in Moodle.
{{Moodle 2.0}}


== DRAFT 2 ==
The features described here are available since Moodle 2.0.2.
 
This is the second draft of the proposal, with changes based on Petr's comments. As well as/because of adding all the class stuff, this version no longer requires rebuild_course_cache and should be 100% backward compatible with all existing uses of modinfo.
 
Notes:
 
* Petr wanted dynamic/lazy computation of some of the access stuff - I think that would be nice as a future objective that is possible given the new classes, rather than in this version - it would be better for this version to port across the existing initialisation code directly, especially because of the requirement for 100% BC which is not possible with __get methods, so meaning the property values must be initialised (see below). This also means it's a less scary change, i.e. more moving code around and less new code.
 
* I wanted a class for the overall thing, which I called course_modinfo (because it's basically from the modinfo field of the course table). Then I neeed a name for the class for an individual module instance info. Petr suggested module_info but I thought cm_info was a bit closer (module_info might sound like it was about a whole module, not one instance). Anyhow, both names subject to change.
 
* Linking the cm_info to its parent course_modinfo gives a nice framework to do the caching/lazy-initialisation stuff later (ie if we stick cached data into a property on course_modinfo then it automatically gets cached or cleared from cache as needed, rather than creating another cache that has to be handled separately) - for instance let's say we didn't calculate availability stuff until somebody asks for it, they will be asking for a particular cm_info, but we probably need to have a way to calculate it for whole course at once, so need the reference.
 
* I didn't specify exactly how to address modinfo caching issues (...if there are any) but because this proposal suggests moving the 'work out the values' stuff into the class, and leaving get_fast_modinfo to deal only with caching, it should be a whole lot simpler to see how the caching works and alter it in future.


== Summary ==
== Summary ==


We would like to create a generic API that allows the following:
A new API allows you to customise how your module displays on the main course page:
 
* Module display on front page can be customised, for example making it possible to create another module that behaves like Label (displaying arbitrary html rather than a link to activity view.php) - currently this is hardcoded hack for Label. This change should apply to other areas such as navigation block as well as course page.
 
* Some modules can provide dynamic text, such as forum displaying unread messages. At present this is hardcoded so that only forum can do it.
 
* Modules can be hidden completely, or greyed out, from the view of particular students according to either custom module behaviour, or (if not specified by module) default behaviour regarding a new capability moodle/course:viewactivity and the existing option for whether a non-available module is greyed out or hidden entirely.
 
This should take advantage of the existing modinfo cache in order that performance is not adversely affected.
 
== API improvement ==
 
Following Petr's suggestions, we would also like to improve the API by switching the modinfo in-memory data structures to use defined classes instead of anonymous stdClass objects. This achieves the following:
 
* Provides a location for documentation of these structures. (Currently they are undocumented.)
 
* In future, allows functions to specify the required type (i.e. some functions require the information from modinfo, not just a row from the table; they will now be able to specify this). There is no plan to change function definitions immediately.
 
* Where the type is defined, makes metadata available to IDEs, allowing code completion and automatic documentation viewing.
 
The following requirements should also be met:
 
* 100% backward compatibility for existing use of these structures by core and third-party modules. (Obviously, where these want to support the new features, such as the navigation supporting other label-like modules, there do need to be changes which are included in these patch. But if we forget something or it's third-party, it should keep working as at present.)
 
* 100% backward compatibility for existing modinfo data (in database).
 
== Removing existing hacks ==
 
* Existing hacks regarding label in all areas of code (e.g. navigation, etc) will be changed from the logic 'is this a label?' to the logic 'does this activity have a view page?' (which will work for label too)
* Existing hacks regarding forum unread data will be removed and the forum unread code will be moved into the new API function. The code will be written in such a way as to have the same performance characteristics.
 
== get_fast_modinfo change ==
 
The get_fast_modinfo function will be changed to return a new object of type course_modinfo, which will be compatible with the existing $modinfo return value.
 
While constructing this object, in addition to current behaviour, the system will:
 
* support new values defined in _get_coursemodule_info
 
* extend dynamic per-user calculation (that checks is something is visible to current user, ->uservisible) with additional checks
 
* call the new module API (if provided) after calculating modinfo, to get dynamic information
 
== course_modinfo ==
 
The new class course_modinfo will contain properties:
 
* courseid, userid (ints)
* sections (array of int => array of int)
* cms (array of int => cm_info)
* instances (array of string => array of int => cm_info)
* groups - at present I can't tell what this is for as it seems to be empty for me even though there are groups on the course and I'm a member of one of them - hmmm - anyhow I will figure it out and implement it
 
These are identical to the values currently returned by modinfo, except for the use of cm_info class instead of bare stdClass for the information about modules.
 
There are three options for handling this class:
 
# Make these properties public so that they can be accessed exactly as at present.
# Make them private and use PHP magic __set and __get functions so that they can be read as normal but any attempt to write them would (while working) also cause a developer debug warning.
# Make the properties public, but deprecate them, and create separate get methods (get_courseid, get_userid, etc) which are recommended for future use.
 
I initially favoured the second option but unfortunately, PHP being PHP, this doesn't quite work properly: specifically, you can't use the empty() function on such values. Since there might be places in the code that call empty(), maybe this isn't a good idea.
 
Consequently I tend to the third one; we should make better get methods such as get_cm($cmid) as well as just get_cms(), which may help make new code more readable.
 
=== Construction ===
 
The code that creates this information should largely be moved to this class (either in a constructor or in init methods etc) from its current location in get_fast_modinfo. get_fast_modinfo should remain responsible only for caching these objects.
 
== cm_info class ==
 
=== Basic structure ===
 
The new class cm_info will be constructed with a $parent (course_modinfo). Knowing its parent allows the class to carry out operations with regard to the whole course, where this is beneficial for performance, without needing extra parameters. There will be a get_modinfo() method to return this.
 
=== Existing properties ===
 
These properties are in current modinfo:
 
* id, instance, course, idnumber, visible, groupmode, groupingid, groupmembersonly, indent, completion, availablefrom, availableuntil, showavailability - data from course_modules row
 
* extra, icon, iconcomponent, modname, name, sectionnum, conditionscompletion, conditionsgrade, modplural (wtf is this there when the singular name isn't and both should be available with get_string?!) - data from modinfo which was computed when generating the cache


* availableinfo, available, uservisible - data relating to the current user which is computed dynamically when obtaining the modinfo object
* You can display custom HTML below the link to your module.


This is the list of properties currently available in modinfo objects, so will be implemented as-is. The same question regarding use of __set, __get applies as above, but in this case it's perhaps more likely that people might call empty() on the existing properties; again, I propose retaining the above as public properties, but deprecated, and providing get_x methods for new use. Any new properties would be private and available only with get_ methods.
* If your module does not have a link (like Label, where it is only for display on the main page) then you can remove the link from the main page and from all navigation etc.


Some of the get methods might have a slightly different definition to the raw properties. For example get_icon should use the provided data to return a moodle_icon object, not a string.
* You can display HTML next to the link to your module that indicates dynamic information (like Forum, where it displays information about unread messages).


=== New data / get methods ===
* You can display additional icons next to the other module editing icons when the user is editing the page.


The new data and corresponding get methods are:
In addition, existing things you could already do (like change the icon on the main page) are still available when using the new API.


* has_view() - true if the module should have a link to its view.php shown in navigation, be included in lists of 'did the user visit this module' stats, etc. (Basically this is the 'is not a label' method.)
The <tt>get_fast_modinfo</tt> function now returns specific classes which are documented and which you can use to obtain new information about modules.
* get_url() - returns moodle_url to module view.php, or null if has_view is false (this reduces code duplication in various places)
* get_content() # - returns HTML content to be displayed on the course page where this module is placed; appears below the link, if present (this is how Label displays content on course page)
* get_extra_classes() # - returns additional CSS classes to be added to the A or DIV tag(s) for this item on main course page.
* get_custom_data() - returns an optional mixed value containing custom data for this module, which needs to be available course-wide via the get_fast_modinfo function.
* get_after_link() # - returns HTML code which displays after the link.
* get_after_edit_icons() # - returns HTML code which displays after the standard icons  (hide, edit, delete, etc) when editing.


Functions marked # require 'view information'. This is additional information intended for use on the course view page and not in other places that use modinfo: the most obvious example (and the only one that affects core) is forum unread data. Consequently it is only obtained on request. The class will maintain a ->hasviewinfo boolean; if this is false, it will obtain view info before returning data. Obtaining view info requires calling the module's _cm_info_view function if it has one.
== Backward compatibility ==


=== Set methods ===
All modules and code written for Moodle 2.0 should continue to behave in exactly the same manner. There is no need to change existing modules for this API unless you want to use the new features.


Set methods are available for use only by _dynamic_cm_info function; see below.
== Removing your link ==


== Modify _get_coursemodule_info ==
If your module should not appear in navigation and in other lists of modules to visit or get information for, like Label, the easiest way to remove that link is to return true for FEATURE_NO_VIEW_LINK in your module's <tt>_supports</tt> function.


There will be a change to the existing module API function _get_coursemodule_info which is used to update information before storing it in the database modinfo field. This change will retain backward compatibility.
== Customising module display, in cache ==


The function returns an object which is currently a stdClass object. This possibility will be retained for backward compatibility, with unchanged behaviour. All existing fields are supported:
The first place you can customise your module display is in the existing <tt>_get_coursemodule_info</tt> API function. This function obtains information about the module which will be stored in the course cache (the <tt>modinfo</tt> field of the course table).


* name: name of instance or (for labels only) content of instance
The course cache is only updated when somebody edits a module, so it can't be used for dynamic information - but it's okay if it takes a few database queries to calculate the data because it will be cached for future use.
* icon: name of icon or special weird string
* iconcomponent: component of icon, possibly works
* extra: extra data inserted somewhere horrible in the html


These fields are all optional and may be left unset to accept the defaults.
The function should return a value of class <tt>cached_cm_info</tt>. For example:


However a new class cached_cm_info can be returned instead.
<code php>
function mod_frog_get_coursemodule_info($cm) {
    $info = new cached_cm_info();
    $info->content = '<p>This will display below the module.</p>';
    return $info;
}
</code>


The class cached_cm_info has the following properties:
You can change several properties which are documented in that class definition. If you don't change a property, its value remains default.


* name, icon, iconcomponent: as before
* <tt>name</tt> - name of activity (text of the link on course page).
* content: HTML content to be displayed on the course page where this module is placed; appears below the link, if present (this is how a Label-like module can display content on course page)
* <tt>icon</tt>, <tt>iconcomponent</tt> - name and component name of icon to display by the link.
* customdata: A place to store a string or object containing custom data for this module, which needs to be available course-wide via the get_fast_modinfo function. If present, this data should be small in size.
* <tt>content</tt> - extra HTML content to display below the module link on course page (not shown in navigation etc).
* extraclasses: Extra CSS classes to add to the item on course page display, if required.
* <tt>customdata</tt> - arbitrary extra PHP data to store in modinfo cache; useful if, for performance reasons, your module needs to store data that should be accessible very quickly from other parts of the course.
* <tt>extraclasses</tt> - extra CSS class or classes that will be added to the activity on the main page, so that you can alter the styling.
* <tt>onclick</tt> - already-escaped HTML that will be inserted as the value of the onclick attribute.


(It does not support extra, which is deprecated on account of being stupid, at least unless I figure out some existing use case that really needs to be carried forward into a non-deprecated system.)
If you don't need the information to be cached (it can be retrieved very quickly without making any database queries) then you might consider using one of the functions below instead, in order to avoid unnecessarily increasing the size of the course cache. Although the headings mention the current user, you can of course use those functions in a way that doesn't depend on the current user.


=== Storing data ===
== Customising module display, for current user ==


Returning a stdClass object should result in identical content of the modinfo field in the database to present.
You can customise module display dynamically (when the page loads). For example you might want to alter it based on the permissions of the current user.


Return cached_cm_info results in similar content but with extra fields ->content and ->customdata. These fields are only stored if they are non-null, i.e. it won't bulk up the database with empty 'content=nothing' type data against every module.
<code php>
function mod_frog_cm_info_dynamic(cm_info $cm) {
    $context = get_context_instance(CONTEXT_MODULE, $cm->id);
    if (!has_capability('some/capability', $context)) {
        $cm->set_user_visible(false);
    }
}
</code>


=== Reading label data ===
This code can affect the navigation, and whether users are permitted to access the module (as above). It runs on all pages within the course, so it's very important that you do not put slow code in this function: it should not make any database queries.


The new content field is different to existing behaviour of the Label module, which stores its content in the 'name' field as noted. When reading this data or processing it (for example in cm_info class), the system takes the following approach:
In addition to the <tt>set_user_visible</tt> function shown, you can also set many other things such as additional editing icons which will appear if editing mode is enabled. See the cm_info class documentation for more information.


* if the modname is 'label' and there is no 'content' field, then copy the 'name' field into 'content'.
Most things are set using functions (as above; another example would be <tt>set_content</tt> which sets the same content data as mentioned in the previous section) while other things can be set directly using public variables.


Note that this behaviour retains backward compatibility; the label still has a silly 'name' value. For new code, has_view() returns false so it won't use name; for old code, the existing hard-coded exceptions for label will continue to work.
== Customising module display, for current user, on course page only ==


== Extend dynamic calculation ==
Sometimes you need to display custom information for the current user that appears only on the course view page. For example, the forum module displays unread information on the view page. This information doesn't show on other pages (it isn't included in the navigation, for instance).


Currently the modinfo code makes the following checks that apply dynamically per-request (and do not directly come from the cache) in order to create the ->uservisible member variable.
<code php>
function mod_frog_cm_info_view(cm_info $cm) {
    $cm->set_after_link('Last tadpole: 22:17');
}
</code>  


* If ->groupmembersonly is set, checks if the user belongs to group or has accessallgroups.
Because this function only runs when looking at the course page:
* If availability restrictions (date, grade, completion) are set, checks these (also even if available to current user, stores information into ->availableinfo, ->available for information when editing).


My proposal is:
* It is OK to do tasks which may require some database queries (such as checking for unread forum messages), although obviously this should be kept to a minimum. In particular, care should be taken so that if there are 20 instances of the activity on the course page, it doesn't make 20 separate queries to obtain the information.


* Make this part of the code (that 'specialises' a single mod value for the current user/request) into a separate function within cm_info class, just to simplify it.
* Inside this function you cannot set options which affect the appearance or access to the activity on other pages; for example, you cannot turn off the uservisible flag as shown in the previous example. This is because these options are required on other pages (e.g. to display navigation) so it does not make sense to set them only for the course page. If you try, you'll get a <tt>coding_exception</tt>.
* Add a check for the moodle/course:viewactivity capability; if user doesn't have this capability, set ->uservisible to false. Also check the option about what to do with hidden activities; if this is set to the default 'grey it out', then set ->inactive to true.
** Note: The default value for moodle/course:viewactivity should be true for all roles, even guest. This maintains existing behaviour. Sites that don't want guests to view activities can change the main role definition for guest.
* Set the new 'has link' property according to whether the module has a view.php script.
* Call the _dynamic_cm_info function (below) if the current module supplies one.


PERFORMANCE CONCERNS: Minimal. No new database queries are required.
== get_fast_modinfo data ==


== New module API ==
The function <tt>get_fast_modinfo</tt> now returns an object of class course_modinfo, which itself contains cm_info objects about each activity. (These are entirely backward-compatible with the previous return value.)


Two new module API functions will be defined. They both have one parameter: the cm_info object for this module, containing all the data from the modinfo cache.
In addition to the old methods for obtaining data from $modinfo, there are some new functions. For example, here is how to get a single cm_info from $modinfo:


The functions are:
<code php>
$modinfo = get_fast_modinfo($course);
$cm = $modinfo->get_cm($cmid);
</code>


* _cm_info_dynamic - add basic data that is always required (must be fast)
The cm_info objects contain additional information that is not present in the course_modules database row, such as the module's name, and the icon and associated content mentioned above. In order to distinguish these from the plain database objects, you can specify the cm_info class in a function definition:
* _cm_info_view - add data that is required for the course view page (can be slower)


The functions both call set methods in the cm_info object. Examples:
<code php>
function my_clever_function(cm_info $cm) {
    if (!$cm->uservisible) {
        // the module is not visible or available to current user,
        // so do something clever instead
    }
}
</code>


* set_user_visible(false) - hide activity entirely
By specifying cm_info in the parameter list, you'll cause PHP to give an error if anyone tries to call that function with a $cm object that just came from the database row, instead of from <tt>get_fast_modinfo</tt>. (It is good practice to always get $cm from get_fast_modinfo, but there might be exceptions.)  
* set_available(false) - grey out activity while it remains visible
* set_available_info('not available because you suck') - add explanation for why it's not available
* '''set_after_link'''('16 unread') # - add html code to appear after link
* '''set_edit_icons'''($html) # - add some code to appear when editing
* set_has_view(false) - make module not have link, navigation, etc even if it has a view.php
* set_content($html) # - add/change code to appear below the link
* set_name($text) - change text of the link
* set_extra_classes('whatever classes') # - add CSS classes


The bold functions set cm_info data that cannot be set anywhere else (such as by system defaults or modinfo classes). This is basically because there didn't seem any general need to cache this data, particularly bearing in mind that modules can cache anything they like using the customdata property of cached_cm_info.
Of course, this is only necessary if your function relies on a feature that is specific to cm_info, such as the 'uservisible' field above. If your function only uses fields which are present in the database row, then there's no need to require cm_info.


_cm_info_view is restricted: for example, you cannot change user_visible status in view (because then it wouldn't work when checking access etc on other pages). You can only change the 'view' properties. This can be enforced by making the set methods throw exceptions if called when the cm_info is in its already-defined state.
== More documentation ==


Should it be necessary, this function can access the existing information in the cm_info object and also in the parent course_modinfo object (such as the user id) by calling get_modinfo on the cm_info.
All three classes for this API are in the core file <tt>lib/modinfolib.php</tt> and contain complete PHPdoc information for all fields and functions.


PERFORMANCE CONCERNS: None. Of standard modules, only the forum will implement this and only in the 'view' version used just on course page; it will use the same code as at present. Custom modules will need to be written carefully in a similar manner so that they also perform well (ie do any queries once for whole course and store in global cache, not once per module).
* [http://phpdocs.moodle.org/HEAD/core/lib/course_modinfo.html course_modinfo PHPdocs]
* [http://phpdocs.moodle.org/HEAD/core/lib/cm_info.html cminfo PHPdocs]
* [http://phpdocs.moodle.org/HEAD/core/lib/cached_cm_info.html cached_cm_info PHPdocs]

Latest revision as of 11:08, 17 February 2011

Template:Moodle 2.0

The features described here are available since Moodle 2.0.2.

Summary

A new API allows you to customise how your module displays on the main course page:

  • You can display custom HTML below the link to your module.
  • If your module does not have a link (like Label, where it is only for display on the main page) then you can remove the link from the main page and from all navigation etc.
  • You can display HTML next to the link to your module that indicates dynamic information (like Forum, where it displays information about unread messages).
  • You can display additional icons next to the other module editing icons when the user is editing the page.

In addition, existing things you could already do (like change the icon on the main page) are still available when using the new API.

The get_fast_modinfo function now returns specific classes which are documented and which you can use to obtain new information about modules.

Backward compatibility

All modules and code written for Moodle 2.0 should continue to behave in exactly the same manner. There is no need to change existing modules for this API unless you want to use the new features.

Removing your link

If your module should not appear in navigation and in other lists of modules to visit or get information for, like Label, the easiest way to remove that link is to return true for FEATURE_NO_VIEW_LINK in your module's _supports function.

Customising module display, in cache

The first place you can customise your module display is in the existing _get_coursemodule_info API function. This function obtains information about the module which will be stored in the course cache (the modinfo field of the course table).

The course cache is only updated when somebody edits a module, so it can't be used for dynamic information - but it's okay if it takes a few database queries to calculate the data because it will be cached for future use.

The function should return a value of class cached_cm_info. For example:

function mod_frog_get_coursemodule_info($cm) {

   $info = new cached_cm_info();

$info->content = '

This will display below the module.

';

   return $info;

}

You can change several properties which are documented in that class definition. If you don't change a property, its value remains default.

  • name - name of activity (text of the link on course page).
  • icon, iconcomponent - name and component name of icon to display by the link.
  • content - extra HTML content to display below the module link on course page (not shown in navigation etc).
  • customdata - arbitrary extra PHP data to store in modinfo cache; useful if, for performance reasons, your module needs to store data that should be accessible very quickly from other parts of the course.
  • extraclasses - extra CSS class or classes that will be added to the activity on the main page, so that you can alter the styling.
  • onclick - already-escaped HTML that will be inserted as the value of the onclick attribute.

If you don't need the information to be cached (it can be retrieved very quickly without making any database queries) then you might consider using one of the functions below instead, in order to avoid unnecessarily increasing the size of the course cache. Although the headings mention the current user, you can of course use those functions in a way that doesn't depend on the current user.

Customising module display, for current user

You can customise module display dynamically (when the page loads). For example you might want to alter it based on the permissions of the current user.

function mod_frog_cm_info_dynamic(cm_info $cm) {

   $context = get_context_instance(CONTEXT_MODULE, $cm->id);
   if (!has_capability('some/capability', $context)) {
       $cm->set_user_visible(false);
   }

}

This code can affect the navigation, and whether users are permitted to access the module (as above). It runs on all pages within the course, so it's very important that you do not put slow code in this function: it should not make any database queries.

In addition to the set_user_visible function shown, you can also set many other things such as additional editing icons which will appear if editing mode is enabled. See the cm_info class documentation for more information.

Most things are set using functions (as above; another example would be set_content which sets the same content data as mentioned in the previous section) while other things can be set directly using public variables.

Customising module display, for current user, on course page only

Sometimes you need to display custom information for the current user that appears only on the course view page. For example, the forum module displays unread information on the view page. This information doesn't show on other pages (it isn't included in the navigation, for instance).

function mod_frog_cm_info_view(cm_info $cm) {

   $cm->set_after_link('Last tadpole: 22:17');

}

Because this function only runs when looking at the course page:

  • It is OK to do tasks which may require some database queries (such as checking for unread forum messages), although obviously this should be kept to a minimum. In particular, care should be taken so that if there are 20 instances of the activity on the course page, it doesn't make 20 separate queries to obtain the information.
  • Inside this function you cannot set options which affect the appearance or access to the activity on other pages; for example, you cannot turn off the uservisible flag as shown in the previous example. This is because these options are required on other pages (e.g. to display navigation) so it does not make sense to set them only for the course page. If you try, you'll get a coding_exception.

get_fast_modinfo data

The function get_fast_modinfo now returns an object of class course_modinfo, which itself contains cm_info objects about each activity. (These are entirely backward-compatible with the previous return value.)

In addition to the old methods for obtaining data from $modinfo, there are some new functions. For example, here is how to get a single cm_info from $modinfo:

$modinfo = get_fast_modinfo($course); $cm = $modinfo->get_cm($cmid);

The cm_info objects contain additional information that is not present in the course_modules database row, such as the module's name, and the icon and associated content mentioned above. In order to distinguish these from the plain database objects, you can specify the cm_info class in a function definition:

function my_clever_function(cm_info $cm) {

   if (!$cm->uservisible) {
       // the module is not visible or available to current user,
       // so do something clever instead
   }

}

By specifying cm_info in the parameter list, you'll cause PHP to give an error if anyone tries to call that function with a $cm object that just came from the database row, instead of from get_fast_modinfo. (It is good practice to always get $cm from get_fast_modinfo, but there might be exceptions.)

Of course, this is only necessary if your function relies on a feature that is specific to cm_info, such as the 'uservisible' field above. If your function only uses fields which are present in the database row, then there's no need to require cm_info.

More documentation

All three classes for this API are in the core file lib/modinfolib.php and contain complete PHPdoc information for all fields and functions.