Merge branch 'MDL-68645-master-earlyoutputinit' of git://github.com/mudrd8mz/moodle

This commit is contained in:
Eloy Lafuente (stronk7) 2020-05-14 16:35:35 +02:00
commit 9eb1b08f07
16 changed files with 132 additions and 27 deletions

View File

@ -87,7 +87,8 @@ class api {
if ($completionexpectedtime !== null) {
// Calendar event exists so update it.
$event->name = get_string('completionexpectedfor', 'completion', $lang);
$event->description = format_module_intro($modulename, $instance, $cmid);
$event->description = format_module_intro($modulename, $instance, $cmid, false);
$event->format = FORMAT_HTML;
$event->timestart = $completionexpectedtime;
$event->timesort = $completionexpectedtime;
$event->visible = instance_is_visible($modulename, $instance);
@ -104,7 +105,8 @@ class api {
// Event doesn't exist so create one.
if ($completionexpectedtime !== null) {
$event->name = get_string('completionexpectedfor', 'completion', $lang);
$event->description = format_module_intro($modulename, $instance, $cmid);
$event->description = format_module_intro($modulename, $instance, $cmid, false);
$event->format = FORMAT_HTML;
$event->courseid = $instance->course;
$event->groupid = 0;
$event->userid = 0;

View File

@ -1572,6 +1572,15 @@ class moodle_page {
$this->_wherethemewasinitialised = debug_backtrace();
}
/**
* For diagnostic/debugging purposes, find where the theme setup was triggered.
*
* @return null|array null if theme not yet setup. Stacktrace if it was.
*/
public function get_where_theme_was_initialised() {
return $this->_wherethemewasinitialised;
}
/**
* Reset the theme and output for a new context. This only makes sense from
* external::validate_context(). Do not cheat.

View File

@ -114,10 +114,14 @@ abstract class testing_block_generator extends component_generator_base {
* @return stdClass the block_instance record that has just been created.
*/
public function create_instance($record = null, $options = array()) {
global $DB;
global $DB, $PAGE;
$this->instancecount++;
// Creating a block is a back end operation, which should not cause any output to happen.
// This will allow us to check that the theme was not initialised while creating the block instance.
$outputstartedbefore = $PAGE->get_where_theme_was_initialised();
$record = (object)(array)$record;
$this->preprocess_record($record, $options);
$record = $this->prepare_record($record);
@ -133,6 +137,19 @@ abstract class testing_block_generator extends component_generator_base {
context_block::instance($id);
$instance = $DB->get_record('block_instances', array('id' => $id), '*', MUST_EXIST);
// If the theme was initialised while creating the block instance, something somewhere called an output
// function. Rather than leaving this as a hard-to-debug situation, let's make it fail with a clear error.
$outputstartedafter = $PAGE->get_where_theme_was_initialised();
if ($outputstartedbefore === null && $outputstartedafter !== null) {
throw new coding_exception('Creating a block_' . $this->get_blockname() . ' initialised the theme and output!',
'This should not happen. Creating a block should be a pure back-end operation. Unnecessarily initialising ' .
'the output mechanism at the wrong time can cause subtle bugs and is a significant performance hit. There is ' .
'likely a call to an output function that caused it:' . PHP_EOL . PHP_EOL .
format_backtrace($outputstartedafter, true));
}
return $instance;
}

View File

@ -222,11 +222,15 @@ abstract class testing_module_generator extends component_generator_base {
* cmid (corresponding id in course_modules table)
*/
public function create_instance($record = null, array $options = null) {
global $CFG, $DB;
global $CFG, $DB, $PAGE;
require_once($CFG->dirroot.'/course/modlib.php');
$this->instancecount++;
// Creating an activity is a back end operation, which should not cause any output to happen.
// This will allow us to check that the theme was not initialised while creating the module instance.
$outputstartedbefore = $PAGE->get_where_theme_was_initialised();
// Merge options into record and add default values.
$record = $this->prepare_moduleinfo_record($record, $options);
@ -269,6 +273,19 @@ abstract class testing_module_generator extends component_generator_base {
// Prepare object to return with additional field cmid.
$instance = $DB->get_record($this->get_modulename(), array('id' => $moduleinfo->instance), '*', MUST_EXIST);
$instance->cmid = $moduleinfo->coursemodule;
// If the theme was initialised while creating the module instance, something somewhere called an output
// function. Rather than leaving this as a hard-to-debug situation, let's make it fail with a clear error.
$outputstartedafter = $PAGE->get_where_theme_was_initialised();
if ($outputstartedbefore === null && $outputstartedafter !== null) {
throw new coding_exception('Creating a mod_' . $this->get_modulename() . ' activity initialised the theme and output!',
'This should not happen. Creating an activity should be a pure back-end operation. Unnecessarily initialising ' .
'the output mechanism at the wrong time can cause subtle bugs and is a significant performance hit. There is ' .
'likely a call to an output function that caused it:' . PHP_EOL . PHP_EOL .
format_backtrace($outputstartedafter, true));
}
return $instance;
}

View File

@ -111,12 +111,16 @@ class testing_repository_generator extends component_generator_base {
* @return stdClass repository instance record
*/
public function create_instance($record = null, array $options = null) {
global $CFG, $DB;
global $CFG, $DB, $PAGE;
require_once($CFG->dirroot . '/repository/lib.php');
$this->instancecount++;
$record = (array) $record;
// Creating a repository is a back end operation, which should not cause any output to happen.
// This will allow us to check that the theme was not initialised while creating the repository instance.
$outputstartedbefore = $PAGE->get_where_theme_was_initialised();
$typeid = $DB->get_field('repository', 'id', array('type' => $this->get_typename()), MUST_EXIST);
$instanceoptions = repository::static_function($this->get_typename(), 'get_instance_option_names');
@ -146,6 +150,18 @@ class testing_repository_generator extends component_generator_base {
$id = repository::static_function($this->get_typename(), 'create', $this->get_typename(), 0, $context, $record);
}
// If the theme was initialised while creating the repository instance, something somewhere called an output
// function. Rather than leaving this as a hard-to-debug situation, let's make it fail with a clear error.
$outputstartedafter = $PAGE->get_where_theme_was_initialised();
if ($outputstartedbefore === null && $outputstartedafter !== null) {
throw new coding_exception('Creating a repository_' . $this->get_typename() . ' initialised the theme and output!',
'This should not happen. Creating a repository should be a pure back-end operation. Unnecessarily initialising ' .
'the output mechanism at the wrong time can cause subtle bugs and is a significant performance hit. There is ' .
'likely a call to an output function that caused it:' . PHP_EOL . PHP_EOL .
format_backtrace($outputstartedafter, true));
}
return $DB->get_record('repository_instances', array('id' => $id), '*', MUST_EXIST);
}

View File

@ -297,7 +297,8 @@ function assign_update_events($assign, $override = null) {
$event = new stdClass();
$event->type = CALENDAR_EVENT_TYPE_ACTION;
$event->description = format_module_intro('assign', $assigninstance, $cmid);
$event->description = format_module_intro('assign', $assigninstance, $cmid, false);
$event->format = FORMAT_HTML;
// Events module won't show user events when the courseid is nonzero.
$event->courseid = ($userid) ? 0 : $assigninstance->course;
$event->groupid = $groupid;

View File

@ -123,7 +123,8 @@ function chat_add_instance($chat) {
$event = new stdClass();
$event->type = CALENDAR_EVENT_TYPE_ACTION;
$event->name = $chat->name;
$event->description = format_module_intro('chat', $chat, $chat->coursemodule);
$event->description = format_module_intro('chat', $chat, $chat->coursemodule, false);
$event->format = FORMAT_HTML;
$event->courseid = $chat->course;
$event->groupid = 0;
$event->userid = 0;
@ -169,7 +170,8 @@ function chat_update_instance($chat) {
if ($chat->schedule > 0) {
$event->type = CALENDAR_EVENT_TYPE_ACTION;
$event->name = $chat->name;
$event->description = format_module_intro('chat', $chat, $chat->coursemodule);
$event->description = format_module_intro('chat', $chat, $chat->coursemodule, false);
$event->format = FORMAT_HTML;
$event->timestart = $chat->chattime;
$event->timesort = $chat->chattime;
@ -186,7 +188,8 @@ function chat_update_instance($chat) {
$event = new stdClass();
$event->type = CALENDAR_EVENT_TYPE_ACTION;
$event->name = $chat->name;
$event->description = format_module_intro('chat', $chat, $chat->coursemodule);
$event->description = format_module_intro('chat', $chat, $chat->coursemodule, false);
$event->format = FORMAT_HTML;
$event->courseid = $chat->course;
$event->groupid = 0;
$event->userid = 0;
@ -460,7 +463,8 @@ function chat_prepare_update_events($chat, $cm = null) {
$event = new stdClass();
$event->name = $chat->name;
$event->type = CALENDAR_EVENT_TYPE_ACTION;
$event->description = format_module_intro('chat', $chat, $cm->id);
$event->description = format_module_intro('chat', $chat, $cm->id, false);
$event->format = FORMAT_HTML;
$event->timestart = $chat->chattime;
$event->timesort = $chat->chattime;
if ($event->id = $DB->get_field('event', 'id', array('modulename' => 'chat', 'instance' => $chat->id,

View File

@ -52,7 +52,8 @@ function choice_set_events($choice) {
if ((!empty($choice->timeopen)) && ($choice->timeopen > 0)) {
// Calendar event exists so update it.
$event->name = get_string('calendarstart', 'choice', $choice->name);
$event->description = format_module_intro('choice', $choice, $choice->coursemodule);
$event->description = format_module_intro('choice', $choice, $choice->coursemodule, false);
$event->format = FORMAT_HTML;
$event->timestart = $choice->timeopen;
$event->timesort = $choice->timeopen;
$event->visible = instance_is_visible('choice', $choice);
@ -68,7 +69,8 @@ function choice_set_events($choice) {
// Event doesn't exist so create one.
if ((!empty($choice->timeopen)) && ($choice->timeopen > 0)) {
$event->name = get_string('calendarstart', 'choice', $choice->name);
$event->description = format_module_intro('choice', $choice, $choice->coursemodule);
$event->description = format_module_intro('choice', $choice, $choice->coursemodule, false);
$event->format = FORMAT_HTML;
$event->courseid = $choice->course;
$event->groupid = 0;
$event->userid = 0;
@ -91,7 +93,8 @@ function choice_set_events($choice) {
if ((!empty($choice->timeclose)) && ($choice->timeclose > 0)) {
// Calendar event exists so update it.
$event->name = get_string('calendarend', 'choice', $choice->name);
$event->description = format_module_intro('choice', $choice, $choice->coursemodule);
$event->description = format_module_intro('choice', $choice, $choice->coursemodule, false);
$event->format = FORMAT_HTML;
$event->timestart = $choice->timeclose;
$event->timesort = $choice->timeclose;
$event->visible = instance_is_visible('choice', $choice);
@ -107,7 +110,8 @@ function choice_set_events($choice) {
// Event doesn't exist so create one.
if ((!empty($choice->timeclose)) && ($choice->timeclose > 0)) {
$event->name = get_string('calendarend', 'choice', $choice->name);
$event->description = format_module_intro('choice', $choice, $choice->coursemodule);
$event->description = format_module_intro('choice', $choice, $choice->coursemodule, false);
$event->format = FORMAT_HTML;
$event->courseid = $choice->course;
$event->groupid = 0;
$event->userid = 0;

View File

@ -607,7 +607,8 @@ function data_set_events($data) {
if ($data->timeavailablefrom > 0) {
// Calendar event exists so update it.
$event->name = get_string('calendarstart', 'data', $data->name);
$event->description = format_module_intro('data', $data, $data->coursemodule);
$event->description = format_module_intro('data', $data, $data->coursemodule, false);
$event->format = FORMAT_HTML;
$event->timestart = $data->timeavailablefrom;
$event->timesort = $data->timeavailablefrom;
$event->visible = instance_is_visible('data', $data);
@ -623,7 +624,8 @@ function data_set_events($data) {
// Event doesn't exist so create one.
if (isset($data->timeavailablefrom) && $data->timeavailablefrom > 0) {
$event->name = get_string('calendarstart', 'data', $data->name);
$event->description = format_module_intro('data', $data, $data->coursemodule);
$event->description = format_module_intro('data', $data, $data->coursemodule, false);
$event->format = FORMAT_HTML;
$event->courseid = $data->course;
$event->groupid = 0;
$event->userid = 0;
@ -646,7 +648,8 @@ function data_set_events($data) {
if ($data->timeavailableto > 0) {
// Calendar event exists so update it.
$event->name = get_string('calendarend', 'data', $data->name);
$event->description = format_module_intro('data', $data, $data->coursemodule);
$event->description = format_module_intro('data', $data, $data->coursemodule, false);
$event->format = FORMAT_HTML;
$event->timestart = $data->timeavailableto;
$event->timesort = $data->timeavailableto;
$event->visible = instance_is_visible('data', $data);
@ -662,7 +665,8 @@ function data_set_events($data) {
// Event doesn't exist so create one.
if (isset($data->timeavailableto) && $data->timeavailableto > 0) {
$event->name = get_string('calendarend', 'data', $data->name);
$event->description = format_module_intro('data', $data, $data->coursemodule);
$event->description = format_module_intro('data', $data, $data->coursemodule, false);
$event->format = FORMAT_HTML;
$event->courseid = $data->course;
$event->groupid = 0;
$event->userid = 0;

View File

@ -809,7 +809,8 @@ function feedback_set_events($feedback) {
$event->eventtype = FEEDBACK_EVENT_TYPE_OPEN;
$event->type = empty($feedback->timeclose) ? CALENDAR_EVENT_TYPE_ACTION : CALENDAR_EVENT_TYPE_STANDARD;
$event->name = get_string('calendarstart', 'feedback', $feedback->name);
$event->description = format_module_intro('feedback', $feedback, $feedback->coursemodule);
$event->description = format_module_intro('feedback', $feedback, $feedback->coursemodule, false);
$event->format = FORMAT_HTML;
$event->timestart = $feedback->timeopen;
$event->timesort = $feedback->timeopen;
$event->visible = instance_is_visible('feedback', $feedback);
@ -844,7 +845,8 @@ function feedback_set_events($feedback) {
$event->type = CALENDAR_EVENT_TYPE_ACTION;
$event->eventtype = FEEDBACK_EVENT_TYPE_CLOSE;
$event->name = get_string('calendarend', 'feedback', $feedback->name);
$event->description = format_module_intro('feedback', $feedback, $feedback->coursemodule);
$event->description = format_module_intro('feedback', $feedback, $feedback->coursemodule, false);
$event->format = FORMAT_HTML;
$event->timestart = $feedback->timeclose;
$event->timesort = $feedback->timeclose;
$event->visible = instance_is_visible('feedback', $feedback);

View File

@ -723,7 +723,8 @@ function forum_update_calendar($forum, $cmid) {
if (!empty($forum->duedate)) {
$event->name = get_string('calendardue', 'forum', $forum->name);
$event->description = format_module_intro('forum', $forum, $cmid);
$event->description = format_module_intro('forum', $forum, $cmid, false);
$event->format = FORMAT_HTML;
$event->courseid = $forum->course;
$event->modulename = 'forum';
$event->instance = $forum->id;

View File

@ -162,7 +162,8 @@ function lesson_update_events($lesson, $override = null) {
$event = new stdClass();
$event->type = !$deadline ? CALENDAR_EVENT_TYPE_ACTION : CALENDAR_EVENT_TYPE_STANDARD;
$event->description = format_module_intro('lesson', $lesson, $cmid);
$event->description = format_module_intro('lesson', $lesson, $cmid, false);
$event->format = FORMAT_HTML;
// Events module won't show user events when the courseid is nonzero.
$event->courseid = ($userid) ? 0 : $lesson->course;
$event->groupid = $groupid;

View File

@ -1260,7 +1260,8 @@ function quiz_update_events($quiz, $override = null) {
$event = new stdClass();
$event->type = !$timeclose ? CALENDAR_EVENT_TYPE_ACTION : CALENDAR_EVENT_TYPE_STANDARD;
$event->description = format_module_intro('quiz', $quiz, $cmid);
$event->description = format_module_intro('quiz', $quiz, $cmid, false);
$event->format = FORMAT_HTML;
// Events module won't show user events when the courseid is nonzero.
$event->courseid = ($userid) ? 0 : $quiz->course;
$event->groupid = $groupid;

View File

@ -2424,7 +2424,8 @@ function scorm_update_calendar(stdClass $scorm, $cmid) {
if ((!empty($scorm->timeopen)) && ($scorm->timeopen > 0)) {
// Calendar event exists so update it.
$event->name = get_string('calendarstart', 'scorm', $scorm->name);
$event->description = format_module_intro('scorm', $scorm, $cmid);
$event->description = format_module_intro('scorm', $scorm, $cmid, false);
$event->format = FORMAT_HTML;
$event->timestart = $scorm->timeopen;
$event->timesort = $scorm->timeopen;
$event->visible = instance_is_visible('scorm', $scorm);
@ -2441,7 +2442,8 @@ function scorm_update_calendar(stdClass $scorm, $cmid) {
// Event doesn't exist so create one.
if ((!empty($scorm->timeopen)) && ($scorm->timeopen > 0)) {
$event->name = get_string('calendarstart', 'scorm', $scorm->name);
$event->description = format_module_intro('scorm', $scorm, $cmid);
$event->description = format_module_intro('scorm', $scorm, $cmid, false);
$event->format = FORMAT_HTML;
$event->courseid = $scorm->course;
$event->groupid = 0;
$event->userid = 0;
@ -2465,7 +2467,8 @@ function scorm_update_calendar(stdClass $scorm, $cmid) {
if ((!empty($scorm->timeclose)) && ($scorm->timeclose > 0)) {
// Calendar event exists so update it.
$event->name = get_string('calendarend', 'scorm', $scorm->name);
$event->description = format_module_intro('scorm', $scorm, $cmid);
$event->description = format_module_intro('scorm', $scorm, $cmid, false);
$event->format = FORMAT_HTML;
$event->timestart = $scorm->timeclose;
$event->timesort = $scorm->timeclose;
$event->visible = instance_is_visible('scorm', $scorm);
@ -2482,7 +2485,8 @@ function scorm_update_calendar(stdClass $scorm, $cmid) {
// Event doesn't exist so create one.
if ((!empty($scorm->timeclose)) && ($scorm->timeclose > 0)) {
$event->name = get_string('calendarend', 'scorm', $scorm->name);
$event->description = format_module_intro('scorm', $scorm, $cmid);
$event->description = format_module_intro('scorm', $scorm, $cmid, false);
$event->format = FORMAT_HTML;
$event->courseid = $scorm->course;
$event->groupid = 0;
$event->userid = 0;

View File

@ -5,6 +5,27 @@ information provided here is intended especially for developers.
* The callback get_shortcuts() is now deprecated. Please use get_course_content_items and get_all_content_items instead.
See source code examples in get_course_content_items() and get_all_content_items() in mod/lti/lib.php for details.
* When creating the calendar events and setting the event description to match the module intro description, the filters
must not be applied on the passed description text. Doing so leads to loosing some expected text filters features and
causes unnecessarily early theme and output initialisation in unit tests. If your activity creates calendar events,
you probably have code like:
```
$event->description = format_module_intro('quiz', $quiz, $cmid);
```
You need to change it to:
```
$event->description = format_module_intro('quiz', $quiz, $cmid, false);
$event->format = FORMAT_HTML;
```
Even this is still technically wrong. Content should normally only be formatted just before it is output. Ideally, we
should pass the raw description text, format and have a way to copy the embedded files; or provide another way for the
calendar to call the right format_text() later. The calendar API does not allow us to do these things easily at the
moment. Therefore, this compromise approach is used. The false parameter added ensures that text filters are not run
at this time which is important. And the format must be set to HTML, because otherwise it would use the current user's
preferred editor default format.
* Related to the above and to help with detecting the problematic places in contributed 3rd party modules, the
testing_module_generator::create_instance() now throws coding_exception if creating a module instance initialised the
theme and output as a side effect.
=== 3.8 ===

View File

@ -1668,6 +1668,7 @@ function workshop_calendar_update(stdClass $workshop, $cmid) {
// the common properties for all events
$base = new stdClass();
$base->description = format_module_intro('workshop', $workshop, $cmid, false);
$base->format = FORMAT_HTML;
$base->courseid = $workshop->course;
$base->groupid = 0;
$base->userid = 0;