|
Moodle
2.2.1
http://www.collinsharper.com
|
00001 <?php 00002 // This file is part of Moodle - http://moodle.org/ 00003 // 00004 // Moodle is free software: you can redistribute it and/or modify 00005 // it under the terms of the GNU General Public License as published by 00006 // the Free Software Foundation, either version 3 of the License, or 00007 // (at your option) any later version. 00008 // 00009 // Moodle is distributed in the hope that it will be useful, 00010 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00012 // GNU General Public License for more details. 00013 // 00014 // You should have received a copy of the GNU General Public License 00015 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 00016 00035 defined('MOODLE_INTERNAL') || die(); 00036 00037 require_once($CFG->dirroot . '/question/engine/lib.php'); 00038 require_once($CFG->dirroot . '/question/type/questiontypebase.php'); 00039 00040 00041 00043 00048 define("QUESTION_NUMANS", 10); 00049 00055 define("QUESTION_NUMANS_START", 3); 00056 00062 define("QUESTION_NUMANS_ADD", 3); 00063 00075 function question_reorder_qtypes($sortedqtypes, $tomove, $direction) { 00076 $neworder = array_keys($sortedqtypes); 00077 // Find the element to move. 00078 $key = array_search($tomove, $neworder); 00079 if ($key === false) { 00080 return $neworder; 00081 } 00082 // Work out the other index. 00083 $otherkey = $key + $direction; 00084 if (!isset($neworder[$otherkey])) { 00085 return $neworder; 00086 } 00087 // Do the swap. 00088 $swap = $neworder[$otherkey]; 00089 $neworder[$otherkey] = $neworder[$key]; 00090 $neworder[$key] = $swap; 00091 return $neworder; 00092 } 00093 00100 function question_save_qtype_order($neworder, $config = null) { 00101 global $DB; 00102 00103 if (is_null($config)) { 00104 $config = get_config('question'); 00105 } 00106 00107 foreach ($neworder as $index => $qtype) { 00108 $sortvar = $qtype . '_sortorder'; 00109 if (!isset($config->$sortvar) || $config->$sortvar != $index + 1) { 00110 set_config($sortvar, $index + 1, 'question'); 00111 } 00112 } 00113 } 00114 00116 00125 function question_list_instances($questionid) { 00126 throw new coding_exception('question_list_instances has been deprectated. ' . 00127 'Please use questions_in_use instead.'); 00128 } 00129 00134 function questions_in_use($questionids) { 00135 global $CFG; 00136 00137 if (question_engine::questions_in_use($questionids)) { 00138 return true; 00139 } 00140 00141 foreach (get_plugin_list('mod') as $module => $path) { 00142 $lib = $path . '/lib.php'; 00143 if (is_readable($lib)) { 00144 include_once($lib); 00145 00146 $fn = $module . '_questions_in_use'; 00147 if (function_exists($fn)) { 00148 if ($fn($questionids)) { 00149 return true; 00150 } 00151 } else { 00152 00153 // Fallback for legacy modules. 00154 $fn = $module . '_question_list_instances'; 00155 if (function_exists($fn)) { 00156 foreach ($questionids as $questionid) { 00157 $instances = $fn($questionid); 00158 if (!empty($instances)) { 00159 return true; 00160 } 00161 } 00162 } 00163 } 00164 } 00165 } 00166 00167 return false; 00168 } 00169 00179 function question_context_has_any_questions($context) { 00180 global $DB; 00181 if (is_object($context)) { 00182 $contextid = $context->id; 00183 } else if (is_numeric($context)) { 00184 $contextid = $context; 00185 } else { 00186 print_error('invalidcontextinhasanyquestions', 'question'); 00187 } 00188 return $DB->record_exists_sql("SELECT * 00189 FROM {question} q 00190 JOIN {question_categories} qc ON qc.id = q.category 00191 WHERE qc.contextid = ? AND q.parent = 0", array($contextid)); 00192 } 00193 00203 function get_grade_options() { 00204 $grades = new stdClass(); 00205 $grades->gradeoptions = question_bank::fraction_options(); 00206 $grades->gradeoptionsfull = question_bank::fraction_options_full(); 00207 00208 return $grades; 00209 } 00210 00219 function match_grade_options($gradeoptionsfull, $grade, $matchgrades='error') { 00220 if ($matchgrades == 'error') { 00221 // if we just need an error... 00222 foreach ($gradeoptionsfull as $value => $option) { 00223 // slightly fuzzy test, never check floats for equality :-) 00224 if (abs($grade - $value) < 0.00001) { 00225 return $grade; 00226 } 00227 } 00228 // didn't find a match so that's an error 00229 return false; 00230 } else if ($matchgrades == 'nearest') { 00231 // work out nearest value 00232 $hownear = array(); 00233 foreach ($gradeoptionsfull as $value => $option) { 00234 if ($grade==$value) { 00235 return $grade; 00236 } 00237 $hownear[ $value ] = abs( $grade - $value ); 00238 } 00239 // reverse sort list of deltas and grab the last (smallest) 00240 asort( $hownear, SORT_NUMERIC ); 00241 reset( $hownear ); 00242 return key( $hownear ); 00243 } else { 00244 return false; 00245 } 00246 } 00247 00254 function question_category_isused($categoryid, $recursive = false) { 00255 throw new coding_exception('question_category_isused has been deprectated. ' . 00256 'Please use question_category_in_use instead.'); 00257 } 00258 00266 function question_category_in_use($categoryid, $recursive = false) { 00267 global $DB; 00268 00269 //Look at each question in the category 00270 if ($questions = $DB->get_records_menu('question', 00271 array('category' => $categoryid), '', 'id, 1')) { 00272 if (questions_in_use(array_keys($questions))) { 00273 return true; 00274 } 00275 } 00276 if (!$recursive) { 00277 return false; 00278 } 00279 00280 //Look under child categories recursively 00281 if ($children = $DB->get_records('question_categories', 00282 array('parent' => $categoryid), '', 'id, 1')) { 00283 foreach ($children as $child) { 00284 if (question_category_in_use($child->id, $recursive)) { 00285 return true; 00286 } 00287 } 00288 } 00289 00290 return false; 00291 } 00292 00299 function question_delete_question($questionid) { 00300 global $DB; 00301 00302 $question = $DB->get_record_sql(' 00303 SELECT q.*, qc.contextid 00304 FROM {question} q 00305 JOIN {question_categories} qc ON qc.id = q.category 00306 WHERE q.id = ?', array($questionid)); 00307 if (!$question) { 00308 // In some situations, for example if this was a child of a 00309 // Cloze question that was previously deleted, the question may already 00310 // have gone. In this case, just do nothing. 00311 return; 00312 } 00313 00314 // Do not delete a question if it is used by an activity module 00315 if (questions_in_use(array($questionid))) { 00316 return; 00317 } 00318 00319 // Check permissions. 00320 question_require_capability_on($question, 'edit'); 00321 00322 $dm = new question_engine_data_mapper(); 00323 $dm->delete_previews($questionid); 00324 00325 // delete questiontype-specific data 00326 question_bank::get_qtype($question->qtype, false)->delete_question( 00327 $questionid, $question->contextid); 00328 00329 // Now recursively delete all child questions 00330 if ($children = $DB->get_records('question', 00331 array('parent' => $questionid), '', 'id, qtype')) { 00332 foreach ($children as $child) { 00333 if ($child->id != $questionid) { 00334 question_delete_question($child->id); 00335 } 00336 } 00337 } 00338 00339 // Finally delete the question record itself 00340 $DB->delete_records('question', array('id' => $questionid)); 00341 } 00342 00350 function question_delete_course($course, $feedback=true) { 00351 global $DB, $OUTPUT; 00352 00353 //To store feedback to be showed at the end of the process 00354 $feedbackdata = array(); 00355 00356 //Cache some strings 00357 $strcatdeleted = get_string('unusedcategorydeleted', 'quiz'); 00358 $coursecontext = get_context_instance(CONTEXT_COURSE, $course->id); 00359 $categoriescourse = $DB->get_records('question_categories', 00360 array('contextid' => $coursecontext->id), 'parent', 'id, parent, name, contextid'); 00361 00362 if ($categoriescourse) { 00363 00364 //Sort categories following their tree (parent-child) relationships 00365 //this will make the feedback more readable 00366 $categoriescourse = sort_categories_by_tree($categoriescourse); 00367 00368 foreach ($categoriescourse as $category) { 00369 00370 //Delete it completely (questions and category itself) 00371 //deleting questions 00372 if ($questions = $DB->get_records('question', 00373 array('category' => $category->id), '', 'id,qtype')) { 00374 foreach ($questions as $question) { 00375 question_delete_question($question->id); 00376 } 00377 $DB->delete_records("question", array("category" => $category->id)); 00378 } 00379 //delete the category 00380 $DB->delete_records('question_categories', array('id' => $category->id)); 00381 00382 //Fill feedback 00383 $feedbackdata[] = array($category->name, $strcatdeleted); 00384 } 00385 //Inform about changes performed if feedback is enabled 00386 if ($feedback) { 00387 $table = new html_table(); 00388 $table->head = array(get_string('category', 'quiz'), get_string('action')); 00389 $table->data = $feedbackdata; 00390 echo html_writer::table($table); 00391 } 00392 } 00393 return true; 00394 } 00395 00407 function question_delete_course_category($category, $newcategory, $feedback=true) { 00408 global $DB, $OUTPUT; 00409 00410 $context = get_context_instance(CONTEXT_COURSECAT, $category->id); 00411 if (empty($newcategory)) { 00412 $feedbackdata = array(); // To store feedback to be showed at the end of the process 00413 $rescueqcategory = null; // See the code around the call to question_save_from_deletion. 00414 $strcatdeleted = get_string('unusedcategorydeleted', 'quiz'); 00415 00416 // Loop over question categories. 00417 if ($categories = $DB->get_records('question_categories', 00418 array('contextid'=>$context->id), 'parent', 'id, parent, name')) { 00419 foreach ($categories as $category) { 00420 00421 // Deal with any questions in the category. 00422 if ($questions = $DB->get_records('question', 00423 array('category' => $category->id), '', 'id,qtype')) { 00424 00425 // Try to delete each question. 00426 foreach ($questions as $question) { 00427 question_delete_question($question->id); 00428 } 00429 00430 // Check to see if there were any questions that were kept because 00431 // they are still in use somehow, even though quizzes in courses 00432 // in this category will already have been deteted. This could 00433 // happen, for example, if questions are added to a course, 00434 // and then that course is moved to another category (MDL-14802). 00435 $questionids = $DB->get_records_menu('question', 00436 array('category'=>$category->id), '', 'id, 1'); 00437 if (!empty($questionids)) { 00438 if (!$rescueqcategory = question_save_from_deletion( 00439 array_keys($questionids), get_parent_contextid($context), 00440 print_context_name($context), $rescueqcategory)) { 00441 return false; 00442 } 00443 $feedbackdata[] = array($category->name, 00444 get_string('questionsmovedto', 'question', $rescueqcategory->name)); 00445 } 00446 } 00447 00448 // Now delete the category. 00449 if (!$DB->delete_records('question_categories', array('id'=>$category->id))) { 00450 return false; 00451 } 00452 $feedbackdata[] = array($category->name, $strcatdeleted); 00453 00454 } // End loop over categories. 00455 } 00456 00457 // Output feedback if requested. 00458 if ($feedback and $feedbackdata) { 00459 $table = new html_table(); 00460 $table->head = array(get_string('questioncategory', 'question'), get_string('action')); 00461 $table->data = $feedbackdata; 00462 echo html_writer::table($table); 00463 } 00464 00465 } else { 00466 // Move question categories ot the new context. 00467 if (!$newcontext = get_context_instance(CONTEXT_COURSECAT, $newcategory->id)) { 00468 return false; 00469 } 00470 $DB->set_field('question_categories', 'contextid', $newcontext->id, 00471 array('contextid'=>$context->id)); 00472 if ($feedback) { 00473 $a = new stdClass(); 00474 $a->oldplace = print_context_name($context); 00475 $a->newplace = print_context_name($newcontext); 00476 echo $OUTPUT->notification( 00477 get_string('movedquestionsandcategories', 'question', $a), 'notifysuccess'); 00478 } 00479 } 00480 00481 return true; 00482 } 00483 00494 function question_save_from_deletion($questionids, $newcontextid, $oldplace, 00495 $newcategory = null) { 00496 global $DB; 00497 00498 // Make a category in the parent context to move the questions to. 00499 if (is_null($newcategory)) { 00500 $newcategory = new stdClass(); 00501 $newcategory->parent = 0; 00502 $newcategory->contextid = $newcontextid; 00503 $newcategory->name = get_string('questionsrescuedfrom', 'question', $oldplace); 00504 $newcategory->info = get_string('questionsrescuedfrominfo', 'question', $oldplace); 00505 $newcategory->sortorder = 999; 00506 $newcategory->stamp = make_unique_id_code(); 00507 $newcategory->id = $DB->insert_record('question_categories', $newcategory); 00508 } 00509 00510 // Move any remaining questions to the 'saved' category. 00511 if (!question_move_questions_to_category($questionids, $newcategory->id)) { 00512 return false; 00513 } 00514 return $newcategory; 00515 } 00516 00524 function question_delete_activity($cm, $feedback=true) { 00525 global $DB, $OUTPUT; 00526 00527 //To store feedback to be showed at the end of the process 00528 $feedbackdata = array(); 00529 00530 //Cache some strings 00531 $strcatdeleted = get_string('unusedcategorydeleted', 'quiz'); 00532 $modcontext = get_context_instance(CONTEXT_MODULE, $cm->id); 00533 if ($categoriesmods = $DB->get_records('question_categories', 00534 array('contextid' => $modcontext->id), 'parent', 'id, parent, name, contextid')) { 00535 //Sort categories following their tree (parent-child) relationships 00536 //this will make the feedback more readable 00537 $categoriesmods = sort_categories_by_tree($categoriesmods); 00538 00539 foreach ($categoriesmods as $category) { 00540 00541 //Delete it completely (questions and category itself) 00542 //deleting questions 00543 if ($questions = $DB->get_records('question', 00544 array('category' => $category->id), '', 'id,qtype')) { 00545 foreach ($questions as $question) { 00546 question_delete_question($question->id); 00547 } 00548 $DB->delete_records("question", array("category"=>$category->id)); 00549 } 00550 //delete the category 00551 $DB->delete_records('question_categories', array('id'=>$category->id)); 00552 00553 //Fill feedback 00554 $feedbackdata[] = array($category->name, $strcatdeleted); 00555 } 00556 //Inform about changes performed if feedback is enabled 00557 if ($feedback) { 00558 $table = new html_table(); 00559 $table->head = array(get_string('category', 'quiz'), get_string('action')); 00560 $table->data = $feedbackdata; 00561 echo html_writer::table($table); 00562 } 00563 } 00564 return true; 00565 } 00566 00577 function question_move_questions_to_category($questionids, $newcategoryid) { 00578 global $DB; 00579 00580 $newcontextid = $DB->get_field('question_categories', 'contextid', 00581 array('id' => $newcategoryid)); 00582 list($questionidcondition, $params) = $DB->get_in_or_equal($questionids); 00583 $questions = $DB->get_records_sql(" 00584 SELECT q.id, q.qtype, qc.contextid 00585 FROM {question} q 00586 JOIN {question_categories} qc ON q.category = qc.id 00587 WHERE q.id $questionidcondition", $params); 00588 foreach ($questions as $question) { 00589 if ($newcontextid != $question->contextid) { 00590 question_bank::get_qtype($question->qtype)->move_files( 00591 $question->id, $question->contextid, $newcontextid); 00592 } 00593 } 00594 00595 // Move the questions themselves. 00596 $DB->set_field_select('question', 'category', $newcategoryid, 00597 "id $questionidcondition", $params); 00598 00599 // Move any subquestions belonging to them. 00600 $DB->set_field_select('question', 'category', $newcategoryid, 00601 "parent $questionidcondition", $params); 00602 00603 // TODO Deal with datasets. 00604 00605 return true; 00606 } 00607 00616 function question_move_category_to_context($categoryid, $oldcontextid, $newcontextid) { 00617 global $DB; 00618 00619 $questionids = $DB->get_records_menu('question', 00620 array('category' => $categoryid), '', 'id,qtype'); 00621 foreach ($questionids as $questionid => $qtype) { 00622 question_bank::get_qtype($qtype)->move_files( 00623 $questionid, $oldcontextid, $newcontextid); 00624 } 00625 00626 $subcatids = $DB->get_records_menu('question_categories', 00627 array('parent' => $categoryid), '', 'id,1'); 00628 foreach ($subcatids as $subcatid => $notused) { 00629 $DB->set_field('question_categories', 'contextid', $newcontextid, 00630 array('id' => $subcatid)); 00631 question_move_category_to_context($subcatid, $oldcontextid, $newcontextid); 00632 } 00633 } 00634 00647 function question_preview_url($questionid, $preferredbehaviour = null, 00648 $maxmark = null, $displayoptions = null, $variant = null, $context = null) { 00649 00650 $params = array('id' => $questionid); 00651 00652 if (is_null($context)) { 00653 global $PAGE; 00654 $context = $PAGE->context; 00655 } 00656 if ($context->contextlevel == CONTEXT_MODULE) { 00657 $params['cmid'] = $context->instanceid; 00658 } else if ($context->contextlevel == CONTEXT_COURSE) { 00659 $params['courseid'] = $context->instanceid; 00660 } 00661 00662 if (!is_null($preferredbehaviour)) { 00663 $params['behaviour'] = $preferredbehaviour; 00664 } 00665 00666 if (!is_null($maxmark)) { 00667 $params['maxmark'] = $maxmark; 00668 } 00669 00670 if (!is_null($displayoptions)) { 00671 $params['correctness'] = $displayoptions->correctness; 00672 $params['marks'] = $displayoptions->marks; 00673 $params['markdp'] = $displayoptions->markdp; 00674 $params['feedback'] = (bool) $displayoptions->feedback; 00675 $params['generalfeedback'] = (bool) $displayoptions->generalfeedback; 00676 $params['rightanswer'] = (bool) $displayoptions->rightanswer; 00677 $params['history'] = (bool) $displayoptions->history; 00678 } 00679 00680 if ($variant) { 00681 $params['variant'] = $variant; 00682 } 00683 00684 return new moodle_url('/question/preview.php', $params); 00685 } 00686 00690 function question_preview_popup_params() { 00691 return array( 00692 'height' => 600, 00693 'width' => 800, 00694 ); 00695 } 00696 00714 function question_preload_questions($questionids, $extrafields = '', $join = '', 00715 $extraparams = array()) { 00716 global $DB; 00717 if (empty($questionids)) { 00718 return array(); 00719 } 00720 if ($join) { 00721 $join = ' JOIN '.$join; 00722 } 00723 if ($extrafields) { 00724 $extrafields = ', ' . $extrafields; 00725 } 00726 list($questionidcondition, $params) = $DB->get_in_or_equal( 00727 $questionids, SQL_PARAMS_NAMED, 'qid0000'); 00728 $sql = 'SELECT q.*, qc.contextid' . $extrafields . ' FROM {question} q 00729 JOIN {question_categories} qc ON q.category = qc.id' . 00730 $join . 00731 ' WHERE q.id ' . $questionidcondition; 00732 00733 // Load the questions 00734 if (!$questions = $DB->get_records_sql($sql, $extraparams + $params)) { 00735 return array(); 00736 } 00737 00738 foreach ($questions as $question) { 00739 $question->_partiallyloaded = true; 00740 } 00741 00742 // Note, a possible optimisation here would be to not load the TEXT fields 00743 // (that is, questiontext and generalfeedback) here, and instead load them in 00744 // question_load_questions. That would add one DB query, but reduce the amount 00745 // of data transferred most of the time. I am not going to do this optimisation 00746 // until it is shown to be worthwhile. 00747 00748 return $questions; 00749 } 00750 00764 function question_load_questions($questionids, $extrafields = '', $join = '') { 00765 $questions = question_preload_questions($questionids, $extrafields, $join); 00766 00767 // Load the question type specific information 00768 if (!get_question_options($questions)) { 00769 return 'Could not load the question options'; 00770 } 00771 00772 return $questions; 00773 } 00774 00781 function _tidy_question($question, $loadtags = false) { 00782 global $CFG; 00783 if (!question_bank::is_qtype_installed($question->qtype)) { 00784 $question->questiontext = html_writer::tag('p', get_string('warningmissingtype', 00785 'qtype_missingtype')) . $question->questiontext; 00786 } 00787 question_bank::get_qtype($question->qtype)->get_question_options($question); 00788 if (isset($question->_partiallyloaded)) { 00789 unset($question->_partiallyloaded); 00790 } 00791 if ($loadtags && !empty($CFG->usetags)) { 00792 require_once($CFG->dirroot . '/tag/lib.php'); 00793 $question->tags = tag_get_tags_array('question', $question->id); 00794 } 00795 } 00796 00809 function get_question_options(&$questions, $loadtags = false) { 00810 if (is_array($questions)) { // deal with an array of questions 00811 foreach ($questions as $i => $notused) { 00812 _tidy_question($questions[$i], $loadtags); 00813 } 00814 } else { // deal with single question 00815 _tidy_question($questions, $loadtags); 00816 } 00817 return true; 00818 } 00819 00827 function print_question_icon($question) { 00828 global $PAGE; 00829 return $PAGE->get_renderer('question', 'bank')->qtype_icon($question->qtype); 00830 } 00831 00841 function question_hash($question) { 00842 return make_unique_id_code(); 00843 } 00844 00846 00851 function save_question_options($question) { 00852 question_bank::get_qtype($question->qtype)->save_question_options($question); 00853 } 00854 00856 00862 function sort_categories_by_tree(&$categories, $id = 0, $level = 1) { 00863 global $DB; 00864 00865 $children = array(); 00866 $keys = array_keys($categories); 00867 00868 foreach ($keys as $key) { 00869 if (!isset($categories[$key]->processed) && $categories[$key]->parent == $id) { 00870 $children[$key] = $categories[$key]; 00871 $categories[$key]->processed = true; 00872 $children = $children + sort_categories_by_tree( 00873 $categories, $children[$key]->id, $level+1); 00874 } 00875 } 00876 //If level = 1, we have finished, try to look for non processed categories 00877 // (bad parent) and sort them too 00878 if ($level == 1) { 00879 foreach ($keys as $key) { 00880 // If not processed and it's a good candidate to start (because its 00881 // parent doesn't exist in the course) 00882 if (!isset($categories[$key]->processed) && !$DB->record_exists('question_categories', 00883 array('contextid' => $categories[$key]->contextid, 00884 'id' => $categories[$key]->parent))) { 00885 $children[$key] = $categories[$key]; 00886 $categories[$key]->processed = true; 00887 $children = $children + sort_categories_by_tree( 00888 $categories, $children[$key]->id, $level + 1); 00889 } 00890 } 00891 } 00892 return $children; 00893 } 00894 00909 function flatten_category_tree(&$categories, $id, $depth = 0, $nochildrenof = -1) { 00910 00911 // Indent the name of this category. 00912 $newcategories = array(); 00913 $newcategories[$id] = $categories[$id]; 00914 $newcategories[$id]->indentedname = str_repeat(' ', $depth) . 00915 $categories[$id]->name; 00916 00917 // Recursively indent the children. 00918 foreach ($categories[$id]->childids as $childid) { 00919 if ($childid != $nochildrenof) { 00920 $newcategories = $newcategories + flatten_category_tree( 00921 $categories, $childid, $depth + 1, $nochildrenof); 00922 } 00923 } 00924 00925 // Remove the childids array that were temporarily added. 00926 unset($newcategories[$id]->childids); 00927 00928 return $newcategories; 00929 } 00930 00937 function add_indented_names($categories, $nochildrenof = -1) { 00938 00939 // Add an array to each category to hold the child category ids. This array 00940 // will be removed again by flatten_category_tree(). It should not be used 00941 // outside these two functions. 00942 foreach (array_keys($categories) as $id) { 00943 $categories[$id]->childids = array(); 00944 } 00945 00946 // Build the tree structure, and record which categories are top-level. 00947 // We have to be careful, because the categories array may include published 00948 // categories from other courses, but not their parents. 00949 $toplevelcategoryids = array(); 00950 foreach (array_keys($categories) as $id) { 00951 if (!empty($categories[$id]->parent) && 00952 array_key_exists($categories[$id]->parent, $categories)) { 00953 $categories[$categories[$id]->parent]->childids[] = $id; 00954 } else { 00955 $toplevelcategoryids[] = $id; 00956 } 00957 } 00958 00959 // Flatten the tree to and add the indents. 00960 $newcategories = array(); 00961 foreach ($toplevelcategoryids as $id) { 00962 $newcategories = $newcategories + flatten_category_tree( 00963 $categories, $id, 0, $nochildrenof); 00964 } 00965 00966 return $newcategories; 00967 } 00968 00981 function question_category_select_menu($contexts, $top = false, $currentcat = 0, 00982 $selected = "", $nochildrenof = -1) { 00983 global $OUTPUT; 00984 $categoriesarray = question_category_options($contexts, $top, $currentcat, 00985 false, $nochildrenof); 00986 if ($selected) { 00987 $choose = ''; 00988 } else { 00989 $choose = 'choosedots'; 00990 } 00991 $options = array(); 00992 foreach ($categoriesarray as $group => $opts) { 00993 $options[] = array($group => $opts); 00994 } 00995 00996 echo html_writer::select($options, 'category', $selected, $choose); 00997 } 00998 01003 function question_get_default_category($contextid) { 01004 global $DB; 01005 $category = $DB->get_records('question_categories', 01006 array('contextid' => $contextid), 'id', '*', 0, 1); 01007 if (!empty($category)) { 01008 return reset($category); 01009 } else { 01010 return false; 01011 } 01012 } 01013 01021 function question_make_default_categories($contexts) { 01022 global $DB; 01023 static $preferredlevels = array( 01024 CONTEXT_COURSE => 4, 01025 CONTEXT_MODULE => 3, 01026 CONTEXT_COURSECAT => 2, 01027 CONTEXT_SYSTEM => 1, 01028 ); 01029 01030 $toreturn = null; 01031 $preferredness = 0; 01032 // If it already exists, just return it. 01033 foreach ($contexts as $key => $context) { 01034 if (!$exists = $DB->record_exists("question_categories", 01035 array('contextid' => $context->id))) { 01036 // Otherwise, we need to make one 01037 $category = new stdClass(); 01038 $contextname = print_context_name($context, false, true); 01039 $category->name = get_string('defaultfor', 'question', $contextname); 01040 $category->info = get_string('defaultinfofor', 'question', $contextname); 01041 $category->contextid = $context->id; 01042 $category->parent = 0; 01043 // By default, all categories get this number, and are sorted alphabetically. 01044 $category->sortorder = 999; 01045 $category->stamp = make_unique_id_code(); 01046 $category->id = $DB->insert_record('question_categories', $category); 01047 } else { 01048 $category = question_get_default_category($context->id); 01049 } 01050 if ($preferredlevels[$context->contextlevel] > $preferredness && has_any_capability( 01051 array('moodle/question:usemine', 'moodle/question:useall'), $context)) { 01052 $toreturn = $category; 01053 $preferredness = $preferredlevels[$context->contextlevel]; 01054 } 01055 } 01056 01057 if (!is_null($toreturn)) { 01058 $toreturn = clone($toreturn); 01059 } 01060 return $toreturn; 01061 } 01062 01071 function get_categories_for_contexts($contexts, $sortorder = 'parent, sortorder, name ASC') { 01072 global $DB; 01073 return $DB->get_records_sql(" 01074 SELECT c.*, (SELECT count(1) FROM {question} q 01075 WHERE c.id = q.category AND q.hidden='0' AND q.parent='0') AS questioncount 01076 FROM {question_categories} c 01077 WHERE c.contextid IN ($contexts) 01078 ORDER BY $sortorder"); 01079 } 01080 01084 function question_category_options($contexts, $top = false, $currentcat = 0, 01085 $popupform = false, $nochildrenof = -1) { 01086 global $CFG; 01087 $pcontexts = array(); 01088 foreach ($contexts as $context) { 01089 $pcontexts[] = $context->id; 01090 } 01091 $contextslist = join($pcontexts, ', '); 01092 01093 $categories = get_categories_for_contexts($contextslist); 01094 01095 $categories = question_add_context_in_key($categories); 01096 01097 if ($top) { 01098 $categories = question_add_tops($categories, $pcontexts); 01099 } 01100 $categories = add_indented_names($categories, $nochildrenof); 01101 01102 // sort cats out into different contexts 01103 $categoriesarray = array(); 01104 foreach ($pcontexts as $pcontext) { 01105 $contextstring = print_context_name( 01106 get_context_instance_by_id($pcontext), true, true); 01107 foreach ($categories as $category) { 01108 if ($category->contextid == $pcontext) { 01109 $cid = $category->id; 01110 if ($currentcat != $cid || $currentcat == 0) { 01111 $countstring = !empty($category->questioncount) ? 01112 " ($category->questioncount)" : ''; 01113 $categoriesarray[$contextstring][$cid] = $category->indentedname.$countstring; 01114 } 01115 } 01116 } 01117 } 01118 if ($popupform) { 01119 $popupcats = array(); 01120 foreach ($categoriesarray as $contextstring => $optgroup) { 01121 $group = array(); 01122 foreach ($optgroup as $key => $value) { 01123 $key = str_replace($CFG->wwwroot, '', $key); 01124 $group[$key] = $value; 01125 } 01126 $popupcats[] = array($contextstring => $group); 01127 } 01128 return $popupcats; 01129 } else { 01130 return $categoriesarray; 01131 } 01132 } 01133 01134 function question_add_context_in_key($categories) { 01135 $newcatarray = array(); 01136 foreach ($categories as $id => $category) { 01137 $category->parent = "$category->parent,$category->contextid"; 01138 $category->id = "$category->id,$category->contextid"; 01139 $newcatarray["$id,$category->contextid"] = $category; 01140 } 01141 return $newcatarray; 01142 } 01143 01144 function question_add_tops($categories, $pcontexts) { 01145 $topcats = array(); 01146 foreach ($pcontexts as $context) { 01147 $newcat = new stdClass(); 01148 $newcat->id = "0,$context"; 01149 $newcat->name = get_string('top'); 01150 $newcat->parent = -1; 01151 $newcat->contextid = $context; 01152 $topcats["0,$context"] = $newcat; 01153 } 01154 //put topcats in at beginning of array - they'll be sorted into different contexts later. 01155 return array_merge($topcats, $categories); 01156 } 01157 01161 function question_categorylist($categoryid) { 01162 global $DB; 01163 01164 $subcategories = $DB->get_records('question_categories', 01165 array('parent' => $categoryid), 'sortorder ASC', 'id, 1'); 01166 01167 $categorylist = array($categoryid); 01168 foreach ($subcategories as $subcategory) { 01169 $categorylist = array_merge($categorylist, question_categorylist($subcategory->id)); 01170 } 01171 01172 return $categorylist; 01173 } 01174 01175 //=========================== 01176 // Import/Export Functions 01177 //=========================== 01178 01184 function get_import_export_formats($type) { 01185 global $CFG; 01186 require_once($CFG->dirroot . '/question/format.php'); 01187 01188 $formatclasses = get_plugin_list_with_class('qformat', '', 'format.php'); 01189 01190 $fileformatname = array(); 01191 foreach ($formatclasses as $component => $formatclass) { 01192 01193 $format = new $formatclass(); 01194 if ($type == 'import') { 01195 $provided = $format->provide_import(); 01196 } else { 01197 $provided = $format->provide_export(); 01198 } 01199 01200 if ($provided) { 01201 list($notused, $fileformat) = explode('_', $component, 2); 01202 $fileformatnames[$fileformat] = get_string('pluginname', $component); 01203 } 01204 } 01205 01206 collatorlib::asort($fileformatnames); 01207 return $fileformatnames; 01208 } 01209 01210 01218 function question_default_export_filename($course, $category) { 01219 // We build a string that is an appropriate name (questions) from the lang pack, 01220 // then the corse shortname, then the question category name, then a timestamp. 01221 01222 $base = clean_filename(get_string('exportfilename', 'question')); 01223 01224 $dateformat = str_replace(' ', '_', get_string('exportnameformat', 'question')); 01225 $timestamp = clean_filename(userdate(time(), $dateformat, 99, false)); 01226 01227 $shortname = clean_filename($course->shortname); 01228 if ($shortname == '' || $shortname == '_' ) { 01229 $shortname = $course->id; 01230 } 01231 01232 $categoryname = clean_filename(format_string($category->name)); 01233 01234 return "{$base}-{$shortname}-{$categoryname}-{$timestamp}"; 01235 01236 return $export_name; 01237 } 01238 01246 class context_to_string_translator{ 01250 protected $contexttostringarray = array(); 01251 01252 public function __construct($contexts) { 01253 $this->generate_context_to_string_array($contexts); 01254 } 01255 01256 public function context_to_string($contextid) { 01257 return $this->contexttostringarray[$contextid]; 01258 } 01259 01260 public function string_to_context($contextname) { 01261 $contextid = array_search($contextname, $this->contexttostringarray); 01262 return $contextid; 01263 } 01264 01265 protected function generate_context_to_string_array($contexts) { 01266 if (!$this->contexttostringarray) { 01267 $catno = 1; 01268 foreach ($contexts as $context) { 01269 switch ($context->contextlevel) { 01270 case CONTEXT_MODULE : 01271 $contextstring = 'module'; 01272 break; 01273 case CONTEXT_COURSE : 01274 $contextstring = 'course'; 01275 break; 01276 case CONTEXT_COURSECAT : 01277 $contextstring = "cat$catno"; 01278 $catno++; 01279 break; 01280 case CONTEXT_SYSTEM : 01281 $contextstring = 'system'; 01282 break; 01283 } 01284 $this->contexttostringarray[$context->id] = $contextstring; 01285 } 01286 } 01287 } 01288 01289 } 01290 01299 function question_has_capability_on($question, $cap, $cachecat = -1) { 01300 global $USER, $DB; 01301 01302 // these are capabilities on existing questions capabilties are 01303 //set per category. Each of these has a mine and all version. Append 'mine' and 'all' 01304 $question_questioncaps = array('edit', 'view', 'use', 'move'); 01305 static $questions = array(); 01306 static $categories = array(); 01307 static $cachedcat = array(); 01308 if ($cachecat != -1 && array_search($cachecat, $cachedcat) === false) { 01309 $questions += $DB->get_records('question', array('category' => $cachecat)); 01310 $cachedcat[] = $cachecat; 01311 } 01312 if (!is_object($question)) { 01313 if (!isset($questions[$question])) { 01314 if (!$questions[$question] = $DB->get_record('question', 01315 array('id' => $question), 'id,category,createdby')) { 01316 print_error('questiondoesnotexist', 'question'); 01317 } 01318 } 01319 $question = $questions[$question]; 01320 } 01321 if (empty($question->category)) { 01322 // This can happen when we have created a fake 'missingtype' question to 01323 // take the place of a deleted question. 01324 return false; 01325 } 01326 if (!isset($categories[$question->category])) { 01327 if (!$categories[$question->category] = $DB->get_record('question_categories', 01328 array('id'=>$question->category))) { 01329 print_error('invalidcategory', 'quiz'); 01330 } 01331 } 01332 $category = $categories[$question->category]; 01333 $context = get_context_instance_by_id($category->contextid); 01334 01335 if (array_search($cap, $question_questioncaps)!== false) { 01336 if (!has_capability('moodle/question:' . $cap . 'all', $context)) { 01337 if ($question->createdby == $USER->id) { 01338 return has_capability('moodle/question:' . $cap . 'mine', $context); 01339 } else { 01340 return false; 01341 } 01342 } else { 01343 return true; 01344 } 01345 } else { 01346 return has_capability('moodle/question:' . $cap, $context); 01347 } 01348 01349 } 01350 01354 function question_require_capability_on($question, $cap) { 01355 if (!question_has_capability_on($question, $cap)) { 01356 print_error('nopermissions', '', '', $cap); 01357 } 01358 return true; 01359 } 01360 01368 function question_get_real_state($state) { 01369 global $OUTPUT; 01370 $realstate = clone($state); 01371 $matches = array(); 01372 if (!preg_match('|^random([0-9]+)-(.*)|', $state->answer, $matches)) { 01373 echo $OUTPUT->notification(get_string('errorrandom', 'quiz_statistics')); 01374 return false; 01375 } else { 01376 $realstate->question = $matches[1]; 01377 $realstate->answer = $matches[2]; 01378 return $realstate; 01379 } 01380 } 01381 01386 function question_edit_url($context) { 01387 global $CFG, $SITE; 01388 if (!has_any_capability(question_get_question_capabilities(), $context)) { 01389 return false; 01390 } 01391 $baseurl = $CFG->wwwroot . '/question/edit.php?'; 01392 $defaultcategory = question_get_default_category($context->id); 01393 if ($defaultcategory) { 01394 $baseurl .= 'cat=' . $defaultcategory->id . ',' . $context->id . '&'; 01395 } 01396 switch ($context->contextlevel) { 01397 case CONTEXT_SYSTEM: 01398 return $baseurl . 'courseid=' . $SITE->id; 01399 case CONTEXT_COURSECAT: 01400 // This is nasty, becuase we can only edit questions in a course 01401 // context at the moment, so for now we just return false. 01402 return false; 01403 case CONTEXT_COURSE: 01404 return $baseurl . 'courseid=' . $context->instanceid; 01405 case CONTEXT_MODULE: 01406 return $baseurl . 'cmid=' . $context->instanceid; 01407 } 01408 01409 } 01410 01418 function question_extend_settings_navigation(navigation_node $navigationnode, $context) { 01419 global $PAGE; 01420 01421 if ($context->contextlevel == CONTEXT_COURSE) { 01422 $params = array('courseid'=>$context->instanceid); 01423 } else if ($context->contextlevel == CONTEXT_MODULE) { 01424 $params = array('cmid'=>$context->instanceid); 01425 } else { 01426 return; 01427 } 01428 01429 if (($cat = $PAGE->url->param('cat')) && preg_match('~\d+,\d+~', $cat)) { 01430 $params['cat'] = $cat; 01431 } 01432 01433 $questionnode = $navigationnode->add(get_string('questionbank', 'question'), 01434 new moodle_url('/question/edit.php', $params), navigation_node::TYPE_CONTAINER); 01435 01436 $contexts = new question_edit_contexts($context); 01437 if ($contexts->have_one_edit_tab_cap('questions')) { 01438 $questionnode->add(get_string('questions', 'quiz'), new moodle_url( 01439 '/question/edit.php', $params), navigation_node::TYPE_SETTING); 01440 } 01441 if ($contexts->have_one_edit_tab_cap('categories')) { 01442 $questionnode->add(get_string('categories', 'quiz'), new moodle_url( 01443 '/question/category.php', $params), navigation_node::TYPE_SETTING); 01444 } 01445 if ($contexts->have_one_edit_tab_cap('import')) { 01446 $questionnode->add(get_string('import', 'quiz'), new moodle_url( 01447 '/question/import.php', $params), navigation_node::TYPE_SETTING); 01448 } 01449 if ($contexts->have_one_edit_tab_cap('export')) { 01450 $questionnode->add(get_string('export', 'quiz'), new moodle_url( 01451 '/question/export.php', $params), navigation_node::TYPE_SETTING); 01452 } 01453 01454 return $questionnode; 01455 } 01456 01460 function question_get_question_capabilities() { 01461 return array( 01462 'moodle/question:add', 01463 'moodle/question:editmine', 01464 'moodle/question:editall', 01465 'moodle/question:viewmine', 01466 'moodle/question:viewall', 01467 'moodle/question:usemine', 01468 'moodle/question:useall', 01469 'moodle/question:movemine', 01470 'moodle/question:moveall', 01471 ); 01472 } 01473 01477 function question_get_all_capabilities() { 01478 $caps = question_get_question_capabilities(); 01479 $caps[] = 'moodle/question:managecategory'; 01480 $caps[] = 'moodle/question:flag'; 01481 return $caps; 01482 } 01483 01484 01492 class question_edit_contexts { 01493 01494 public static $caps = array( 01495 'editq' => array('moodle/question:add', 01496 'moodle/question:editmine', 01497 'moodle/question:editall', 01498 'moodle/question:viewmine', 01499 'moodle/question:viewall', 01500 'moodle/question:usemine', 01501 'moodle/question:useall', 01502 'moodle/question:movemine', 01503 'moodle/question:moveall'), 01504 'questions'=>array('moodle/question:add', 01505 'moodle/question:editmine', 01506 'moodle/question:editall', 01507 'moodle/question:viewmine', 01508 'moodle/question:viewall', 01509 'moodle/question:movemine', 01510 'moodle/question:moveall'), 01511 'categories'=>array('moodle/question:managecategory'), 01512 'import'=>array('moodle/question:add'), 01513 'export'=>array('moodle/question:viewall', 'moodle/question:viewmine')); 01514 01515 protected $allcontexts; 01516 01521 public function __construct(context $thiscontext) { 01522 $this->allcontexts = array_values($thiscontext->get_parent_contexts(true)); 01523 } 01524 01528 public function all() { 01529 return $this->allcontexts; 01530 } 01531 01535 public function lowest() { 01536 return $this->allcontexts[0]; 01537 } 01538 01543 public function having_cap($cap) { 01544 $contextswithcap = array(); 01545 foreach ($this->allcontexts as $context) { 01546 if (has_capability($cap, $context)) { 01547 $contextswithcap[] = $context; 01548 } 01549 } 01550 return $contextswithcap; 01551 } 01552 01557 public function having_one_cap($caps) { 01558 $contextswithacap = array(); 01559 foreach ($this->allcontexts as $context) { 01560 foreach ($caps as $cap) { 01561 if (has_capability($cap, $context)) { 01562 $contextswithacap[] = $context; 01563 break; //done with caps loop 01564 } 01565 } 01566 } 01567 return $contextswithacap; 01568 } 01569 01574 public function having_one_edit_tab_cap($tabname) { 01575 return $this->having_one_cap(self::$caps[$tabname]); 01576 } 01577 01584 public function have_cap($cap) { 01585 return (count($this->having_cap($cap))); 01586 } 01587 01594 public function have_one_cap($caps) { 01595 foreach ($caps as $cap) { 01596 if ($this->have_cap($cap)) { 01597 return true; 01598 } 01599 } 01600 return false; 01601 } 01602 01609 public function have_one_edit_tab_cap($tabname) { 01610 return $this->have_one_cap(self::$caps[$tabname]); 01611 } 01612 01618 public function require_cap($cap) { 01619 if (!$this->have_cap($cap)) { 01620 print_error('nopermissions', '', '', $cap); 01621 } 01622 } 01623 01629 public function require_one_cap($caps) { 01630 if (!$this->have_one_cap($caps)) { 01631 $capsstring = join($caps, ', '); 01632 print_error('nopermissions', '', '', $capsstring); 01633 } 01634 } 01635 01641 public function require_one_edit_tab_cap($tabname) { 01642 if (!$this->have_one_edit_tab_cap($tabname)) { 01643 print_error('nopermissions', '', '', 'access question edit tab '.$tabname); 01644 } 01645 } 01646 } 01647 01648 01662 function question_rewrite_question_urls($text, $file, $contextid, $component, 01663 $filearea, array $ids, $itemid, array $options=null) { 01664 01665 $idsstr = ''; 01666 if (!empty($ids)) { 01667 $idsstr .= implode('/', $ids); 01668 } 01669 if ($itemid !== null) { 01670 $idsstr .= '/' . $itemid; 01671 } 01672 return file_rewrite_pluginfile_urls($text, $file, $contextid, $component, 01673 $filearea, $idsstr, $options); 01674 } 01675 01688 function question_rewrite_questiontext_preview_urls($questiontext, $contextid, 01689 $component, $questionid, $options=null) { 01690 01691 return file_rewrite_pluginfile_urls($questiontext, 'pluginfile.php', $contextid, 01692 'question', 'questiontext_preview', "$component/$questionid", $options); 01693 } 01694 01701 function question_send_questiontext_file($questionid, $args, $forcedownload) { 01702 global $DB; 01703 01704 $question = $DB->get_record_sql(' 01705 SELECT q.id, qc.contextid 01706 FROM {question} q 01707 JOIN {question_categories} qc ON qc.id = q.category 01708 WHERE q.id = :id', array('id' => $questionid), MUST_EXIST); 01709 01710 $fs = get_file_storage(); 01711 $fullpath = "/$question->contextid/question/questiontext/$question->id/" . implode('/', $args); 01712 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) { 01713 send_file_not_found(); 01714 } 01715 01716 send_stored_file($file, 0, 0, $forcedownload); 01717 } 01718 01739 function question_pluginfile($course, $context, $component, $filearea, $args, $forcedownload) { 01740 global $DB, $CFG; 01741 01742 if ($filearea === 'questiontext_preview') { 01743 $component = array_shift($args); 01744 $questionid = array_shift($args); 01745 01746 component_callback($component, 'questiontext_preview_pluginfile', array( 01747 $context, $questionid, $args, $forcedownload)); 01748 01749 send_file_not_found(); 01750 } 01751 01752 list($context, $course, $cm) = get_context_info_array($context->id); 01753 require_login($course, false, $cm); 01754 01755 if ($filearea === 'export') { 01756 require_once($CFG->dirroot . '/question/editlib.php'); 01757 $contexts = new question_edit_contexts($context); 01758 // check export capability 01759 $contexts->require_one_edit_tab_cap('export'); 01760 $category_id = (int)array_shift($args); 01761 $format = array_shift($args); 01762 $cattofile = array_shift($args); 01763 $contexttofile = array_shift($args); 01764 $filename = array_shift($args); 01765 01766 // load parent class for import/export 01767 require_once($CFG->dirroot . '/question/format.php'); 01768 require_once($CFG->dirroot . '/question/editlib.php'); 01769 require_once($CFG->dirroot . '/question/format/' . $format . '/format.php'); 01770 01771 $classname = 'qformat_' . $format; 01772 if (!class_exists($classname)) { 01773 send_file_not_found(); 01774 } 01775 01776 $qformat = new $classname(); 01777 01778 if (!$category = $DB->get_record('question_categories', array('id' => $category_id))) { 01779 send_file_not_found(); 01780 } 01781 01782 $qformat->setCategory($category); 01783 $qformat->setContexts($contexts->having_one_edit_tab_cap('export')); 01784 $qformat->setCourse($course); 01785 01786 if ($cattofile == 'withcategories') { 01787 $qformat->setCattofile(true); 01788 } else { 01789 $qformat->setCattofile(false); 01790 } 01791 01792 if ($contexttofile == 'withcontexts') { 01793 $qformat->setContexttofile(true); 01794 } else { 01795 $qformat->setContexttofile(false); 01796 } 01797 01798 if (!$qformat->exportpreprocess()) { 01799 send_file_not_found(); 01800 print_error('exporterror', 'question', $thispageurl->out()); 01801 } 01802 01803 // export data to moodle file pool 01804 if (!$content = $qformat->exportprocess(true)) { 01805 send_file_not_found(); 01806 } 01807 01808 send_file($content, $filename, 0, 0, true, true, $qformat->mime_type()); 01809 } 01810 01811 $qubaid = (int)array_shift($args); 01812 $slot = (int)array_shift($args); 01813 01814 $module = $DB->get_field('question_usages', 'component', 01815 array('id' => $qubaid)); 01816 01817 if ($module === 'core_question_preview') { 01818 require_once($CFG->dirroot . '/question/previewlib.php'); 01819 return question_preview_question_pluginfile($course, $context, 01820 $component, $filearea, $qubaid, $slot, $args, $forcedownload); 01821 01822 } else { 01823 $dir = get_component_directory($module); 01824 if (!file_exists("$dir/lib.php")) { 01825 send_file_not_found(); 01826 } 01827 include_once("$dir/lib.php"); 01828 01829 $filefunction = $module . '_question_pluginfile'; 01830 if (!function_exists($filefunction)) { 01831 send_file_not_found(); 01832 } 01833 01834 $filefunction($course, $context, $component, $filearea, $qubaid, $slot, 01835 $args, $forcedownload); 01836 01837 send_file_not_found(); 01838 } 01839 } 01840 01848 function core_question_questiontext_preview_pluginfile($context, $questionid, $args, $forcedownload) { 01849 global $DB; 01850 01851 // Verify that contextid matches the question. 01852 $question = $DB->get_record_sql(' 01853 SELECT q.*, qc.contextid 01854 FROM {question} q 01855 JOIN {question_categories} qc ON qc.id = q.category 01856 WHERE q.id = :id AND qc.contextid = :contextid', 01857 array('id' => $questionid, 'contextid' => $context->id), MUST_EXIST); 01858 01859 // Check the capability. 01860 list($context, $course, $cm) = get_context_info_array($context->id); 01861 require_login($course, false, $cm); 01862 01863 question_require_capability_on($question, 'use'); 01864 01865 question_send_questiontext_file($questionid, $args, $forcedownload); 01866 } 01867 01878 function question_make_export_url($contextid, $categoryid, $format, $withcategories, 01879 $withcontexts, $filename) { 01880 global $CFG; 01881 $urlbase = "$CFG->httpswwwroot/pluginfile.php"; 01882 return moodle_url::make_file_url($urlbase, 01883 "/$contextid/question/export/{$categoryid}/{$format}/{$withcategories}" . 01884 "/{$withcontexts}/{$filename}", true); 01885 } 01886 01893 function question_page_type_list($pagetype, $parentcontext, $currentcontext) { 01894 global $CFG; 01895 $types = array( 01896 'question-*'=>get_string('page-question-x', 'question'), 01897 'question-edit'=>get_string('page-question-edit', 'question'), 01898 'question-category'=>get_string('page-question-category', 'question'), 01899 'question-export'=>get_string('page-question-export', 'question'), 01900 'question-import'=>get_string('page-question-import', 'question') 01901 ); 01902 if ($currentcontext->contextlevel == CONTEXT_COURSE) { 01903 require_once($CFG->dirroot . '/course/lib.php'); 01904 return array_merge(course_page_type_list($pagetype, $parentcontext, $currentcontext), $types); 01905 } else { 01906 return $types; 01907 } 01908 }