diff --git a/.upgradenotes/MDL-86256-2025080815284881.yml b/.upgradenotes/MDL-86256-2025080815284881.yml new file mode 100644 index 00000000000..5b3da1b3579 --- /dev/null +++ b/.upgradenotes/MDL-86256-2025080815284881.yml @@ -0,0 +1,16 @@ +issueNumber: MDL-86256 +notes: + core: + - message: > + The following classes have been moved into namespaces and now support + autoloading: + + + | Old class name | New class name | + + | --- | --- | + + | `\core_xml_parser` | `\core\xml_parser` | + + | `\xml_format_exception` | `\core\exception\xml_format_exception` | + type: improved diff --git a/public/lib/classes/exception/xml_format_exception.php b/public/lib/classes/exception/xml_format_exception.php new file mode 100644 index 00000000000..e1393edc814 --- /dev/null +++ b/public/lib/classes/exception/xml_format_exception.php @@ -0,0 +1,53 @@ +. + +namespace core\exception; + +use stdClass; + +/** + * Exception thrown when there is an error parsing an XML file. + * + * @package core + * @copyright 2010 The Open University + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class xml_format_exception extends moodle_exception { + + /** @var string */ + public $errorstring; + /** @var string */ + public $char; + /** + * Constructor function + * + * @param string $errorstring Errorstring + * @param int $line Linenumber + * @param string $char Errorcharacter + * @param string $link Link + */ + public function __construct($errorstring, $line, $char, $link = '') { + $this->errorstring = $errorstring; + $this->line = $line; + $this->char = $char; + + $a = new stdClass(); + $a->errorstring = $errorstring; + $a->errorline = $line; + $a->errorchar = $char; + parent::__construct('errorparsingxml', 'error', $link, $a); + } +} diff --git a/public/lib/classes/xml_parser.php b/public/lib/classes/xml_parser.php new file mode 100644 index 00000000000..3a1af390ff3 --- /dev/null +++ b/public/lib/classes/xml_parser.php @@ -0,0 +1,180 @@ +. + +namespace core; + +use core\exception\xml_format_exception; + +/** + * Class for parsing xml files. + * + * Handles functionality for: + * + * Import of xml files in questionbank and course import. + * Can handle xml files larger than 10MB through chunking the input file. + * Replaces the original xmlize by Hans Anderson, {@link http://www.hansanderson.com/contact/} + * with equal interface. + * + * @package core + * @subpackage lib + * @copyright Kilian Singer + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +class xml_parser { + /** @var array resulting $xml tree */ + private $xml; + /** @var array stores references to constructed $xml tree */ + private $current; + /** @var int tores the level in the XML tree */ + private $level; + /** + * Is called when tags are opened. + * + * Note: Used by xml element handler as callback. + * + * @author Kilian Singer + * @param resource $parser The XML parser resource. + * @param string $name The XML source to parse. + * @param array $attrs Stores attributes of XML tag. + */ + private function startelement($parser, $name, $attrs) { + $current = &$this->current; + $level = &$this->level; + if (!empty($name)) { + if ($level == 0) { + $current[$level][$name] = array(); + $current[$level][$name]["@"] = $attrs; // Attribute. + $current[$level][$name]["#"] = array(); // Other tags. + $current[$level + 1] = & $current[$level][$name]["#"]; + $level++; + } else { + if (empty($current[$level][$name])) { + $current[$level][$name] = array(); + } + $siz = count($current[$level][$name]); + if (!empty($attrs)) { + $current[$level][$name][$siz]["@"] = $attrs; // Attribute. + } + $current[$level][$name][$siz]["#"] = array(); // Other tags. + $current[$level + 1] = & $current[$level][$name][$siz]["#"]; + $level++; + } + } + } + + /** + * Is called when tags are closed. + * + * Note: Used by xml element handler as callback. + * + * @author Kilian Singer + * @param resource $parser The XML parser resource. + * @param string $name The XML source to parse. + */ + private function endelement($parser, $name) { + $current = &$this->current; + $level = &$this->level; + if (!empty($name)) { + if (empty($current[$level])) { + $current[$level] = ''; + } else if (array_key_exists(0, $current[$level])) { + if (count($current[$level]) == 1) { + $current[$level] = $current[$level][0]; // We remove array index if we only have a single entry. + } + } + + $level--; + } + } + /** + * Is called for text between the start and the end of tags. + * + * Note: Used by xml element handler as callback. + * + * @author Kilian Singer + * @param resource $parser The XML parser resource. + * @param string $data The XML source to parse. + */ + private function characterdata($parser, $data) { + $current = &$this->current; + $level = &$this->level; + if (($data == "0") || (!empty($data) && trim($data) != "")) { + $siz = count($current[$level]); + if ($siz == 0) { + $current[$level][0] = $data; + } else { + $key = max(array_keys($current[$level])); + if (is_int($key)) { + end($current[$level]); + if (is_int(key($current[$level]))) { // If last index is nummeric we have CDATA and concat. + $current[$level][$key] = $current[$level][$key] . $data; + } else { + $current[$level][$key + 1] = $data; // Otherwise we make a new key. + } + } else { + $current[$level][0] = $data; + } + } + } + } + + /** + * Parses XML string. + * + * Note: Interface is kept equal to previous version. + * + * @author Kilian Singer + * @param string $data the XML source to parse. + * @param int $whitespace If set to 1 allows the parser to skip "space" characters in xml document. Default is 1 + * @param string $encoding Specify an OUTPUT encoding. If not specified, it defaults to UTF-8. + * @param bool $reporterrors if set to true, then a {@link xml_format_exception} + * exception will be thrown if the XML is not well-formed. Otherwise errors are ignored. + * @return array|false representation of the parsed XML. + */ + public function parse($data, $whitespace = 1, $encoding = 'UTF-8', $reporterrors = false) { + $data = trim($data); + $this->xml = array(); + $this->current = array(); + $this->level = 0; + $this->current[0] = & $this->xml; + $parser = xml_parser_create($encoding); + xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); + xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, $whitespace); + xml_set_element_handler($parser, [$this, "startelement"], [$this, "endelement"]); + xml_set_character_data_handler($parser, [$this, "characterdata"]); + // Start parsing an xml document. + for ($i = 0; $i < strlen($data); $i += 4096) { + if (!xml_parse($parser, substr($data, $i, 4096))) { + break; + } + } + if ($reporterrors) { + $errorcode = xml_get_error_code($parser); + if ($errorcode) { + $exception = new xml_format_exception(xml_error_string($errorcode), + xml_get_current_line_number($parser), + xml_get_current_column_number($parser)); + xml_parser_free($parser); + throw $exception; + } + } + xml_parser_free($parser); // Deletes the parser. + if (empty($this->xml)) { // XML file is invalid or empty, return false. + return false; + } + return $this->xml; + } +} diff --git a/public/lib/db/renamedclasses.php b/public/lib/db/renamedclasses.php index aaf471a6c89..9b286aa665f 100644 --- a/public/lib/db/renamedclasses.php +++ b/public/lib/db/renamedclasses.php @@ -42,4 +42,7 @@ $renamedclasses = [ 'core_reportbuilder\\source_unavailable_exception' => 'core_reportbuilder\\exception\\source_unavailable_exception', // Since Moodle 5.0. 'PNG_MetaDataHandler' => 'core_badges\\png_metadata_handler', + // Since Moodle 5.1. + 'core_xml_parser' => 'core\\xml_parser', + 'xml_format_exception' => 'core\\exception\\xml_format_exception', ]; diff --git a/public/lib/tests/xmlize_test.php b/public/lib/tests/xmlize_test.php index 7d488b25abc..221eaf3e736 100644 --- a/public/lib/tests/xmlize_test.php +++ b/public/lib/tests/xmlize_test.php @@ -16,20 +16,27 @@ namespace core; -defined('MOODLE_INTERNAL') || die(); - -global $CFG; -require_once($CFG->libdir . '/xmlize.php'); +use core\exception\xml_format_exception; /** * This test compares library against the original xmlize XML importer. * * @package core * @category test + * @covers \xmlize * @copyright 2017 Kilian Singer {@link http://quantumtechnology.info} * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ final class xmlize_test extends \basic_testcase { + /** + * Load required test libraries + */ + public static function setUpBeforeClass(): void { + global $CFG; + require_once("{$CFG->libdir}/xmlize.php"); + parent::setUpBeforeClass(); + } + /** * Test an XML import using a valid XML file. * @@ -47,7 +54,7 @@ final class xmlize_test extends \basic_testcase { */ public function test_xmlimport_of_wrong_file(): void { $xml = file_get_contents(__DIR__ . '/sample_questions_wrong.xml'); - $this->expectException('xml_format_exception'); + $this->expectException(xml_format_exception::class); $this->expectExceptionMessage('Error parsing XML: Mismatched tag at line 18, char 23'); $xmlnew = xmlize($xml, 1, "UTF-8", true); } diff --git a/public/lib/xmlize.php b/public/lib/xmlize.php index dcd5e6f05a9..e3c7d364cf8 100644 --- a/public/lib/xmlize.php +++ b/public/lib/xmlize.php @@ -14,217 +14,6 @@ // You should have received a copy of the GNU General Public License // along with Moodle. If not, see . -/** - * Code for parsing xml files. - * - * Handles functionality for: - * - * Import of xml files in questionbank and course import. - * Can handle xml files larger than 10MB through chunking the input file. - * Replaces the original xmlize by Hans Anderson, {@link http://www.hansanderson.com/contact/} - * with equal interface. - * - * @package core - * @subpackage lib - * @copyright Kilian Singer - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -/** - * Exception thrown when there is an error parsing an XML file. - * - * @copyright 2010 The Open University - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ - -defined('MOODLE_INTERNAL') || die(); - -class xml_format_exception extends moodle_exception { - /** @var string */ - public $errorstring; - /** @var string */ - public $char; - /** - * Constructor function - * - * @param string $errorstring Errorstring - * @param int $line Linenumber - * @param string $char Errorcharacter - * @param string $link Link - */ - public function __construct($errorstring, $line, $char, $link = '') { - $this->errorstring = $errorstring; - $this->line = $line; - $this->char = $char; - - $a = new stdClass(); - $a->errorstring = $errorstring; - $a->errorline = $line; - $a->errorchar = $char; - parent::__construct('errorparsingxml', 'error', $link, $a); - } -} - -/** - * Class for parsing xml files. - * - * Handles functionality for: - * - * Import of xml files in questionbank and course import. - * Can handle xml files larger than 10MB through chunking the input file. - * Uses a similar interface to the original version xmlize() by Hans Anderson. - * - * @package core - * @subpackage lib - * @copyright Kilian Singer - * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later - */ -class core_xml_parser { - /** @var array resulting $xml tree */ - private $xml; - /** @var array stores references to constructed $xml tree */ - private $current; - /** @var int tores the level in the XML tree */ - private $level; - /** - * Is called when tags are opened. - * - * Note: Used by xml element handler as callback. - * - * @author Kilian Singer - * @param resource $parser The XML parser resource. - * @param string $name The XML source to parse. - * @param array $attrs Stores attributes of XML tag. - */ - private function startelement($parser, $name, $attrs) { - $current = &$this->current; - $level = &$this->level; - if (!empty($name)) { - if ($level == 0) { - $current[$level][$name] = array(); - $current[$level][$name]["@"] = $attrs; // Attribute. - $current[$level][$name]["#"] = array(); // Other tags. - $current[$level + 1] = & $current[$level][$name]["#"]; - $level++; - } else { - if (empty($current[$level][$name])) { - $current[$level][$name] = array(); - } - $siz = count($current[$level][$name]); - if (!empty($attrs)) { - $current[$level][$name][$siz]["@"] = $attrs; // Attribute. - } - $current[$level][$name][$siz]["#"] = array(); // Other tags. - $current[$level + 1] = & $current[$level][$name][$siz]["#"]; - $level++; - } - } - } - - /** - * Is called when tags are closed. - * - * Note: Used by xml element handler as callback. - * - * @author Kilian Singer - * @param resource $parser The XML parser resource. - * @param string $name The XML source to parse. - */ - private function endelement($parser, $name) { - $current = &$this->current; - $level = &$this->level; - if (!empty($name)) { - if (empty($current[$level])) { - $current[$level] = ''; - } else if (array_key_exists(0, $current[$level])) { - if (count($current[$level]) == 1) { - $current[$level] = $current[$level][0]; // We remove array index if we only have a single entry. - } - } - - $level--; - } - } - /** - * Is called for text between the start and the end of tags. - * - * Note: Used by xml element handler as callback. - * - * @author Kilian Singer - * @param resource $parser The XML parser resource. - * @param string $data The XML source to parse. - */ - private function characterdata($parser, $data) { - $current = &$this->current; - $level = &$this->level; - if (($data == "0") || (!empty($data) && trim($data) != "")) { - $siz = count($current[$level]); - if ($siz == 0) { - $current[$level][0] = $data; - } else { - $key = max(array_keys($current[$level])); - if (is_int($key)) { - end($current[$level]); - if (is_int(key($current[$level]))) { // If last index is nummeric we have CDATA and concat. - $current[$level][$key] = $current[$level][$key] . $data; - } else { - $current[$level][$key + 1] = $data; // Otherwise we make a new key. - } - } else { - $current[$level][0] = $data; - } - } - } - } - - /** - * Parses XML string. - * - * Note: Interface is kept equal to previous version. - * - * @author Kilian Singer - * @param string $data the XML source to parse. - * @param int $whitespace If set to 1 allows the parser to skip "space" characters in xml document. Default is 1 - * @param string $encoding Specify an OUTPUT encoding. If not specified, it defaults to UTF-8. - * @param bool $reporterrors if set to true, then a {@link xml_format_exception} - * exception will be thrown if the XML is not well-formed. Otherwise errors are ignored. - * @return array|false representation of the parsed XML. - */ - public function parse($data, $whitespace = 1, $encoding = 'UTF-8', $reporterrors = false) { - $data = trim($data); - $this->xml = array(); - $this->current = array(); - $this->level = 0; - $this->current[0] = & $this->xml; - $parser = xml_parser_create($encoding); - xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0); - xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, $whitespace); - xml_set_element_handler($parser, [$this, "startelement"], [$this, "endelement"]); - xml_set_character_data_handler($parser, [$this, "characterdata"]); - // Start parsing an xml document. - for ($i = 0; $i < strlen($data); $i += 4096) { - if (!xml_parse($parser, substr($data, $i, 4096))) { - break; - } - } - if ($reporterrors) { - $errorcode = xml_get_error_code($parser); - if ($errorcode) { - $exception = new xml_format_exception(xml_error_string($errorcode), - xml_get_current_line_number($parser), - xml_get_current_column_number($parser)); - xml_parser_free($parser); - throw $exception; - } - } - xml_parser_free($parser); // Deletes the parser. - if (empty($this->xml)) { // XML file is invalid or empty, return false. - return false; - } - return $this->xml; - } -} - /** * XML parsing function calles into class. * @@ -238,6 +27,6 @@ class core_xml_parser { * @return array representation of the parsed XML. */ function xmlize($data, $whitespace = 1, $encoding = 'UTF-8', $reporterrors = false) { - $hxml = new core_xml_parser(); + $hxml = new \core\xml_parser(); return $hxml->parse($data, $whitespace, $encoding, $reporterrors); } diff --git a/public/question/format/blackboard_six/formatpool.php b/public/question/format/blackboard_six/formatpool.php index a139e93c2e5..e84192083d9 100644 --- a/public/question/format/blackboard_six/formatpool.php +++ b/public/question/format/blackboard_six/formatpool.php @@ -22,6 +22,8 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +use core\exception\xml_format_exception; + defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir . '/xmlize.php'); diff --git a/public/question/format/blackboard_six/formatqti.php b/public/question/format/blackboard_six/formatqti.php index 1eb5957f1c8..66598570b4f 100644 --- a/public/question/format/blackboard_six/formatqti.php +++ b/public/question/format/blackboard_six/formatqti.php @@ -22,6 +22,8 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +use core\exception\xml_format_exception; + defined('MOODLE_INTERNAL') || die(); require_once($CFG->libdir . '/xmlize.php'); diff --git a/public/question/format/xml/format.php b/public/question/format/xml/format.php index fca520c2468..2ab9317f15a 100644 --- a/public/question/format/xml/format.php +++ b/public/question/format/xml/format.php @@ -22,6 +22,7 @@ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ +use core\exception\xml_format_exception; defined('MOODLE_INTERNAL') || die();