Navigation 2.0 implementation plan
Moodle 2.0
This page lists the various strands of work that will be needed to implement Navigation 2.0 and tried to break them down into manageable chunks.
I think the aim is for me to do as much as this as possible during the 13 weeks I am still working for moodle.com. Therefore, I have focussed on the core work it is necessary to do to achieve the core goals, and then noted ways we could do more later as 'Future ideas'.
Note, all the estimates below should include writing unit tests for the new code, and updateing the corresponding developer documentation on this wiki.
Please discuss this proposal in this General Developer Forum thread.
Brief summary in code
Currently, a 'Hello world' Moodle script (for example mod/mymod/helloworld.php) looks something like:
<?php
require_once(dirname(__FILE__) . '/../../config.php');
$cmid = required_param('cmid', 0, PARAM_INT);
if (!$cm = get_coursemodule_from_id('quiz', $id)) {
print_error('invalidcoursemodule');
}
if (!$course = $DB->get_record('course', array('id' => $cm->course))) {
print_error('coursemisconf');
}
require_login($course, false, $cm);
$context = get_context_instance(CONTEXT_MODULE, $cm->id);
require_capability('mod/helloworld:begreeted', $context);
require_js('mod/helloworld/flashyeffects.js');
$title = get_string('greetingtitle', 'helloworld');
$PAGE = page_create_instance($quiz->id);
$PAGE->print_header($title, $title); // Does build_navigation($title, $cm)
echo '
';
if (!empty($CFG->showblocksonmodpages) && (blocks_have_content($pageblocks, BLOCK_POS_LEFT) || $PAGE->user_is_editing())) {
echo '\n";
}
echo '';
if (!empty($CFG->showblocksonmodpages) && (blocks_have_content($pageblocks, BLOCK_POS_RIGHT) || $PAGE->user_is_editing())) {
echo '';
}
echo '';
print_container_start();
blocks_print_group($PAGE, $pageblocks, BLOCK_POS_LEFT);
print_container_end();
echo " ';
print_heading(get_string('helloworld', 'helloworld'));
echo '', get_string('greeting', 'helloworld', fullname($USER)), '
';
echo ' ';
print_container_start();
blocks_print_group($PAGE, $pageblocks, BLOCK_POS_RIGHT);
print_container_end();
echo '
';
print_footer($course);
In Moodle 2.0, it will look like
As you can see, quite a lot of magic goes on by default, for example, automatically creating $OUTPUT once we have enough information to to work out the current theme, and automatically setting up the navigation bar once we know that this page belongs to a course_module, and what the page title is. Naturally, any of this magic can be explicitly overridden by making extra method calls if the default behaviour is not what you want.
Also, these objects will track their own state, so, for example, if you try to do $PAGE->set_theme() after you have used $OUTPUT to output anything, you will get an exception explaining to you what you have done wrong, rather than mysterious weird behaviour.
Page object
$PAGE is a central store of information about the current page we are generating in response to the user's request. It does not do very much itself except keep track of information, however, it serves as the access point to some more significant components like $PAGE->theme, $PAGE->requires, $PAGE->blocks, etc.
There was a $PAGE global in 1.9 that scripts had to create themselves, and which was not used consistently everywhere. In Moodle 2.0, there will always be a $PAGE, and it will be created automatically during require_once('config.php'); However, as far as possible, we will try to implement deprecated functions and methods so that old code using $PAGE does not break, but that will probably require some trickiness.
- General information - there will be fields
- $PAGE->course - alias for $COURSE.
- $PAGE->cm - it we are in an activity, otherwise null.
- $PAGE->context
- $PAGE->title - goes into <title> in the HTML
- $PAGE->url - a moodle_url object for this page.
- $PAGE->bodyclasses - (MDL-14305, MDL-14306)
- $PAGE->pagetype - e.g. 'course-view' or 'mod-quiz-attempt'. Same as the id attribute on <body>.
- $PAGE->generaltype - 'normal', 'popup', etc. (MDL-10681, MDL-12093)
- $PAGE->docslink -
- $PAGE->lang
- $PAGE->theme
- $PAGE->cachable - true by default, can be set to other things by pages that need to restrict HTTP caching.
- $PAGE->focuselement the HTML element to focus by default.
- Other stuff, covered in the more specific sections below.
Most of this is set up automatically as a result of require_once('config.php'); but could later be overridden by a particular script in special circumstances. Other parts, (e.g. ->course and ->cm) are set up when we have the relevant information, normally as a side-effect of require_login().
Although it looks like we are using an object with public fields, I am actually, proposing we use private fields, with __get magic to allow read-only access with $PAGE->url. However, write access to these fields will be via $PAGE->set_url(...) which does appropriate sanity checking.
Estimates
- MDL-12213 Document the sequence in which things get set up during the start of any Moodle page (from require_once('config.php'); to require_login(), or so, showing where all the globals get set up. Both the current flow, and the propsed new order: 2 days
- MDL-12212, MDL-15817 Based on that implement the basic information storage role of $PAGE, and kill the existing $PAGE subclasses, moving any important code elsewhere: 1 week, but depends on the previous task.
Themability
See Theme_engines_for_Moodle? (We could remove the question mark from the page name now ;-))
The main change is that there will be a new $OUTPUT global for generating HTML. So, for example, instead of calling the function print_heading(...) from weblib.php, you will instead call $OUTPUT->heading(...). The point is that since $OUTPUT is an object, the theme can choose exactly which object it is, and so the theme can change how standard elements are rendered.
We will also change header.html and footer.html in themes. In the past they could only use the variable that the print_header function had prepared for them - there was code like
echo " $bodytags";
// ...
if ($navigation) { ?>
}
// ...
echo $loggedinas;
with corresponding code in print header/footer like
function print_header(/* ... */, $bodytags, /* ... */) {
// ...
$bodytags .= ' class="'.$pageclass.'" id="'.$pageid.'"';
// ...
include($CFG->header);
// ...
}
function print_footer(/* ... */) {
// ...
if (!isset($loggedinas)) {
$loggedinas = user_login_string($usercourse, $USER);
}
if ($loggedinas == $menu) {
$menu = ;
}
// ...
include($CFG->footer);
// ...
}
(and this was done, even if the theme did not want to use those variables). In Moodle 2.0, $OUTPUT->header/footer, will do little more than.
public function header() {
include($this->theme->get_path() . '/header.html');
}
and then in header.html and footer.html you will have things like
<body class="<?php echo $PAGE->htmlclass; ?>" id="<?php echo $PAGE->pagetype; ?>" ... >
// ...
// ...
<?php echo $OUTPUT->loggedinas(); ?>
That is, the theme files will use $OUTPUT to generate just the standard bits of output they want. The header can also take some low-level information like $PAGE->pagetype directly from $PAGE.
In fact, $OUTPUT->header will not be that simple because, to ensure backwards-compatibility, we will have to build all of the standard variables as we currently do before including header.html, unless the theme config.php defines a variable like $doesnotneedlegacyvariables = true;
Estimates
- Devise a consistent way to manage the parameter lists of all the print_xxx methods in weblib.php, so that we don't have to have functions with millions of parameters: 1 day.
- New moodle_core_renderer class and a global instance $OUTPUT, and make deprecated versions of all the old weblib.php functions that delegate to it. 1+ week
- Implement default_renderer_factory, with the option for themes to specify a different renderer factory in their config.php. The current renderer factory will be accessible as $PAGE->theme, as in $PAGE->theme->get_renderer('forum'); 1- week
- Create moodle_custom_corners_renderer to get the custom corners mess out of moodle_core_renderer, and to prove that bit of the concept. 1 day
- MDL-3625, MDL-3626, MDL-14539, MDL-15400 Add standard header and footer bits to moodle_core_renderer and rework header.html and footer.html in the core themes to use it: 1 week
- Implement a very simple version of themeengine/phptemplates, as a proof of concept. 1- week
Note: need to get tracker issue numbers from Navigation_2.0#Theme_related, or create new issues as needed.
Future ideas
- Convert a couple of other parts of moodle to start using a renderer class, to prove the concept. Perhaps just forum post, and a block. We should pick things that are not too difficult, and things that themers are likely to want to customise.
- Refactor other core plugins to use a renderer class (a bit at a time).
- Create a theme/standardtemplate.
- Develop a highly optimised themeengine/phptemplates.
Misc themeability issues:
- MDL-9306 Main course formats need to have tables removed but keep AJAX working - there seem to be some regressions from this work ;-)
- MDL-10522 Hard-coded <br /> used for spacing.
- MDL-14058 Code includes
tags in many places, which breaks theming, or makes it more difficult
- MDL-15959 patch any theme to have a personal visual style (CSS file) for each course - not sure this is a good solution, but it is an interesting requirement.
Blocks infrastructure
See Very_flexible_block_system_proposal.
The concept is that themes are responsible for printing the blocks, and can also determine where blocks may appear on the page. To manage all this there is a block_manager class, accessible as $PAGE->blocks. When this is created, it does
/// In block_manager::__construct
$this->pageslots = $PAGE->theme->get_block_slots(); // Might return array('left', 'right')
In addition, a particular script may decide to allow blocks in its content area. For example, the main index.php may decide to do
$PAGE->blocks->add_block_slot('mainpage');
During $OUTPUT->header(), which calls into theme/.../header.html, it is the theme's responsibility to do something like
<?php
$blockoutput = $PAGE->theme->get_renderer('blocks');
$blockoutput->blocks($PAGE->blocks->get_blocks_in_slot('left'));
?>
This is explained a bit more, with diagrams, in this forum thread.
Estimates
- New database tables 1 day
- Upgrade from existing tables. 3 days
- block_manager class get and manage the blocks on a page. (MDL-12191, MDL-11131, MDL-17447, MDL-15379, MDL-6748) 3 days
- Functions to get the information about blocks required by the editing UI 3 days
Blocks editing UI changes
Estimates
- New rearrange blocks UI (MDL-6024) 1 week
- New settings forms for block using formslib 1 week
- Detailed sticky blocks UI on block settings form 3 days
- Blocks admin reporting UI (MDL-13708) 2 days
- MDL-13888 Allow RSS block on MyMoodle pages only for users who can configure it
- MDL-13891 Admin Tree block display in myMoodle controlled by capabilities
Future ideas
- MDL-12240 Admin screen for configuring the default course blocks ($CFG->defaultblocks) which can currently only be set in config.php.
- MDL-13627 Blocks - Default Closed View
There are two main concepts here:
1. We classify every page in Moodle as either a 'normal' page or a 'settings' page. 'normal' pages are the ones you see when browsing around the site, or learning or teaching. 'settings' pages are the ones that control that 'normal' experience. That is, all the things that you can access only as an Editing Teacher, Course Creator or Administrator. There are also a few settings links that are used by Students, and other lowly people. I would classify 'Change my password' and 'Unenrol me from this course' as settings pages.
2. All the 'normal' pages fall into a big navigation tree See the diagram in this forum thread for an early sketch. The structure of this tree can mostly be deduced form the navigation bar in Moodle.
There will be several objects accessible from $PAGE to record this information and allow it to be queried.
- $PAGE->navbar - replaces the build_navigation function with a more OO approach.
- $PAGE->settingspages - all the 'settings' pages (like roles, settings, filter, files) that relate to where we are.
- $PAGE->globalnav - how this page fits into the global navigation structure.
(The details are still a little vague here. The next section, New blocks, may make this a bit clearer.)
Estimates
- Object oriented approach to the nav bar (MDL-2347, MDL-14061, MDL-14901) 3 days
- Way to set $PAGE->settingspages without duplicating code Probably involves looking at each tabs.php file. (MDL-14632) 1 Week
- $PAGE->globalnav 3 days.
New blocks
These are intended to be used globally across the whole site a sticky blocks, to render the navigation.
Estimates
- New block_tree base class, for blocks that display nested trees. With separate renderer class. (MDL-12183)4 days
- New settings_block, based on block_tree and taking data from $PAGE->settingspages, replaces admin_tree and admin. 3 days
- MDL-8369 New navigation block, based on block_tree for displaying global navigation. (MDL-8369) 3 days
Future ideas
- MDL-16244 Custom menu block (like on moodle.org now).
Convert existing pages
For each existing moodle script:
- Switch to using the new $PAGE object
- Take any blocks rendering code out of the page (MDL-18765)
- Change how the nav bar is built, and the call to print_header/footer
- Initialise $PAGE->settingspages - possibly from any existing tab bar and update button
- Convert any require_js calls. Move inline JavaScript out
Estimates
Probably possible to do several pages per day. However, there seem to be about 600 pages in Moodle that need to be converted. Looks like I will need help here.
Bugs this should fix
- MDL-15946 Add ability to add blocks to Category page
- MDL-11960 Cannot configure rss block on tag page
- MDL-6692 blocks on forum pages
- MDL-13582 "Course Sticky Blocks" don't get displayed when the "Show the course blocks" option is chosen when using "Add a resource" > "Compose a web page"
- MDL-13606 Sticky blocks on tag pages
- MDL-5898 customizable blogs blocks
JavaScript clean-up
- New class requirements_manager, accessible via $PAGE->requires.
- Replaces require_js and friends with methods like
- $PAGE->requires->js('mod/mymod/script.js');
- $PAGE->requires->css('mod/mymod/styles.css');
- $PAGE->requires->call_js($fnname, $arguments);
- $PAGE->requires->string_for_js('answer', 'question');
- $PAGE->requires->data_for_js(...);
- $PAGE->requires->skip_link_to('questionbank); (MDL-17730)
- Normally (apart from CSS) these requirements are output at the foot of the page. To override that and output the associated HTML as soon as possible, you can do
- $PAGE->requires->js('mod/mymod/script.js')->immediately(); but this is not recommended and normally not necessary.
- $PAGE->requires->call_js('init_my_feature')->on_dom_ready();
- requirements_manager tracks what needs to be linked to, and whether a link has been output yet.
- print_footer and print_header call it get extra HTML to output.
- keep a deprecated require_js function for backwards compatibility.
There is then just a the small matter of converting all the legacy JS inclusion to user the new API to include itself.
Estimates
- Tracking bug: MDL-16583
- MDL-16151, MDL-16695, MDL-16693 Building the requirements_manager class, as above and hooking it up: 1 week
- MDL-6981, MDL-14542 give blocks, filters, etc. a sensible opportunity to call require->js and require->css: 1 day
- MDL-16673, MDL-16703, MDL-16704, MDL-16706, MDL-17922, ... Cleaning up the legacy mess: indeterminate, but a lot could be done in 2 weeks
Future ideas
- MDL-10932 conditional get support for stylesheets.
- Extend requirements_manager so it could automatically concatenate CSS and JS into fewer larger files. (This could be done on upgrade and install.) Then serve the appropriate concatenation instead of many separate files, which is better for performance.
- Investigate open source CSS and JS minifiers, that could be used in in combination with the above.
Filters
Implement Filter enable/disable by context.
- Create moodle/filter:manage capability.
- Create DB filter_active table.
- Upgrade existing $CFG->filters into that table.
- Update admin page to work with the settings table.
- New settings page for other contexts.
- New get_active_filters($context); function.
- Update caching key in format_text.
- Backup and restore new settings.
Estimates
- ✔MDL-7336 Building the requirements_manager manager class, as above and hooking it up: 1 week Done, will be committed after code review.
Meta
Estimates
- ✔Produce these estimates ;-) 2 days
- ✔Final TODO: check I have extracting everything important from Navigation/Pagelib/Blocks_2.0_design.
See also
- Navigation 2.0 and related pages.
- General Developer Forum thread for discussing this.
<?php
require_once(dirname(__FILE__) . '/../../config.php'); // Creates $PAGE
$cmid = required_param('cmid', 0, PARAM_INT);
if (!$cm = get_coursemodule_from_id('quiz', $id)) {
print_error('invalidcoursemodule');
}
if (!$course = $DB->get_record('course', array('id' => $cm->course))) {
print_error('coursemisconf');
}
require_login($course, false, $cm); // Adds to $PAGE, creates $OUTPUT
$context = $PAGE->context;
require_capability('mod/helloworld:begreeted', $context);
$PAGE->requires->js('mod/helloworld/flashyeffects.js');
$PAGE->set_title(get_string('greetingtitle', 'helloworld')); // Sets $PAGE->navbar
/// Some core rendering functions
$OUTPUT->header();
$OUTPUT->heading(get_string('helloworld', 'helloworld'));
/// Some module-specific rendering functions
$helloworldoutput = $PAGE->theme->get_renderer('helloworld'); // Uses same modules names as get_string()
$helloworldoutput->greeting(get_string('greeting', 'helloworld', fullname($USER)));
$OUTPUT->footer();
<?php