diff --git a/backup/cc/cc2moodle.php b/backup/cc/cc2moodle.php index b3c1a2f4138..a13362b4d0d 100644 --- a/backup/cc/cc2moodle.php +++ b/backup/cc/cc2moodle.php @@ -257,9 +257,9 @@ class cc2moodle { '[#date_now#]', '[#section_count#]'); - $replace_values = array(htmlentities($course_title), - htmlentities($course_short_name), - htmlentities($course_description), + $replace_values = array(entities::safexml($course_title), + entities::safexml($course_short_name), + entities::safexml($course_description), time(), $section_count); @@ -565,7 +565,7 @@ class cc2moodle { } $replace_values = array($instances[$i - 1]['instance'], - htmlentities($instances[$i - 1]['title']), + entities::safexml($instances[$i - 1]['title']), $user_info); $instance .= str_replace($find_tags, $replace_values, $sheet); diff --git a/backup/cc/entities.class.php b/backup/cc/entities.class.php index a221d2c69ca..e61fe1ee03b 100644 --- a/backup/cc/entities.class.php +++ b/backup/cc/entities.class.php @@ -24,6 +24,18 @@ defined('MOODLE_INTERNAL') or die('Direct access to this script is forbidden.'); class entities { + /** + * + * Prepares convert for inclusion into XML + * @param string $value + */ + public static function safexml($value) { + $result = htmlspecialchars(html_entity_decode($value, ENT_QUOTES, 'UTF-8'), + ENT_NOQUOTES, + 'UTF-8', + false); + return $result; + } protected function prepare_content($content) { $result = $content; @@ -31,13 +43,12 @@ class entities { return ''; } $encoding = null; + $xml_error = new libxml_errors_mgr(); $dom = new DOMDocument(); - try { - if ($dom->loadHTML($content)) { - $encoding = $dom->xmlEncoding; - } - } catch(DOMException $e) { - //silence the exception if any + $dom->validateOnParse = false; + $dom->strictErrorChecking = false; + if ($dom->loadHTML($content)) { + $encoding = $dom->xmlEncoding; } if (empty($encoding)) { $encoding = mb_detect_encoding($content, 'auto', true); @@ -223,6 +234,7 @@ class entities { } protected function get_all_files () { + global $CFG; $all_files = array(); diff --git a/backup/cc/entity.forum.class.php b/backup/cc/entity.forum.class.php index 143de304bd7..828bca965a9 100644 --- a/backup/cc/entity.forum.class.php +++ b/backup/cc/entity.forum.class.php @@ -99,8 +99,8 @@ class cc_forum extends entities { $replace_values = array($instance['instance'], //To be more true to the actual forum name we use only forum title - htmlentities($topic_data['title']), - htmlentities($topic_data['description']), + self::safexml($topic_data['title']), + self::safexml($topic_data['description']), time()); $result = str_replace($find_tags, $replace_values, $sheet_mod_forum); diff --git a/backup/cc/entity.label.class.php b/backup/cc/entity.label.class.php index cc43b952719..6c27bf2b27b 100644 --- a/backup/cc/entity.label.class.php +++ b/backup/cc/entity.label.class.php @@ -54,8 +54,8 @@ class cc_label extends entities { $title = isset($instance['title']) && !empty($instance['title']) ? $instance['title'] : 'Untitled'; $content = "\"Folder\" {$title}"; $replace_values = array($instance['instance'], - $title, - htmlentities($content), + self::safexml($title), + self::safexml($content), time()); return str_replace($find_tags, $replace_values, $sheet_mod_label); diff --git a/backup/cc/entity.quiz.class.php b/backup/cc/entity.quiz.class.php index 79ff905ffb0..34e9b273eab 100644 --- a/backup/cc/entity.quiz.class.php +++ b/backup/cc/entity.quiz.class.php @@ -140,10 +140,10 @@ class cc_quiz extends entities { '[#node_questions_feedback#]'); $replace_values = array($instance['id'], - htmlentities($instance['title']), - htmlentities($instance['title']), - htmlentities($quiz_stamp), - htmlentities($questions_strings), + self::safexml($instance['title']), + self::safexml($instance['title']), + self::safexml($quiz_stamp), + self::safexml($questions_strings), time(), $instance['options']['max_attempts'], $instance['options']['timelimit'], @@ -256,7 +256,7 @@ class cc_quiz extends entities { $quiz_stamp = 'localhost+' . time() . '+' . $this->generate_random_string(6); $replace_values = array($instance['id'], - htmlentities($instance['title']), + self::safexml($instance['title']), $quiz_stamp, $node_course_question_categories_questions); @@ -304,10 +304,10 @@ class cc_quiz extends entities { $question_type_node = ($question_moodle_type == MOODLE_QUIZ_SHORTANSWER) ? $this->create_node_course_question_categories_question_category_question_shortanswer($question) : $question_type_node; $replace_values = array($question['id'], - htmlentities($this->truncate_text($question['title'], 255, true)), - htmlentities($question['title']), + self::safexml($this->truncate_text($question['title'], 255, true)), + self::safexml($question['title']), $question_moodle_type, - htmlentities($question['feedback']), + self::safexml($question['feedback']), time(), $question_type_node, $quiz_stamp, @@ -764,7 +764,7 @@ class cc_quiz extends entities { '[#is_single#]'); $replace_values = array($node_course_question_categories_question_answer, - htmlentities($answer_string), + self::safexml($answer_string), $is_single); $node_question_categories_question = str_replace($find_tags, $replace_values, $sheet_question_categories_question); @@ -819,8 +819,8 @@ class cc_quiz extends entities { '[#use_case#]', '[#node_course_question_categories_question_category_question_answer#]'); - $replace_values = array(htmlentities($answers_string), - htmlentities($use_case), + $replace_values = array(self::safexml($answers_string), + self::safexml($use_case), $node_course_question_categories_question_answer); @@ -866,7 +866,7 @@ class cc_quiz extends entities { '[#false_answer_id#]'); $replace_values = array($node_course_question_categories_question_answer, - htmlentities($true_answer_id), + $true_answer_id, $false_answer_id); $node_question_categories_question = str_replace($find_tags, $replace_values, $sheet_question_categories_question); @@ -900,9 +900,9 @@ class cc_quiz extends entities { '[#answer_feedback#]'); $replace_values = array($answer['id'], - htmlentities($answer['title']), + self::safexml($answer['title']), $answer['score'], - $answer['feedback']); + self::safexml($answer['feedback'])); $node_question_categories_question_answer = str_replace($find_tags, $replace_values, $sheet_question_categories_question_answer); diff --git a/backup/cc/entity.resource.class.php b/backup/cc/entity.resource.class.php index 7c64b5c6b90..b6100111ae8 100644 --- a/backup/cc/entity.resource.class.php +++ b/backup/cc/entity.resource.class.php @@ -149,13 +149,13 @@ class cc_resource extends entities { //silence the complaints } chdir($cdir); - $mod_alltext = htmlspecialchars($mod_alltext, ENT_COMPAT, 'UTF-8', false); + $mod_alltext = self::safexml($mod_alltext); } } } $replace_values = array($instance['instance'], - htmlspecialchars($instance['title']), + self::safexml($instance['title']), $mod_type, $mod_reference, '', diff --git a/backup/cc/entity11.basiclti.class.php b/backup/cc/entity11.basiclti.class.php index d15366bfc9b..c44c07cd73d 100644 --- a/backup/cc/entity11.basiclti.class.php +++ b/backup/cc/entity11.basiclti.class.php @@ -80,7 +80,7 @@ class cc11_basiclti extends entities11 { protected function getValue($node, $default = '') { $result = $default; if (is_object($node) && ($node->length > 0) && !empty($node->item(0)->nodeValue)) { - $result = htmlentities(trim($node->item(0)->nodeValue)); + $result = htmlspecialchars(trim($node->item(0)->nodeValue), ENT_COMPAT, 'UTF-8', false); } return $result; } diff --git a/backup/cc/entity11.forum.class.php b/backup/cc/entity11.forum.class.php index 970fae1af16..1fc3625d857 100644 --- a/backup/cc/entity11.forum.class.php +++ b/backup/cc/entity11.forum.class.php @@ -98,8 +98,8 @@ class cc11_forum extends entities11 { $replace_values = array($instance['instance'], //To be more true to the actual forum name we use only forum title - htmlentities($topic_data['title']), - htmlentities($topic_data['description']), + self::safexml($topic_data['title']), + self::safexml($topic_data['description']), time()); $result = str_replace($find_tags, $replace_values, $sheet_mod_forum); diff --git a/backup/cc/entity11.lti.class.php b/backup/cc/entity11.lti.class.php index d3cfd721989..7fad7126445 100644 --- a/backup/cc/entity11.lti.class.php +++ b/backup/cc/entity11.lti.class.php @@ -75,7 +75,7 @@ class cc11_lti extends entities11 { protected function getValue($node, $default = '') { $result = $default; if (is_object($node) && ($node->length > 0) && !empty($node->item(0)->nodeValue)) { - $result = htmlentities(trim($node->item(0)->nodeValue)); + $result = htmlspecialchars(trim($node->item(0)->nodeValue), ENT_COMPAT, 'UTF-8', false); } return $result; } diff --git a/backup/cc/entity11.quiz.class.php b/backup/cc/entity11.quiz.class.php index a56fe2de31f..83e50d177fe 100644 --- a/backup/cc/entity11.quiz.class.php +++ b/backup/cc/entity11.quiz.class.php @@ -140,10 +140,10 @@ class cc11_quiz extends entities11 { '[#node_questions_feedback#]'); $replace_values = array($instance['id'], - htmlentities($instance['title']), - htmlentities($instance['title']), - htmlentities($quiz_stamp), - htmlentities($questions_strings), + self::safexml($instance['title']), + self::safexml($instance['title']), + self::safexml($quiz_stamp), + self::safexml($questions_strings), time(), $instance['options']['max_attempts'], $instance['options']['timelimit'], @@ -256,7 +256,7 @@ class cc11_quiz extends entities11 { $quiz_stamp = 'localhost+' . time() . '+' . $this->generate_random_string(6); $replace_values = array($instance['id'], - htmlentities($instance['title']), + self::safexml($instance['title']), $quiz_stamp, $node_course_question_categories_questions); @@ -304,10 +304,10 @@ class cc11_quiz extends entities11 { $question_type_node = ($question_moodle_type == MOODLE_QUIZ_SHORTANSWER) ? $this->create_node_course_question_categories_question_category_question_shortanswer($question) : $question_type_node; $replace_values = array($question['id'], - htmlentities($this->truncate_text($question['title'], 255, true)), - htmlentities($question['title']), + self::safexml($this->truncate_text($question['title'], 255, true)), + self::safexml($question['title']), $question_moodle_type, - htmlentities($question['feedback']), + self::safexml($question['feedback']), time(), $question_type_node, $quiz_stamp, @@ -764,7 +764,7 @@ class cc11_quiz extends entities11 { '[#is_single#]'); $replace_values = array($node_course_question_categories_question_answer, - htmlentities($answer_string), + self::safexml($answer_string), $is_single); $node_question_categories_question = str_replace($find_tags, $replace_values, $sheet_question_categories_question); @@ -819,8 +819,8 @@ class cc11_quiz extends entities11 { '[#use_case#]', '[#node_course_question_categories_question_category_question_answer#]'); - $replace_values = array(htmlentities($answers_string), - htmlentities($use_case), + $replace_values = array(self::safexml($answers_string), + self::safexml($use_case), $node_course_question_categories_question_answer); @@ -866,7 +866,7 @@ class cc11_quiz extends entities11 { '[#false_answer_id#]'); $replace_values = array($node_course_question_categories_question_answer, - htmlentities($true_answer_id), + $true_answer_id, $false_answer_id); $node_question_categories_question = str_replace($find_tags, $replace_values, $sheet_question_categories_question); @@ -900,9 +900,9 @@ class cc11_quiz extends entities11 { '[#answer_feedback#]'); $replace_values = array($answer['id'], - htmlentities($answer['title']), + self::safexml($answer['title']), $answer['score'], - $answer['feedback']); + self::safexml($answer['feedback'])); $node_question_categories_question_answer = str_replace($find_tags, $replace_values, $sheet_question_categories_question_answer); diff --git a/backup/cc/entity11.resource.class.php b/backup/cc/entity11.resource.class.php index 4d9544d2b4c..adadce00e80 100644 --- a/backup/cc/entity11.resource.class.php +++ b/backup/cc/entity11.resource.class.php @@ -157,13 +157,13 @@ class cc11_resource extends entities11 { //silence the complaints } chdir($cdir); - $mod_alltext = htmlspecialchars($mod_alltext, ENT_COMPAT, 'UTF-8', false); + $mod_alltext = self::safexml($mod_alltext); } } } $replace_values = array($instance['instance'], - htmlspecialchars($instance['title']), + self::safexml($instance['title']), $mod_type, $mod_reference, '', diff --git a/backup/cc/validator.php b/backup/cc/validator.php index 43de9031149..ba125544c10 100644 --- a/backup/cc/validator.php +++ b/backup/cc/validator.php @@ -109,7 +109,14 @@ final class libxml_errors_mgr { * @var boolean */ private $previous = false; - public function __construct(){ + + /** + * @param boolean $reset + */ + public function __construct($reset=false){ + if ($reset) { + error_messages::instance()->reset(); + } $this->previous = libxml_use_internal_errors(true); libxml_clear_errors(); } @@ -146,6 +153,10 @@ final class libxml_errors_mgr { libxml_use_internal_errors($this->previous); } } + + public function collect() { + $this->collect_errors(); + } } diff --git a/backup/converter/imscc1/lib.php b/backup/converter/imscc1/lib.php index 86694fc70ab..6b780a47afb 100644 --- a/backup/converter/imscc1/lib.php +++ b/backup/converter/imscc1/lib.php @@ -103,12 +103,18 @@ class imscc1_converter extends base_converter { } $status = $cc2moodle->generate_moodle_xml(); //Final cleanup + $xml_error = new libxml_errors_mgr(true); $mdoc = new DOMDocument(); $mdoc->preserveWhiteSpace = false; $mdoc->formatOutput = true; $mdoc->validateOnParse = false; + $mdoc->strictErrorChecking = false; if ($mdoc->load($manifestdir.'/moodle.xml', LIBXML_NONET)) { $mdoc->save($this->get_workdir_path().'/moodle.xml', LIBXML_NOEMPTYTAG); + } else { + $xml_error->collect(); + $this->log('validation error(s): '.PHP_EOL.error_messages::instance(), backup::LOG_DEBUG, null, 2); + throw new imscc1_convert_exception(error_messages::instance()->to_string(true)); } //Move the files to the workdir rename($manifestdir.'/course_files', $this->get_workdir_path().'/course_files'); diff --git a/backup/converter/imscc11/lib.php b/backup/converter/imscc11/lib.php index 15ddace70fc..3ca8267d745 100644 --- a/backup/converter/imscc11/lib.php +++ b/backup/converter/imscc11/lib.php @@ -103,12 +103,18 @@ class imscc11_converter extends base_converter { } $status = $cc112moodle->generate_moodle_xml(); //Final cleanup + $xml_error = new libxml_errors_mgr(true); $mdoc = new DOMDocument(); $mdoc->preserveWhiteSpace = false; $mdoc->formatOutput = true; $mdoc->validateOnParse = false; + $mdoc->strictErrorChecking = false; if ($mdoc->load($manifestdir.'/moodle.xml', LIBXML_NONET)) { $mdoc->save($this->get_workdir_path().'/moodle.xml', LIBXML_NOEMPTYTAG); + } else { + $xml_error->collect(); + $this->log('validation error(s): '.PHP_EOL.error_messages::instance(), backup::LOG_DEBUG, null, 2); + throw new imscc11_convert_exception(error_messages::instance()->to_string(true)); } //Move the files to the workdir rename($manifestdir.'/course_files', $this->get_workdir_path().'/course_files');