3 * Class representing question categories
5 * @author Martin Dougiamas and many others. {@link http://moodle.org}
6 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
7 * @package questionbank
10 // number of categories to display on page
11 define("QUESTION_PAGE_LENGTH", 20);
13 require_once("$CFG->libdir/listlib.php");
15 class question_category_list
extends moodle_list
{
16 var $table = "question_categories";
17 var $listitemclassname = 'question_category_list_item';
20 function get_records() {
22 $categories = get_records($this->table
, 'course', "{$COURSE->id}", $this->sortby
);
24 $catids = array_keys($categories);
25 $select = "WHERE category IN ('".join("', '", $catids)."') AND hidden='0' AND parent='0'";
26 $questioncounts = get_records_sql_menu('SELECT category, COUNT(*) FROM '. $CFG->prefix
. 'question' .' '. $select.' GROUP BY category');
27 foreach ($categories as $categoryid => $category){
28 if (isset($questioncounts[$categoryid])){
29 $categories[$categoryid]->questioncount
= $questioncounts[$categoryid];
31 $categories[$categoryid]->questioncount
= 0;
34 $this->records
= $categories;
38 class question_category_list_item
extends list_item
{
41 function item_html($extraargs = array()){
43 $pixpath = $CFG->pixpath
;
44 $str = $extraargs['str'];
45 $category = $this->item
;
47 $linkcss = $category->publish ?
' class="published" ' : ' class="unpublished" ';
50 /// Each section adds html to be displayed as part of this list item
53 $item = '<a ' . $linkcss . ' title="' . $str->edit
. '" href="'.$this->parentlist
->pageurl
->out_action(array('edit'=>$this->id
)).'">
54 <img src="' . $pixpath . '/t/edit.gif" class="iconsmall"
55 alt="' .$str->edit
. '" /> ' . $category->name
. '('.$category->questioncount
.')'. '</a>';
57 $item .= ' '. $category->info
;
60 if (!empty($category->publish
)) {
61 $item .= '<a title="' . $str->hide
. '" href="'.$this->parentlist
->pageurl
->out_action(array('hide'=>$this->id
)).'">
62 <img src="' . $pixpath . '/t/hide.gif" class="iconsmall" alt="' .$str->hide
. '" /></a> ';
64 $item .= '<a title="' . $str->publish
. '" href="'.$this->parentlist
->pageurl
->out_action(array('publish'=>$this->id
)).'">
65 <img src="' . $pixpath . '/t/show.gif" class="iconsmall" alt="' .$str->publish
. '" /></a> ';
68 if ($category->id
!= $extraargs['defaultcategory']->id
) {
69 $item .= '<a title="' . $str->delete
. '" href="'.$this->parentlist
->pageurl
->out_action(array('delete'=>$this->id
)).'">
70 <img src="' . $pixpath . '/t/delete.gif" class="iconsmall" alt="' .$str->delete
. '" /></a> ';
82 * Class representing question categories
84 * @package questionbank
86 class question_category_object
{
91 * Nested list to display categories.
93 * @var question_category_list
100 var $categorystrings;
101 var $defaultcategory;
102 //------------------------------------------------------
104 * @var moodle_url Object representing url for this page
111 * Gets necessary strings and sets relevant path information
113 function question_category_object($page, $pageurl) {
114 global $CFG, $COURSE;
116 $this->tab
= str_repeat(' ', $this->tabsize
);
118 $this->str
->course
= get_string('course');
119 $this->str
->category
= get_string('category', 'quiz');
120 $this->str
->categoryinfo
= get_string('categoryinfo', 'quiz');
121 $this->str
->questions
= get_string('questions', 'quiz');
122 $this->str
->add
= get_string('add');
123 $this->str
->delete
= get_string('delete');
124 $this->str
->moveup
= get_string('moveup');
125 $this->str
->movedown
= get_string('movedown');
126 $this->str
->edit
= get_string('editthiscategory');
127 $this->str
->hide
= get_string('hide');
128 $this->str
->publish
= get_string('publish', 'quiz');
129 $this->str
->order
= get_string('order');
130 $this->str
->parent
= get_string('parent', 'quiz');
131 $this->str
->add
= get_string('add');
132 $this->str
->action
= get_string('action');
133 $this->str
->top
= get_string('top', 'quiz');
134 $this->str
->addcategory
= get_string('addcategory', 'quiz');
135 $this->str
->editcategory
= get_string('editcategory', 'quiz');
136 $this->str
->cancel
= get_string('cancel');
137 $this->str
->editcategories
= get_string('editcategories', 'quiz');
138 $this->str
->page
= get_string('page');
139 $this->pixpath
= $CFG->pixpath
;
141 $this->editlist
= new question_category_list('ul', '', true, $pageurl, $page, 'cpage', QUESTION_PAGE_LENGTH
);
143 $this->pageurl
= $pageurl;
150 * Displays the user interface
153 function display_user_interface() {
155 /// Interface for editing existing categories
156 print_heading_with_help($this->str
->editcategories
, 'categories', 'quiz');
157 $this->output_edit_list();
161 /// Interface for adding a new category:
162 print_heading_with_help($this->str
->addcategory
, 'categories_edit', 'quiz');
163 $this->output_new_table();
170 * Initializes this classes general category-related variables
172 function initialize() {
173 global $COURSE, $CFG;
175 /// Get the existing categories
176 if (!$this->defaultcategory
= get_default_question_category($COURSE->id
)) {
177 error("Error: Could not find or make a category!");
180 $this->editlist
->list_from_records();
182 $this->categories
= $this->editlist
->records
;
184 // create the array of id=>full_name strings
185 $this->categorystrings
= $this->expanded_category_strings($this->categories
);
192 * Outputs a table to allow entry of a new category
194 function output_new_table() {
195 global $USER, $COURSE;
196 $publishoptions[0] = get_string("no");
197 $publishoptions[1] = get_string("yes");
199 $this->newtable
->head
= array ($this->str
->parent
, $this->str
->category
, $this->str
->categoryinfo
, $this->str
->publish
, $this->str
->action
);
200 $this->newtable
->width
= '200';
201 $this->newtable
->data
[] = array();
202 $this->newtable
->tablealign
= 'center';
204 /// Each section below adds a data cell to the table row
207 $viableparents[0] = $this->str
->top
;
208 $viableparents = $viableparents +
$this->categorystrings
;
209 $this->newtable
->align
['parent'] = "left";
210 $this->newtable
->wrap
['parent'] = "nowrap";
211 $row['parent'] = choose_from_menu ($viableparents, "newparent", $this->str
->top
, "", "", "", true);
213 $this->newtable
->align
['category'] = "left";
214 $this->newtable
->wrap
['category'] = "nowrap";
215 $row['category'] = '<input type="text" name="newcategory" value="" size="15" />';
217 $this->newtable
->align
['info'] = "left";
218 $this->newtable
->wrap
['info'] = "nowrap";
219 $row['info'] = '<input type="text" name="newinfo" value="" size="50" />';
221 $this->newtable
->align
['publish'] = "left";
222 $this->newtable
->wrap
['publish'] = "nowrap";
223 $row['publish'] = choose_from_menu ($publishoptions, "newpublish", "", "", "", "", true);
225 $this->newtable
->align
['action'] = "left";
226 $this->newtable
->wrap
['action'] = "nowrap";
227 $row['action'] = '<input type="submit" value="' . $this->str
->add
. '" />';
230 $this->newtable
->data
[] = $row;
232 // wrap the table in a form and output it
233 echo '<form action="category.php" method="post">';
234 echo '<fieldset class="invisiblefieldset" style="display: block">';
235 echo "<input type=\"hidden\" name=\"sesskey\" value=\"$USER->sesskey\" />";
236 echo $this->pageurl
->hidden_params_out();
237 echo '<input type="hidden" name="addcategory" value="true" />';
238 print_table($this->newtable
);
245 * Outputs a list to allow editing/rearranging of existing categories
247 * $this->initialize() must have already been called
250 function output_edit_list() {
251 print_box_start('boxwidthwide boxaligncenter generalbox');
252 echo $this->editlist
->to_html(0, array('str'=>$this->str
,
253 'defaultcategory' => $this->defaultcategory
));
255 echo $this->editlist
->display_page_numbers();
262 * gets all the courseids for the given categories
264 * @param array categories contains category objects in a tree representation
265 * @return array courseids flat array in form categoryid=>courseid
267 function get_course_ids($categories) {
268 $courseids = array();
269 foreach ($categories as $key=>$cat) {
270 $courseids[$key] = $cat->course
;
271 if (!empty($cat->children
)) {
272 $courseids = array_merge($courseids, $this->get_course_ids($cat->children
));
280 function edit_single_category($categoryid) {
281 /// Interface for adding a new category
282 global $USER, $COURSE;
284 /// Interface for editing existing categories
285 if ($category = get_record("question_categories", "id", $categoryid)) {
286 echo '<h2 align="center">';
287 echo $this->str
->edit
;
288 helpbutton("categories_edit", $this->str
->editcategory
, "quiz");
290 echo '<table width="100%"><tr><td>';
291 $this->output_edit_single_table($category);
292 echo '</td></tr></table>';
294 echo '<p><div align="center"><form action="category.php" method="get">
296 echo $this->pageurl
->hidden_params_out();
297 echo '<input type="hidden" name="sesskey" value="'.$USER->sesskey
.'" />
298 <input type="submit" value="' . $this->str
->cancel
. '" />
302 print_footer($COURSE);
305 error("Category $categoryid not found", "category.php?id={$COURSE->id}");
310 * Outputs a table to allow editing of an existing category
312 * @param object category
313 * @param int page current page
315 function output_edit_single_table($category) {
317 $publishoptions[0] = get_string("no");
318 $publishoptions[1] = get_string("yes");
319 $strupdate = get_string('update');
321 $edittable = new stdClass
;
323 $edittable->head
= array ($this->str
->parent
, $this->str
->category
, $this->str
->categoryinfo
, $this->str
->publish
, $this->str
->action
);
324 $edittable->width
= 200;
325 $edittable->data
[] = array();
326 $edittable->tablealign
= 'center';
328 /// Each section below adds a data cell to the table row
330 $viableparents = $this->categorystrings
;
331 $this->set_viable_parents($viableparents, $category);
332 $viableparents = array(0=>$this->str
->top
) +
$viableparents;
333 $edittable->align
['parent'] = "left";
334 $edittable->wrap
['parent'] = "nowrap";
335 $row['parent'] = choose_from_menu ($viableparents, "updateparent", "{$category->parent}", "", "", "", true);
337 $edittable->align
['category'] = "left";
338 $edittable->wrap
['category'] = "nowrap";
339 $row['category'] = '<input type="text" name="updatename" value="' . format_string($category->name
) . '" size="15" />';
341 $edittable->align
['info'] = "left";
342 $edittable->wrap
['info'] = "nowrap";
343 $row['info'] = '<input type="text" name="updateinfo" value="' . $category->info
. '" size="50" />';
345 $edittable->align
['publish'] = "left";
346 $edittable->wrap
['publish'] = "nowrap";
347 $selected = (boolean
)$category->publish ?
1 : 0;
348 $row['publish'] = choose_from_menu ($publishoptions, "updatepublish", $selected, "", "", "", true);
350 $edittable->align
['action'] = "left";
351 $edittable->wrap
['action'] = "nowrap";
352 $row['action'] = '<input type="submit" value="' . $strupdate . '" />';
354 $edittable->data
[] = $row;
356 // wrap the table in a form and output it
357 echo '<p><form action="category.php" method="post">';
358 echo '<fieldset class="invisiblefieldset">';
359 echo "<input type=\"hidden\" name=\"sesskey\" value=\"$USER->sesskey\" />";
360 echo $this->pageurl
->hidden_params_out();
361 echo '<input type="hidden" name="updateid" value="' . $category->id
. '" />';
362 print_table($edittable);
368 * Creates an array of "full-path" category strings
371 * where key is the category id, and string contains the name of all ancestors as well as the particular category name
372 * E.g. '123'=>'Language / English / Grammar / Modal Verbs"
374 * @param array $categories an array containing categories arranged in a tree structure
376 function expanded_category_strings($categories, $parent=null) {
377 $prefix = is_null($parent) ?
'' : "$parent / ";
378 $categorystrings = array();
379 foreach ($categories as $key => $category) {
380 $expandedname = "$prefix$category->name";
381 $categorystrings[$key] = $expandedname;
382 if (isset($category->children
)) {
383 $categorystrings = $categorystrings +
$this->expanded_category_strings($category->children
, $expandedname);
386 return $categorystrings;
391 * Sets the viable parents
393 * Viable parents are any except for the category itself, or any of it's descendants
394 * The parentstrings parameter is passed by reference and changed by this function.
396 * @param array parentstrings a list of parentstrings
397 * @param object category
399 function set_viable_parents(&$parentstrings, $category) {
401 unset($parentstrings[$category->id
]);
402 if (isset($category->children
)) {
403 foreach ($category->children
as $child) {
404 $this->set_viable_parents($parentstrings, $child);
410 * Gets question categories
412 * @param int parent - if given, restrict records to those with this parent id.
413 * @param string sort - [[sortfield [,sortfield]] {ASC|DESC}]
414 * @return array categories
416 function get_question_categories($parent=null, $sort="sortorder ASC") {
418 if (is_null($parent)) {
419 $categories = get_records('question_categories', 'course', "{$COURSE->id}", $sort);
421 $select = "parent = '$parent' AND course = '{$COURSE->id}'";
422 $categories = get_records_select('question_categories', $select, $sort);
428 * Deletes an existing question category
430 * @param int deletecat id of category to delete
431 * @param int destcategoryid id of category which will inherit the orphans of deletecat
433 function delete_category($deletecat, $destcategoryid = null) {
434 global $USER, $COURSE;
436 if (!$category = get_record("question_categories", "id", $deletecat)) { // security
437 error("No such category $deletecat!", "category.php?id={$COURSE->id}");
440 if (!is_null($destcategoryid)) { // Need to move some questions before deleting the category
441 if (!$category2 = get_record("question_categories", "id", $destcategoryid)) { // security
442 error("No such category $destcategoryid!", "category.php?id={$COURSE->id}");
444 if (! set_field('question', 'category', $destcategoryid, 'category', $deletecat)) {
445 error("Error while moving questions from category '" . format_string($category->name
) . "' to '$category2->name'", "category.php?id={$COURSE->id}");
449 // todo: delete any hidden questions that are not actually in use any more
450 if ($count = count_records("question", "category", $category->id
)) {
451 $vars = new stdClass
;
452 $vars->name
= $category->name
;
453 $vars->count
= $count;
454 print_simple_box(get_string("categorymove", "quiz", $vars), "center");
456 $categorystrings = $this->categorystrings
;
457 unset ($categorystrings[$category->id
]);
458 echo "<p><div align=\"center\"><form action=\"category.php\" method=\"get\">";
459 echo '<fieldset class="invisiblefieldset">';
460 echo "<input type=\"hidden\" name=\"sesskey\" value=\"$USER->sesskey\" />";
461 echo "<input type=\"hidden\" name=\"id\" value=\"{$COURSE->id}\" />";
462 echo "<input type=\"hidden\" name=\"delete\" value=\"$category->id\" />";
463 choose_from_menu($categorystrings, "confirm", "", "");
464 echo "<input type=\"submit\" value=\"". get_string("categorymoveto", "quiz") . "\" />";
465 echo "<input type=\"submit\" name=\"cancel\" value=\"{$this->str->cancel}\" />";
467 echo "</form></div></p>";
468 print_footer($COURSE);
473 /// Send the children categories to live with their grandparent
474 if ($childcats = get_records("question_categories", "parent", $category->id
)) {
475 foreach ($childcats as $childcat) {
476 if (! set_field("question_categories", "parent", $category->parent
, "id", $childcat->id
)) {
477 error("Could not update a child category!", "category.php?id={$COURSE->id}");
482 /// Finally delete the category itself
483 if (delete_records("question_categories", "id", $category->id
)) {
484 notify(get_string("categorydeleted", "quiz", format_string($category->name
)), 'notifysuccess');
485 redirect($this->pageurl
->out());//always redirect after successful action
492 * Changes the published status of a category
494 * @param boolean publish
495 * @param int categoryid
497 function publish_category($publish, $categoryid) {
498 /// Hide or publish a category
500 $publish = ($publish == false) ?
0 : 1;
501 $tempcat = get_record("question_categories", "id", $categoryid);
503 if (! set_field("question_categories", "publish", $publish, "id", $tempcat->id
)) {
504 notify("Could not update that category!");
506 redirect($this->pageurl
->out());//always redirect after successful action
513 * Creates a new category with given params
515 * @param int $newparent id of the parent category
516 * @param string $newcategory the name for the new category
517 * @param string $newinfo the info field for the new category
518 * @param int $newpublish whether to publish the category
519 * @param int $newcourse the id of the associated course
521 function add_category($newparent, $newcategory, $newinfo, $newpublish, $newcourse) {
522 if (empty($newcategory)) {
523 notify(get_string('categorynamecantbeblank', 'quiz'), 'notifyproblem');
528 // first check that the parent category is in the correct course
529 if(!(get_field('question_categories', 'course', 'id', $newparent) == $newcourse)) {
535 $cat->parent
= $newparent;
536 $cat->name
= $newcategory;
537 $cat->info
= $newinfo;
538 $cat->publish
= $newpublish;
539 $cat->course
= $newcourse;
540 $cat->sortorder
= 999;
541 $cat->stamp
= make_unique_id_code();
542 if (!insert_record("question_categories", $cat)) {
543 error("Could not insert the new question category '$newcategory'", "category.php?id={$newcourse}");
545 notify(get_string("categoryadded", "quiz", $newcategory), 'notifysuccess');
546 redirect($this->pageurl
->out());//always redirect after successful action
551 * Updates an existing category with given params
553 * @param int updateid
554 * @param int updateparent
555 * @param string updatename
556 * @param string updateinfo
557 * @param int updatepublish
558 * @param int courseid the id of the associated course
560 function update_category($updateid, $updateparent, $updatename, $updateinfo, $updatepublish, $courseid) {
561 if (empty($updatename)) {
562 notify(get_string('categorynamecantbeblank', 'quiz'), 'notifyproblem');
567 $cat->id
= $updateid;
568 $cat->parent
= $updateparent;
569 $cat->name
= $updatename;
570 $cat->info
= $updateinfo;
571 $cat->publish
= $updatepublish;
572 if (!update_record("question_categories", $cat)) {
573 error("Could not update the category '$updatename'", "category.php?id={$courseid}");
575 notify(get_string("categoryupdated", 'quiz'), 'notifysuccess');
576 redirect($this->pageurl
->out());