question.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. <?php
  2. // This file is part of Moodle - http://moodle.org/
  3. //
  4. // Moodle is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // Moodle is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU General Public License
  15. // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
  16. /**
  17. * Page for editing questions.
  18. *
  19. * @package moodlecore
  20. * @subpackage questionbank
  21. * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
  22. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  23. */
  24. require_once(__DIR__ . '/../config.php');
  25. require_once(__DIR__ . '/editlib.php');
  26. require_once($CFG->libdir . '/filelib.php');
  27. require_once($CFG->libdir . '/formslib.php');
  28. // Read URL parameters telling us which question to edit.
  29. $id = optional_param('id', 0, PARAM_INT); // question id
  30. $makecopy = optional_param('makecopy', 0, PARAM_BOOL);
  31. $qtype = optional_param('qtype', '', PARAM_FILE);
  32. $categoryid = optional_param('category', 0, PARAM_INT);
  33. $cmid = optional_param('cmid', 0, PARAM_INT);
  34. $courseid = optional_param('courseid', 0, PARAM_INT);
  35. $wizardnow = optional_param('wizardnow', '', PARAM_ALPHA);
  36. $originalreturnurl = optional_param('returnurl', 0, PARAM_LOCALURL);
  37. $appendqnumstring = optional_param('appendqnumstring', '', PARAM_ALPHA);
  38. $inpopup = optional_param('inpopup', 0, PARAM_BOOL);
  39. $scrollpos = optional_param('scrollpos', 0, PARAM_INT);
  40. $url = new moodle_url('/question/question.php');
  41. if ($id !== 0) {
  42. $url->param('id', $id);
  43. }
  44. if ($makecopy) {
  45. $url->param('makecopy', $makecopy);
  46. }
  47. if ($qtype !== '') {
  48. $url->param('qtype', $qtype);
  49. }
  50. if ($categoryid !== 0) {
  51. $url->param('category', $categoryid);
  52. }
  53. if ($cmid !== 0) {
  54. $url->param('cmid', $cmid);
  55. }
  56. if ($courseid !== 0) {
  57. $url->param('courseid', $courseid);
  58. }
  59. if ($wizardnow !== '') {
  60. $url->param('wizardnow', $wizardnow);
  61. }
  62. if ($originalreturnurl !== 0) {
  63. $url->param('returnurl', $originalreturnurl);
  64. }
  65. if ($appendqnumstring !== '') {
  66. $url->param('appendqnumstring', $appendqnumstring);
  67. }
  68. if ($inpopup !== 0) {
  69. $url->param('inpopup', $inpopup);
  70. }
  71. if ($scrollpos) {
  72. $url->param('scrollpos', $scrollpos);
  73. }
  74. $PAGE->set_url($url);
  75. if ($cmid) {
  76. $questionbankurl = new moodle_url('/question/edit.php', array('cmid' => $cmid));
  77. } else {
  78. $questionbankurl = new moodle_url('/question/edit.php', array('courseid' => $courseid));
  79. }
  80. navigation_node::override_active_url($questionbankurl);
  81. if ($originalreturnurl) {
  82. if (strpos($originalreturnurl, '/') !== 0) {
  83. throw new coding_exception("returnurl must be a local URL starting with '/'. $originalreturnurl was given.");
  84. }
  85. $returnurl = new moodle_url($originalreturnurl);
  86. } else {
  87. $returnurl = $questionbankurl;
  88. }
  89. if ($scrollpos) {
  90. $returnurl->param('scrollpos', $scrollpos);
  91. }
  92. if ($cmid){
  93. list($module, $cm) = get_module_from_cmid($cmid);
  94. require_login($cm->course, false, $cm);
  95. $thiscontext = context_module::instance($cmid);
  96. } elseif ($courseid) {
  97. require_login($courseid, false);
  98. $thiscontext = context_course::instance($courseid);
  99. $module = null;
  100. $cm = null;
  101. } else {
  102. print_error('missingcourseorcmid', 'question');
  103. }
  104. $contexts = new question_edit_contexts($thiscontext);
  105. $PAGE->set_pagelayout('admin');
  106. if (optional_param('addcancel', false, PARAM_BOOL)) {
  107. redirect($returnurl);
  108. }
  109. if ($id) {
  110. if (!$question = $DB->get_record('question', array('id' => $id))) {
  111. print_error('questiondoesnotexist', 'question', $returnurl);
  112. }
  113. get_question_options($question, true);
  114. } else if ($categoryid && $qtype) { // only for creating new questions
  115. $question = new stdClass();
  116. $question->category = $categoryid;
  117. $question->qtype = $qtype;
  118. $question->createdby = $USER->id;
  119. // Check that users are allowed to create this question type at the moment.
  120. if (!question_bank::qtype_enabled($qtype)) {
  121. print_error('cannotenable', 'question', $returnurl, $qtype);
  122. }
  123. } else if ($categoryid) {
  124. // Category, but no qtype. They probably came from the addquestion.php
  125. // script without choosing a question type. Send them back.
  126. $addurl = new moodle_url('/question/addquestion.php', $url->params());
  127. $addurl->param('validationerror', 1);
  128. redirect($addurl);
  129. } else {
  130. print_error('notenoughdatatoeditaquestion', 'question', $returnurl);
  131. }
  132. $qtypeobj = question_bank::get_qtype($question->qtype);
  133. // Validate the question category.
  134. if (!$category = $DB->get_record('question_categories', array('id' => $question->category))) {
  135. print_error('categorydoesnotexist', 'question', $returnurl);
  136. }
  137. // Check permissions
  138. $question->formoptions = new stdClass();
  139. $categorycontext = context::instance_by_id($category->contextid);
  140. $addpermission = has_capability('moodle/question:add', $categorycontext);
  141. if ($id) {
  142. $question->formoptions->canedit = question_has_capability_on($question, 'edit');
  143. $question->formoptions->canmove = $addpermission && question_has_capability_on($question, 'move');
  144. $question->formoptions->cansaveasnew = $addpermission &&
  145. (question_has_capability_on($question, 'view') || $question->formoptions->canedit);
  146. $question->formoptions->repeatelements = $question->formoptions->canedit || $question->formoptions->cansaveasnew;
  147. $formeditable = $question->formoptions->canedit || $question->formoptions->cansaveasnew || $question->formoptions->canmove;
  148. if (!$formeditable) {
  149. question_require_capability_on($question, 'view');
  150. }
  151. if ($makecopy) {
  152. // If we are duplicating a question, add some indication to the question name.
  153. $question->name = get_string('questionnamecopy', 'question', $question->name);
  154. $question->beingcopied = true;
  155. }
  156. } else { // creating a new question
  157. $question->formoptions->canedit = question_has_capability_on($question, 'edit');
  158. $question->formoptions->canmove = (question_has_capability_on($question, 'move') && $addpermission);
  159. $question->formoptions->cansaveasnew = false;
  160. $question->formoptions->repeatelements = true;
  161. $formeditable = true;
  162. require_capability('moodle/question:add', $categorycontext);
  163. }
  164. $question->formoptions->mustbeusable = (bool) $appendqnumstring;
  165. // Validate the question type.
  166. $PAGE->set_pagetype('question-type-' . $question->qtype);
  167. // Create the question editing form.
  168. if ($wizardnow !== '') {
  169. $mform = $qtypeobj->next_wizard_form('question.php', $question, $wizardnow, $formeditable);
  170. } else {
  171. $mform = $qtypeobj->create_editing_form('question.php', $question, $category, $contexts, $formeditable);
  172. }
  173. $toform = fullclone($question); // send the question object and a few more parameters to the form
  174. $toform->category = "{$category->id},{$category->contextid}";
  175. $toform->scrollpos = $scrollpos;
  176. if ($formeditable && $id){
  177. $toform->categorymoveto = $toform->category;
  178. }
  179. $toform->appendqnumstring = $appendqnumstring;
  180. $toform->returnurl = $originalreturnurl;
  181. $toform->makecopy = $makecopy;
  182. if ($cm !== null){
  183. $toform->cmid = $cm->id;
  184. $toform->courseid = $cm->course;
  185. } else {
  186. $toform->courseid = $COURSE->id;
  187. }
  188. $toform->inpopup = $inpopup;
  189. $mform->set_data($toform);
  190. if ($mform->is_cancelled()) {
  191. if ($inpopup) {
  192. close_window();
  193. } else {
  194. redirect($returnurl);
  195. }
  196. } else if ($fromform = $mform->get_data()) {
  197. // If we are saving as a copy, break the connection to the old question.
  198. if ($makecopy) {
  199. $question->id = 0;
  200. $question->hidden = 0; // Copies should not be hidden.
  201. }
  202. /// Process the combination of usecurrentcat, categorymoveto and category form
  203. /// fields, so the save_question method only has to consider $fromform->category
  204. if (!empty($fromform->usecurrentcat)) {
  205. // $fromform->category is the right category to save in.
  206. } else {
  207. if (!empty($fromform->categorymoveto)) {
  208. $fromform->category = $fromform->categorymoveto;
  209. } else {
  210. // $fromform->category is the right category to save in.
  211. }
  212. }
  213. /// If we are moving a question, check we have permission to move it from
  214. /// whence it came. (Where we are moving to is validated by the form.)
  215. list($newcatid, $newcontextid) = explode(',', $fromform->category);
  216. if (!empty($question->id) && $newcatid != $question->category) {
  217. $contextid = $newcontextid;
  218. question_require_capability_on($question, 'move');
  219. } else {
  220. $contextid = $category->contextid;
  221. }
  222. // Ensure we redirect back to the category the question is being saved into.
  223. $returnurl->param('category', $fromform->category);
  224. // We are acutally saving the question.
  225. if (!empty($question->id)) {
  226. question_require_capability_on($question, 'edit');
  227. } else {
  228. require_capability('moodle/question:add', context::instance_by_id($contextid));
  229. if (!empty($fromform->makecopy) && !$question->formoptions->cansaveasnew) {
  230. print_error('nopermissions', '', '', 'edit');
  231. }
  232. }
  233. $question = $qtypeobj->save_question($question, $fromform);
  234. if (isset($fromform->tags)) {
  235. core_tag_tag::set_item_tags('core_question', 'question', $question->id,
  236. context::instance_by_id($contextid), $fromform->tags);
  237. }
  238. // Purge this question from the cache.
  239. question_bank::notify_question_edited($question->id);
  240. // If we are saving and continuing to edit the question.
  241. if (!empty($fromform->updatebutton)) {
  242. $url->param('id', $question->id);
  243. $url->remove_params('makecopy');
  244. redirect($url);
  245. }
  246. if ($qtypeobj->finished_edit_wizard($fromform)) {
  247. if ($inpopup) {
  248. echo $OUTPUT->notification(get_string('changessaved'), '');
  249. close_window(3);
  250. } else {
  251. $returnurl->param('lastchanged', $question->id);
  252. if ($appendqnumstring) {
  253. $returnurl->param($appendqnumstring, $question->id);
  254. $returnurl->param('sesskey', sesskey());
  255. $returnurl->param('cmid', $cmid);
  256. }
  257. redirect($returnurl);
  258. }
  259. } else {
  260. $nexturlparams = array(
  261. 'returnurl' => $originalreturnurl,
  262. 'appendqnumstring' => $appendqnumstring,
  263. 'scrollpos' => $scrollpos);
  264. if (isset($fromform->nextpageparam) && is_array($fromform->nextpageparam)){
  265. //useful for passing data to the next page which is not saved in the database.
  266. $nexturlparams += $fromform->nextpageparam;
  267. }
  268. $nexturlparams['id'] = $question->id;
  269. $nexturlparams['wizardnow'] = $fromform->wizard;
  270. $nexturl = new moodle_url('/question/question.php', $nexturlparams);
  271. if ($cmid){
  272. $nexturl->param('cmid', $cmid);
  273. } else {
  274. $nexturl->param('courseid', $COURSE->id);
  275. }
  276. redirect($nexturl);
  277. }
  278. }
  279. $streditingquestion = $qtypeobj->get_heading();
  280. $PAGE->set_title($streditingquestion);
  281. $PAGE->set_heading($COURSE->fullname);
  282. $PAGE->navbar->add($streditingquestion);
  283. // Display a heading, question editing form and possibly some extra content needed for
  284. // for this question type.
  285. echo $OUTPUT->header();
  286. $qtypeobj->display_question_editing_page($mform, $question, $wizardnow);
  287. echo $OUTPUT->footer();