Trace: • course_construction
Course Construction
A Moodle course consists of sections (topics) and modules (activities or resources).
Information about the course structure is stored in a cache (which is read into memory from mdl_course.modinfo
). This information can be accessed as an object of class course_modinfo
by calling get_fast_modinfo($course_object)
.
If you need all sections in a course, call get_section_info_all
on the course_info object, which will return an array of section_info
objects. Example:
$course_modinfo = get_fast_modinfo($COURSE); $sections = $course_modinfo->get_section_info_all();
The useful properties of section_info are all private, so normally there wouldn't be any way to access them. However, section_info implements php's IteratorAggregate interface. So, it has a getIterator
function which in this case allows you to loop over all properties, after all. It looks as though this was done to make it easier to convert the section_info object into a 'clean' array: only properties prefixed with a '_' are exposed through the iterator.
You can also access all private properties by using the same variable name without the prefix, because section_info maps these names using the 'magic' __get($name)
function. So, $section→name
actually retrieves $section→_name
…
Fetch Only Sections Which Contain Modules
Instead of calling course_modinfo#get_section_info_all
, you can also call course_modinfo#get_sections
. This method retrieves just the sections which actually contain content. The return value is different though:
exit(print_object($course_modinfo->get_sections())); // yields: Array ( [0] => Array ( [0] => 188 [1] => 192 [2] => 244 ) [1] => Array ( [0] => 190 ) [2] => Array ( [0] => 191 ) [3] => Array ( [0] => 193 ) )
Whereas calling course_modinf$course_modinfo→get_section_info_all()o#get_section_info_all
returns:
exit(print_object($course_modinfo->get_section_info_all())); // yields: Array ( [0] => section_info Object ( [_id:section_info:private] => 246 [_course:section_info:private] => 32 [_section:section_info:private] => 0 [_name:section_info:private] => [_visible:section_info:private] => 1 [_summary:section_info:private] => [_summaryformat:section_info:private] => 1 [_showavailability:section_info:private] => 0 [_availablefrom:section_info:private] => 0 [_availableuntil:section_info:private] => 0 [_groupingid:section_info:private] => 0 [_conditionscompletion:section_info:private] => Array ( ) [_conditionsgrade:section_info:private] => Array ( ) [_conditionsfield:section_info:private] => Array ( ) [_available:section_info:private] => 1 [_availableinfo:section_info:private] => [_uservisible:section_info:private] => 1 [cachedformatoptions:section_info:private] => Array ( ) [_sequence] => 188,192,244 ) [1] => section_info Object ( [_id:section_info:private] => 247 [_course:section_info:private] => 32 [_section:section_info:private] => 1 [_name:section_info:private] => My Section Name for Topic 1 // And so on...
Please keep in mind that even though the properties are all private, you can still access them if you leave out the _
prefix (see previous section).
As an example, if you've got a specific section object, say $current_section, for which you want to retrieve all course_module ids, then you can do something like this:
$sections = $course_modinfo->get_section_info_all(); // array of course_module ids for each section $current_course_module_ids = $sections[$current_section->section];
So, even though _section
is actually a private property, you can still access it by omitting the underscore.
Current Section Number
To get the current section number:
$section_number = ($n = optional_param('section', 0, PARAM_INT)) ? $n : $PAGE->cm->sectionnum;
Printing Section Names
First, retrieve all (relevant) sections, then call get_section_name($course_or_id, $section):
$modinfo = get_fast_modinfo($COURSE); $sections = $modinfo->get_sections(); foreach($sections as $section) { echo "<h2>" . get_section_name($COURSE, $section) . "</h2>"; }
Retrieve All Activities
There is a very straightforward function call to make if you want to retrieve all activities in a course:
$activities = get_array_of_activities($COURSE->id);
The course/lib.php
function print_section
does not use this function however. Here, something like the following is done:
$sections = $course_modinfo->get_section_info_all(); foreach ($sections as $section) { if (empty($course_modinfo->sections[$section->section])) continue; // approximation of the code used in ''print_section'': echo "<h2>" . get_section_name($COURSE, $section->section) . "</h2>"; foreach ($course_modinfo->sections[$section->section] as $modnumber) { $mod = $course_modinfo->cms[$modnumber]; if (!$mod->uservisible) continue; list($content, $instancename) = get_print_section_cm_text($course_modinfo->cms[$modnumber], $COURSE); echo "$instancename<br/>"; } }
(This is not the actual code, but a compact, working version of it. Looping over the sections actually takes place elsewhere: for each section, print_section
is called.)
Get A Specific Section
To get the first section:
$first_section = $course_modinfo->get_section_info(0);
Get A Specific Activity
Here's how you retrieve the first forum of a course:
static function get_first_forum($course) { $course_modinfo = get_fast_modinfo($course); $all_forums = $course_modinfo->get_instances_of('forum'); return array_shift($all_forums); } // function get_first_forum
Print Course Module / Instance Specific Info in the Topics Overview
In your mod's library, add a function:
function mymod_cm_info_view(cm_info $cm) { global $CFG; $cm->set_after_link(" <span><b>Hello!</b></span>"); }
This will print “Hello!” after each instance of your module. Please keep in mind that this string will not be cached!
The forum module uses this function to print the number of unread messages in a specific forum.
UPDATE: it looks as though this function is now called [mymod]_get_coursemodule_info
. Example:
function multilabel_get_coursemodule_info($coursemodule) { // Simplistic example $info = new cached_cm_info(); $info->name = 'Multilabel instance'; $info->content = 'Multilabel content'; return $info; }
To output dynamic (i.e. uncached) data:
function multilabel_cm_info_dynamic(cm_info $cm) { $cm->set_content("Time: " . date('Y-m-d H:i:s')); }
Add Course Settings
There is no clean way to add a field to the course settings form, e.g. through a local plugin. Instead, you have to hack the /course/edit_form.php
file. Here's an example:
/* Added by Solin, 2015 */ $mform->addElement('date_selector', 'enddate', get_string('enddate', 'report_trainingreport')); $mform->addHelpButton('enddate', 'enddate', 'report_trainingreport'); /* End of addition by Solin, 2015 */
To add the field to the mdl_course table, include this code in /your_plugin/db/install.php
:
/** * Add column to course database table * @return bool */ function xmldb_local_your_plugin_install() { global $DB; $dbman = $DB->get_manager(); // do the install $table = new xmldb_table('course'); $field = new xmldb_field('enddate', XMLDB_TYPE_INTEGER, '11', null, null, null, null); if (!$dbman->field_exists($table, $field)) { $dbman->add_field($table, $field); } } // function xmldb_local_your_plugin_install
Course Settings in Custom Course Format
You can create your own course format by adding a new directory, e.g. course/format/planner
. Define a class for your format in course/format/planner/lib.php
and use the course_format_options
to add course settings. Here's an (incomplete) example.
class format_planner extends format_base { public function course_format_options($foreditform = false) { global $DB, $CFG; static $courseformatoptions = false; // stuff left out... $courseformatoptionsedit = array( 'replan' => array( 'label' => new lang_string('replan', 'format_planner'), 'element_type' => 'select', 'element_attributes' => array( array( 1 => new lang_string('yes_to_all', 'format_planner'), 2 => new lang_string('yes_by_manager', 'format_planner'), 3 => new lang_string('yes_by_role', 'format_planner'), 0 => new lang_string('no'), ) ), 'help' => 'replan', 'help_component' => 'format_planner', ), 'replan_role' => array( 'label' => new lang_string('replan_role', 'format_planner'), 'element_type' => 'select', 'element_attributes' => array( replan_selector() ), 'help' => 'replan_role', ), 'slow' => array( 'label' => new lang_string('slow', 'format_planner'), 'element_type' => 'select', 'element_attributes' => array( array( 0 => new lang_string('no'), 1 => new lang_string('yes'), ) ), 'help' => 'slow', 'help_component' => 'format_planner', ) ); } }
Now, apparently due to a bug in Moodle (at least version 2.4 - 2.6), you cannot use the type checkbox
in your $courseformatoptionsedit array. Moodle will display the checkbox in the course settings form, but it does not add a hidden input field in the background. In other words: once checked, the checkbox can never be unchecked.
To circumvent this problem, simply use a select
type with 'yes' and 'no' (see the 'slow' example above).
Custom Section Settings
To add custom settings to the section form of your favorite course format, add this code to the file course/format/[course_format_name]/lib.php
:
function section_format_options($foreditform = false) { return array( 'drip_opening_day' => array( 'default' => 0, 'type' => PARAM_INT, 'label' => new lang_string('opening_day', 'format_drip'), 'help' => 'opening_day', 'element_attributes' => array(" size='3' ") ) ); }
This example adds text box which stores its content in a configuration field called drip_opening_day
.
Reading Custom Section Settings
To use the custom section settings in your renderer, you need access to the format object:
$course = $DB->get_record('course', array('id' => $course_id)); $course_format_object = course_get_format($course);
Now, using the course format object, we have access to the array containing the section format options:
$section_format_options = $course_format_object->get_format_options($section); $opening_day = $section_format_options['drip_opening_day'];
Output Column Headers in Topic Course Format or Planner Format
To adapt the topics overview in the topic course format or derivatives such as the Planner format, look in files such as course/format/planner/lib_planner_section.php
for section customizations.
Specifically, in the Planner format, each section gets a section header through the function section_header
, in /course/format/planner/renderer.php
.
Who Prints The Activities in The Topic Course Format?
For a “single section page”, this is the 'call stack' for printing the activities:
format_section_renderer_base#print_single_section_page() -> core_course_renderer#course_section_cm_list() -> core_course_renderer#course_section_cm_list_item() -> core_course_renderer#course_section_cm()
The core_course_renderer is actually an instance, created as follows:
$this->courserenderer = $this->page->get_renderer('core', 'course');
In More Detail
/course/format/topics/renderer.php contains the class format_section_renderer_base
, which has a method print_single_section_page
. This method has access to an instance of core_course_renderer, through $this→courserenderer
.
$this→courserenderer is instantiated in lib/pagelib.php, by moodle_page#get_renderer
.
$this→course_section_cm_list() is called to output all activities in this section.
course/renderer.php contains course_section_cm_list
, which loops over all activities in the section. For each activity, it calls: $this→course_section_cm_list_item
. This function then calls $this→course_section_cm
, which finally prints the actual activity in the section overview.
Extending / Overwriting The core_course_renderer Class
In your custom course format, create a file containing a new class, e.g.:
// Filename: /course/format/tiles/class.tiles_course_renderer.php require_once($CFG->dirroot.'/course/renderer.php'); class tiles_course_renderer extends core_course_renderer { public function course_section_cm($course, &$completioninfo, cm_info $mod, $sectionreturn, $displayoptions = array()) { ...
Instantiate Your Own Course Renderer
Instead of using the factory method get_renderer
, we'll just instantiate this class ourselves:
// Filename: /course/format/tiles/renderer.php class format_tiles_renderer extends format_section_renderer_base { public function [2underscores]construct(moodle_page $page, $target) { parent::[2underscores]construct($page, $target); $this->courserenderer = new tiles_course_renderer($page, $target); ...
(Apologies for the [2underscores], but the wiki software wreaks havoc on the formatting otherwise)
That's it, you are now ready to overwrite any core_course_renderer method you wish, in your own “course renderer” class.
How Modules Are Displayed
- The course format fetches all the cm class instances for the modules to be displayed in the page
- During the export of context for the template, it calls the add_alternative_content_data: https://bitbucket.org/solindevs/icm/src/7a5f65b5d9b5e15337e0a5b54e7285dd682b0935/course/format/classes/output/local/content/cm.php#lines-117
- This in turns calls the cm_info get_formatted_content function from the cm_info: https://bitbucket.org/solindevs/icm/src/7a5f65b5d9b5e15337e0a5b54e7285dd682b0935/course/format/classes/output/local/content/cm.php#lines-183:185
Here three distinct steps are performed:
- Module information cache is either built or retrieved, but information for each module is generated by their own libs: https://bitbucket.org/solindevs/icm/src/7a5f65b5d9b5e15337e0a5b54e7285dd682b0935/lib/modinfolib.php#lines-1672:1678
- The full content of the icmupcomingcalendar is rendered from its lib.php file, in the icmupcomingcalendar_get_coursemodule_info function: https://bitbucket.org/solindevs/icm/src/7a5f65b5d9b5e15337e0a5b54e7285dd682b0935/mod/icmupcomingcalendar/lib.php#lines-141:160
- Next the LMS filters are loaded and cached: https://bitbucket.org/solindevs/icm/src/7a5f65b5d9b5e15337e0a5b54e7285dd682b0935/lib/modinfolib.php#lines-1683
- Lastly, before returning the module’s content, it applies the format_text function to provide support for LMS installed and security filters.
You are here: start » moodle » course_construction