merging MOODLE_19_QUESTIONS with HEAD
[moodle.git] / question / category_class.php
CommitLineData
107e7612 1<?php // $Id$
516cf3eb 2/**
4323d029 3 * Class representing question categories
bc649d80 4 *
bc649d80 5 * @author Martin Dougiamas and many others. {@link http://moodle.org}
6 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License
4323d029 7 * @package questionbank
bc649d80 8 */
516cf3eb 9
10// number of categories to display on page
d340fde6 11define("QUESTION_PAGE_LENGTH", 20);
dac786f3 12
13require_once("$CFG->libdir/listlib.php");
14
15class question_category_list extends moodle_list {
16 var $table = "question_categories";
17 var $listitemclassname = 'question_category_list_item';
986effb6 18
19
dac786f3 20 function get_records() {
21 global $COURSE, $CFG;
22 $categories = get_records($this->table, 'course', "{$COURSE->id}", $this->sortby);
23
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];
30 } else {
31 $categories[$categoryid]->questioncount = 0;
32 }
33 }
34 $this->records = $categories;
35 }
36}
37
38class question_category_list_item extends list_item {
39
40
41 function item_html($extraargs = array()){
42 global $CFG;
43 $pixpath = $CFG->pixpath;
44 $str = $extraargs['str'];
45 $category = $this->item;
46
47 $linkcss = $category->publish ? ' class="published" ' : ' class="unpublished" ';
48
dac786f3 49
50 /// Each section adds html to be displayed as part of this list item
986effb6 51
dac786f3 52
986effb6 53 $item = '<a ' . $linkcss . ' title="' . $str->edit. '" href="'.$this->parentlist->pageurl->out_action(array('edit'=>$this->id)).'">
dac786f3 54 <img src="' . $pixpath . '/t/edit.gif" class="iconsmall"
55 alt="' .$str->edit. '" /> ' . $category->name . '('.$category->questioncount.')'. '</a>';
56
57 $item .= '&nbsp;'. $category->info;
58
59
60 if (!empty($category->publish)) {
986effb6 61 $item .= '<a title="' . $str->hide . '" href="'.$this->parentlist->pageurl->out_action(array('hide'=>$this->id)).'">
dac786f3 62 <img src="' . $pixpath . '/t/hide.gif" class="iconsmall" alt="' .$str->hide. '" /></a> ';
63 } else {
986effb6 64 $item .= '<a title="' . $str->publish . '" href="'.$this->parentlist->pageurl->out_action(array('publish'=>$this->id)).'">
dac786f3 65 <img src="' . $pixpath . '/t/show.gif" class="iconsmall" alt="' .$str->publish. '" /></a> ';
66 }
67
68 if ($category->id != $extraargs['defaultcategory']->id) {
986effb6 69 $item .= '<a title="' . $str->delete . '" href="'.$this->parentlist->pageurl->out_action(array('delete'=>$this->id)).'">
dac786f3 70 <img src="' . $pixpath . '/t/delete.gif" class="iconsmall" alt="' .$str->delete. '" /></a> ';
71 }
72
73 return $item;
74
75
76 }
77
78}
79
516cf3eb 80
4323d029 81/**
82 * Class representing question categories
dac786f3 83 *
4323d029 84 * @package questionbank
85 */
dc1f00de 86class question_category_object {
516cf3eb 87
88 var $str;
89 var $pixpath;
dac786f3 90 /**
91 * Nested list to display categories.
92 *
93 * @var question_category_list
94 */
95 var $editlist;
516cf3eb 96 var $newtable;
97 var $tab;
98 var $tabsize = 3;
99 var $categories;
100 var $categorystrings;
101 var $defaultcategory;
a982d582 102//------------------------------------------------------
986effb6 103 /**
104 * @var moodle_url Object representing url for this page
105 */
a982d582 106 var $pageurl;
516cf3eb 107
bc649d80 108 /**
109 * Constructor
110 *
111 * Gets necessary strings and sets relevant path information
112 */
986effb6 113 function question_category_object($page, $pageurl) {
dac786f3 114 global $CFG, $COURSE;
516cf3eb 115
116 $this->tab = str_repeat('&nbsp;', $this->tabsize);
117
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');
2befe778 138 $this->str->page = get_string('page');
516cf3eb 139 $this->pixpath = $CFG->pixpath;
140
bdc66c2a 141 $this->editlist = new question_category_list('ul', '', true, $pageurl, $page, 'cpage', QUESTION_PAGE_LENGTH);
a982d582 142
986effb6 143 $this->pageurl = $pageurl;
a982d582 144
dac786f3 145 $this->initialize();
516cf3eb 146
516cf3eb 147 }
a982d582 148
bc649d80 149 /**
150 * Displays the user interface
151 *
bc649d80 152 */
dac786f3 153 function display_user_interface() {
154
155 /// Interface for editing existing categories
156 print_heading_with_help($this->str->editcategories, 'categories', 'quiz');
157 $this->output_edit_list();
158
516cf3eb 159
dac786f3 160 echo '<br />';
516cf3eb 161 /// Interface for adding a new category:
162 print_heading_with_help($this->str->addcategory, 'categories_edit', 'quiz');
163 $this->output_new_table();
164 echo '<br />';
165
516cf3eb 166 }
167
168
bc649d80 169 /**
170 * Initializes this classes general category-related variables
171 */
516cf3eb 172 function initialize() {
dac786f3 173 global $COURSE, $CFG;
516cf3eb 174
175 /// Get the existing categories
dac786f3 176 if (!$this->defaultcategory = get_default_question_category($COURSE->id)) {
516cf3eb 177 error("Error: Could not find or make a category!");
178 }
179
bdc66c2a 180 $this->editlist->list_from_records();
516cf3eb 181
dac786f3 182 $this->categories = $this->editlist->records;
516cf3eb 183
184 // create the array of id=>full_name strings
185 $this->categorystrings = $this->expanded_category_strings($this->categories);
186
516cf3eb 187
516cf3eb 188 }
189
dac786f3 190
bc649d80 191 /**
192 * Outputs a table to allow entry of a new category
193 */
516cf3eb 194 function output_new_table() {
dac786f3 195 global $USER, $COURSE;
516cf3eb 196 $publishoptions[0] = get_string("no");
197 $publishoptions[1] = get_string("yes");
198
199 $this->newtable->head = array ($this->str->parent, $this->str->category, $this->str->categoryinfo, $this->str->publish, $this->str->action);
dac786f3 200 $this->newtable->width = '200';
516cf3eb 201 $this->newtable->data[] = array();
202 $this->newtable->tablealign = 'center';
203
204 /// Each section below adds a data cell to the table row
205
206
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);
212
213 $this->newtable->align['category'] = "left";
214 $this->newtable->wrap['category'] = "nowrap";
215 $row['category'] = '<input type="text" name="newcategory" value="" size="15" />';
216
217 $this->newtable->align['info'] = "left";
218 $this->newtable->wrap['info'] = "nowrap";
219 $row['info'] = '<input type="text" name="newinfo" value="" size="50" />';
220
221 $this->newtable->align['publish'] = "left";
222 $this->newtable->wrap['publish'] = "nowrap";
223 $row['publish'] = choose_from_menu ($publishoptions, "newpublish", "", "", "", "", true);
224
225 $this->newtable->align['action'] = "left";
226 $this->newtable->wrap['action'] = "nowrap";
227 $row['action'] = '<input type="submit" value="' . $this->str->add . '" />';
228
229
230 $this->newtable->data[] = $row;
231
232 // wrap the table in a form and output it
233 echo '<form action="category.php" method="post">';
d187f660 234 echo '<fieldset class="invisiblefieldset" style="display: block">';
516cf3eb 235 echo "<input type=\"hidden\" name=\"sesskey\" value=\"$USER->sesskey\" />";
986effb6 236 echo $this->pageurl->hidden_params_out();
516cf3eb 237 echo '<input type="hidden" name="addcategory" value="true" />';
238 print_table($this->newtable);
09275894 239 echo '</fieldset>';
516cf3eb 240 echo '</form>';
241 }
242
dac786f3 243
bc649d80 244 /**
dac786f3 245 * Outputs a list to allow editing/rearranging of existing categories
bc649d80 246 *
247 * $this->initialize() must have already been called
248 *
bc649d80 249 */
dac786f3 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));
254 print_box_end();
255 echo $this->editlist->display_page_numbers();
256
516cf3eb 257 }
258
dac786f3 259
260
bc649d80 261 /**
262 * gets all the courseids for the given categories
263 *
264 * @param array categories contains category objects in a tree representation
265 * @return array courseids flat array in form categoryid=>courseid
266 */
516cf3eb 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));
273 }
274 }
275 return $courseids;
276 }
277
516cf3eb 278
516cf3eb 279
986effb6 280 function edit_single_category($categoryid) {
516cf3eb 281 /// Interface for adding a new category
d349d4f0 282 global $CFG, $USER, $COURSE;
516cf3eb 283
284 /// Interface for editing existing categories
dc1f00de 285 if ($category = get_record("question_categories", "id", $categoryid)) {
d349d4f0 286 print_heading_with_help($this->str->edit, 'categories_edit', 'quiz');
986effb6 287 $this->output_edit_single_table($category);
d349d4f0 288 echo '<div class="centerpara">';
289 print_single_button($CFG->wwwroot . '/question/category.php',
290 $this->pageurl->params, $this->str->cancel);
291 echo '</div>';
dac786f3 292 print_footer($COURSE);
516cf3eb 293 exit;
294 } else {
dac786f3 295 error("Category $categoryid not found", "category.php?id={$COURSE->id}");
516cf3eb 296 }
297 }
298
bc649d80 299 /**
300 * Outputs a table to allow editing of an existing category
301 *
302 * @param object category
303 * @param int page current page
304 */
986effb6 305 function output_edit_single_table($category) {
306 global $USER;
516cf3eb 307 $publishoptions[0] = get_string("no");
308 $publishoptions[1] = get_string("yes");
309 $strupdate = get_string('update');
310
4abc23b2 311 $edittable = new stdClass;
516cf3eb 312
313 $edittable->head = array ($this->str->parent, $this->str->category, $this->str->categoryinfo, $this->str->publish, $this->str->action);
314 $edittable->width = 200;
315 $edittable->data[] = array();
316 $edittable->tablealign = 'center';
317
318 /// Each section below adds a data cell to the table row
319
320 $viableparents = $this->categorystrings;
321 $this->set_viable_parents($viableparents, $category);
322 $viableparents = array(0=>$this->str->top) + $viableparents;
323 $edittable->align['parent'] = "left";
324 $edittable->wrap['parent'] = "nowrap";
325 $row['parent'] = choose_from_menu ($viableparents, "updateparent", "{$category->parent}", "", "", "", true);
326
327 $edittable->align['category'] = "left";
328 $edittable->wrap['category'] = "nowrap";
6ba65fa0 329 $row['category'] = '<input type="text" name="updatename" value="' . format_string($category->name) . '" size="15" />';
516cf3eb 330
331 $edittable->align['info'] = "left";
332 $edittable->wrap['info'] = "nowrap";
333 $row['info'] = '<input type="text" name="updateinfo" value="' . $category->info . '" size="50" />';
334
335 $edittable->align['publish'] = "left";
336 $edittable->wrap['publish'] = "nowrap";
337 $selected = (boolean)$category->publish ? 1 : 0;
338 $row['publish'] = choose_from_menu ($publishoptions, "updatepublish", $selected, "", "", "", true);
339
340 $edittable->align['action'] = "left";
341 $edittable->wrap['action'] = "nowrap";
342 $row['action'] = '<input type="submit" value="' . $strupdate . '" />';
343
344 $edittable->data[] = $row;
345
346 // wrap the table in a form and output it
347 echo '<p><form action="category.php" method="post">';
d349d4f0 348 echo '<fieldset class="invisiblefieldset" style="display: block;">';
349 echo '<input type="hidden" name="sesskey" value="' . $USER->sesskey . '" />';
986effb6 350 echo $this->pageurl->hidden_params_out();
516cf3eb 351 echo '<input type="hidden" name="updateid" value="' . $category->id . '" />';
516cf3eb 352 print_table($edittable);
09275894 353 echo '</fieldset>';
516cf3eb 354 echo '</form></p>';
355 }
356
bc649d80 357 /**
358 * Creates an array of "full-path" category strings
359 * Structure:
360 * key => string
361 * where key is the category id, and string contains the name of all ancestors as well as the particular category name
362 * E.g. '123'=>'Language / English / Grammar / Modal Verbs"
363 *
364 * @param array $categories an array containing categories arranged in a tree structure
365 */
516cf3eb 366 function expanded_category_strings($categories, $parent=null) {
367 $prefix = is_null($parent) ? '' : "$parent / ";
368 $categorystrings = array();
369 foreach ($categories as $key => $category) {
370 $expandedname = "$prefix$category->name";
371 $categorystrings[$key] = $expandedname;
372 if (isset($category->children)) {
373 $categorystrings = $categorystrings + $this->expanded_category_strings($category->children, $expandedname);
374 }
375 }
376 return $categorystrings;
377 }
378
516cf3eb 379
bc649d80 380 /**
381 * Sets the viable parents
382 *
383 * Viable parents are any except for the category itself, or any of it's descendants
384 * The parentstrings parameter is passed by reference and changed by this function.
385 *
386 * @param array parentstrings a list of parentstrings
387 * @param object category
388 */
516cf3eb 389 function set_viable_parents(&$parentstrings, $category) {
390
391 unset($parentstrings[$category->id]);
392 if (isset($category->children)) {
393 foreach ($category->children as $child) {
394 $this->set_viable_parents($parentstrings, $child);
395 }
396 }
397 }
398
bc649d80 399 /**
400 * Gets question categories
401 *
402 * @param int parent - if given, restrict records to those with this parent id.
403 * @param string sort - [[sortfield [,sortfield]] {ASC|DESC}]
404 * @return array categories
405 */
dc1f00de 406 function get_question_categories($parent=null, $sort="sortorder ASC") {
dac786f3 407 global $COURSE;
516cf3eb 408 if (is_null($parent)) {
dac786f3 409 $categories = get_records('question_categories', 'course', "{$COURSE->id}", $sort);
516cf3eb 410 } else {
dac786f3 411 $select = "parent = '$parent' AND course = '{$COURSE->id}'";
dc1f00de 412 $categories = get_records_select('question_categories', $select, $sort);
516cf3eb 413 }
414 return $categories;
415 }
416
bc649d80 417 /**
418 * Deletes an existing question category
419 *
420 * @param int deletecat id of category to delete
421 * @param int destcategoryid id of category which will inherit the orphans of deletecat
422 */
516cf3eb 423 function delete_category($deletecat, $destcategoryid = null) {
dac786f3 424 global $USER, $COURSE;
516cf3eb 425
dc1f00de 426 if (!$category = get_record("question_categories", "id", $deletecat)) { // security
dac786f3 427 error("No such category $deletecat!", "category.php?id={$COURSE->id}");
516cf3eb 428 }
429
430 if (!is_null($destcategoryid)) { // Need to move some questions before deleting the category
dc1f00de 431 if (!$category2 = get_record("question_categories", "id", $destcategoryid)) { // security
dac786f3 432 error("No such category $destcategoryid!", "category.php?id={$COURSE->id}");
516cf3eb 433 }
4abc23b2 434 if (! set_field('question', 'category', $destcategoryid, 'category', $deletecat)) {
dac786f3 435 error("Error while moving questions from category '" . format_string($category->name) . "' to '$category2->name'", "category.php?id={$COURSE->id}");
516cf3eb 436 }
437
438 } else {
439 // todo: delete any hidden questions that are not actually in use any more
4f48fb42 440 if ($count = count_records("question", "category", $category->id)) {
4abc23b2 441 $vars = new stdClass;
516cf3eb 442 $vars->name = $category->name;
443 $vars->count = $count;
444 print_simple_box(get_string("categorymove", "quiz", $vars), "center");
445 $this->initialize();
446 $categorystrings = $this->categorystrings;
447 unset ($categorystrings[$category->id]);
448 echo "<p><div align=\"center\"><form action=\"category.php\" method=\"get\">";
09275894 449 echo '<fieldset class="invisiblefieldset">';
516cf3eb 450 echo "<input type=\"hidden\" name=\"sesskey\" value=\"$USER->sesskey\" />";
dac786f3 451 echo "<input type=\"hidden\" name=\"id\" value=\"{$COURSE->id}\" />";
516cf3eb 452 echo "<input type=\"hidden\" name=\"delete\" value=\"$category->id\" />";
453 choose_from_menu($categorystrings, "confirm", "", "");
454 echo "<input type=\"submit\" value=\"". get_string("categorymoveto", "quiz") . "\" />";
455 echo "<input type=\"submit\" name=\"cancel\" value=\"{$this->str->cancel}\" />";
09275894 456 echo '</fieldset>';
516cf3eb 457 echo "</form></div></p>";
dac786f3 458 print_footer($COURSE);
516cf3eb 459 exit;
460 }
461 }
516cf3eb 462
463 /// Send the children categories to live with their grandparent
dc1f00de 464 if ($childcats = get_records("question_categories", "parent", $category->id)) {
516cf3eb 465 foreach ($childcats as $childcat) {
dc1f00de 466 if (! set_field("question_categories", "parent", $category->parent, "id", $childcat->id)) {
dac786f3 467 error("Could not update a child category!", "category.php?id={$COURSE->id}");
516cf3eb 468 }
469 }
470 }
471
472 /// Finally delete the category itself
dc1f00de 473 if (delete_records("question_categories", "id", $category->id)) {
6ba65fa0 474 notify(get_string("categorydeleted", "quiz", format_string($category->name)), 'notifysuccess');
986effb6 475 redirect($this->pageurl->out());//always redirect after successful action
516cf3eb 476 }
477 }
478
516cf3eb 479
516cf3eb 480
bc649d80 481 /**
482 * Changes the published status of a category
483 *
484 * @param boolean publish
485 * @param int categoryid
486 */
516cf3eb 487 function publish_category($publish, $categoryid) {
488 /// Hide or publish a category
489
490 $publish = ($publish == false) ? 0 : 1;
dc1f00de 491 $tempcat = get_record("question_categories", "id", $categoryid);
516cf3eb 492 if ($tempcat) {
dc1f00de 493 if (! set_field("question_categories", "publish", $publish, "id", $tempcat->id)) {
516cf3eb 494 notify("Could not update that category!");
a982d582 495 } else {
986effb6 496 redirect($this->pageurl->out());//always redirect after successful action
516cf3eb 497 }
a982d582 498
516cf3eb 499 }
500 }
501
bc649d80 502 /**
503 * Creates a new category with given params
504 *
505 * @param int $newparent id of the parent category
506 * @param string $newcategory the name for the new category
507 * @param string $newinfo the info field for the new category
508 * @param int $newpublish whether to publish the category
509 * @param int $newcourse the id of the associated course
510 */
516cf3eb 511 function add_category($newparent, $newcategory, $newinfo, $newpublish, $newcourse) {
bc649d80 512 if (empty($newcategory)) {
513 notify(get_string('categorynamecantbeblank', 'quiz'), 'notifyproblem');
514 return false;
515 }
516cf3eb 516
517 if ($newparent) {
518 // first check that the parent category is in the correct course
dc1f00de 519 if(!(get_field('question_categories', 'course', 'id', $newparent) == $newcourse)) {
516cf3eb 520 return false;
521 }
522 }
523
524 $cat = NULL;
525 $cat->parent = $newparent;
526 $cat->name = $newcategory;
527 $cat->info = $newinfo;
528 $cat->publish = $newpublish;
529 $cat->course = $newcourse;
530 $cat->sortorder = 999;
531 $cat->stamp = make_unique_id_code();
dc1f00de 532 if (!insert_record("question_categories", $cat)) {
f5565b69 533 error("Could not insert the new question category '$newcategory'", "category.php?id={$newcourse}");
516cf3eb 534 } else {
bc649d80 535 notify(get_string("categoryadded", "quiz", $newcategory), 'notifysuccess');
986effb6 536 redirect($this->pageurl->out());//always redirect after successful action
516cf3eb 537 }
516cf3eb 538 }
539
bc649d80 540 /**
541 * Updates an existing category with given params
542 *
543 * @param int updateid
544 * @param int updateparent
545 * @param string updatename
546 * @param string updateinfo
547 * @param int updatepublish
548 * @param int courseid the id of the associated course
549 */
516cf3eb 550 function update_category($updateid, $updateparent, $updatename, $updateinfo, $updatepublish, $courseid) {
bc649d80 551 if (empty($updatename)) {
552 notify(get_string('categorynamecantbeblank', 'quiz'), 'notifyproblem');
553 return false;
554 }
516cf3eb 555
556 $cat = NULL;
557 $cat->id = $updateid;
558 $cat->parent = $updateparent;
559 $cat->name = $updatename;
560 $cat->info = $updateinfo;
561 $cat->publish = $updatepublish;
dc1f00de 562 if (!update_record("question_categories", $cat)) {
516cf3eb 563 error("Could not update the category '$updatename'", "category.php?id={$courseid}");
564 } else {
bc649d80 565 notify(get_string("categoryupdated", 'quiz'), 'notifysuccess');
986effb6 566 redirect($this->pageurl->out());
516cf3eb 567 }
568 }
516cf3eb 569}
570
571?>