MDL-71919 core_message: Fix unclosed html tags in email notification
authorcescobedo <carlos.escobedo@gmail.com>
Mon, 5 Jul 2021 13:57:23 +0000 (15:57 +0200)
committerEloy Lafuente (stronk7) <stronk7@moodle.org>
Thu, 8 Jul 2021 21:30:35 +0000 (23:30 +0200)
lib/classes/message/manager.php
message/classes/helper.php
message/tests/helper_test.php

index 0a26e59..7e7e6c4 100644 (file)
@@ -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 .=
                     "<br><br>---------------------------------------------------------------------<br>" . $emailtagline;
             }
index 84d9bbc..3d1e0e4 100644 (file)
@@ -23,6 +23,7 @@
  */
 
 namespace core_message;
+use DOMDocument;
 
 defined('MOODLE_INTERNAL') || die();
 
@@ -678,4 +679,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 <body> element added in C14N function.
+                    $html = preg_replace('~<(/?(?:body))[^>]*>\s*~i', '', $html);
+                }
+            }
+
+        return $html;
+    }
 }
index 2bfa83e..bf2af9a 100644 (file)
@@ -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' => [
+                '<h1>Title</h1><p>Paragraph</p><b>Bold', '<h1>Title</h1><p>Paragraph</p><b>Bold</b>', true
+            ],
+            'Prevent unclosed html elements including comments' => [
+                '<h1>Title</h1><p>Paragraph</p><!-- Comments //--><b>Bold', '<h1>Title</h1><p>Paragraph</p><!-- Comments //--><b>Bold</b>', true
+            ],
+            'Prevent unclosed comments' => ['<h1>Title</h1><p>Paragraph</p><!-- Comments', '<h1>Title</h1><p>Paragraph</p>', true
+            ],
+            'Prevent unclosed html elements without removing tag body' => [
+                '<body><h2>Title 2</h2><p>Paragraph</p><b>Bold</body>', '<body><h2>Title 2</h2><p>Paragraph</p><b>Bold</b></body>', false
+            ],
+            'Empty html' => [
+                '', '', false
+            ],
+        ];
+    }
 }