/** * Serve question files when they are displayed in this export format. * * @param context $previewcontext the quiz context * @param int $questionid the question id. * @param context $filecontext the file (question) context * @param string $filecomponent the component the file belongs to. * @param string $filearea the file area. * @param array $args remaining file args. * @param bool $forcedownload. * @param array $options additional options affecting the file serving. */ function qformat_xhtml_questiontext_preview_pluginfile($context, $questionid, $args, $forcedownload, array $options = array()) { global $CFG; list($context, $course, $cm) = get_context_info_array($context->id); require_login($course, false, $cm); question_require_capability_on($questionid, 'view'); question_send_questiontext_file($questionid, $args, $forcedownload, $options); }
/** * Serve question files when they are displayed in this export format. * * @param context $previewcontext the quiz context * @param int $questionid the question id. * @param context $filecontext the file (question) context * @param string $filecomponent the component the file belongs to. * @param string $filearea the file area. * @param array $args remaining file args. * @param bool $forcedownload. * @param array $options additional options affecting the file serving. */ function qformat_xhtml_question_preview_pluginfile($previewcontext, $questionid, $filecontext, $filecomponent, $filearea, $args, $forcedownload, $options = array()) { global $CFG; list($context, $course, $cm) = get_context_info_array($previewcontext->id); require_login($course, false, $cm); question_require_capability_on($questionid, 'view'); $fs = get_file_storage(); $relativepath = implode('/', $args); $fullpath = "/{$filecontext->id}/{$filecomponent}/{$filearea}/{$relativepath}"; if (!($file = $fs->get_file_by_hash(sha1($fullpath))) or $file->is_directory()) { send_file_not_found(); } send_stored_file($file, 0, 0, $forcedownload, $options); }
/** * Verify that the question exists, and the user has permission to use it. * Does not return. Throws an exception if the question cannot be used. * @param int $questionid The id of the question. */ function quiz_require_question_use($questionid) { global $DB; $question = $DB->get_record('question', array('id' => $questionid), '*', MUST_EXIST); question_require_capability_on($question, 'use'); }
/// whence it came. (Where we are moving to is validated by the form.) list($newcatid, $newcontextid) = explode(',', $fromform->category); if (!empty($question->id) && $newcatid != $question->category) { question_require_capability_on($question, 'move'); } // Ensure we redirect back to the category the question is being saved into. $returnurl->param('category', $fromform->category); if ($movecontext) { // We are just moving the question to a different context. list($tocatid, $tocontextid) = explode(',', $fromform->categorymoveto); require_capability('moodle/question:add', context::instance_by_id($tocontextid)); question_move_questions_to_category(array($question->id), $tocatid); } else { // We are acutally saving the question. if (!empty($question->id)) { question_require_capability_on($question, 'edit'); } else { require_capability('moodle/question:add', context::instance_by_id($newcontextid)); if (!empty($fromform->makecopy) && !$question->formoptions->cansaveasnew) { print_error('nopermissions', '', '', 'edit'); } } $question = $qtypeobj->save_question($question, $fromform); if (!empty($CFG->usetags) && isset($fromform->tags)) { // A wizardpage from multipe pages questiontype like calculated may not // allow editing the question tags, hence the isset($fromform->tags) test. require_once $CFG->dirroot . '/tag/lib.php'; tag_set('question', $question->id, $fromform->tags); } } // Purge this question from the cache.
/// Process the combination of usecurrentcat, categorymoveto and category form /// fields, so the save_question method only has to consider $fromform->category if (!empty($fromform->usecurrentcat)) { // $fromform->category is the right category to save in. } else { if (!empty($fromform->categorymoveto)) { $fromform->category = $fromform->categorymoveto; } else { // $fromform->category is the right category to save in. } } /// If we are moving a question, check we have permission to move it from /// whence it came. (Where we are moving to is validated by the form.) list($newcatid) = explode(',', $fromform->category); if (!empty($question->id) && $newcatid != $question->category) { question_require_capability_on($question, 'move'); } /// Ensure we redirect back to the category the question is being saved into. $returnurl = new moodle_url($returnurl); $returnurl->param('category', $fromform->category); $returnurl = $returnurl->out(); /// Call the appropriate method. if ($movecontext) { list($tocatid, $tocontextid) = explode(',', $fromform->categorymoveto); $tocontext = get_context_instance_by_id($tocontextid); require_capability('moodle/question:add', $tocontext); if (get_filesdir_from_context($categorycontext) != get_filesdir_from_context($tocontext)) { $movecontexturl = new moodle_url($CFG->wwwroot . '/question/contextmoveq.php', array('returnurl' => $returnurl, 'ids' => $question->id, 'tocatid' => $tocatid)); if ($cmid) { $movecontexturl->param('cmid', $cmid); } else {
/** * Shows the question bank editing interface. * * The function also processes a number of actions: * * Actions affecting the question pool: * move Moves a question to a different category * deleteselected Deletes the selected questions from the category * Other actions: * category Chooses the category * displayoptions Sets display options * * @author Martin Dougiamas and many others. This has recently been extensively * rewritten by Gustav Delius and other members of the Serving Mathematics project * {@link http://maths.york.ac.uk/serving_maths} * @param moodle_url $pageurl object representing this pages url. */ function question_showbank($tabname, $contexts, $pageurl, $cm, $page, $perpage, $sortorder, $sortorderdecoded, $cat, $recurse, $showhidden, $showquestiontext) { global $COURSE; if (optional_param('deleteselected', false, PARAM_BOOL)) { // teacher still has to confirm // make a list of all the questions that are selected $rawquestions = $_REQUEST; // This code is called by both POST forms and GET links, so cannot use data_submitted. $questionlist = ''; // comma separated list of ids of questions to be deleted $questionnames = ''; // string with names of questions separated by <br /> with // an asterix in front of those that are in use $inuse = false; // set to true if at least one of the questions is in use foreach ($rawquestions as $key => $value) { // Parse input for question ids if (preg_match('!^q([0-9]+)$!', $key, $matches)) { $key = $matches[1]; $questionlist .= $key . ','; question_require_capability_on($key, 'edit'); if (record_exists('quiz_question_instances', 'question', $key)) { $questionnames .= '* '; $inuse = true; } $questionnames .= get_field('question', 'name', 'id', $key) . '<br />'; } } if (!$questionlist) { // no questions were selected redirect($pageurl->out()); } $questionlist = rtrim($questionlist, ','); // Add an explanation about questions in use if ($inuse) { $questionnames .= '<br />' . get_string('questionsinuse', 'quiz'); } notice_yesno(get_string("deletequestionscheck", "quiz", $questionnames), $pageurl->out_action(), $pageurl->out(true), array('deleteselected' => $questionlist, 'confirm' => md5($questionlist)), $pageurl->params(), 'post', 'get'); echo '</td></tr>'; echo '</table>'; print_footer($COURSE); exit; } // starts with category selection form print_box_start('generalbox questionbank'); print_heading(get_string('questionbank', 'question'), '', 2); question_category_form($contexts->having_one_edit_tab_cap($tabname), $pageurl, $cat, $recurse, $showhidden, $showquestiontext); // continues with list of questions question_list($contexts->having_one_edit_tab_cap($tabname), $pageurl, $cat, isset($cm) ? $cm : null, $recurse, $page, $perpage, $showhidden, $sortorder, $sortorderdecoded, $showquestiontext, $contexts->having_cap('moodle/question:add')); print_box_end(); }
/** * Deletes question and all associated data from the database * * It will not delete a question if it is used by an activity module * @param object $question The question being deleted */ function delete_question($questionid) { global $QTYPES; if (!($question = get_record('question', 'id', $questionid))) { // In some situations, for example if this was a child of a // Cloze question that was previously deleted, the question may already // have gone. In this case, just do nothing. return; } // Do not delete a question if it is used by an activity module if (count(question_list_instances($questionid))) { return; } // delete questiontype-specific data question_require_capability_on($question, 'edit'); if ($question) { if (isset($QTYPES[$question->qtype])) { $QTYPES[$question->qtype]->delete_question($questionid); } } else { echo "Question with id {$questionid} does not exist.<br />"; } if ($states = get_records('question_states', 'question', $questionid)) { $stateslist = implode(',', array_keys($states)); // delete questiontype-specific data foreach ($QTYPES as $qtype) { $qtype->delete_states($stateslist); } } // delete entries from all other question tables // It is important that this is done only after calling the questiontype functions delete_records("question_answers", "question", $questionid); delete_records("question_states", "question", $questionid); delete_records("question_sessions", "questionid", $questionid); // Now recursively delete all child questions if ($children = get_records('question', 'parent', $questionid)) { foreach ($children as $child) { if ($child->id != $questionid) { delete_question($child->id); } } } // Finally delete the question record itself delete_records('question', 'id', $questionid); return; }
/** * Serve questiontext files in the question text when they are displayed in this report. * * @package core_files * @category files * @param context $previewcontext the context in which the preview is happening. * @param int $questionid the question id. * @param context $filecontext the file (question) context. * @param string $filecomponent the component the file belongs to. * @param string $filearea the file area. * @param array $args remaining file args. * @param bool $forcedownload. * @param array $options additional options affecting the file serving. */ function core_question_question_preview_pluginfile($previewcontext, $questionid, $filecontext, $filecomponent, $filearea, $args, $forcedownload, $options = array()) { global $DB; // Verify that contextid matches the question. $question = $DB->get_record_sql(' SELECT q.*, qc.contextid FROM {question} q JOIN {question_categories} qc ON qc.id = q.category WHERE q.id = :id AND qc.contextid = :contextid', array('id' => $questionid, 'contextid' => $filecontext->id), MUST_EXIST); // Check the capability. list($context, $course, $cm) = get_context_info_array($previewcontext->id); require_login($course, false, $cm); question_require_capability_on($question, 'use'); $fs = get_file_storage(); $relativepath = implode('/', $args); $fullpath = "/{$filecontext->id}/{$filecomponent}/{$filearea}/{$relativepath}"; if (!($file = $fs->get_file_by_hash(sha1($fullpath))) or $file->is_directory()) { send_file_not_found(); } send_stored_file($file, 0, 0, $forcedownload, $options); }
} if (!($tocat = get_record('question_categories', 'id', $tocatid))) { print_error('categorydoesnotexist', 'question', $returnurl); } $tocat->context = get_context_instance_by_id($tocat->contextid); require_capability('moodle/question:add', $tocat->context); $tocoursefilesid = get_filesdir_from_context($tocat->context); $urls = array(); if ($tocoursefilesid == SITEID) { $toareaname = get_string('filesareasite', 'question'); } else { $toareaname = get_string('filesareacourse', 'question'); } $fromcoursefilesid = 0; foreach (array_keys($questions) as $id) { question_require_capability_on($questions[$id], 'move'); get_question_options($questions[$id]); $questions[$id]->context = get_context_instance_by_id($questions[$id]->contextid); $thisfilesid = get_filesdir_from_context($questions[$id]->context); if ($fromcoursefilesid && $thisfilesid != $fromcoursefilesid) { error('You can\'t use this script to move questions that have files associated with them from different areas.'); } else { $fromcoursefilesid = $thisfilesid; } if ($tocoursefilesid != $fromcoursefilesid) { $urls = array_merge_recursive($urls, $QTYPES[$questions[$id]->qtype]->find_file_links($questions[$id], $fromcoursefilesid)); } } $brokenurls = array(); foreach (array_keys($urls) as $url) { if (!file_exists($CFG->dataroot . "/{$fromcoursefilesid}/" . $url)) {
$navlinks[] = array('name' => $strqcreates, 'link' => "index.php?id={$course->id}", 'type' => 'activity'); $navlinks[] = array('name' => format_string($qcreate->name), 'link' => '', 'type' => 'activityinstance'); $navigation = build_navigation($navlinks); $headerargs = array(format_string($qcreate->name), "", $navigation, "", "", true, update_module_button($cm->id, $course->id, $strqcreate), navmenu($course, $cm)); if (!($cats = get_categories_for_contexts($modulecontext->id))) { //if it has not been made yet then make a default cat question_make_default_categories(array($modulecontext)); $cats = get_categories_for_contexts($modulecontext->id); } $catsinctx = array(); foreach ($cats as $catinctx) { $catsinctx[] = $catinctx->id; } $catsinctxlist = join($catsinctx, ','); $cat = array_shift($cats); if ($delete && question_require_capability_on($delete, 'edit')) { if ($confirm && confirm_sesskey()) { if (!delete_records_select('question', "id = {$delete} AND category IN ({$catsinctxlist})")) { print_error('question_not_found'); } else { qcreate_update_grades($qcreate, $USER->id); redirect($CFG->wwwroot . '/mod/qcreate/view.php?id=' . $cm->id); } } else { call_user_func_array('print_header_simple', $headerargs); print_heading(get_string('delete')); notice_yesno(get_string('confirmdeletequestion', 'qcreate'), 'view.php', 'view.php', array('id' => $cm->id, 'sesskey' => sesskey(), 'confirm' => 1, 'delete' => $delete), array('id' => $cm->id), 'POST', 'GET'); print_footer('none'); die; } }
/** * Deletes question and all associated data from the database * * It will not delete a question if it is used by an activity module * * @global object * @global object * @param object $question The question being deleted */ function delete_question($questionid) { global $QTYPES, $DB; $question = $DB->get_record_sql(' SELECT q.*, qc.contextid FROM {question} q JOIN {question_categories} qc ON qc.id = q.category WHERE q.id = ?', array($questionid)); if (!$question) { // In some situations, for example if this was a child of a // Cloze question that was previously deleted, the question may already // have gone. In this case, just do nothing. return; } // Do not delete a question if it is used by an activity module if (count(question_list_instances($questionid))) { return; } // delete questiontype-specific data question_require_capability_on($question, 'edit'); if (isset($QTYPES[$question->qtype])) { $QTYPES[$question->qtype]->delete_question($questionid, $question->contextid); } if ($states = $DB->get_records('question_states', array('question' => $questionid))) { $stateslist = implode(',', array_keys($states)); // delete questiontype-specific data foreach ($QTYPES as $qtype) { $qtype->delete_states($stateslist); } } // Delete entries from all other question tables // It is important that this is done only after calling the questiontype functions $DB->delete_records('question_answers', array('question' => $questionid)); $DB->delete_records('question_states', array('question' => $questionid)); $DB->delete_records('question_sessions', array('questionid' => $questionid)); // Now recursively delete all child questions if ($children = $DB->get_records('question', array('parent' => $questionid), '', 'id,qtype')) { foreach ($children as $child) { if ($child->id != $questionid) { delete_question($child->id); } } } // Finally delete the question record itself $DB->delete_records('question', array('id' => $questionid)); }
/** * Serve questiontext files in the question text when they are displayed in this report. * * @package core_files * @category files * @param stdClass $context the context * @param int $questionid the question id * @param array $args remaining file args * @param bool $forcedownload */ function core_question_questiontext_preview_pluginfile($context, $questionid, $args, $forcedownload) { global $DB; // Verify that contextid matches the question. $question = $DB->get_record_sql(' SELECT q.*, qc.contextid FROM {question} q JOIN {question_categories} qc ON qc.id = q.category WHERE q.id = :id AND qc.contextid = :contextid', array('id' => $questionid, 'contextid' => $context->id), MUST_EXIST); // Check the capability. list($context, $course, $cm) = get_context_info_array($context->id); require_login($course, false, $cm); question_require_capability_on($question, 'use'); question_send_questiontext_file($questionid, $args, $forcedownload); }
/** * Deletes question and all associated data from the database * * It will not delete a question if it is used by an activity module * @param object $question The question being deleted */ function question_delete_question($questionid) { global $DB; $question = $DB->get_record_sql(' SELECT q.*, qc.contextid FROM {question} q JOIN {question_categories} qc ON qc.id = q.category WHERE q.id = ?', array($questionid)); if (!$question) { // In some situations, for example if this was a child of a // Cloze question that was previously deleted, the question may already // have gone. In this case, just do nothing. return; } // Do not delete a question if it is used by an activity module if (questions_in_use(array($questionid))) { return; } // Check permissions. question_require_capability_on($question, 'edit'); $dm = new question_engine_data_mapper(); $dm->delete_previews($questionid); // delete questiontype-specific data question_bank::get_qtype($question->qtype, false)->delete_question($questionid, $question->contextid); // Now recursively delete all child questions if ($children = $DB->get_records('question', array('parent' => $questionid), '', 'id, qtype')) { foreach ($children as $child) { if ($child->id != $questionid) { question_delete_question($child->id); } } } // Finally delete the question record itself $DB->delete_records('question', array('id' => $questionid)); }
public function process_actions_needing_ui() { global $DB, $OUTPUT; if (optional_param('deleteselected', false, PARAM_BOOL)) { // make a list of all the questions that are selected $rawquestions = $_REQUEST; // This code is called by both POST forms and GET links, so cannot use data_submitted. $questionlist = ''; // comma separated list of ids of questions to be deleted $questionnames = ''; // string with names of questions separated by <br /> with // an asterix in front of those that are in use $inuse = false; // set to true if at least one of the questions is in use foreach ($rawquestions as $key => $value) { // Parse input for question ids if (preg_match('!^q([0-9]+)$!', $key, $matches)) { $key = $matches[1]; $questionlist .= $key.','; question_require_capability_on($key, 'edit'); if (questions_in_use(array($key))) { $questionnames .= '* '; $inuse = true; } $questionnames .= $DB->get_field('question', 'name', array('id' => $key)) . '<br />'; } } if (!$questionlist) { // no questions were selected redirect($this->baseurl); } $questionlist = rtrim($questionlist, ','); // Add an explanation about questions in use if ($inuse) { $questionnames .= '<br />'.get_string('questionsinuse', 'question'); } $baseurl = new moodle_url('edit.php', $this->baseurl->params()); $deleteurl = new moodle_url($baseurl, array('deleteselected'=>$questionlist, 'confirm'=>md5($questionlist), 'sesskey'=>sesskey())); echo $OUTPUT->confirm(get_string('deletequestionscheck', 'question', $questionnames), $deleteurl, $baseurl); return true; } }
/** * Saves or updates a question after editing by a teacher * * Given some question info and some data about the answers * this function parses, organises and saves the question * It is used by {@link question.php} when saving new data from * a form, and also by {@link import.php} when importing questions * This function in turn calls {@link save_question_options} * to save question-type specific options * @param object $question the question object which should be updated * @param object $form the form submitted by the teacher * @param object $course the course we are in * @return object On success, return the new question object. On failure, * return an object as follows. If the error object has an errors field, * display that as an error message. Otherwise, the editing form will be * redisplayed with validation errors, from validation_errors field, which * is itself an object, shown next to the form fields. */ function save_question($question, $form, $course) { global $USER; // This default implementation is suitable for most // question types. // First, save the basic question itself $question->name = trim($form->name); $question->questiontext = trim($form->questiontext); $question->questiontextformat = $form->questiontextformat; $question->parent = isset($form->parent) ? $form->parent : 0; $question->length = $this->actual_number_of_questions($question); $question->penalty = isset($form->penalty) ? $form->penalty : 0; if (empty($form->image)) { $question->image = ""; } else { $question->image = $form->image; } if (empty($form->generalfeedback)) { $question->generalfeedback = ''; } else { $question->generalfeedback = trim($form->generalfeedback); } if (empty($question->name)) { $question->name = shorten_text(strip_tags($question->questiontext), 15); if (empty($question->name)) { $question->name = '-'; } } if ($question->penalty > 1 or $question->penalty < 0) { $question->errors['penalty'] = get_string('invalidpenalty', 'quiz'); } if (isset($form->defaultgrade)) { $question->defaultgrade = $form->defaultgrade; } if (!empty($question->id)) { // Question already exists if (isset($form->categorymoveto)) { question_require_capability_on($question, 'move'); list($question->category, $movetocontextid) = explode(',', $form->categorymoveto); //don't need to test add permission of category we are moving question to. //Only categories that we have permission to add //a question to will get through the form cleaning code for the select box. } // keep existing unique stamp code $question->stamp = get_field('question', 'stamp', 'id', $question->id); $question->modifiedby = $USER->id; $question->timemodified = time(); if (!update_record('question', $question)) { error('Could not update question!'); } } else { // Question is a new one // Set the unique code list($question->category, $notused) = explode(',', $form->category); $question->stamp = make_unique_id_code(); $question->createdby = $USER->id; $question->modifiedby = $USER->id; $question->timecreated = time(); $question->timemodified = time(); if (!($question->id = insert_record('question', $question))) { error('Could not insert new question!'); } } // Now to save all the answers and type-specific options $form->id = $question->id; $form->qtype = $question->qtype; $form->category = $question->category; $form->questiontext = $question->questiontext; $result = $this->save_question_options($form); if (!empty($result->error)) { error($result->error); } if (!empty($result->notice)) { notice($result->notice, "question.php?id={$question->id}"); } if (!empty($result->noticeyesno)) { notice_yesno($result->noticeyesno, "question.php?id={$question->id}&courseid={$course->id}", "edit.php?courseid={$course->id}"); print_footer($course); exit; } // Give the question a unique version stamp determined by question_hash() if (!set_field('question', 'version', question_hash($question), 'id', $question->id)) { error('Could not update question version field'); } return $question; }
$question->formoptions->canedit = false; $question->formoptions->canmove = question_has_capability_on($question, 'move') && $contexts->have_cap('moodle/question:add'); $question->formoptions->cansaveasnew = false; $question->formoptions->repeatelements = false; $question->formoptions->movecontext = true; $formeditable = true; question_require_capability_on($question, 'view'); } else { $question->formoptions->canedit = question_has_capability_on($question, 'edit'); $question->formoptions->canmove = question_has_capability_on($question, 'move') && $addpermission; $question->formoptions->cansaveasnew = ($canview || question_has_capability_on($question, 'edit')) && $addpermission; $question->formoptions->repeatelements = $question->formoptions->canedit || $question->formoptions->cansaveasnew; $formeditable = $question->formoptions->canedit || $question->formoptions->cansaveasnew || $question->formoptions->canmove; $question->formoptions->movecontext = false; if (!$formeditable) { question_require_capability_on($question, 'view'); } } } else { // creating a new question require_capability('moodle/question:add', $categorycontext); $formeditable = true; $question->formoptions->repeatelements = true; $question->formoptions->movecontext = false; } // Validate the question type. if (!isset($QTYPES[$question->qtype])) { print_error('unknownquestiontype', 'question', $returnurl, $question->qtype); } $CFG->pagepath = 'question/type/' . $question->qtype; // Create the question editing form.