diff --git a/lib/classes/message/manager.php b/lib/classes/message/manager.php index 0a26e592830..7e7e6c4d163 100644 --- a/lib/classes/message/manager.php +++ b/lib/classes/message/manager.php @@ -186,10 +186,18 @@ class manager { $localisedeventdata->fullmessage = $eventdata->fullmessage; $localisedeventdata->fullmessagehtml = $eventdata->fullmessagehtml; if (!empty($localisedeventdata->fullmessage)) { + // Prevent unclosed HTML elements. + $localisedeventdata->fullmessage = + \core_message\helper::prevent_unclosed_html_tags($localisedeventdata->fullmessage, true); + $localisedeventdata->fullmessage .= "\n\n---------------------------------------------------------------------\n" . $emailtagline; } if (!empty($localisedeventdata->fullmessagehtml)) { + // Prevent unclosed HTML elements. + $localisedeventdata->fullmessagehtml = + \core_message\helper::prevent_unclosed_html_tags($localisedeventdata->fullmessagehtml, true); + $localisedeventdata->fullmessagehtml .= "

---------------------------------------------------------------------
" . $emailtagline; } diff --git a/message/classes/helper.php b/message/classes/helper.php index 5074c0e9472..98ee40fadaf 100644 --- a/message/classes/helper.php +++ b/message/classes/helper.php @@ -23,6 +23,7 @@ */ namespace core_message; +use DOMDocument; defined('MOODLE_INTERNAL') || die(); @@ -677,4 +678,30 @@ class helper { } return []; } + + /** + * Prevent unclosed HTML elements in a message. + * + * @param string $message The html message. + * @param bool $removebody True if we want to remove tag body. + * @return string The html properly structured. + */ + public static function prevent_unclosed_html_tags( + string $message, + bool $removebody = false + ) : string + { + $html = ''; + if (!empty($message)) { + $doc = new DOMDocument(); + @$doc->loadHTML($message); + $html = $doc->getElementsByTagName('body')->item(0)->C14N(false, true); + if ($removebody) { + // Remove element added in C14N function. + $html = preg_replace('~<(/?(?:body))[^>]*>\s*~i', '', $html); + } + } + + return $html; + } } diff --git a/message/tests/helper_test.php b/message/tests/helper_test.php index 2bfa83e0986..bf2af9a83b2 100644 --- a/message/tests/helper_test.php +++ b/message/tests/helper_test.php @@ -177,4 +177,43 @@ class core_message_helper_testcase extends advanced_testcase { $this->assertNotEmpty(\core_message\helper::search_get_user_details($user6)); // Teacher in same course. $this->assertNotEmpty(\core_message\helper::search_get_user_details($user7)); // Teacher (course contact) in another course. } + + /** + * Test prevent_unclosed_html_tags returns the correct html. + * + * @dataProvider prevent_unclosed_html_tags_data + * @param string $text text to preview unclosed html tags. + * @param string $goodhtml html good structured. + * @param bool $removebody true if we want to remove tag body. + */ + public function test_prevent_unclosed_html_tags(string $message, string $goodhtml, bool $removebody) { + $this->setAdminUser(); + + $html = \core_message\helper::prevent_unclosed_html_tags($message, $removebody); + $this->assertSame($goodhtml, $html); + } + + /** + * Data provider for the test_prevent_unclosed_html_tags_data tests. + * + * @return array + */ + public function prevent_unclosed_html_tags_data(): array { + return [ + 'Prevent unclosed html elements' => [ + '

Title

Paragraph

Bold', '

Title

Paragraph

Bold', true + ], + 'Prevent unclosed html elements including comments' => [ + '

Title

Paragraph

Bold', '

Title

Paragraph

Bold', true + ], + 'Prevent unclosed comments' => ['

Title

Paragraph