diff --git a/mod/quiz/addrandomform.php b/mod/quiz/addrandomform.php
index 60ebf7bcf6a..09f4d26b7e0 100644
--- a/mod/quiz/addrandomform.php
+++ b/mod/quiz/addrandomform.php
@@ -44,7 +44,7 @@ class quiz_add_random_form extends moodleform {
$usablecontexts = $contexts->having_cap('moodle/question:useall');
// Random from existing category section.
- $mform->addElement('header', 'categoryheader',
+ $mform->addElement('header', 'existingcategoryheader',
get_string('randomfromexistingcategory', 'quiz'));
$mform->addElement('questioncategory', 'category', get_string('category'),
@@ -74,7 +74,7 @@ class quiz_add_random_form extends moodleform {
$mform->addElement('submit', 'existingcategory', get_string('addrandomquestion', 'quiz'));
// Random from a new category section.
- $mform->addElement('header', 'categoryheader',
+ $mform->addElement('header', 'newcategoryheader',
get_string('randomquestionusinganewcategory', 'quiz'));
$mform->addElement('text', 'name', get_string('name'), 'maxlength="254" size="50"');
diff --git a/mod/quiz/amd/build/modal_add_random_question.min.js b/mod/quiz/amd/build/modal_add_random_question.min.js
new file mode 100644
index 00000000000..3690bfb0058
--- /dev/null
+++ b/mod/quiz/amd/build/modal_add_random_question.min.js
@@ -0,0 +1 @@
+define(["jquery","core/yui","core/notification","core/modal","core/modal_events","core/modal_registry","core/fragment","core/templates"],function(a,b,c,d,e,f,g,h){var i=!1,j={EXISTING_CATEGORY_CONTAINER:'[data-region="existing-category-container"]',EXISTING_CATEGORY_FORM_ELEMENT:"#id_existingcategoryheader",NEW_CATEGORY_CONTAINER:'[data-region="new-category-container"]',NEW_CATEGORY_FORM_ELEMENT:"#id_newcategoryheader",TAB_CONTENT:'[data-region="tab-content"]',ADD_ON_PAGE_FORM_ELEMENT:'[name="addonpage"]',SUBMIT_BUTTON_ELEMENT:'input[type="submit"]',CANCEL_BUTTON_ELEMENT:'input[type="submit"][name="cancel"]',FORM_HEADER:"legend",BUTTON_CONTAINER:".fitem"},k=function(a){d.call(this,a),this.contextId=null,this.addOnPageId=null,this.category=null,this.returnUrl=null,this.cmid=null,this.loadedForm=!1};return k.TYPE="mod_quiz-quiz-add-random-question",k.prototype=Object.create(d.prototype),k.prototype.constructor=k,k.prototype.setContextId=function(a){this.contextId=a},k.prototype.getContextId=function(){return this.contextId},k.prototype.setAddOnPageId=function(a){this.addOnPageId=a,this.getBody().find(j.ADD_ON_PAGE_FORM_ELEMENT).val(a)},k.prototype.getAddOnPageId=function(){return this.addOnPageId},k.prototype.setCategory=function(a){this.category=a},k.prototype.getCategory=function(){return this.category},k.prototype.setReturnUrl=function(a){this.returnUrl=a},k.prototype.getReturnUrl=function(){return this.returnUrl},k.prototype.setCMID=function(a){this.cmid=a},k.prototype.getCMID=function(){return this.cmid},k.prototype.moveFormElementIntoTab=function(b,c){var d=b.find(j.SUBMIT_BUTTON_ELEMENT),e=a('
');b.find(j.FORM_HEADER).addClass("hidden"),b.wrap(c),d.closest(j.BUTTON_CONTAINER).remove(),d.appendTo(e),e.appendTo(b)},k.prototype.moveTabsIntoTabContent=function(a){var b=this.getBody().find(j.TAB_CONTENT).empty();a.find('[role="tabpanel"]').wrapAll(b)},k.prototype.moveCancelButtonToTabs=function(a){var b=a.find(j.CANCEL_BUTTON_ELEMENT).addClass("m-l-1"),c=a.find('[data-region="footer"]');b.closest(j.BUTTON_CONTAINER).remove(),b.clone().appendTo(c)},k.prototype.loadForm=function(){return g.loadFragment("mod_quiz","add_random_question_form",this.getContextId(),{addonpage:this.getAddOnPageId(),cat:this.getCategory(),returnurl:this.getReturnUrl(),cmid:this.getCMID()}).then(function(b,c){var d=a(b),e=d.find(j.EXISTING_CATEGORY_FORM_ELEMENT),f=this.getBody().find(j.EXISTING_CATEGORY_CONTAINER),g=d.find(j.NEW_CATEGORY_FORM_ELEMENT),i=this.getBody().find(j.NEW_CATEGORY_CONTAINER);this.moveFormElementIntoTab(e,f),this.moveFormElementIntoTab(g,i),this.moveTabsIntoTabContent(d),this.moveCancelButtonToTabs(d),h.replaceNode(this.getBody().find(j.TAB_CONTENT),d,c)}.bind(this)).then(function(){b.use("moodle-core-formchangechecker",function(){M.core_formchangechecker.reset_form_dirty_state()})}).fail(c.exception)},k.prototype.show=function(){d.prototype.show.call(this),this.loadedForm||(this.loadForm(),this.loadedForm=!0)},i||(f.register(k.TYPE,k,"mod_quiz/modal_add_random_question"),i=!0),k});
\ No newline at end of file
diff --git a/mod/quiz/amd/src/modal_add_random_question.js b/mod/quiz/amd/src/modal_add_random_question.js
new file mode 100644
index 00000000000..a47f3b68a30
--- /dev/null
+++ b/mod/quiz/amd/src/modal_add_random_question.js
@@ -0,0 +1,315 @@
+// This file is part of Moodle - http://moodle.org/
+//
+// Moodle is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// Moodle is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with Moodle. If not, see .
+
+/**
+ * Contain the logic for the add random question modal.
+ *
+ * @module mod_quiz/modal_add_random_question
+ * @package mod_quiz
+ * @copyright 2018 Ryan Wyllie
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+define([
+ 'jquery',
+ 'core/yui',
+ 'core/notification',
+ 'core/modal',
+ 'core/modal_events',
+ 'core/modal_registry',
+ 'core/fragment',
+ 'core/templates',
+],
+function(
+ $,
+ Y,
+ Notification,
+ Modal,
+ ModalEvents,
+ ModalRegistry,
+ Fragment,
+ Templates
+) {
+
+ var registered = false;
+ var SELECTORS = {
+ EXISTING_CATEGORY_CONTAINER: '[data-region="existing-category-container"]',
+ EXISTING_CATEGORY_FORM_ELEMENT: '#id_existingcategoryheader',
+ NEW_CATEGORY_CONTAINER: '[data-region="new-category-container"]',
+ NEW_CATEGORY_FORM_ELEMENT: '#id_newcategoryheader',
+ TAB_CONTENT: '[data-region="tab-content"]',
+ ADD_ON_PAGE_FORM_ELEMENT: '[name="addonpage"]',
+ SUBMIT_BUTTON_ELEMENT: 'input[type="submit"]',
+ CANCEL_BUTTON_ELEMENT: 'input[type="submit"][name="cancel"]',
+ FORM_HEADER: 'legend',
+ BUTTON_CONTAINER: '.fitem'
+ };
+
+ /**
+ * Constructor for the Modal.
+ *
+ * @param {object} root The root jQuery element for the modal
+ */
+ var ModalAddRandomQuestion = function(root) {
+ Modal.call(this, root);
+ this.contextId = null;
+ this.addOnPageId = null;
+ this.category = null;
+ this.returnUrl = null;
+ this.cmid = null;
+ this.loadedForm = false;
+ };
+
+ ModalAddRandomQuestion.TYPE = 'mod_quiz-quiz-add-random-question';
+ ModalAddRandomQuestion.prototype = Object.create(Modal.prototype);
+ ModalAddRandomQuestion.prototype.constructor = ModalAddRandomQuestion;
+
+ /**
+ * Save the Moodle context id that the question bank is being
+ * rendered in.
+ *
+ * @method setContextId
+ * @param {int} id
+ */
+ ModalAddRandomQuestion.prototype.setContextId = function(id) {
+ this.contextId = id;
+ };
+
+ /**
+ * Retrieve the saved Moodle context id.
+ *
+ * @method getContextId
+ * @return {int}
+ */
+ ModalAddRandomQuestion.prototype.getContextId = function() {
+ return this.contextId;
+ };
+
+ /**
+ * Set the id of the page that the question should be added to
+ * when the user clicks the add to quiz link.
+ *
+ * @method setAddOnPageId
+ * @param {int} id
+ */
+ ModalAddRandomQuestion.prototype.setAddOnPageId = function(id) {
+ this.addOnPageId = id;
+ this.getBody().find(SELECTORS.ADD_ON_PAGE_FORM_ELEMENT).val(id);
+ };
+
+ /**
+ * Returns the saved page id for the question to be added to.
+ *
+ * @method getAddOnPageId
+ * @return {int}
+ */
+ ModalAddRandomQuestion.prototype.getAddOnPageId = function() {
+ return this.addOnPageId;
+ };
+
+ /**
+ * Set the category for this form. The category is a comma separated
+ * category id and category context id.
+ *
+ * @method setCategory
+ * @param {string} category
+ */
+ ModalAddRandomQuestion.prototype.setCategory = function(category) {
+ this.category = category;
+ };
+
+ /**
+ * Returns the saved category.
+ *
+ * @method getCategory
+ * @return {string}
+ */
+ ModalAddRandomQuestion.prototype.getCategory = function() {
+ return this.category;
+ };
+
+ /**
+ * Set the return URL for the form.
+ *
+ * @method setReturnUrl
+ * @param {string} url
+ */
+ ModalAddRandomQuestion.prototype.setReturnUrl = function(url) {
+ this.returnUrl = url;
+ };
+
+ /**
+ * Returns the return URL for the form.
+ *
+ * @method getReturnUrl
+ * @return {string}
+ */
+ ModalAddRandomQuestion.prototype.getReturnUrl = function() {
+ return this.returnUrl;
+ };
+
+ /**
+ * Set the course module id for the form.
+ *
+ * @method setCMID
+ * @param {int} id
+ */
+ ModalAddRandomQuestion.prototype.setCMID = function(id) {
+ this.cmid = id;
+ };
+
+ /**
+ * Returns the course module id for the form.
+ *
+ * @method getCMID
+ * @return {int}
+ */
+ ModalAddRandomQuestion.prototype.getCMID = function() {
+ return this.cmid;
+ };
+
+ /**
+ * Moves a given form element inside (a child of) a given tab element.
+ *
+ * Hides the 'legend' (e.g. header) element of the form element because the
+ * tab has the name.
+ *
+ * Moves the submit button into a footer element at the bottom of the form
+ * element for styling purposes.
+ *
+ * @method moveFormElementIntoTab
+ * @param {jquery} formElement The form element to move into the tab.
+ * @param {jquey} tabElement The tab element for the form element to move into.
+ */
+ ModalAddRandomQuestion.prototype.moveFormElementIntoTab = function(formElement, tabElement) {
+ var submitButtons = formElement.find(SELECTORS.SUBMIT_BUTTON_ELEMENT);
+ var footer = $('');
+ // Hide the header because the tabs show us which part of the form we're
+ // looking at.
+ formElement.find(SELECTORS.FORM_HEADER).addClass('hidden');
+ // Move the element inside a tab.
+ formElement.wrap(tabElement);
+ // Remove the buttons container element.
+ submitButtons.closest(SELECTORS.BUTTON_CONTAINER).remove();
+ // Put the button inside a footer.
+ submitButtons.appendTo(footer);
+ // Add the footer to the end of the category form element.
+ footer.appendTo(formElement);
+ };
+
+ /**
+ * Empty the tab content container and move all tabs from the form into the
+ * tab container element.
+ *
+ * @method moveTabsIntoTabContent
+ * @param {jquery} form The form element.
+ */
+ ModalAddRandomQuestion.prototype.moveTabsIntoTabContent = function(form) {
+ // Empty it to remove the loading icon.
+ var tabContent = this.getBody().find(SELECTORS.TAB_CONTENT).empty();
+ // Make sure all tabs are inside the tab content element.
+ form.find('[role="tabpanel"]').wrapAll(tabContent);
+ };
+
+ /**
+ * Make sure all of the tabs have a cancel button in their fotter to sit along
+ * side the submit button.
+ *
+ * @method moveCancelButtonToTabs
+ * @param {jquey} form The form element.
+ */
+ ModalAddRandomQuestion.prototype.moveCancelButtonToTabs = function(form) {
+ var cancelButton = form.find(SELECTORS.CANCEL_BUTTON_ELEMENT).addClass('m-l-1');
+ var tabFooters = form.find('[data-region="footer"]');
+ // Remove the buttons container element.
+ cancelButton.closest(SELECTORS.BUTTON_CONTAINER).remove();
+ cancelButton.clone().appendTo(tabFooters);
+ };
+
+ /**
+ * Load the add random question form in a fragement and perform some transformation
+ * on the HTML to convert it into tabs for rendering in the modal.
+ *
+ * @method loadForm
+ * @return {promise} Resolved with form HTML and JS.
+ */
+ ModalAddRandomQuestion.prototype.loadForm = function() {
+ return Fragment.loadFragment(
+ 'mod_quiz',
+ 'add_random_question_form',
+ this.getContextId(),
+ {
+ addonpage: this.getAddOnPageId(),
+ cat: this.getCategory(),
+ returnurl: this.getReturnUrl(),
+ cmid: this.getCMID()
+ }
+ )
+ .then(function(html, js) {
+ var form = $(html);
+ var existingCategoryFormElement = form.find(SELECTORS.EXISTING_CATEGORY_FORM_ELEMENT);
+ var existingCategoryTab = this.getBody().find(SELECTORS.EXISTING_CATEGORY_CONTAINER);
+ var newCategoryFormElement = form.find(SELECTORS.NEW_CATEGORY_FORM_ELEMENT);
+ var newCategoryTab = this.getBody().find(SELECTORS.NEW_CATEGORY_CONTAINER);
+
+ // Transform the form into tabs for better rendering in the modal.
+ this.moveFormElementIntoTab(existingCategoryFormElement, existingCategoryTab);
+ this.moveFormElementIntoTab(newCategoryFormElement, newCategoryTab);
+ this.moveTabsIntoTabContent(form);
+ this.moveCancelButtonToTabs(form);
+
+ Templates.replaceNode(this.getBody().find(SELECTORS.TAB_CONTENT), form, js);
+ return;
+ }.bind(this))
+ .then(function() {
+ // Make sure the form change checker is disabled otherwise it'll
+ // stop the user from navigating away from the page once the modal
+ // is hidden.
+ Y.use('moodle-core-formchangechecker', function() {
+ M.core_formchangechecker.reset_form_dirty_state();
+ });
+ return;
+ })
+ .fail(Notification.exception);
+ };
+
+ /**
+ * Override the modal show function to load the form when this modal is first
+ * shown.
+ *
+ * @method show
+ */
+ ModalAddRandomQuestion.prototype.show = function() {
+ Modal.prototype.show.call(this);
+
+ if (!this.loadedForm) {
+ this.loadForm();
+ this.loadedForm = true;
+ }
+ };
+
+ // Automatically register with the modal registry the first time this module is
+ // imported so that you can create modals of this type using the modal factory.
+ if (!registered) {
+ ModalRegistry.register(
+ ModalAddRandomQuestion.TYPE,
+ ModalAddRandomQuestion,
+ 'mod_quiz/modal_add_random_question'
+ );
+
+ registered = true;
+ }
+
+ return ModalAddRandomQuestion;
+});
diff --git a/mod/quiz/lang/en/quiz.php b/mod/quiz/lang/en/quiz.php
index fd30f87a72f..80f1f0a7393 100644
--- a/mod/quiz/lang/en/quiz.php
+++ b/mod/quiz/lang/en/quiz.php
@@ -341,6 +341,7 @@ $string['eventreportviewed'] = 'Quiz report viewed';
$string['everynquestions'] = 'Every {$a} questions';
$string['everyquestion'] = 'Every question';
$string['everythingon'] = 'Everything on';
+$string['existingcategory'] = 'Existing category';
$string['exportcategory'] = 'export category';
$string['exporterror'] = 'An error occurred during export processing';
$string['exportingquestions'] = 'Questions are being exported to file';
@@ -509,6 +510,7 @@ $string['navmethod_seq'] = 'Sequential';
$string['navnojswarning'] = 'Warning: these links will not save your answers. Use the next button at the bottom of the page.';
$string['neverallononepage'] = 'Never, all questions on one page';
$string['newattemptfail'] = 'Error: Could not start a new attempt at the quiz';
+$string['newcategory'] = 'New category';
$string['newpage'] = 'New page';
$string['newpage_help'] = 'For longer quizzes it makes sense to stretch the quiz over several pages by limiting the number of questions per page. When adding questions to the quiz, page breaks will automatically be inserted according to this setting. However page breaks may later be moved manually on the editing page.';
$string['newpageevery'] = 'Automatically start a new page';
diff --git a/mod/quiz/lib.php b/mod/quiz/lib.php
index 629edc02f1d..91fdb40c7c4 100644
--- a/mod/quiz/lib.php
+++ b/mod/quiz/lib.php
@@ -2404,7 +2404,7 @@ function mod_quiz_core_calendar_event_timestart_updated(\calendar_event $event,
}
/**
- * Generates the question bank in a fargment output. This allows
+ * Generates the question bank in a fragment output. This allows
* the question bank to be displayed in a modal.
*
* The only expected argument provided in the $args array is
@@ -2443,3 +2443,46 @@ function mod_quiz_output_fragment_quiz_question_bank($args) {
$renderer = $PAGE->get_renderer('mod_quiz', 'edit');
return $renderer->question_bank_contents($questionbank, $pagevars);
}
+
+/**
+ * Generates the add random question in a fragment output. This allows the
+ * form to be rendered in javascript, for example inside a modal.
+ *
+ * The required arguments as keys in the $args array are:
+ * cat {string} The category and category context ids comma separated.
+ * addonpage {int} The page id to add this question to.
+ * returnurl {string} URL to return to after form submission.
+ * cmid {int} The course module id the questions are being added to.
+ *
+ * @param array $args The fragment arguments.
+ * @return string The rendered mform fragment.
+ */
+function mod_quiz_output_fragment_add_random_question_form($args) {
+ global $CFG;
+ require_once($CFG->dirroot . '/mod/quiz/addrandomform.php');
+
+ $contexts = new \question_edit_contexts($args['context']);
+ $formoptions = [
+ 'contexts' => $contexts,
+ 'cat' => $args['cat']
+ ];
+ $formdata = [
+ 'category' => $args['cat'],
+ 'addonpage' => $args['addonpage'],
+ 'returnurl' => $args['returnurl'],
+ 'cmid' => $args['cmid']
+ ];
+
+ $form = new quiz_add_random_form(
+ new \moodle_url('/mod/quiz/addrandom.php'),
+ $formoptions,
+ 'post',
+ '',
+ null,
+ true,
+ $formdata
+ );
+ $form->set_data($formdata);
+
+ return $form->render();
+}
diff --git a/mod/quiz/templates/modal_add_random_question.mustache b/mod/quiz/templates/modal_add_random_question.mustache
new file mode 100644
index 00000000000..8883d1657d9
--- /dev/null
+++ b/mod/quiz/templates/modal_add_random_question.mustache
@@ -0,0 +1,75 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see .
+}}
+{{!
+ @template mod_quiz/modal_add_random_question
+
+ Modal for adding a random question to a quiz activity.
+
+ Classes required for JS:
+ * none
+
+ Data attributes required for JS:
+ * none
+
+ Context variables required for this template:
+ * title A cleaned string (use clean_text()) to display.
+ * body HTML content for the boday
+
+ Example context (json):
+ {
+ "title": "Example modal",
+ "body": "Some example content for the body"
+ }
+}}
+
+{{< core/modal }}
+ {{$body}}
+
+
+
+ {{> core/loading }}
+
+
+
+
+
+
+ {{/body}}
+{{/ core/modal }}
diff --git a/theme/bootstrapbase/templates/mod_quiz/modal_add_random_question.mustache b/theme/bootstrapbase/templates/mod_quiz/modal_add_random_question.mustache
new file mode 100644
index 00000000000..160f8c07c04
--- /dev/null
+++ b/theme/bootstrapbase/templates/mod_quiz/modal_add_random_question.mustache
@@ -0,0 +1,75 @@
+{{!
+ This file is part of Moodle - http://moodle.org/
+
+ Moodle is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ Moodle is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with Moodle. If not, see .
+}}
+{{!
+ @template calendar/modal_add_random_question
+
+ Modal for adding a random question to a quiz activity.
+
+ Classes required for JS:
+ * none
+
+ Data attributes required for JS:
+ * none
+
+ Context variables required for this template:
+ * title A cleaned string (use clean_text()) to display.
+ * body HTML content for the boday
+
+ Example context (json):
+ {
+ "title": "Example modal",
+ "body": "Some example content for the body"
+ }
+}}
+
+{{< core/modal }}
+ {{$body}}
+
+
+
+ {{> core/loading }}
+
+
+
+
+
+
+ {{/body}}
+{{/ core/modal }}