diff --git a/question/format.php b/question/format.php
index 1a53ca1bbf9..fdbcc1f3a6b 100644
--- a/question/format.php
+++ b/question/format.php
@@ -307,7 +307,7 @@ class qformat_default {
if ($this->catfromfile) {
// find/create category object
$catpath = $question->category;
- $newcategory = $this->create_category_path( $catpath, '/');
+ $newcategory = $this->create_category_path($catpath);
if (!empty($newcategory)) {
$this->category = $newcategory;
}
@@ -379,14 +379,12 @@ class qformat_default {
* but if $getcontext is set then ignore the context and use selected category context.
*
* @param string catpath delimited category path
- * @param string delimiter path delimiting character
* @param int courseid course to search for categories
* @return mixed category object or null if fails
*/
- function create_category_path($catpath, $delimiter='/') {
+ function create_category_path($catpath) {
global $DB;
- $catpath = clean_param($catpath, PARAM_PATH);
- $catnames = explode($delimiter, $catpath);
+ $catnames = $this->split_category_path($catpath);
$parent = 0;
$category = null;
@@ -396,14 +394,17 @@ class qformat_default {
$contextid = $this->translator->string_to_context($matches[1]);
array_shift($catnames);
} else {
- $contextid = FALSE;
+ $contextid = false;
}
- if ($this->contextfromfile && ($contextid !== FALSE)){
+
+ if ($this->contextfromfile && $contextid !== false) {
$context = get_context_instance_by_id($contextid);
require_capability('moodle/question:add', $context);
} else {
$context = get_context_instance_by_id($this->category->contextid);
}
+
+ // Now create any categories that need to be created.
foreach ($catnames as $catname) {
if ($category = $DB->get_record( 'question_categories', array('name' => $catname, 'contextid' => $context->id, 'parent' => $parent))) {
$parent = $category->id;
@@ -698,16 +699,16 @@ class qformat_default {
if ($this->cattofile) {
if ($question->category != $trackcategory) {
$trackcategory = $question->category;
- $categoryname = $this->get_category_path($trackcategory, '/', $this->contexttofile);
+ $categoryname = $this->get_category_path($trackcategory, $this->contexttofile);
// create 'dummy' question for category export
$dummyquestion = new object;
$dummyquestion->qtype = 'category';
$dummyquestion->category = $categoryname;
- $dummyquestion->name = "switch category to $categoryname";
+ $dummyquestion->name = 'Switch category to ' . $categoryname;
$dummyquestion->id = 0;
$dummyquestion->questiontextformat = '';
- $expout .= $this->writequestion( $dummyquestion ) . "\n";
+ $expout .= $this->writequestion($dummyquestion) . "\n";
}
}
@@ -746,34 +747,79 @@ class qformat_default {
/**
* get the category as a path (e.g., tom/dick/harry)
* @param int id the id of the most nested catgory
- * @param string delimiter the delimiter you want
* @return string the path
*/
- function get_category_path($id, $delimiter='/', $includecontext = true) {
+ function get_category_path($id, $includecontext = true) {
global $DB;
- $path = '';
- if (!$firstcategory = $DB->get_record('question_categories',array('id' =>$id))) {
+
+ if (!$category = $DB->get_record('question_categories',array('id' =>$id))) {
print_error('cannotfindcategory', 'error', '', $id);
}
- $category = $firstcategory;
$contextstring = $this->translator->context_to_string($category->contextid);
+
+ $pathsections = array();
do {
- $name = $category->name;
+ $pathsections[] = $category->name;
$id = $category->parent;
- if (!empty($path)) {
- $path = "{$name}{$delimiter}{$path}";
- }
- else {
- $path = $name;
- }
- } while ($category = $DB->get_record( 'question_categories',array('id' =>$id )));
+ } while ($category = $DB->get_record( 'question_categories', array('id' => $id )));
if ($includecontext){
- $path = '$'.$contextstring.'$'."{$delimiter}{$path}";
+ $pathsections[] = '$' . $contextstring . '$';
}
+
+ $path = $this->assemble_category_path(array_reverse($pathsections));
+
return $path;
}
+ /**
+ * Convert a list of category names, possibly preceeded by one of the
+ * context tokens like $course$, into a string representation of the
+ * category path.
+ *
+ * Names are separated by / delimiters. And /s in the name are replaced by //.
+ *
+ * To reverse the process and split the paths into names, use
+ * {@link split_category_path()}.
+ *
+ * @param array $names
+ * @return string
+ */
+ protected function assemble_category_path($names) {
+ $escapednames = array();
+ foreach ($names as $name) {
+ $escapedname = str_replace('/', '//', $name);
+ if (substr($escapedname, 0, 1) == '/') {
+ $escapedname = ' ' . $escapedname;
+ }
+ if (substr($escapedname, -1) == '/') {
+ $escapedname = $escapedname . ' ';
+ }
+ $escapednames[] = $escapedname;
+ }
+ return implode('/', $escapednames);
+ }
+
+ /**
+ * Convert a string, as returned by {@link assemble_category_path()},
+ * back into an array of category names.
+ *
+ * Each category name is cleaned by a call to clean_param(, PARAM_MULTILANG),
+ * which matches the cleaning in question/category_form.php. Not that this
+ * addslashes the names, ready for insertion into the database.
+ *
+ * @param string $path
+ * @return array of category names.
+ */
+ protected function split_category_path($path) {
+ $rawnames = preg_split('~(?.
+
+
+/**
+ * Unit tests for the question import and export system.
+ *
+ * @package core
+ * @subpackage questionbank
+ * @copyright 2010 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once($CFG->libdir . '/questionlib.php');
+require_once($CFG->dirroot . '/question/format.php');
+
+
+/**
+ * Subclass to make it easier to test qformat_default.
+ *
+ * @copyright 2009 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class testable_qformat extends qformat_default {
+ public function assemble_category_path($names) {
+ return parent::assemble_category_path($names);
+ }
+
+ public function split_category_path($names) {
+ return parent::split_category_path($names);
+ }
+}
+
+
+/**
+ * Unit tests for the matching question definition class.
+ *
+ * @copyright 2009 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class qformat_default_test extends UnitTestCase {
+ public function test_assemble_category_path() {
+ $format = new testable_qformat();
+ $pathsections = array(
+ '$course$',
+ "Tim's questions",
+ "Tricky things like / // and so on",
+ 'Category name ending in /',
+ '/ and one that starts with one',
+ 'Matematically Matematiskt (svenska)'
+ );
+ $this->assertEqual('$course$/Tim\'s questions/Tricky things like // //// and so on/Category name ending in // / // and one that starts with one/Matematically/span> Matematiskt (svenska)/span>',
+ $format->assemble_category_path($pathsections));
+ }
+
+ public function test_split_category_path() {
+ $format = new testable_qformat();
+ $path = '$course$/Tim\'s questions/Tricky things like // //// and so on/Category name ending in // / // and one that starts with one/Matematically/span> Matematiskt (svenska)/span>';
+ $this->assertEqual(array(
+ '$course$',
+ "Tim's questions",
+ "Tricky things like / // and so on",
+ 'Category name ending in /',
+ '/ and one that starts with one',
+ 'Matematically Matematiskt (svenska)'
+ ), $format->split_category_path($path));
+ }
+
+ public function test_split_category_path_cleans() {
+ $format = new testable_qformat();
+ $path = 'Nasty thing/evil>';
+ $this->assertEqual(array('Nasty thing'), $format->split_category_path($path));
+ }
+}