1
0
mirror of https://github.com/moodle/moodle.git synced 2025-04-23 09:23:09 +02:00

question import/export: MDL-22100 ' / etc. in category names confuse the import/export.

This commit is contained in:
Tim Hunt 2010-07-02 13:36:49 +00:00
parent 995f2d51d7
commit 728d60a1f6
2 changed files with 158 additions and 24 deletions

@ -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('~(?<!/)/(?!/)~', $path);
$names = array();
foreach ($rawnames as $rawname) {
$names[] = clean_param(trim(str_replace('//', '/', $rawname)), PARAM_MULTILANG);
}
return $names;
}
/**
* Do an post-processing that may be required
* @return boolean success

@ -0,0 +1,88 @@
<?php
// This file is part of Moodle - http://moodle.org/
//
// Moodle is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Moodle is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
/**
* 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',
'<span lang="en" class="multilang">Matematically</span> <span lang="sv" class="multilang">Matematiskt (svenska)</span>'
);
$this->assertEqual('$course$/Tim\'s questions/Tricky things like // //// and so on/Category name ending in // / // and one that starts with one/<span lang="en" class="multilang">Matematically<//span> <span lang="sv" class="multilang">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/<span lang="en" class="multilang">Matematically<//span> <span lang="sv" class="multilang">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',
'<span lang="en" class="multilang">Matematically</span> <span lang="sv" class="multilang">Matematiskt (svenska)</span>'
), $format->split_category_path($path));
}
public function test_split_category_path_cleans() {
$format = new testable_qformat();
$path = '<evil>Nasty <virus //> thing<//evil>';
$this->assertEqual(array('Nasty thing'), $format->split_category_path($path));
}
}