From ac27e47dc48dbe82b19e24ae59e281b74e758213 Mon Sep 17 00:00:00 2001 From: gustav_delius Date: Sun, 2 Jan 2005 15:10:50 +0000 Subject: [PATCH] Hierarchical question categories from mediagonal, see http://moodle.org/bugs/bug.php?op=show&bugid=1947 --- lang/de/quiz.php | 5 + lang/en/help/quiz/categories.html | 14 +- lang/en/help/quiz/categories_edit.html | 24 + lang/en/quiz.php | 5 + lang/fr/quiz.php | 5 + mod/quiz/category.php | 863 +++++++++++++++++++++---- mod/quiz/db/mysql.php | 7 +- mod/quiz/db/mysql.sql | 2 + mod/quiz/db/postgres7.php | 5 + mod/quiz/db/postgres7.sql | 5 +- mod/quiz/lib.php | 75 ++- mod/quiz/version.php | 4 +- 12 files changed, 871 insertions(+), 143 deletions(-) create mode 100644 lang/en/help/quiz/categories_edit.html diff --git a/lang/de/quiz.php b/lang/de/quiz.php index 9c821760ed..2f2e600048 100644 --- a/lang/de/quiz.php +++ b/lang/de/quiz.php @@ -3,6 +3,7 @@ $string['acceptederror'] = 'Akzeptierter Fehler'; +$string['addcategory'] = 'Kategorie hinzufügen'; $string['addingquestions'] = 'Diese Spalte der Seite ist zum Verwalten von Testfragen. Fragen werden in Kategorien gespeichert, um sie besser organisieren zu können, und können von jedem Test Ihres Kurses oder sogar von anderen Kursen (Wenn man \'veröffentlichen\' ausgewählt hat) benutzt werden.

Nachdem Sie eine Kategorie erzeugt haben, können Sie Fragen erzeugen oder bearbeiten. Sie können jede dieser Fragen auswählen und Ihrem Test in der anderen Spalte hinzufügen.'; $string['addquestions'] = 'Fragen hinzufügen'; $string['addquestionstoquiz'] = 'Frage zu aktuellem Test hinzufügen'; @@ -41,6 +42,7 @@ $string['category'] = 'Kategorie'; $string['categoryinfo'] = 'Kategorie-Information'; $string['categorymove'] = 'Die Kategorie \'$a->name\' enthält $a->count Fragen. Bitte wählen Sie eine andere Kategorie, um sie zu verschieben.'; $string['categorymoveto'] = 'In diese Kategorie verschieben'; +$string['categoryupdated'] = 'Änderung an Kategorie gespeichert'; $string['checkanswer'] = 'Prüfen'; $string['choice'] = 'Auswahl'; $string['choices'] = 'Verfügbare Auswahl'; @@ -72,6 +74,7 @@ $string['description'] = 'Beschreibung'; $string['discrimination'] = 'Index für die Abgrenzung'; $string['eachattemptbuildsonthelast'] = 'Jeder Versuch basiert auf dem Letzten'; $string['editcategories'] = 'Kategorien bearbeiten'; +$string['editcategory'] = 'Kategorie bearbeiten'; $string['editdatasets'] = 'Datensatz bearbeiten'; $string['editingcalculated'] = 'Bearbeiten einer Rechen-Frage'; $string['editingdescription'] = 'Eine Beschreibung bearbeiten'; @@ -167,6 +170,7 @@ $string['numerical'] = 'Numerisch'; $string['optional'] = 'optional'; $string['overdue'] = 'Überfällig'; $string['paragraphquestion'] = 'Fragezeichen in Zeile $a wird nicht unterstützt. Die Frage wird ignoriert.'; +$string['parent'] = 'Übergeordnete Kategorie'; $string['passworderror'] = 'Das eingegebene Passwort war falsch'; $string['percentcorrect'] = 'Richtig in Prozent'; $string['preview'] = 'Vorschau'; @@ -240,6 +244,7 @@ $string['timesup'] = 'Zeitraum ist abgelaufen'; $string['timetaken'] = 'Verbrauchte Zeit'; $string['tolerance'] = 'Toleranz'; $string['toomanyrandom'] = 'Die Anzahl der benötigten Zufallsfragen ist größer als die, die die Kategorie enthält! ($a)'; +$string['top'] = 'Oberste Ebene'; $string['true'] = 'Wahr'; $string['truefalse'] = 'Wahr/Falsch'; $string['type'] = 'Typ'; diff --git a/lang/en/help/quiz/categories.html b/lang/en/help/quiz/categories.html index 5d22556e6c..ea1a95fa75 100644 --- a/lang/en/help/quiz/categories.html +++ b/lang/en/help/quiz/categories.html @@ -1,17 +1,23 @@

Question categories

Rather than keeping all your questions in one big list, - you can create categories to keep them in. + you can create categories to keep them in.

-

Each category consists of a name and a short description. +

Each category consists of a name and a short description.

Each category can also be "published", which means that the category (and all questions in it) will be available to all courses on this server, so that - other courses can use your questions in their quizzes. + other courses can use your questions in their quizzes.

Categories can also be created or deleted at will. However, if you try to delete a category containing questions, then you will be asked to specify another - category to move them to. + category to move them to.

+

You can also arrange the categories in a heirarchy so + that they are easier to manage. The 'Move category to' + field lets you move a category to another category.

+ +

By clicking on the arrows in the 'Order' field, you + can change the order in which the categories are listed.

\ No newline at end of file diff --git a/lang/en/help/quiz/categories_edit.html b/lang/en/help/quiz/categories_edit.html new file mode 100644 index 0000000000..0efab74c63 --- /dev/null +++ b/lang/en/help/quiz/categories_edit.html @@ -0,0 +1,24 @@ +

Question categories

+ +

Rather than keeping all your questions in one big list, + you can create categories to keep them in.

+ +

Each category consists of a name and a short description.

+ +

Each category can also be "published", which means + that the category (and all questions in it) will be + available to all courses on this server, so that + other courses can use your questions in their quizzes.

+ +

Field descriptions

+ +

Parent: The category in which this one will be placed. If + no other categories have been created, only 'Top' will be + available

+ +

Category: The name of the category.

+ +

Category info: A brief description of the category.

+ +

Publish: Whether or not to immediately publish this + category.

diff --git a/lang/en/quiz.php b/lang/en/quiz.php index e683ba58b0..17474deb10 100644 --- a/lang/en/quiz.php +++ b/lang/en/quiz.php @@ -3,6 +3,7 @@ $string['acceptederror'] = 'Accepted error'; +$string['addcategory'] = 'Add category'; $string['addingquestions'] = 'This side of the page is where you manage your database of questions. Questions are stored in categories to help you keep them organised, and can be used by any quiz in your course or even other courses if you choose to \'publish\' them.

After you select or create a question category you will be able to create or edit questions. You can select any of these questions to add to your quiz over on the other side of this page.'; $string['addquestions'] = 'Add questions'; $string['addquestionstoquiz'] = 'Add questions to current quiz'; @@ -44,6 +45,7 @@ $string['category'] = 'Category'; $string['categoryinfo'] = 'Category info'; $string['categorymove'] = 'The category \'$a->name\' contains $a->count questions. Please choose another category to move them to.'; $string['categorymoveto'] = 'Move them to this category'; +$string['categoryupdated'] = 'The category was successfully updated'; $string['checkanswer'] = 'Check'; $string['choice'] = 'Choice'; $string['choices'] = 'Available choices'; @@ -78,6 +80,7 @@ $string['description'] = 'Description'; $string['discrimination'] = 'Discrim. Index'; $string['eachattemptbuildsonthelast'] = 'Each attempt builds on the last'; $string['editcategories'] = 'Edit categories'; +$string['editcategory'] = 'Edit category'; $string['editdatasets'] = 'Edit the datasets'; $string['editingcalculated'] = 'Editing a Calculated question'; $string['editingdescription'] = 'Editing a Description'; @@ -182,6 +185,7 @@ $string['numerical'] = 'Numerical'; $string['optional'] = 'optional'; $string['overdue'] = 'Overdue'; $string['paragraphquestion'] = 'Paragraph Question not supported at line $a. The question will be ignored'; +$string['parent'] = 'Parent'; $string['passworderror'] = 'The password entered was incorrect'; $string['percentcorrect'] = 'Percent Correct'; $string['popup'] = 'Show quiz in a \"secure\" window'; @@ -265,6 +269,7 @@ $string['timetaken'] = 'Time taken'; $string['tolerance'] = 'Tolerance'; $string['tolerancetype'] = 'Tolerance Type'; $string['toomanyrandom'] = 'The number of random questions required is more than this category contains! ($a)'; +$string['top'] = 'Top'; $string['true'] = 'True'; $string['truefalse'] = 'True/False'; $string['type'] = 'Type'; diff --git a/lang/fr/quiz.php b/lang/fr/quiz.php index 0c5999ff67..76c83bdb0f 100644 --- a/lang/fr/quiz.php +++ b/lang/fr/quiz.php @@ -1,6 +1,7 @@
Créez d\'abord une catégorie. Vous pourrez ensuite créer ou modifier des questions. Vous pouvez choisir une de ces questions pour l\'ajouter à votre test dans l\'autre partie de la page.'; $string['addquestions'] = 'Ajouter des questions'; $string['addquestionstoquiz'] = 'Ajouter ces questions au test en cours'; @@ -42,6 +43,7 @@ $string['category'] = 'Cat $string['categoryinfo'] = 'Information sur la catégorie'; $string['categorymove'] = 'La catégorie « $a->name » contient $a->count questions. Choisir une autre catégorie dans laquelle les déplacer.'; $string['categorymoveto'] = 'Les déplacer dans cette catégorie'; +$string['categoryupdated'] = 'Catégorie enregistrée'; $string['checkanswer'] = 'Vérifier'; $string['choice'] = 'Proposition'; $string['choices'] = 'Propositions'; @@ -76,6 +78,7 @@ $string['description'] = 'Description'; $string['discrimination'] = 'Index de discrimination'; $string['eachattemptbuildsonthelast'] = 'Chaque tentative complète la précédente'; $string['editcategories'] = 'Modifier les catégories'; +$string['editcategory'] = 'Modifier la catégorie'; $string['editdatasets'] = 'Modifier les jeux de données'; $string['editingcalculated'] = 'Modifier une question calculée'; $string['editingdescription'] = 'Modifier une description'; @@ -180,6 +183,7 @@ $string['numerical'] = 'Num $string['optional'] = 'facultatif'; $string['overdue'] = 'En retard'; $string['paragraphquestion'] = 'Question paragraphe non supportée à la ligne $a. La question sera ignorée.'; +$string['parent'] = 'Catégorie supérieure'; $string['passworderror'] = 'La clef n\'est pas correcte'; $string['percentcorrect'] = 'Pourcentage de réponses correctes'; $string['popup'] = 'Afficher le test dans une fenêtre « sécurisée »'; @@ -257,6 +261,7 @@ $string['timesup'] = 'Le chrono est enclench $string['tolerance'] = 'Tolerance'; $string['tolerancetype'] = 'Type de tolerance'; $string['toomanyrandom'] = 'Le nombre de questions aléatoires demandées est plus grand que le total ($a) des questions de cette catégorie !'; +$string['top'] = 'Catégorie racine'; $string['true'] = 'Vrai'; $string['truefalse'] = 'Vrai/Faux'; $string['type'] = 'Type'; diff --git a/mod/quiz/category.php b/mod/quiz/category.php index c7c2f058f0..6f97ec93a6 100644 --- a/mod/quiz/category.php +++ b/mod/quiz/category.php @@ -16,167 +16,786 @@ require_login($course->id); - if (!isteacher($course->id)) { - error("Only teachers can use this page!"); + if (!isteacheredit($course->id)) { + error("Only teachers authorized to edit the course '{$course->fullname}' can use this page!"); + } + + /// CHECK FOR AND ACT UPON VARIABLES SUBMITTED VIA GET OR POST + $qcobject = new quiz_category_object(); + $qcobject->set_course($course); + + if (isset($delete) and !isset($cancel)) { + /// Delete category if the user wants to delete it + if (isset($confirm)) { + /// 'confirm' is the category to move existing questions to + $qcobject->delete_category($delete, $confirm); + } else { + $qcobject->delete_category($delete); + } + } else if (isset($moveup)) { + $qcobject->move_category_up_down('up', $moveup); + } else if (isset($movedown)) { + $qcobject->move_category_up_down('down', $movedown); + } else if (isset($hide)) { + $qcobject->publish_category(false, $hide); + } else if (isset($move) and isset($moveto)) { + $qcobject->move_category($move, $moveto); + } else if (isset($publish)) { + $qcobject->publish_category(true, $publish); + } else if (isset($addcategory)) { + require_variable($newparent); + require_variable($newcategory); + require_variable($newinfo); + require_variable($newpublish); + $qcobject->add_category($newparent, $newcategory, $newinfo, $newpublish, $course->id); + } else if (isset($edit)) { + $qcobject->edit_single_category($edit); + } else if (isset($updateid)) { + require_variable($updateparent); + require_variable($updatename); + require_variable($updateinfo); + require_variable($updatepublish); + $qcobject->update_category($updateid, $updateparent, $updatename, $updateinfo, $updatepublish, $course->id); } + /// DISPLAY THE NORMAL USER INTERFACE + if (isset($modform)) { + $qcobject->display_user_interface($modform); + } else { + $qcobject->display_user_interface(); + } -/// Print headings +/** +* Class quiz_category_object +* +* Used for handling changes to the quiz categories +* +*/ +class quiz_category_object { + + var $str; + var $pixpath; + var $edittable; + var $newtable; + var $tab; + var $tabsize = 3; + var $categories; + var $categorystrings; + var $defaultcategory; + var $course; + +/** +* Constructor +* +* Gets necessary strings and sets relevant path information +* +*/ + function quiz_category_object() { + global $CFG; + + $this->tab = str_repeat(' ', $this->tabsize); + + $this->str->course = get_string('course'); + $this->str->category = get_string('category', 'quiz'); + $this->str->categoryinfo = get_string('categoryinfo', 'quiz'); + $this->str->questions = get_string('questions', 'quiz'); + $this->str->add = get_string('add'); + $this->str->movecategoryto = get_string('movecategoryto'); + $this->str->delete = get_string('delete'); + $this->str->moveup = get_string('moveup'); + $this->str->movedown = get_string('movedown'); + $this->str->edit = get_string('editthiscategory'); + $this->str->hide = get_string('hide'); + $this->str->publish = get_string('publish', 'quiz'); + $this->str->order = get_string('order'); + $this->str->parent = get_string('parent', 'quiz'); + $this->str->add = get_string('add'); + $this->str->action = get_string('action'); + $this->str->top = get_string('top', 'quiz'); + $this->str->addcategory = get_string('addcategory', 'quiz'); + $this->str->editcategory = get_string('editcategory', 'quiz'); + $this->str->cancel = get_string('cancel'); + $this->str->editcategories = get_string('editcategories', 'quiz'); + $this->pixpath = $CFG->pixpath; + + /// Header: + print_header_simple($this->str->editcategories, '', + "course->id\">".get_string('modulenameplural', 'quiz').''. + " -> {$this->str->editcategories}"); - $strcategory = get_string("category", "quiz"); - $strcategoryinfo = get_string("categoryinfo", "quiz"); - $strquestions = get_string("questions", "quiz"); - $strpublish = get_string("publish", "quiz"); - $strdelete = get_string("delete"); - $straction = get_string("action"); - $stradd = get_string("add"); - $strcancel = get_string("cancel"); - $strsavechanges = get_string("savechanges"); - $strbacktoquiz = get_string("backtoquiz", "quiz"); + } - $streditingquiz = get_string(isset($modform->instance) ? "editingquiz" - : "editquestions", - "quiz"); - $streditcategories = get_string("editcategories", "quiz"); +/** +* Sets the course for this object +* +* @param object course +*/ + function set_course($course) { + $this->course = $course; + } + +/** +* Displays the user interface +* +* @param object modform +*/ + function display_user_interface($modform = null) { + $this->initialize(); + + /// Interface for adding a new category: + echo '

'; + echo $this->str->addcategory; + helpbutton("categories_edit", $this->str->addcategory, "quiz"); + echo '

'; + echo '
'; + $this->output_new_table(); + echo '
'; + + /// Interface for editing existing categories + echo '

'; + echo $this->str->editcategories; + helpbutton("categories", $this->str->editcategories, "quiz"); + echo '

'; + echo '
'; + $this->output_edit_table(); + echo '
'; + print_continue('edit.php'); + print_footer($this->course); + } - print_header_simple("$streditcategories", " $streditcategories", - "$streditingquiz -> $streditcategories"); + +/** +* Initializes this classes general category-related variables +* +*/ + function initialize() { + + /// Get the existing categories + if (!$this->categories = $this->get_quiz_categories(null, "parent, sortorder, name ASC")) { + unset($this->categories); + $defaultcategory = quiz_get_default_category($this->course->id); + if (!$defaultcategory) { + error("Error: Could not find or make a category!"); + } else { + $this->categories[$defaultcategory->id] = $defaultcategory; + $this->defaultcategory = $defaultcategory->id; + } + } else { + /// Find lowest ID category for this course - this is the default category + $this->defaultcategory = 99999; + foreach ($this->categories as $category) { + if ($category->course == $this->course->id && $category->id < $this->defaultcategory) { + $this->defaultcategory = $category->id; + } + } + } + + $this->categories = $this->arrange_categories($this->categories); + + // create the array of id=>full_name strings + $this->categorystrings = $this->expanded_category_strings($this->categories); + } + + +/** +* Outputs a table to allow entry of a new category +* +*/ + function output_new_table() { + $publishoptions[0] = get_string("no"); + $publishoptions[1] = get_string("yes"); + + $this->newtable->head = array ($this->str->parent, $this->str->category, $this->str->categoryinfo, $this->str->publish, $this->str->action); + $this->newtable->width = 200; + $this->newtable->data[] = array(); + $this->newtable->tablealign = 'left'; + + /// Each section below adds a data cell to the table row + + + $viableparents[0] = $this->str->top; + $viableparents = $viableparents + $this->categorystrings; + $this->newtable->align['parent'] = "left"; + $this->newtable->wrap['parent'] = "nowrap"; + $row['parent'] = choose_from_menu ($viableparents, "newparent", $this->str->top, "", "", "", true); + + $this->newtable->align['category'] = "left"; + $this->newtable->wrap['category'] = "nowrap"; + $row['category'] = ''; + + $this->newtable->align['info'] = "left"; + $this->newtable->wrap['info'] = "nowrap"; + $row['info'] = ''; + + $this->newtable->align['publish'] = "left"; + $this->newtable->wrap['publish'] = "nowrap"; + $row['publish'] = choose_from_menu ($publishoptions, "newpublish", "", "", "", "", true); + + $this->newtable->align['action'] = "left"; + $this->newtable->wrap['action'] = "nowrap"; + $row['action'] = ''; + + + $this->newtable->data[] = $row; + + // wrap the table in a form and output it + echo '
'; + echo ''; + echo ''; + echo make_table($this->newtable); + echo '
'; + } + +/** +* Outputs a table to allow editing/rearranging of existing categories +* +* $this->initialize() must have already been called +* +* @param object course +*/ + function output_edit_table() { + $this->edittable->head = array ($this->str->course, $this->str->category, $this->str->categoryinfo, $this->str->questions, $this->str->publish, + $this->str->delete, $this->str->order, $this->str->movecategoryto); + $this->edittable->width = 200; + $this->edittable->tablealign = 'left'; + + // get list of short names to add to the course display. Only necessary if user has admin view. + if (isadmin()) { + $keys = implode(',', $this->get_course_ids($this->categories)); + $courses = get_records_select_menu('course', "id in ($keys)", '', 'id, shortname'); + } else { + $courses = $this->course->shortname; + } + $this->build_edit_table_body($this->categories, $courses); + echo make_table($this->edittable); + } +/** +* Recursively builds up the edit-categories table body +* +* @param array categories contains category objects in a tree representation +* @param mixed courses String with shortname of course | array containing courseid=>shortname +* @param int depth controls the indenting +*/ + function build_edit_table_body($categories, $courses, $depth = 0) { + $countcats = count($categories); + $count = 0; + $first = true; + $last = false; + + foreach ($categories as $category) { + $count++; + if ($count == $countcats) { + $last = true; + } + $up = $first ? false : true; + $down = $last ? false : true; + $first = false; + $courseshortname = is_string($courses) ? $courses : $courses[$category->course]; + $this->quiz_edit_category_row($category, $courseshortname, $depth, $up, $down); + if (isset($category->children)) { + $this->build_edit_table_body($category->children, $courses, $depth + 1); + } + } + } + +/** +* gets all the courseids for the given categories +* +* @param array categories contains category objects in a tree representation +* @return array courseids flat array in form categoryid=>courseid +*/ + function get_course_ids($categories) { + $courseids = array(); + foreach ($categories as $key=>$cat) { + $courseids[$key] = $cat->course; + if (!empty($cat->children)) { + $courseids = array_merge($courseids, $this->get_course_ids($cat->children)); + } + } + return $courseids; + } + +/** +* Constructs each row of the edit-categories table +* +* @param object category +* @param int depth controls the indenting +* @param string shortname short name of the course +* @param boolean up can it be moved up? +* @param boolean down can it be moved down? +*/ + function quiz_edit_category_row($category, $shortname, $depth, $up = false, $down = false) { + $fill = str_repeat($this->tab, $depth); + + $linkcss = $category->publish ? '' : ' class="dimmed"'; + + /// Each section below adds a data cell to this table row + $this->edittable->align["$category->id.course"] = "left"; + $this->edittable->wrap["$category->id.course"] = "nowrap"; + $row["$category->id.course"] = $shortname; + + $this->edittable->align["$category->id.name"] = "left"; + $this->edittable->wrap["$category->id.name"] = "nowrap"; + $row["$category->id.name"] = '' .$this->str->edit. ' ' . $fill . $category->name . ''; + + $this->edittable->align["$category->id.info"] = "left"; + $this->edittable->wrap["$category->id.info"] = "nowrap"; + $row["$category->id.info"] = '' . $category->info . ''; + + $this->edittable->align["$category->id.qcount"] = "center"; + $row["$category->id.qcount"] = $category->questioncount; + + $this->edittable->align["$category->id.publish"] = "center"; + $this->edittable->wrap["$category->id.publish"] = "nowrap"; + if (!empty($category->publish)) { + $row["$category->id.publish"] = '' .$this->str->hide. ' '; + } else { + $row["$category->id.publish"] = '' .$this->str->publish. ' '; + } + + if ($category->id != $this->defaultcategory) { + $this->edittable->align["$category->id.delete"] = "center"; + $this->edittable->wrap["$category->id.delete"] = "nowrap"; + $row["$category->id.delete"] = '' .$this->str->delete. ' '; + } else { + $row["$category->id.delete"] = ''; + } + + $this->edittable->align["$category->id.order"] = "left"; + $this->edittable->wrap["$category->id.order"] = "nowrap"; + $icons = ''; + if ($up) { + $icons .= ' + ' . $this->str->moveup. ' '; + } + if ($down) { + $icons .= ' + ' .$this->str->movedown. ' '; + } + $row["$category->id.order"]= $icons; + + $this->edittable->align["$category->id.moveto"] = "left"; + $this->edittable->wrap["$category->id.moveto"] = "nowrap"; + if ($category->id != $this->defaultcategory) { + $viableparents = $this->categorystrings; + $this->set_viable_parents($viableparents, $category); + $viableparents = array(0=>$this->str->top) + $viableparents; + + $row["$category->id.moveto"] = popup_form ("category.php?id={$this->course->id}&move={$category->id}&moveto=", + $viableparents, "moveform{$category->id}", "$category->parent", "", "", "", true); + } else { + $row["$category->id.moveto"]='---'; + } + + + $this->edittable->data[$category->id] = $row; + } -/// Delete category if the user wants to delete it + + function edit_single_category($categoryid) { + /// Interface for adding a new category + $this->initialize(); + + /// Interface for editing existing categories + if ($category = get_record("quiz_categories", "id", $categoryid)) { + echo '

'; + echo $this->str->edit; + helpbutton("categories_edit", $this->str->editcategory, "quiz"); + echo '

'; + echo '
'; + $this->output_edit_single_table($category); + echo '
'; + echo '

+ +

'; + print_footer($this->course); + exit; + } else { + error("Category $categoryid not found", "category.php?id={$this->course->id}"); + } + } + + +/** +* Outputs a table to allow editing of an existing category +* +* @param object category +*/ + function output_edit_single_table($category) { + $publishoptions[0] = get_string("no"); + $publishoptions[1] = get_string("yes"); + $strupdate = get_string('update'); + + unset ($edittable); + + $edittable->head = array ($this->str->parent, $this->str->category, $this->str->categoryinfo, $this->str->publish, $this->str->action); + $edittable->width = 200; + $edittable->data[] = array(); + $edittable->tablealign = 'center'; + + /// Each section below adds a data cell to the table row + + + $viableparents = $this->categorystrings; + $this->set_viable_parents($viableparents, $category); + $viableparents = array(0=>$this->str->top) + $viableparents; + $edittable->align['parent'] = "left"; + $edittable->wrap['parent'] = "nowrap"; + $row['parent'] = choose_from_menu ($viableparents, "updateparent", "{$category->parent}", "", "", "", true); + + $edittable->align['category'] = "left"; + $edittable->wrap['category'] = "nowrap"; + $row['category'] = ''; + + $edittable->align['info'] = "left"; + $edittable->wrap['info'] = "nowrap"; + $row['info'] = ''; + + $edittable->align['publish'] = "left"; + $edittable->wrap['publish'] = "nowrap"; + $selected = (boolean)$category->publish ? 1 : 0; + $row['publish'] = choose_from_menu ($publishoptions, "updatepublish", $selected, "", "", "", true); + + $edittable->align['action'] = "left"; + $edittable->wrap['action'] = "nowrap"; + $row['action'] = ''; + + $edittable->data[] = $row; + + // wrap the table in a form and output it + echo '

'; + echo ''; + echo ''; + echo make_table($edittable); + echo '

'; + } - if (isset($delete) and !isset($cancel)) { - if (!$category = get_record("quiz_categories", "id", $delete)) { // security - error("No such category $delete!"); + +/** +* Creates an array of "full-path" category strings +* Structure: +* key => string +* where key is the category id, and string contains the name of all ancestors as well as the particular category name +* E.g. '123'=>'Language / English / Grammar / Modal Verbs" +* +* @param array $categories an array containing categories arranged in a tree structure +*/ + function expanded_category_strings($categories, $parent=null) { + $prefix = is_null($parent) ? '' : "$parent / "; + $categorystrings = array(); + foreach ($categories as $key => $category) { + $expandedname = "$prefix$category->name"; + $categorystrings[$key] = $expandedname; + if (isset($category->children)) { + $categorystrings = $categorystrings + $this->expanded_category_strings($category->children, $expandedname); + } + } + return $categorystrings; + } + + +/** +* Arranges the categories into a heirarchical tree +* +* If a category has children, it's "children" property holds an array of children +* The questioncount for each category is also calculated +* +* @param array records a flat list of the categories +* @return array categorytree a hierarchical list of the categories +*/ + function arrange_categories($records) { + //todo: get the question count for all records with one sql statement: select category, count(*) from quiz_questions group by category + $levels = array(); + + // build a levels array, which places each record according to it's depth from the top level + $parents = array(0); + while (!empty($parents)) { + $children = array(); + foreach ($records as $record) { + if (in_array($record->parent, $parents)) { + $children[] = $record->id; + } + } + if (!empty($children)) { + $levels[] = $children; + } + $parents = $children; + } + // if there is no heirarchy (e.g., if all records have parent == 0), set level[0] to these keys + if (empty($levels)) { + $levels[0] = array_keys($records); + } + + // build a heirarchical array that depicts the parent-child relationships of the categories + $categorytree = array(); + for ($index = count($levels) - 1; $index >= 0; $index--) { + foreach($levels[$index] as $key) { + $parentkey = $records[$key]->parent; + if (!($records[$key]->questioncount = count_records('quiz_questions', 'category', $records[$key]->id))) { + $records[$key]->questioncount = 0; + } + if ($parentkey == 0) { + $categorytree[$key] = $records[$key]; + } else { + $records[$parentkey]->children[$key] = $records[$key]; + } + } + } + return $categorytree; + } + + +/** +* Sets the viable parents +* +* Viable parents are any except for the category itself, or any of it's descendants +* The parentstrings parameter is passed by reference and changed by this function. +* +* @param array parentstrings a list of parentstrings +* @param object category +*/ + function set_viable_parents(&$parentstrings, $category) { + + unset($parentstrings[$category->id]); + if (isset($category->children)) { + foreach ($category->children as $child) { + $this->set_viable_parents($parentstrings, $child); + } + } + } +/** +* Gets quiz categories +* +* @param int parent - if given, restrict records to those with this parent id. +* @param string sort - [[sortfield [,sortfield]] {ASC|DESC}] +* @return array categories +*/ + function get_quiz_categories($parent=null, $sort="sortorder ASC") { + + $admin = isadmin(); + if ($admin) { + if (is_null($parent)) { + $categories = get_records("quiz_categories", "", "", $sort); + } else { + $categories = get_records("quiz_categories", "parent", $parent, $sort); + } + } else { + if (is_null($parent)) { + $categories = get_records('quiz_categories', 'course', "{$this->course->id}", $sort); + } else { + $select = "parent = '$parent' AND course = '{$this->course->id}'"; + $categories = get_records_select('quiz_categories', $select, $sort); + } + } + return $categories; + } + + +/** +* Deletes an existing quiz category +* +* @param int deletecat id of category to delete +* @param int destcategoryid id of category which will inherit the orphans of deletecat +*/ + function delete_category($deletecat, $destcategoryid = null) { + + if (!$category = get_record("quiz_categories", "id", $deletecat)) { // security + error("No such category $deletecat!", "category.php?id={$this->course->id}"); } - if (isset($confirm)) { // Need to move some questions before deleting the category - if (!$category2 = get_record("quiz_categories", "id", $confirm)) { // security - error("No such category $confirm!"); + if (!is_null($destcategoryid)) { // Need to move some questions before deleting the category + if (!$category2 = get_record("quiz_categories", "id", $destcategoryid)) { // security + error("No such category $destcategoryid!", "category.php?id={$this->course->id}"); } if (! quiz_move_questions($category->id, $category2->id)) { - error("Error while moving questions from category '$category->name' to '$category2->name'"); + error("Error while moving questions from category '$category->name' to '$category2->name'", "category.php?id={$this->course->id}"); } } else { if ($count = count_records("quiz_questions", "category", $category->id)) { $vars->name = $category->name; $vars->count = $count; - print_simple_box(get_string("categorymove", "quiz", $vars), "CENTER"); - $categories = quiz_get_category_menu($course->id); - unset($categories[$category->id]); - echo "

"; - echo "id\" />"; + print_simple_box(get_string("categorymove", "quiz", $vars), "center"); + $this->initialize(); + $categorystrings = $this->categorystrings; + unset ($categorystrings[$category->id]); + echo "

"; + echo "course->id}\" />"; echo "id\" />"; - choose_from_menu($categories, "confirm", "", ""); - echo ""; - echo ""; - echo "

"; - print_footer($course); + choose_from_menu($categorystrings, "confirm", "", ""); + echo ""; + echo "str->cancel}\" />"; + echo "

"; + print_footer($this->course); exit; } } delete_records("quiz_categories", "id", $category->id); - notify(get_string("categorydeleted", "", $category->name)); + + /// Send the children categories to live with their grandparent + if ($childcats = get_records("quiz_categories", "parent", $category->id)) { + foreach ($childcats as $childcat) { + if (! set_field("quiz_categories", "parent", $category->parent, "id", $childcat->id)) { + error("Could not update a child category!", "category.php?id={$this->course->id}"); + } + } + } + + /// Finally delete the category itself + if (delete_records("quiz_categories", "id", $category->id)) { + notify(get_string("categorydeleted", "", $category->name), 'green'); + } } -/// Print heading - - echo "

"; - echo $streditcategories; - helpbutton("categories", $streditcategories, "quiz"); - echo "

"; - -/// If data submitted, then process and store. - - if ($form = data_submitted()) { - - $form = (array)$form; - - // Peel out all the data from variable names. - foreach ($form as $key => $val) { - $cat = NULL; - if ($key == "new" and $val != "") { - $cat->name = $val; - $cat->info = $form['newinfo']; - $cat->publish = $form['newpublish']; - $cat->course = $course->id; - $cat->stamp = make_unique_id_code(); - if (!insert_record("quiz_categories", $cat)) { - error("Could not insert the new quiz category '$val'"); - } else { - notify(get_string("categoryadded", "", $val)); +/** +* Moves a category up or down in the display order +* +* @param string direction up|down +* @param int categoryid id of category to move +*/ + function move_category_up_down ($direction, $categoryid) { + /// Move a category up or down + $swapcategory = NULL; + $movecategory = NULL; + + if ($direction == 'up') { + if ($movecategory = get_record("quiz_categories", "id", $categoryid)) { + $categories = $this->get_quiz_categories("$movecategory->parent", 'parent, sortorder, name'); + + foreach ($categories as $category) { + if ($category->id == $movecategory->id) { + break; + } + $swapcategory = $category; } - - } else if (substr($key,0,1) == "c") { - $cat->id = substr($key,1); - $cat->name = $val; - $cat->info = $form["i$cat->id"]; - $cat->publish = $form["p$cat->id"]; - $cat->course = $course->id; - if (!update_record("quiz_categories", $cat)) { - error("Could not update the quiz category '$val'"); + } + } + if ($direction == 'down') { + if ($movecategory = get_record("quiz_categories", "id", $categoryid)) { + $categories = $this->get_quiz_categories("$movecategory->parent", 'parent, sortorder, name'); + $choosenext = false; + foreach ($categories as $category) { + if ($choosenext) { + $swapcategory = $category; + break; + } + if ($category->id == $movecategory->id) { + $choosenext = true; + } } } } + if ($swapcategory and $movecategory) { // Renumber everything for robustness + $count=0; + foreach ($categories as $category) { + $count++; + if ($category->id == $swapcategory->id) { + $category = $movecategory; + } else if ($category->id == $movecategory->id) { + $category = $swapcategory; + } + if (! set_field("quiz_categories", "sortorder", $count, "id", $category->id)) { + notify("Could not update that category!"); + } + } + } } - -/// Get the existing categories - if (!$categories = get_records("quiz_categories", "course", $course->id, "id ASC")) { - unset($categories); - if (!$categories[] = quiz_get_default_category($course->id)) { - error("Error: Could not find or make a category!"); +/** +* Changes the parent of a category +* +* @param int categoryid +* @param int parentid +*/ + function move_category($categoryid, $parentid) { + /// Move a category to a new parent + + if ($tempcat = get_record("quiz_categories", "id", $categoryid)) { + if ($tempcat->parent != $parentid) { + if (! set_field("quiz_categories", "parent", $parentid, "id", $tempcat->id)) { + notify("Could not update that category!"); + } + } } } - -/// Find lowest ID category - this is the default category - $default = 99999; - foreach ($categories as $category) { - if ($category->id < $default) { - $default = $category->id; +/** +* Changes the published status of a category +* +* @param boolean publish +* @param int categoryid +*/ + function publish_category($publish, $categoryid) { + /// Hide or publish a category + + $publish = ($publish == false) ? 0 : 1; + $tempcat = get_record("quiz_categories", "id", $categoryid); + if ($tempcat) { + if (! set_field("quiz_categories", "publish", $publish, "id", $tempcat->id)) { + notify("Could not update that category!"); + } } } +/** +* Creates a new category with given params +* +* @param int newparent +* @param string newcategory the name +* @param string newinfo +* @param int newpublish +* @param int newcourse the id of the associated course +*/ + function add_category($newparent, $newcategory, $newinfo, $newpublish, $newcourse) { + + $cat = NULL; + $cat->parent = $newparent; + $cat->name = $newcategory; + $cat->info = $newinfo; + $cat->publish = $newpublish; + $cat->course = $newcourse; + $cat->sortorder = 999; + $cat->stamp = make_unique_id_code(); + if (!insert_record("quiz_categories", $cat)) { + error("Could not insert the new quiz category '$newcategory'", "category.php?id={$newcourse}"); + } else { + notify(get_string("categoryadded", "", $newcategory), 'green'); + } - $publishoptions[0] = get_string("no"); - $publishoptions[1] = get_string("yes"); - - -/// Print the table of all categories - $table->head = array ($strcategory, $strcategoryinfo, $strpublish, $strquestions, $straction); - $table->align = array ("left", "left", "center", "center", "center"); - $table->size = array ("80", "80", "40", "40", "50"); - $table->width = 200; - $table->nowrap = true; - - echo "
"; - foreach ($categories as $category) { - $count = count_records("quiz_questions", "category", $category->id); - if ($category->id == $default) { - $delete = ""; // Can't delete default category + } + +/** +* Updates an existing category with given params +* +* @param int updateid +* @param int updateparent +* @param string updatename +* @param string updateinfo +* @param int updatepublish +* @param int courseid the id of the associated course +*/ + function update_category($updateid, $updateparent, $updatename, $updateinfo, $updatepublish, $courseid) { + + $cat = NULL; + $cat->id = $updateid; + $cat->parent = $updateparent; + $cat->name = $updatename; + $cat->info = $updateinfo; + $cat->publish = $updatepublish; + if (!update_record("quiz_categories", $cat)) { + error("Could not update the category '$updatename'", "category.php?id={$courseid}"); } else { - $delete = "id&delete=$category->id\">$strdelete"; - } - $table->data[] = array ("id\" value=\"$category->name\" size=\"15\" />", - "id\" value=\"$category->info\" size=\"50\" />", - choose_from_menu ($publishoptions, "p$category->id", "$category->publish", "", "", "", true), - "$count", - $delete); - } - $table->data[] = array ("", - "", - choose_from_menu ($publishoptions, "newpublish", "", "", "", "", true), - "", - "$stradd"); - print_table($table); - echo "id\" />"; - echo "

"; - echo "

"; - echo "
"; - echo "
"; - - print_footer(); + notify(get_string("categoryupdated", 'quiz'), 'green'); + } + } +} + ?> diff --git a/mod/quiz/db/mysql.php b/mod/quiz/db/mysql.php index a3b15f8d9e..ce3f8a7ffb 100644 --- a/mod/quiz/db/mysql.php +++ b/mod/quiz/db/mysql.php @@ -296,7 +296,12 @@ function quiz_upgrade($oldversion) { if ($oldversion < 2004121400) { // New field to determine popup window behaviour table_column("quiz", "", "popup", "integer", "4", "", "0", "not null", "subnet"); } - + + if ($oldversion < 2005010201) { + table_column('quiz_categories', '', 'parent'); + table_column('quiz_categories', '', 'sortorder', '', '', '', '999'); + } + return true; } diff --git a/mod/quiz/db/mysql.sql b/mod/quiz/db/mysql.sql index d881a43a72..a99cb95d11 100644 --- a/mod/quiz/db/mysql.sql +++ b/mod/quiz/db/mysql.sql @@ -119,6 +119,8 @@ CREATE TABLE `prefix_quiz_categories` ( `info` text NOT NULL, `publish` tinyint(4) NOT NULL default '0', `stamp` varchar(255) NOT NULL default '', + `parent` int(10) unsigned NOT NULL default '0', + `sortorder` int(10) unsigned NOT NULL default '999', PRIMARY KEY (`id`), KEY `course` (`course`) ) TYPE=MyISAM COMMENT='Categories are for grouping questions'; diff --git a/mod/quiz/db/postgres7.php b/mod/quiz/db/postgres7.php index 2d290078ca..874388c426 100644 --- a/mod/quiz/db/postgres7.php +++ b/mod/quiz/db/postgres7.php @@ -282,6 +282,11 @@ function quiz_upgrade($oldversion) { table_column("quiz", "", "popup", "integer", "4", "", "0", "not null", "subnet"); } + if ($oldversion < 2005010201) { + table_column('quiz_categories', '', 'parent'); + table_column('quiz_categories', '', 'sortorder', '', '', '', '999'); + } + return true; } diff --git a/mod/quiz/db/postgres7.sql b/mod/quiz/db/postgres7.sql index f788d5ad64..ff3ca3b8bf 100644 --- a/mod/quiz/db/postgres7.sql +++ b/mod/quiz/db/postgres7.sql @@ -89,7 +89,9 @@ CREATE TABLE prefix_quiz_categories ( name varchar(255) NOT NULL default '', info text NOT NULL default '', publish integer NOT NULL default '0', - stamp varchar(255) NOT NULL default '' + stamp varchar(255) NOT NULL default '', + parent integer NOT NULL default '0', + sortorder integer NOT NULL default '999', ); CREATE INDEX prefix_quiz_categories_course_idx ON prefix_quiz_categories (course); @@ -344,4 +346,3 @@ INSERT INTO prefix_log_display VALUES ('quiz', 'report', 'quiz', 'name'); INSERT INTO prefix_log_display VALUES ('quiz', 'attempt', 'quiz', 'name'); INSERT INTO prefix_log_display VALUES ('quiz', 'submit', 'quiz', 'name'); INSERT INTO prefix_log_display VALUES ('quiz', 'review', 'quiz', 'name'); - diff --git a/mod/quiz/lib.php b/mod/quiz/lib.php index ea6233e313..6afb62d47e 100644 --- a/mod/quiz/lib.php +++ b/mod/quiz/lib.php @@ -42,6 +42,7 @@ define("QUIZ_PICTURE_MAX_WIDTH", "600"); // Not currently implemented define("QUIZ_MAX_NUMBER_ANSWERS", "10"); define("QUIZ_MAX_EVENT_LENGTH", "432000"); // 5 days maximum +define("QUIZ_CATEGORIES_SORTORDER", "999"); define('QUIZ_REVIEW_AFTER', 1); define('QUIZ_REVIEW_BEFORE', 2); @@ -1105,6 +1106,8 @@ function quiz_get_default_category($courseid) { $category->name = get_string("default", "quiz"); $category->info = get_string("defaultinfo", "quiz"); $category->course = $courseid; + $category->parent = 0; + $category->sortorder = QUIZ_CATEGORIES_SORTORDER; $category->publish = 0; $category->stamp = make_unique_id_code(); @@ -1121,7 +1124,26 @@ function quiz_get_category_menu($courseid, $published=false) { if ($published) { $publish = "OR publish = '1'"; } - return get_records_select_menu("quiz_categories", "course='$courseid' $publish", "name ASC", "id,name"); + + if (!isadmin()) { + $categories = get_records_select("quiz_categories", "course = '$courseid' $publish", 'parent, sortorder, name ASC'); + } else { + $categories = get_records_select("quiz_categories", '', 'parent, sortorder, name ASC'); + } + if (!$categories) { + return false; + } + $categories = add_indented_names($categories); + + foreach ($categories as $category) { + if ($catcourse = get_record("course", "id", $category->course)) { + if ($category->publish && ($category->course != $courseid)) { + $category->indentedname .= " ($catcourse->shortname)"; + } + $catmenu[$category->id] = $category->indentedname; + } + } + return $catmenu; } function quiz_print_category_form($course, $current) { @@ -1135,16 +1157,17 @@ function quiz_print_category_form($course, $current) { } /// Get all the existing categories now - if (!$categories = get_records_select("quiz_categories", "course = '$course->id' OR publish = '1'", "name ASC")) { + if (!$categories = get_records_select("quiz_categories", "course = '{$course->id}' OR publish = '1'", "parent, sortorder, name ASC")) { notify("Could not find any question categories!"); return false; // Something is really wrong } + $categories = add_indented_names($categories); foreach ($categories as $key => $category) { if ($catcourse = get_record("course", "id", $category->course)) { - if ($category->publish) { - $category->name .= " ($catcourse->shortname)"; + if ($category->publish && $category->course != $course->id) { + $category->indentedname .= " ($catcourse->shortname)"; } - $catmenu[$category->id] = $category->name; + $catmenu[$category->id] = $category->indentedname; } } $strcategory = get_string("category", "quiz"); @@ -1163,6 +1186,26 @@ function quiz_print_category_form($course, $current) { echo ""; } + +function add_indented_names(&$categories, $id = 0, $indent = 0) { +// returns the categories with their names indented to show parent-child relationships + $fillstr = '   '; + $fill = str_repeat($fillstr, $indent); + $children = array(); + $keys = array_keys($categories); + + foreach ($keys as $key) { + if (!isset($categories[$key]->processed) && $categories[$key]->parent == $id) { + $children[$key] = $categories[$key]; + $children[$key]->indentedname = $fill . $children[$key]->name; + $categories[$key]->processed = true; + $children = $children + add_indented_names($categories, $children[$key]->id, $indent + 1); + } + } + return $children; +} + + function quiz_category_select_menu($courseid,$published=false,$only_editable=false,$selected="") { /// displays a select menu of categories with appended coursenames /// optionaly non editable categories may be excluded @@ -1172,11 +1215,19 @@ function quiz_category_select_menu($courseid,$published=false,$only_editable=fal if ($published) { $publishsql = "or publish=1"; } - $categories = get_records_select("quiz_categories","course=$courseid $publishsql"); + + if (!isadmin()) { + $categories = get_records_select("quiz_categories","course=$courseid $publishsql", 'parent, sortorder, name ASC'); + } else { + $categories = get_records_select("quiz_categories", '', 'parent, sortorder, name ASC'); + } + + $categories = add_indented_names($categories); + echo "\n"; } -function quiz_get_category_coursename($category) { -/// if the category is published, adds on the course +function quiz_get_category_coursename($category, $courseid = 0) { +/// if the category is not from this course and is published , adds on the course /// name - $cname=$category->name; - if ($category->publish) { - if ($catcourse=get_record("course","id",$category->id)) { + $cname = (isset($category->indentedname)) ? $category->indentedname : $category->name; + if ($category->course != $courseid && $category->publish) { + if ($catcourse=get_record("course","id",$category->course)) { $cname .= " ($catcourse->shortname) "; } } diff --git a/mod/quiz/version.php b/mod/quiz/version.php index 6535ddc3af..9d272aba95 100644 --- a/mod/quiz/version.php +++ b/mod/quiz/version.php @@ -5,8 +5,8 @@ // This fragment is called by moodle_needs_upgrading() and /admin/index.php //////////////////////////////////////////////////////////////////////////////// -$module->version = 2004121400; // The (date) version of this module -$module->requires = 2004112300; // Requires this Moodle version +$module->version = 2005010201; // The (date) version of this module +$module->requires = 2005010100; // Requires this Moodle version $module->cron = 0; // How often should cron check this module (seconds)? ?> -- 2.39.5