";
-
- if ($reordertool) {
- echo '';
- }
-}
-
-/**
- * Print all the controls for adding questions directly into the
- * specific page in the edit tab of edit.php
- *
- * @param object $quiz The quiz settings.
- * @param moodle_url $pageurl The url of the current page with the parameters required
- * for links returning to the current page, as a moodle_url object
- * @param int $page the current page number.
- * @param bool $hasattempts Indicates whether the quiz has attempts
- * @param object $defaultcategoryobj
- * @param bool $canaddquestion is the user able to add and use questions anywere?
- * @param bool $canaddrandom is the user able to add random questions anywere?
- */
-function quiz_print_pagecontrols($quiz, $pageurl, $page, $hasattempts,
- $defaultcategoryobj, $canaddquestion, $canaddrandom) {
- global $CFG, $OUTPUT;
- static $randombuttoncount = 0;
- $randombuttoncount++;
- echo '
';
-
- // Get the current context.
- $thiscontext = context_course::instance($quiz->course);
- $contexts = new question_edit_contexts($thiscontext);
-
- // Get the default category.
- list($defaultcategoryid) = explode(',', $pageurl->param('cat'));
- if (empty($defaultcategoryid)) {
- $defaultcategoryid = $defaultcategoryobj->id;
- }
-
- if ($canaddquestion) {
- // Create the url the question page will return to.
- $returnurladdtoquiz = new moodle_url($pageurl, array('addonpage' => $page));
-
- // Print a button linking to the choose question type page.
- $returnurladdtoquiz = $returnurladdtoquiz->out_as_local_url(false);
- $newquestionparams = array('returnurl' => $returnurladdtoquiz,
- 'cmid' => $quiz->cmid, 'appendqnumstring' => 'addquestion');
- create_new_question_button($defaultcategoryid, $newquestionparams,
- get_string('addaquestion', 'quiz'),
- get_string('createquestionandadd', 'quiz'), $hasattempts);
- }
-
- if ($hasattempts) {
- $disabled = 'disabled="disabled"';
- } else {
- $disabled = '';
- }
- if ($canaddrandom) {
- ?>
-
";
-}
-
-/**
- * Print a given single question in quiz for the edit tab of edit.php.
- * Meant to be used from quiz_print_question_list()
- *
- * @param object $question A question object from the database questions table
- * @param object $returnurl The url to get back to this page, for example after editing.
- * @param object $quiz The quiz in the context of which the question is being displayed
- */
-function quiz_print_singlequestion($question, $returnurl, $quiz) {
- echo '
\n";
-}
-/**
- * Print a given random question in quiz for the edit tab of edit.php.
- * Meant to be used from quiz_print_question_list()
- *
- * @param object $question A question object from the database questions table
- * @param object $questionurl The url of the question editing page as a moodle_url object
- * @param object $quiz The quiz in the context of which the question is being displayed
- * @param bool $quiz_qbanktool Indicate to this function if the question bank window open
- */
-function quiz_print_randomquestion($question, $pageurl, $quiz, $quiz_qbanktool) {
- global $DB, $OUTPUT;
- echo '
';
-}
-
-/**
- * Print a given single question in quiz for the reordertool tab of edit.php.
- * Meant to be used from quiz_print_question_list()
- *
- * @param object $question A question object from the database questions table
- * @param object $questionurl The url of the question editing page as a moodle_url object
- * @param object $quiz The quiz in the context of which the question is being displayed
- */
-function quiz_print_singlequestion_reordertool($question, $returnurl, $quiz) {
- echo '
\n";
-}
-
-/**
- * Print a given random question in quiz for the reordertool tab of edit.php.
- * Meant to be used from quiz_print_question_list()
- *
- * @param object $question A question object from the database questions table
- * @param object $questionurl The url of the question editing page as a moodle_url object
- * @param object $quiz The quiz in the context of which the question is being displayed
- */
-function quiz_print_randomquestion_reordertool($question, $pageurl, $quiz) {
- global $DB, $OUTPUT;
-
- // Load the category, and the number of available questions in it.
- if (!$category = $DB->get_record('question_categories', array('id' => $question->category))) {
- echo $OUTPUT->notification('Random question category not found!');
- return;
- }
- $questioncount = count(question_bank::get_qtype(
- 'random')->get_available_questions_from_category(
- $category->id, $question->questiontext == '1', '0'));
-
- $reordercheckboxlabel = '';
-
- echo '
';
-}
-
-/**
- * Print an icon to indicate the 'include subcategories' state of a random question.
- * @param $question the random question.
- */
-function print_random_option_icon($question) {
- global $OUTPUT;
- if (!empty($question->questiontext)) {
- $icon = 'withsubcat';
- $tooltip = get_string('randomwithsubcat', 'quiz');
- } else {
- $icon = 'nosubcat';
- $tooltip = get_string('randomnosubcat', 'quiz');
- }
- echo '';
-}
-
-/**
- * Creates a textual representation of a question for display.
- *
- * @param object $question A question object from the database questions table
- * @param bool $showicon If true, show the question's icon with the question. False by default.
- * @param bool $showquestiontext If true (default), show question text after question name.
- * If false, show only question name.
- * @param bool $return If true (default), return the output. If false, print it.
- */
-function quiz_question_tostring($question, $showicon = false,
- $showquestiontext = true, $return = true) {
- global $COURSE;
- $result = '';
- $result .= '';
- if ($showicon) {
- $result .= print_question_icon($question, true);
- echo ' ';
- }
- $result .= shorten_text(format_string($question->name), 200) . '';
- if ($showquestiontext) {
- $questiontext = question_utils::to_plain_text($question->questiontext,
- $question->questiontextformat, array('noclean' => true, 'para' => false));
- $questiontext = shorten_text($questiontext, 200);
- $result .= '';
- if (!empty($questiontext)) {
- $result .= s($questiontext);
- } else {
- $result .= '';
- $result .= get_string('questiontextisempty', 'quiz');
- $result .= '';
- }
- $result .= '';
- }
- if ($return) {
- return $result;
- } else {
- echo $result;
- }
-}
/**
* A column type for the add this question to the quiz.
@@ -1051,13 +224,7 @@ class question_bank_add_to_quiz_action_column extends question_bank_action_colum
if (!question_has_capability_on($question, 'use')) {
return;
}
- // For RTL languages: switch right and left arrows.
- if (right_to_left()) {
- $movearrow = 't/removeright';
- } else {
- $movearrow = 't/moveleft';
- }
- $this->print_icon($movearrow, $this->stradd, $this->qbank->add_to_quiz_url($question->id));
+ $this->print_icon('t/add', $this->stradd, $this->qbank->add_to_quiz_url($question->id));
}
public function get_required_fields() {
@@ -1082,7 +249,7 @@ class question_bank_question_name_text_column extends question_bank_question_nam
if ($labelfor) {
echo '';
}
@@ -1128,7 +295,7 @@ class quiz_question_bank_view extends core_question\bank\view {
if (empty($CFG->quizquestionbankcolumns)) {
$quizquestionbankcolumns = array('add_to_quiz_action_column', 'checkbox_column', 'question_type_column',
- 'question_name_column', 'edit_action_column', 'preview_action_column');
+ 'question_name_column', 'preview_action_column');
} else {
$quizquestionbankcolumns = explode(',', $CFG->quizquestionbankcolumns);
}
@@ -1184,30 +351,51 @@ class quiz_question_bank_view extends core_question\bank\view {
return new moodle_url('/mod/quiz/edit.php', $params);
}
- public function display($tabname, $page, $perpage, $cat,
- $recurse, $showhidden, $showquestiontext) {
- global $OUTPUT;
- if ($this->process_actions_needing_ui()) {
- return;
+ /**
+ * Renders the html question bank (same as display, but returns the result).
+ *
+ * Note that you can only output this rendered result once per page, as
+ * it contains IDs which must be unique.
+ *
+ * @return string HTML code for the form
+ */
+ public function render($tabname, $page, $perpage, $cat, $recurse, $showhidden, $showquestiontext) {
+ ob_start();
+ $this->display($tabname, $page, $perpage, $cat, $recurse, $showhidden, $showquestiontext);
+ $out = ob_get_contents();
+ ob_end_clean();
+ return $out;
+ }
+
+ /**
+ * Display the controls at the bottom of the list of questions.
+ * @param int $totalnumber Total number of questions that might be shown (if it was not for paging).
+ * @param bool $recurse Whether to include subcategories.
+ * @param stdClass $category The question_category row from the database.
+ * @param context $catcontext The context of the category being displayed.
+ * @param array $addcontexts contexts where the user is allowed to add new questions.
+ */
+ protected function display_bottom_controls($totalnumber, $recurse, $category, \context $catcontext, array $addcontexts) {
+ $cmoptions = new \stdClass();
+ $cmoptions->hasattempts = !empty($this->quizhasattempts);
+
+ $canuseall = has_capability('moodle/question:useall', $catcontext);
+
+ echo '
';
}
-}
-/**
- * Prints the form for setting a quiz' overall grade
- *
- * @param object $quiz The quiz object of the quiz in question
- * @param object $pageurl The url of the current page with the parameters required
- * for links returning to the current page, as a moodle_url object
- * @param int $tabindex The tabindex to start from for the form elements created
- * @return int The tabindex from which the calling page can continue, that is,
- * the last value used +1.
- */
-function quiz_print_grading_form($quiz, $pageurl, $tabindex) {
- global $OUTPUT;
- $strsave = get_string('save', 'quiz');
- echo '
';
- echo '';
- echo "
\n";
- return $tabindex + 1;
-}
-
-/**
- * Print the status bar
- *
- * @param object $quiz The quiz object of the quiz in question
- */
-function quiz_print_status_bar($quiz) {
- global $DB;
-
- $bits = array();
-
- $bits[] = html_writer::tag('span',
- get_string('totalmarksx', 'quiz', quiz_format_grade($quiz, $quiz->sumgrades)),
- array('class' => 'totalpoints'));
-
- $bits[] = html_writer::tag('span',
- get_string('numquestionsx', 'quiz', $DB->count_records('quiz_slots', array('quizid' => $quiz->id))),
- array('class' => 'numberofquestions'));
-
- $timenow = time();
-
- // Exact open and close dates for the tool-tip.
- $dates = array();
- if ($quiz->timeopen > 0) {
- if ($timenow > $quiz->timeopen) {
- $dates[] = get_string('quizopenedon', 'quiz', userdate($quiz->timeopen));
- } else {
- $dates[] = get_string('quizwillopen', 'quiz', userdate($quiz->timeopen));
- }
+ protected function create_new_question_form($category, $canadd) {
+ // Don't display this.
}
- if ($quiz->timeclose > 0) {
- if ($timenow > $quiz->timeclose) {
- $dates[] = get_string('quizclosed', 'quiz', userdate($quiz->timeclose));
- } else {
- $dates[] = get_string('quizcloseson', 'quiz', userdate($quiz->timeclose));
- }
- }
- if (empty($dates)) {
- $dates[] = get_string('alwaysavailable', 'quiz');
- }
- $tooltip = implode(', ', $dates);
-
- // Brief summary on the page.
- if ($timenow < $quiz->timeopen) {
- $currentstatus = get_string('quizisclosedwillopen', 'quiz',
- userdate($quiz->timeopen, get_string('strftimedatetimeshort', 'langconfig')));
- } else if ($quiz->timeclose && $timenow <= $quiz->timeclose) {
- $currentstatus = get_string('quizisopenwillclose', 'quiz',
- userdate($quiz->timeclose, get_string('strftimedatetimeshort', 'langconfig')));
- } else if ($quiz->timeclose && $timenow > $quiz->timeclose) {
- $currentstatus = get_string('quizisclosed', 'quiz');
- } else {
- $currentstatus = get_string('quizisopen', 'quiz');
- }
-
- $bits[] = html_writer::tag('span', $currentstatus,
- array('class' => 'quizopeningstatus', 'title' => implode(', ', $dates)));
-
- echo html_writer::tag('div', implode(' | ', $bits), array('class' => 'statusbar'));
}
diff --git a/mod/quiz/lang/en/quiz.php b/mod/quiz/lang/en/quiz.php
index 4ecc84b680c..76143fb06ac 100644
--- a/mod/quiz/lang/en/quiz.php
+++ b/mod/quiz/lang/en/quiz.php
@@ -27,10 +27,12 @@ $string['action'] = 'Action';
$string['activityoverview'] = 'You have quizzes that are due';
$string['adaptive'] = 'Adaptive mode';
$string['adaptive_help'] = 'If enabled, multiple responses to a question are allowed within the same attempt at the quiz. So for example if a response is marked as incorrect, the student will be allowed to try again immediately. However, depending on the "Apply penalties" setting, a penalty will usually be subtracted for each wrong attempt.';
-$string['addaquestion'] = 'Add a question ...';
-$string['addarandomquestion'] = 'Add a random question ...';
+$string['add'] = 'Add';
+$string['addaquestion'] = 'a new question';
+$string['addarandomquestion'] = 'a random question';
$string['addarandomquestion_help'] = 'When a random question is added, it results in a randomly-chosen question from the category being inserted into the quiz. This means that different students are likely to get a different selection of questions, and when a quiz allows multiple attempts then each attempt is likely to contain a new selection of questions.';
-$string['adddescriptionlabel'] = 'Add description/label';
+$string['addarandomselectedquestion'] = 'Add a random selected question ...';
+$string['adddescriptionlabel'] = 'Add a description item';
$string['addingquestion'] = 'Adding a question';
$string['addingquestions'] = '
This side of the page is where you manage your database of questions. Questions are stored in categories to help you keep them organised, and can be used by any quiz in your course or even other courses if you choose to \'publish\' them.
After you select or create a question category you will be able to create or edit questions. You can select any of these questions to add to your quiz over on the other side of this page.
';
@@ -41,15 +43,18 @@ $string['addnewquestionsqbank'] = 'Add questions to the category {$a->catname}:
$string['addnewuseroverride'] = 'Add user override';
$string['addpagehere'] = 'Add page here';
$string['addquestion'] = 'Add question';
+$string['addquestionfrombanktopage'] = 'Add from the question bank to page {$a}';
$string['addquestions'] = 'Add questions';
$string['addquestionstoquiz'] = 'Add questions to current quiz';
$string['addrandom'] = 'Add {$a} random questions';
$string['addrandomfromcategory'] = 'Add random questions from category:';
$string['addrandomquestion'] = 'Add random question';
$string['addarandomquestion_help'] = 'When a random question is added, it results in a randomly-chosen question from the category being inserted into the quiz. This means that different students are likely to get a different selection of questions, and when a quiz allows multiple attempts then each attempt is likely to contain a new selection of questions.';
+$string['addrandomquestiontopage'] = 'Add a random question to page {$a}';
$string['addrandomquestiontoquiz'] = 'Add a random question to quiz {$a}';
$string['addrandom1'] = '<< Add';
$string['addrandom2'] = 'random questions';
+$string['addselectedquestionstoquiz'] = 'Add selected questions to the quiz';
$string['addselectedtoquiz'] = 'Add selected to quiz';
$string['addtoquiz'] = 'Add to quiz';
$string['affectedstudents'] = 'Affected {$a}';
@@ -183,6 +188,7 @@ $string['configtimelimit'] = 'Default time limit for quizzes in minutes. 0 mean
$string['configtimelimitsec'] = 'Default time limit for quizzes in seconds. 0 mean no time limit.';
$string['configurerandomquestion'] = 'Configure question';
$string['confirmclose'] = 'Once you submit, you will no longer be able to change your answers for this attempt.';
+$string['confirmremovequestion'] = 'Are you sure you want to remove this {$a} question?';
$string['confirmserverdelete'] = 'Are you sure you want to remove the server {$a} from the list?';
$string['confirmstartattemptlimit'] = 'Number of attempts allowed: {$a}. You are about to start a new attempt. Do you wish to proceed?';
$string['confirmstartattempttimelimit'] = 'This quiz has a time limit and is limited to {$a} attempt(s). You are about to start a new attempt. Do you wish to proceed?';
@@ -244,6 +250,8 @@ $string['displayoptions'] = 'Display options';
$string['donotuseautosave'] = 'Do not use auto-save';
$string['download'] = 'Click to download the exported category file';
$string['downloadextra'] = '(file is also stored in the course files in the /backupdata/quiz folder)';
+$string['dragtoafter'] = 'After {$a}';
+$string['dragtostart'] = 'To the start';
$string['duplicateresponse'] = 'This submission has been ignored because you gave an equivalent answer earlier.';
$string['eachattemptbuildsonthelast'] = 'Each attempt builds on the last';
$string['eachattemptbuildsonthelast_help'] = 'If multiple attempts are allowed and this setting is enabled, each new quiz attempt will contain the results of the previous attempt. This allows a quiz to be completed over several attempts.';
@@ -259,8 +267,10 @@ $string['editingquiz_help'] = 'When creating a quiz, the main concepts are:
* Random questions - A student gets different questions each time they attempt the quiz and different students can get different questions';
$string['editingquiz_link'] = 'mod/quiz/edit';
$string['editingquizx'] = 'Editing quiz: {$a}';
+$string['editmaxmark'] = 'Edit maximum mark';
$string['editoverride'] = 'Edit override';
$string['editqcats'] = 'Edit questions categories';
+$string['editquestion'] = 'Edit question';
$string['editquestions'] = 'Edit questions';
$string['editquiz'] = 'Edit quiz';
$string['editquizquestions'] = 'Edit quiz questions';
@@ -421,6 +431,7 @@ $string['invalidquizid'] = 'Invalid quiz ID';
$string['invalidsource'] = 'The source is not accepted as valid.';
$string['invalidsourcetype'] = 'Invalid source type.';
$string['invalidstateid'] = 'Invalid state id';
+$string['joinpages'] = 'Remove page break';
$string['lastanswer'] = 'Your last answer was';
$string['layout'] = 'Layout';
$string['layoutasshown'] = 'Page layout as shown.';
@@ -444,6 +455,7 @@ $string['messageprovider:attempt_overdue'] = 'Warning when your quiz attempt bec
$string['messageprovider:confirmation'] = 'Confirmation of your own quiz submissions';
$string['messageprovider:submission'] = 'Notification of quiz submissions';
$string['max'] = 'Max';
+$string['maxmark'] = 'Maximum mark';
$string['min'] = 'Min';
$string['minutes'] = 'Minutes';
$string['missingcorrectanswer'] = 'Correct answer must be specified';
@@ -563,6 +575,7 @@ $string['overridegroupeventname'] = '{$a->quiz} - {$a->group}';
$string['overrides'] = 'Overrides';
$string['overrideuser'] = 'Override user';
$string['overrideusereventname'] = '{$a->quiz} - Override';
+$string['pageshort'] = 'P';
$string['page-mod-quiz-x'] = 'Any quiz module page';
$string['page-mod-quiz-attempt'] = 'Attempt quiz page';
$string['page-mod-quiz-edit'] = 'Edit quiz page';
@@ -598,7 +611,7 @@ $string['qbrief'] = 'Q. {$a}';
$string['qti'] = 'IMS QTI format';
$string['qtypename'] = 'type, name';
$string['question'] = 'Question';
-$string['questionbankcontents'] = 'Question bank contents';
+$string['questionbank'] = 'from question bank';
$string['questionbankmanagement'] = 'Question bank management';
$string['questionbehaviour'] = 'Question behaviour';
$string['questioncats'] = 'Question categories';
@@ -663,6 +676,7 @@ $string['random'] = 'Random question';
$string['randomcreate'] = 'Create random questions';
$string['randomfromcategory'] = 'Random question from category:';
$string['randomfromexistingcategory'] = 'Random question from an existing category';
+$string['randomnumber'] = 'Number of random questions';
$string['randomnosubcat'] = 'Questions from this category only, not its subcategories.';
$string['randomquestionusinganewcategory'] = 'Random question using a new category';
$string['randomwithsubcat'] = 'Questions from this category and its subcategories.';
@@ -770,6 +784,7 @@ $string['savingnewgradeforquestion'] = 'Saving new grade for question id {$a}.';
$string['savingnewmaximumgrade'] = 'Saving new maximum grade.';
$string['score'] = 'Raw score';
$string['scores'] = 'Scores';
+$string['seequestions'] = '(See questions)';
$string['select'] = 'Select';
$string['selectall'] = 'Select all';
$string['selectcategory'] = 'Select category';
@@ -818,6 +833,7 @@ $string['sortsubmit'] = 'Sort questions';
$string['sorttypealpha'] = 'Sort by type, name';
$string['specificapathnotonquestion'] = 'The specified file path is not on the specified question';
$string['specificquestionnotonquiz'] = 'Specified question is not on the specified quiz';
+$string['splitpages'] = 'Add page break';
$string['startagain'] = 'Start again';
$string['startattempt'] = 'Start attempt';
$string['startedon'] = 'Started on';
diff --git a/mod/quiz/lib.php b/mod/quiz/lib.php
index b04abf06079..3e5b57bff39 100644
--- a/mod/quiz/lib.php
+++ b/mod/quiz/lib.php
@@ -634,21 +634,32 @@ function quiz_format_grade($quiz, $grade) {
}
/**
- * Round a grade to to the correct number of decimal places, and format it for display.
+ * Determine the correct number of decimal places required to format a grade.
+ *
+ * @param object $quiz The quiz table row, only $quiz->decimalpoints is used.
+ * @return integer
+ */
+function quiz_get_grade_format($quiz) {
+ if (empty($quiz->questiondecimalpoints)) {
+ $quiz->questiondecimalpoints = -1;
+ }
+
+ if ($quiz->questiondecimalpoints == -1) {
+ return $quiz->decimalpoints;
+ }
+
+ return $quiz->questiondecimalpoints;
+}
+
+/**
+ * Round a grade to the correct number of decimal places, and format it for display.
*
* @param object $quiz The quiz table row, only $quiz->decimalpoints is used.
* @param float $grade The grade to round.
* @return float
*/
function quiz_format_question_grade($quiz, $grade) {
- if (empty($quiz->questiondecimalpoints)) {
- $quiz->questiondecimalpoints = -1;
- }
- if ($quiz->questiondecimalpoints == -1) {
- return format_float($grade, $quiz->decimalpoints);
- } else {
- return format_float($grade, $quiz->questiondecimalpoints);
- }
+ return format_float($grade, quiz_get_grade_format($quiz));
}
/**
@@ -1791,7 +1802,6 @@ function quiz_get_navigation_options() {
);
}
-
/**
* Obtains the automatic completion state for this quiz on any conditions
* in quiz settings, such as if all attempts are used or a certain grade is achieved.
diff --git a/mod/quiz/locallib.php b/mod/quiz/locallib.php
index ce078fa9abc..993574ab1e0 100644
--- a/mod/quiz/locallib.php
+++ b/mod/quiz/locallib.php
@@ -1119,6 +1119,20 @@ function quiz_get_user_image_options() {
);
}
+/**
+ * Get the choices to offer for the 'Questions per page' option.
+ * @return array int => string.
+ */
+function quiz_questions_per_page_options() {
+ $pageoptions = array();
+ $pageoptions[0] = get_string('neverallononepage', 'quiz');
+ $pageoptions[1] = get_string('everyquestion', 'quiz');
+ for ($i = 2; $i <= QUIZ_MAX_QPP_OPTION; ++$i) {
+ $pageoptions[$i] = get_string('everynquestions', 'quiz', $i);
+ }
+ return $pageoptions;
+}
+
/**
* Get the human-readable name for a quiz attempt state.
* @param string $state one of the state constants like {@link quiz_attempt::IN_PROGRESS}.
@@ -1231,27 +1245,12 @@ function quiz_question_preview_url($quiz, $question) {
* @return the HTML for a preview question icon.
*/
function quiz_question_preview_button($quiz, $question, $label = false) {
- global $CFG, $OUTPUT;
+ global $PAGE;
if (!question_has_capability_on($question, 'use', $question->category)) {
return '';
}
- $url = quiz_question_preview_url($quiz, $question);
-
- // Do we want a label?
- $strpreviewlabel = '';
- if ($label) {
- $strpreviewlabel = get_string('preview', 'quiz');
- }
-
- // Build the icon.
- $strpreviewquestion = get_string('previewquestion', 'quiz');
- $image = $OUTPUT->pix_icon('t/preview', $strpreviewquestion);
-
- $action = new popup_action('click', $url, 'questionpreview',
- question_preview_popup_params());
-
- return $OUTPUT->action_link($url, $image, $action, array('title' => $strpreviewquestion));
+ return $PAGE->get_renderer('mod_quiz', 'edit')->question_preview_icon($quiz, $question, $label);
}
/**
@@ -1876,3 +1875,33 @@ class qubaids_for_quiz extends qubaid_join {
parent::__construct('{quiz_attempts} quiza', 'quiza.uniqueid', $where, $params);
}
}
+
+/**
+ * Creates a textual representation of a question for display.
+ *
+ * @param object $question A question object from the database questions table
+ * @param bool $showicon If true, show the question's icon with the question. False by default.
+ * @param bool $showquestiontext If true (default), show question text after question name.
+ * If false, show only question name.
+ * @return string
+ */
+function quiz_question_tostring($question, $showicon = false, $showquestiontext = true) {
+ $result = '';
+
+ $name = shorten_text(format_string($question->name), 200);
+ if ($showicon) {
+ $name .= print_question_icon($question) . ' ' . $name;
+ }
+ $result .= html_writer::span($name, 'questionname');
+
+ if ($showquestiontext) {
+ $questiontext = question_utils::to_plain_text($question->questiontext,
+ $question->questiontextformat, array('noclean' => true, 'para' => false));
+ $questiontext = shorten_text($questiontext, 200);
+ if ($questiontext) {
+ $result .= ' ' . html_writer::span(s($questiontext), 'questiontext');
+ }
+ }
+
+ return $result;
+}
diff --git a/mod/quiz/mod_form.php b/mod/quiz/mod_form.php
index 6c8c1f53431..a4bee79e84a 100644
--- a/mod/quiz/mod_form.php
+++ b/mod/quiz/mod_form.php
@@ -156,33 +156,15 @@ class mod_quiz_mod_form extends moodleform_mod {
$mform->setAdvanced('shufflequestions', $quizconfig->shufflequestions_adv);
$mform->setDefault('shufflequestions', $quizconfig->shufflequestions);
- // Questions per page.
- $pageoptions = array();
- $pageoptions[0] = get_string('neverallononepage', 'quiz');
- $pageoptions[1] = get_string('everyquestion', 'quiz');
- for ($i = 2; $i <= QUIZ_MAX_QPP_OPTION; ++$i) {
- $pageoptions[$i] = get_string('everynquestions', 'quiz', $i);
- }
-
$pagegroup = array();
$pagegroup[] = $mform->createElement('select', 'questionsperpage',
- get_string('newpage', 'quiz'), $pageoptions, array('id' => 'id_questionsperpage'));
+ get_string('newpage', 'quiz'), quiz_questions_per_page_options(), array('id' => 'id_questionsperpage'));
$mform->setDefault('questionsperpage', $quizconfig->questionsperpage);
if (!empty($this->_cm)) {
$pagegroup[] = $mform->createElement('checkbox', 'repaginatenow', '',
get_string('repaginatenow', 'quiz'), array('id' => 'id_repaginatenow'));
$mform->disabledIf('repaginatenow', 'shufflequestions', 'eq', 1);
-
- $PAGE->requires->js('/question/qengine.js');
- $module = array(
- 'name' => 'mod_quiz_edit',
- 'fullpath' => '/mod/quiz/edit.js',
- 'requires' => array('yui2-dom', 'yui2-event', 'yui2-container'),
- 'strings' => array(),
- 'async' => false,
- );
- $PAGE->requires->js_init_call('quiz_settings_init', null, false, $module);
}
$mform->addGroup($pagegroup, 'questionsperpagegrp',
@@ -408,6 +390,8 @@ class mod_quiz_mod_form extends moodleform_mod {
// -------------------------------------------------------------------------------
$this->add_action_buttons();
+
+ $PAGE->requires->yui_module('moodle-mod_quiz-modform', 'M.mod_quiz.modform.init');
}
protected function add_review_options_group($mform, $quizconfig, $whenname,
diff --git a/mod/quiz/questionbank.ajax.php b/mod/quiz/questionbank.ajax.php
new file mode 100644
index 00000000000..405593816c1
--- /dev/null
+++ b/mod/quiz/questionbank.ajax.php
@@ -0,0 +1,49 @@
+.
+
+
+/**
+ * Ajax script to update the contents of the question bank dialogue.
+ *
+ * @package mod_quiz
+ * @copyright 2014 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+define('AJAX_SCRIPT', true);
+
+
+require_once('../../config.php');
+require_once($CFG->dirroot . '/mod/quiz/editlib.php');
+
+list($thispageurl, $contexts, $cmid, $cm, $quiz, $pagevars) =
+ question_edit_setup('editq', '/mod/quiz/edit.php', true);
+
+// Get the course object and related bits.
+$course = $DB->get_record('course', array('id' => $quiz->course), '*', MUST_EXIST);
+require_capability('mod/quiz:manage', $contexts->lowest());
+
+// Create quiz question bank view.
+$questionbank = new quiz_question_bank_view($contexts, $thispageurl, $course, $cm, $quiz);
+$questionbank->set_quiz_has_attempts(quiz_has_attempts($quiz->id));
+
+// Output.
+$output = $PAGE->get_renderer('mod_quiz', 'edit');
+$contents = $output->question_bank_contents($questionbank, $pagevars);
+echo json_encode(array(
+ 'status' => 'OK',
+ 'contents' => $contents,
+));
diff --git a/mod/quiz/renderer.php b/mod/quiz/renderer.php
index e1ef6fa2ea8..1918c33302d 100644
--- a/mod/quiz/renderer.php
+++ b/mod/quiz/renderer.php
@@ -1174,11 +1174,13 @@ class mod_quiz_renderer extends plugin_renderer_base {
$options = array('filter' => false, 'newlines' => false);
$warning = format_text(get_string('connectionerror', 'quiz'), FORMAT_MARKDOWN, $options);
$ok = format_text(get_string('connectionok', 'quiz'), FORMAT_MARKDOWN, $options);
- return html_writer::tag('div', $warning, array('id' => 'connection-error', 'style' => 'display: none;', 'role' => 'alert')) .
- html_writer::tag('div', $ok, array('id' => 'connection-ok', 'style' => 'display: none;', 'role' => 'alert'));
+ return html_writer::tag('div', $warning,
+ array('id' => 'connection-error', 'style' => 'display: none;', 'role' => 'alert')) .
+ html_writer::tag('div', $ok, array('id' => 'connection-ok', 'style' => 'display: none;', 'role' => 'alert'));
}
}
+
class mod_quiz_links_to_other_attempts implements renderable {
/**
* @var array string attempt number => url, or null for the current attempt.
@@ -1186,6 +1188,7 @@ class mod_quiz_links_to_other_attempts implements renderable {
public $links = array();
}
+
class mod_quiz_view_object {
/** @var array $infomessages of messages with information to display about the quiz. */
public $infomessages;
diff --git a/mod/quiz/repaginate.php b/mod/quiz/repaginate.php
new file mode 100644
index 00000000000..ee46e735182
--- /dev/null
+++ b/mod/quiz/repaginate.php
@@ -0,0 +1,45 @@
+.
+
+/**
+ * Rest endpoint for ajax editing for paging operations on the quiz structure.
+ *
+ * @package mod_quiz
+ * @copyright 2014 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+require_once(__DIR__ . '/../../config.php');
+require_once($CFG->dirroot . '/mod/quiz/locallib.php');
+
+$cmid = required_param('cmid', PARAM_INT);
+$quizid = required_param('quizid', PARAM_INT);
+$slotnumber = required_param('slot', PARAM_INT);
+$repagtype = required_param('repag', PARAM_INT);
+
+require_sesskey();
+$quizobj = quiz::create($quizid);
+require_login($quizobj->get_course(), false, $quizobj->get_cm());
+require_capability('mod/quiz:manage', $quizobj->get_context());
+
+$slotnumber++;
+$repage = new \mod_quiz\repaginate($quizid);
+$repage->repaginate_slots($slotnumber, $repagtype);
+
+$structure = $quizobj->get_structure();
+$slots = $structure->refresh_page_numbers_and_update_db($structure->get_quiz());
+
+redirect(new moodle_url('edit.php', array('cmid' => $quizobj->get_cmid())));
diff --git a/mod/quiz/styles.css b/mod/quiz/styles.css
index 9b2b74f8271..92e752fb2ce 100644
--- a/mod/quiz/styles.css
+++ b/mod/quiz/styles.css
@@ -230,40 +230,6 @@ body.jsenabled .questionflagcheckbox {
text-align: left;
margin-left: 0;
}
-
-#page-mod-quiz-edit div.question div.content .questiontext,
-#categoryquestions .questiontext {
- text-overflow: ellipsis;
- position: relative;
- zoom: 1;
- padding-left: 0.3em;
- max-width: 40%;
- overflow: hidden;
- white-space: nowrap;
- text-overflow: ellipsis;
-}
-
-#page-mod-quiz-edit div.question div.content .questionname,
-#categoryquestions .questionname {
- white-space: nowrap;
- overflow: hidden;
- zoom: 1;
- position: relative;
- max-width: 20%;
-}
-
-#page-mod-quiz-edit div.editq div.question div.content .singlequestion a .questionname,
-div.editq div.question div.content .singlequestion a .questiontext {
- text-decoration: underline;
-}
-
-#page-mod-quiz-edit.ie6 div.question div.content .questiontext {
- width: 50%;
-}
-#page-mod-quiz-edit.ie6 div.question div.content .questionname {
- width: 20%;
-}
-
#page-mod-quiz-mod #id_reviewoptionshdr .fitem {
float: left;
width: 23%;
@@ -370,6 +336,7 @@ body.path-mod-quiz table tbody tr.gradedattempt > td {
margin-left: 0;
margin-right: 0;
}
+
/** Mod quiz summary **/
#page-mod-quiz-summary #content {
text-align: center;
@@ -535,6 +502,236 @@ table.quizreviewsummary td.cell {
clear: left;
}
+#page-mod-quiz-edit .statusbar {
+ margin: 0.6em 0.4em;
+}
+#page-mod-quiz-edit .statusdisplay {
+ background-color: #ffc;
+ clear: both;
+ margin: 0.3em 1em 0.3em 0;
+ padding: 1px ;
+ /* Stop margin collapse. */
+}
+#page-mod-quiz-edit .statusdisplay p {
+ margin: 0.4em;
+}
+
+#page-mod-quiz-edit .maxgrade,
+#page-mod-quiz-edit .totalpoints {
+ display: block;
+ float: right;
+ margin: -2.5em 1em 0em 1em;
+ padding: .2em;
+}
+#page-mod-quiz-edit .maxgrade label {
+ display: inline;
+}
+
+#page-mod-quiz-edit li.activity > div,
+#page-mod-quiz-edit li.pagenumber {
+ position: relative;
+}
+
+#page-mod-quiz-edit .last-add-menu {
+ position: relative;
+ height: 1.5em;
+}
+#page-mod-quiz-edit .add-menu-outer {
+ position: absolute;
+ top: 0;
+ right: 0;
+}
+#page-mod-quiz-edit .add-menu {
+ max-height: 20px;
+ width:10em;
+}
+#page-mod-quiz-edit .slotnumber {
+ background-color: #D3D3D3;
+ text-align: center;
+ margin-right: 0.5em;
+ margin-top: 0.1em;
+ margin-bottom: .1em;
+ min-width: 2em;
+ display: inline-block;
+ float: left;
+}
+
+#page-mod-quiz-edit ul.slots li.section {
+ border: 0;
+}
+#page-mod-quiz-edit ul.slots li.section .content {
+ background-color:#FAFAFA;
+ padding:5px 10px;
+}
+#page-mod-quiz-edit ul.slots li.section .content h3 {
+ margin:0;
+ color:#777;
+ font-weight: normal;
+}
+#page-mod-quiz-edit ul.slots li.section .left {
+ padding:4px 0;
+}
+#page-mod-quiz-edit ul.slots li.section .right {
+ padding:4px 0;
+}
+#page-mod-quiz-edit ul.slots {
+ margin:0;
+}
+#page-mod-quiz-edit ul.slots li.section {
+ list-style: none;
+ margin:0 0 5px 0;
+ padding:0;
+}
+#page-mod-quiz-edit ul.slots li.section .left {
+ float:left;
+}
+#page-mod-quiz-edit ul.slots li.section .right {
+ float:right;
+}
+#page-mod-quiz-edit ul.slots li.section .left,
+#page-mod-quiz-edit ul.slots li.section .right {
+ width:40px;
+ text-align:center;
+ padding: 6px 0;
+}
+#page-mod-quiz-edit ul.slots li.section .right img.icon {
+ padding: 0 0 4px 0;
+}
+#page-mod-quiz-edit ul.slots li.section .left .section-handle img.icon {
+ padding:0;
+ vertical-align: baseline;
+}
+#page-mod-quiz-edit ul.slots li.section li.activity {
+ background: #E6E6E6;
+ margin: 3px 0 3px 0;
+ padding: 0.2em 0 0.2em 0.2em;
+}
+#page-mod-quiz-edit ul.slots li.section li.activity.page {
+ background: transparent;
+}
+
+#page-mod-quiz-edit ul.slots li.section li.activity .instancemaxmarkcontainer {
+ background: white;
+ padding: .2em;
+ margin: .4em;
+}
+#page-mod-quiz-edit ul.slots li.section li.activity .instancemaxmarkcontainer form {
+ display: inline;
+}
+#page-mod-quiz-edit ul.slots li.section li.activity .instancemaxmarkcontainer form input {
+ margin: 0;
+ padding: 0.2em;
+ height: 1em;
+}
+
+#page-mod-quiz-edit ul.slots li.section li.activity .instancemaxmark {
+ display: inline-block;
+ text-align: right;
+}
+#page-mod-quiz-edit ul.slots li.section li.activity .page_split_join_wrapper {
+ position: absolute;
+}
+#page-mod-quiz-edit ul.slots li.section li.activity .page_split_join {
+ position: relative;
+ left: -20px;
+ top: -7px;
+}
+
+#page-mod-quiz-edit ul.slots li.section li.activity .instancemaxmark.decimalplaces_0 {
+ min-width: 1.3em;
+}
+#page-mod-quiz-edit ul.slots li.section li.activity .instancemaxmark.decimalplaces_1 {
+ min-width: 2em;
+}
+
+#page-mod-quiz-edit ul.slots li.section li.activity .instancemaxmark.decimalplaces_2 {
+ min-width: 2.6em;
+}
+
+#page-mod-quiz-edit ul.slots li.section li.activity .instancemaxmark.decimalplaces_3 {
+ min-width: 3.2em;
+}
+
+#page-mod-quiz-edit ul.slots li.section li.activity .instancemaxmark.decimalplaces_4 {
+ min-width: 3.7em;
+}
+
+#page-mod-quiz-edit ul.slots li.section li.activity .instancemaxmark.decimalplaces_5 {
+ min-width: 4.3em;
+}
+
+#page-mod-quiz-edit ul.slots li.section li.activity .instancemaxmark.decimalplaces_6 {
+ min-width: 4.8em;
+}
+
+#page-mod-quiz-edit ul.slots li.section li.activity .instancemaxmark.decimalplaces_7 {
+ min-width: 5.45em;
+}
+
+#page-mod-quiz-edit ul.slots li.section li.activity .edit_icon,
+#page-mod-quiz-edit ul.slots li.section li.activity a.preview,
+#page-mod-quiz-edit ul.slots li.section li.activity .editing_delete,
+#page-mod-quiz-edit ul.slots li.section li.activity .editing_maxmark {
+ margin-left: 5px
+}
+
+#page-mod-quiz-edit ul.slots li.section li.activity .activityinstance {
+ display: block;
+ min-height: 1.7em;
+ position: absolute;
+ top: 0;
+ left: 5em;
+ width: 100%;
+}
+
+#page-mod-quiz-edit ul.slots .activityinstance form {
+ display: inline;
+}
+#page-mod-quiz-edit span.editinstructions {
+ right: 0;
+}
+
+#page-mod-quiz-edit ul.slots .activityinstance span.instancename {
+ overflow-x: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ word-break: break-word;
+ width: 70%;
+ display: inline-block;
+ height: 20px;
+}
+
+#page-mod-quiz-edit ul.slots .activityinstance span.instancename img {
+ margin-right: .2em;
+}
+#page-mod-quiz-edit ul.slots li.activity div.activityinstance .questionname {
+ font-weight: bold;
+ color: #555;
+}
+#page-mod-quiz-edit ul.slots li.activity div.activityinstance .questiontext {
+ color: #555;
+}
+#page-mod-quiz-edit ul.slots li.activity div.activityinstance .mod_quiz_random_qbank_link {
+ font-size: 0.8em;
+}
+
+#page-mod-quiz-edit ul.slots .activityinstance img.activityicon {
+ float: left;
+ margin-top: .2em;
+ margin-right: 0;
+}
+
+#page-mod-quiz-edit .section .activity .actions {
+ white-space: nowrap;
+ background: #e6e6e6;
+ padding: .2em 0 .2em .5em;
+}
+
+#page-mod-quiz-edit .mod_quiz_edit_forms {
+ display: none;
+}
+
#categoryquestions > tbody > tr:nth-of-type(even) {
background: #e4e4e4;
}
@@ -573,417 +770,49 @@ table#categoryquestions {
width: 28px;
padding: 0;
}
+#categoryquestions .questiontext {
+ position: relative;
+ zoom: 1;
+ padding-left: 0.3em;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
+#categoryquestions .questionname {
+ white-space: nowrap;
+ overflow: hidden;
+ zoom: 1;
+ position: relative;
+}
#categoryquestions .questiontext p {
margin: 0;
}
-#page-mod-quiz-edit div.quizcontents {
- float: left;
- width: 70%;
- display: block;
- clear: left;
-}
-#page-mod-quiz-edit div.quizwhenbankcollapsed {
- width: 100%;
-}
-#page-mod-quiz-edit div.quizpage {
- display: block;
- clear: both;
- width: 100%;
-}
-#page-mod-quiz-edit div.quizpage span.pagetitle {
- margin-top: 0.3em;
- float: left;
- display: block;
- color: #006;
-}
-#page-mod-quiz-edit div.quizpage .pagecontent {
- margin-top: 0.3em;
- display: block;
- float: left;
- position: relative;
- margin-left: 0.3em;
- margin-right: 0.3em;
- margin-bottom: 0.2em;
- border-left: thin solid #777;
- line-height: 1.3em;
- border-radius: 0.6em;
- border-bottom-left-radius: 0;
- border-top-left-radius: 0;
- width: 88%;
- padding: 0.15em 0 0.3em;
- background-color: #d6d6d6;
-}
-#page-mod-quiz-edit div.quizpage .pagecontent .pagestatus {
- border-bottom-right-radius: 0.3em;
- border-top-right-radius: 0.3em;
- margin: 0.3em;
- padding: 0.1em 0.1em 0.1em 0.3em;
- background-color: #eee;
- font-weight: bold;
-}
-#page-mod-quiz-edit div.quizpage .pagecontent form#addquestion {
- background-color: #fff;
-}
-#page-mod-quiz-edit div.quizpage .pagecontent form.randomquestionform div {
- /* it is a mystery why this has to be inline-table but otherwise the layout gets screwed, even if it is "inline" */display: inline-table;
-}
-#page-mod-quiz-edit div.quizpage .pagecontent form.randomquestionform div input {
- display: inline;
-}
-
-#page-mod-quiz-edit .addpage {
- clear: both;
- padding-top: 0.3em;
- float: right;
- margin-right: 2em;
-}
-#page-mod-quiz-edit .statusdisplay {
- background-color: #ffc;
- clear: both;
- margin: 0.3em 1em 0.3em 0;
- padding: 1px ;
- /* Stop margin collapse. */
-}
-#page-mod-quiz-edit .statusdisplay p {
- margin: 0.4em;
-}
-
-#page-mod-quiz-edit div.reorder .reordercontrols {
- clear: both;
- padding-right: 1em;
- margin-top: 0.5em;
- padding-top: 0.5em;
- padding-bottom: 0.5em
-}
-#page-mod-quiz-edit div.reorder .reordercontrols .moveselectedonpage {
- clear: right;
- float: right;
- padding: 0.5em 0.3em;
- text-align: right;
-}
-#page-mod-quiz-edit div.reorder .reordercontrols .addnewpagesafterselected,
-#page-mod-quiz-edit .repaginatecommand {
- float: right;
- clear: right;
- padding-right: 1em;
-}
-#page-mod-quiz-edit div.reorder .reordercontrols .deleteselected {
- float: right;
- margin-right: 1em;
-}
-#page-mod-quiz-edit div.reorder div.question {
- padding-top: 0.2em;
-}
-#page-mod-quiz-edit div.reorder div.question div.qnum {
- width: 2.9em;
- padding-top: 0.1em;
-}
-#page-mod-quiz-edit .reorder div.question div.content {
- width: 87%;
- float: left;
- position: relative;
- border-radius: 0.3em;
- border-bottom-left-radius: 0;
- border-top-left-radius: 0;
- line-height: 1.2em;
- padding: 0.1em;
- background-color: #F9F9F9;
-}
-#page-mod-quiz-edit .reorder .questioncontentcontainer .quiz_randomquestion {
- position: relative;
-}
-#page-mod-quiz-edit .reorder div.question div.content div.quiz_randomquestion {
- line-height: 1em;
-}
-
-#page-mod-quiz-edit .reorder .questioncontentcontainer {
- overflow: hidden;
- white-space: nowrap;
-}
-#page-mod-quiz-edit .reorder .questioncontentcontainer .randomquestioncategory {
- overflow: hidden;
- white-space: nowrap;
- display: inline;
- float: none;
-}
-#page-mod-quiz-edit .reorder .questioncontentcontainer .randomquestioncategory label {
- max-width: 25%;
- overflow: hidden;
- padding-left: 0.3em;
- white-space: nowrap;
- display: inline-block;
-}
-#page-mod-quiz-edit .reorder .questioncontentcontainer .randomquestionfromcategory label {
- overflow: hidden;
- white-space: nowrap;
- display: inline-block;
-}
-#page-mod-quiz-edit .reorder .questioncontentcontainer .randomquestionfromcategory,
-#page-mod-quiz-edit .reorder div.question div.content .questionpreview {
- display: inline;
- float: none;
-}
-
-#page-mod-quiz-edit .reorder fieldset {
- display: inline;
-}
-#page-mod-quiz-edit div.reorder div.question div.qnum {
- text-align: right;
- font-size: 1em;
-}
-
-#page-mod-quiz-edit .questioncontentcontainer div.randomquestionqlist {
- padding-left: 0.2em;
- padding-right: 0.2em;
- clear: both;
- margin: 0.5em;
- margin-top: 0.8em;
-}
-#page-mod-quiz-edit .questioncontentcontainer div.randomquestionqlist .totalquestionsinrandomqcategory {
- overflow: auto;
- white-space: normal;
-}
-#page-mod-quiz-edit .questioncontentcontainer div.randomquestionqlist ul {
- list-style-type: none;
- margin: 0;
- padding: 0;
-}
-#page-mod-quiz-edit .questioncontentcontainer div.randomquestionqlist ul li {
- clear: left;
- width: 100%;
- overflow: hidden;
- white-space: nowrap;
-}
-#page-mod-quiz-edit .questioncontentcontainer div.randomquestionqlist ul li img {
- padding-right: 0.3em;
-}
-#page-mod-quiz-edit .questioncontentcontainer div.randomquestionqlist ul li span {
- display: inline;
-}
-#page-mod-quiz-edit .questioncontentcontainer a {
- text-decoration: underline;
-}
-#page-mod-quiz-edit .questioncontentcontainer div.singlequestion a {
- text-decoration: underline;
-}
-#page-mod-quiz-edit .questioncontentcontainer .randomquestioncategory {
- font-weight: bold;
-}
-
-#page-mod-quiz-edit div.question {
- clear: left;
- width: 100%;
-}
-#page-mod-quiz-edit div.question div.qnum {
- display: block;
- float: left;
- width: 1.4em;
- padding-right: 0.3em;
- padding-left: 0;
- text-align: right;
- color: #333;
-}
-#page-mod-quiz-edit div.question div.questioncontainer {
- background-color: #ffc;
-}
-#page-mod-quiz-edit div.editq div.question div.content {
- width: 87%;
- float: left;
- position: relative;
- border-radius: 0.6em;
- border-bottom-left-radius: 0;
- border-top-left-radius: 0;
- line-height: 1.4em;
- padding: 0.5em;
-}
-#page-mod-quiz-edit div.question div.content div.points {
- top: 0.5em;
- border-left: 0.4em solid #FFF;
- width: 8.5em;
- padding: 0.2em;
- line-height: 1em;
- max-width: 30%;
- position: absolute;
- right: 60px;
- border-radius: 0.2em;
- border-bottom-left-radius: 0;
- border-top-left-radius: 0;
- display: block;
- margin: 0;
- background-color: #ddf;
-}
-#page-mod-quiz-edit div.question div.content div.points input {
- width: 2em;
- padding: 0;
-}
-#page-mod-quiz-edit div.question div.content div.points input.pointssubmitbutton {
- width: auto;
-}
-#page-mod-quiz-edit div.question div.content div.qorder {
- line-height: 1em;
- max-width: 30%;
- position: absolute;
- right: 60px;
- border-radius: 0.2em;
- border-bottom-left-radius: 0;
- border-top-left-radius: 0;
- display: block;
- margin: 0;
- background-color: #ddf;
-}
-#page-mod-quiz-edit div.question div.content .editicon {
- width: 15px;
-}
-#page-mod-quiz-edit div.question div.content .singlequestion .questionname,
-#page-mod-quiz-edit div.question div.content .singlequestion .questiontext {
- display: inline-block;
-}
-#page-mod-quiz-edit div.question div.content .singlequestion .questionpreview {
- background-color: #eee;
-}
-#page-mod-quiz-edit div.question div.content .questiontype {
- display: block;
- clear: left;
- float: left;
-}
-#page-mod-quiz-edit.dir-rtl div.question div.content .questiontype {
- clear: right;
- float: right;
-}
-#page-mod-quiz-edit div.question div.content .questionpreview {
- display: block;
- float: left;
- margin-left: 0.3em;
- padding-left: 0.2em;
- padding-right: 0.2em;
-}
-#page-mod-quiz-edit div.question div.content .questionpreview a {
- background-color: #eee;
-}
-#page-mod-quiz-edit div.question div.content div.quiz_randomquestion .questionpreview {
- display: inline;
- float: none;
-}
-#page-mod-quiz-edit div.question div.content div.questioncontrols {
- float: right;
- width: 55px;
- position: absolute;
- right: 0.3em;
- top: 0;
- display: block;
- padding: 0.2em;
- background-color: #F9F9F9;
- text-align: right;
-}
-#page-mod-quiz-edit div.question div.content div.questioncontrols img.upwithoutdown {
- padding-right: 12px;
- display: inline;
-}
-#page-mod-quiz-edit div.question div.content .questiontext {
- font-weight: bold;
-}
-#page-mod-quiz-edit div.question div.content .questiontype {
- font-style: italic;
-}
-
-#page-mod-quiz-edit .editq div.question div.qnum {
- padding-top: 0.2em;
-}
-#page-mod-quiz-edit .editq div.question {
- padding-top: 0.3em;
-}
-#page-mod-quiz-edit .editq div.questioncontentcontainer div.singlequestion img {
- float: left;
- padding-top: 0.3em;
- padding-right: 0.3em;
-}
-#page-mod-quiz-edit .editq div.question div.content {
- background-color: #F9F9F9;
-}
-#page-mod-quiz-edit .editq div.question div.content .randomquestioncategory {
- margin-top: 0.4em;
- position: relative;
- display: inline-block;
-}
-#page-mod-quiz-edit .editq div.question div.content .randomquestioncategory a {
- display: block;
- max-width: 15em;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- float: left;
- position: relative;
-}
-#page-mod-quiz-edit .editq div.question div.content .questionpreview {
- float: left;
-}
-#page-mod-quiz-edit .editq div.question div.content .questionpreview a {
- font-weight: normal;
- margin-left: 0em;
- display: inline;
- float: none;
-}
-#page-mod-quiz-edit .editq div.question div.content .randomquestioncategory .questionpreview img {
- padding-right: 0.3em;
-}
-#page-mod-quiz-edit .editq div.question div.content .singlequestion .questioneditbutton .questionname,
-#page-mod-quiz-edit .editq div.question div.content .singlequestion .questioneditbutton .questiontext {
- float: left;
-}
-#page-mod-quiz-edit .reorder div.question div.content .singlequestion.missingtype .questionname,
-#page-mod-quiz-edit .editq div.question div.content .singlequestion.missingtype .questionname {
- font-style: italic;
- max-width: 75%;
-}
-#page-mod-quiz-edit .editq div.question div.description div.content .questiontext {
- max-width: 75%;
-}
-#page-mod-quiz-edit .editq div.question div.qnum {
- font-size: 1.5em;
-}
-
-table#categoryquestions td,
+#page-mod-quiz-edit table#categoryquestions td,
#page-mod-quiz-edit table#categoryquestions th {
overflow: hidden;
white-space: nowrap;
}
+.mod_quiz_qbank_dialogue {
+ width: 80%;
+ min-height: 200px;
+}
+.mod_quiz_qbank_dialogue.moodle-dialogue-fullscreen {
+ width: 100%;
-.questionbankwindow.block {
- float: right;
- width: 30%;
- right: 0.3em;
- padding-bottom: 0.5em;
- display: block;
- border-width: 0;
}
-.questionbankwindow.block .content {
- padding: 0;
-}
-.questionbankwindow .choosecategory,
-.questionbankwindow .createnewquestion {
- padding: 0.3em;
-}
-.questionbankwindow .createnewquestion .singlebutton {
- display: inline;
-}
-.questionbankwindow #catmenu_jump {
- display: block;
+.mod_quiz_qbank_dialogue .questionbankloading {
+ position: absolute;
+ top: 30px;
+ bottom: 0;
+ left: 0;
+ right: 0;
+ background: #fff;
+ text-align: center;
+ opacity: 0.5;
+ padding-top: 50px;
}
-.questionbank div.categoryquestionscontainer,
-.questionbank .categorysortopotionscontainer,
-.questionbank .categorypagingbarcontainer,
-.questionbank .categoryselectallcontainer {
- padding-left: 0.3em;
- padding-right: 0.3em;
-}
-
-.noquestionsincategory {
- clear: both;
- padding-top: 1em;
- padding-bottom: 1em;
-}
.modulespecificbuttonscontainer {
padding-left: 0.3em;
padding-right: 0.3em;
@@ -1001,112 +830,6 @@ table#categoryquestions td,
font-size: small;
}
-body #quizcontentsblock #repaginatedialog {
- display: none;
-}
-body.jsenabled #quizcontentsblock #repaginatedialog .hd {
- display: block;
-}
-body.jsenabled #quizcontentsblock #repaginatedialog .bd {
- padding: 1em;
-}
-body.jsenabled #quizcontentsblock .repaginatecommand #repaginatecommand {
- display: block;
-}
-
-#page-mod-quiz-edit #randomquestiondialog {
- display: none;
-}
-#page-mod-quiz-edit #qtypechoicecontainer {
- display: none;
-}
-
-#page-mod-quiz-edit .questionbankwindow select#catmenu_jump {
-/* In Opera9, IE6 the width of the
-select obeys the width of its content
-by default. This prevents that. */width: 100%;
-}
-
-/*this color might need to be theme-specific,
-but in terms of usability, as testing showed,
-http: //docs.moodle.org/dev/Quiz_UI_redesign/usability_testing_of_August_2008/Issues#Question_bank_.2F_question_adding_controls_visibility
-it must be ensured that the question
-bank window's title is prominent enough*/
-#page-mod-quiz-edit .questionbankwindow.block div.header {
- background-color: #009;
- background-image: none;
- padding-top: 0.2em;
- font-weight: bold;
- border: 0 none;
-}
-#page-mod-quiz-edit .questionbankwindow.block div.header div.title h2 {
- color: #FFF;
- text-align: center;
-}
-#page-mod-quiz-edit .collapsed .container {
- display: none;
-}
-
-#page-mod-quiz-edit .questionbankwindow a#showbankcmd,
-#page-mod-quiz-edit .questionbankwindow a#hidebankcmd {
- color: #FFF;
- text-decoration: underline;
-}
-#page-mod-quiz-edit .questionbankwindow a#showbankcmd:hover,
-#page-mod-quiz-edit .questionbankwindow a#hidebankcmd:hover {
- color: #009;
- background-color: #fff;
- text-decoration: none;
-}
-#page-mod-quiz-edit .questionbankwindow #showbankcmd {
- display: none;
-}
-#page-mod-quiz-edit .collapsed #showbankcmd {
- display: inline;
-}
-#page-mod-quiz-edit .questionbankwindow #hidebankcmd {
- display: inline;
-}
-#page-mod-quiz-edit .collapsed #hidebankcmd {
- display: none;
-}
-
-#page-mod-quiz-edit .quizquestionlistcontrols {
- display: inline;
-}
-
-#page-mod-quiz-edit .quizpagedelete {
- position: absolute;
- top: 0.2em;
- right: 0.2em;
- display: inline;
-}
-#page-mod-quiz-edit .quizpagedelete img {
- background-color: #d6d6d6;
- padding: 0.6em;
-}
-#page-mod-quiz-edit .pagecontrols {
- clear: both;
- margin-left: 0.5em;
- margin-right: 0.5em;
- padding-top: 0.5em;
-}
-#page-mod-quiz-edit .pagecontrols .singlebutton {
- float: left;
- margin-left: 1em;
-}
-#page-mod-quiz-edit .pagecontrols .helplink {
- float: left;
-}
-
-#page-mod-quiz-edit div#randomquestiondialog_c {
- width: 90%;
-}
-#page-mod-quiz-edit div#randomquestiondialog_c .mform,
-#randomquestiondialog_c select {
- width: 100%;
-}
-
#page-mod-quiz-edit div#repaginatedialog .mform {
margin-left: auto;
margin-right: auto;
@@ -1119,12 +842,6 @@ bank window's title is prominent enough*/
padding: 0;
}
-#page-mod-quiz-edit .questionbankwindow .createnewquestion select,
-#page-mod-quiz-edit .questionbankwindow #catmenu select,
-#page-mod-quiz-edit .questionbankwindow #menucategory {
- width: 100%;
-}
-
#page-mod-quiz-edit .paging {
margin-top: 0;
margin-bottom: 0;
@@ -1143,57 +860,16 @@ bank window's title is prominent enough*/
padding-top: 1em;
}
-.ie6#page-mod-quiz-edit div.question div.content .questiontext,
-.ie6#page-mod-quiz-edit #categoryquestions .questionname {
- /*ie6 shows this as an arrow if this is not specified*/cursor: pointer;
-}
-.ie6#page-mod-quiz-edit div.question div.content .questionname,
-.ie6#page-mod-quiz-edit #categoryquestions .questiontext {
- /*ie6 shows this as an arrow if this is not specified*/cursor: pointer;
-}
-.ie6#page-mod-quiz-edit .reorder .questioncontentcontainer .randomquestioncategory label {
- width: 35%;
-}
-.ie6#page-mod-quiz-edit .editq div.question div.content .randomquestioncategory a {
- width: 40%;
-}
-
-#page-mod-quiz-edit #categoryquestions .questiontext {
- font-weight: bold;
-}
#page-mod-quiz-edit .categoryinfofield {
font-style: italic;
}
#page-mod-quiz-edit .categorynamefield {
font-weight: bold;
}
-#page-mod-quiz-edit a.configurerandomquestion {
- font-size: small;
- text-decoration: underline;
-}
-#page-mod-quiz-edit .questioncontentcontainer div.randomquestionqlist {
- background-color: #eee;
-}
-#page-mod-quiz-edit .questioncontentcontainer div.randomquestionqlist ul {
- color: #555;
-}
-#page-mod-quiz-edit .questioncontentcontainer div.randomquestionqlist .totalquestionsinrandomqcategory {
- color: #000;
-}
-#page-mod-quiz-edit .categoryinfo {
- background-color: #eee;
- border-bottom: 1px solid #bbb;
-}
#page-mod-quiz-edit .questionsortoptions {
background-color: #ddd;
}
-#page-mod-quiz-edit div.questionbank {
- background-color: #e6e6ff;
-}
-#page-mod-quiz-edit div.questionbank form .invisiblefieldset {
- clear: both;
-}
#page-mod-quiz-edit div.questionbank .categorysortopotionscontainer {
padding-top: 0.5em;
margin-top: 0.3em;
@@ -1205,43 +881,6 @@ bank window's title is prominent enough*/
background-color: #FFF;
}
-#categoryquestions .questiontext {
- width: 50%;
-}
-#categoryquestions .questionname {
- width: 50%;
-}
-
-.ie6#page-mod-quiz-edit div.question div.content .questiontext,
-.ie6#page-mod-quiz-edit #categoryquestions .questionname {
- /*ie6 shows this as an arrow if this is not specified*/cursor: pointer;
-}
-.ie6#page-mod-quiz-edit div.question div.content .questionname,
-.ie6#page-mod-quiz-edit #categoryquestions .questiontext {
- /*ie6 shows this as an arrow if this is not specified*/cursor: pointer;
-}
-.ie6.path-mod-quiz div.tabtree a span img.iconsmall {
- margin: 0;
- vertical-align: baseline;
- position: relative;
- top: 1px;
-}
-.ie6#page-mod-quiz-edit div.question div.content .questiontext {
- width: 50%;
-}
-.ie6#page-mod-quiz-edit div.question div.content .questionname {
- width: 20%;
-}
-.ie6#page-mod-quiz-edit .editq div.question div.content .randomquestioncategory a {
- width: 40%;
-}
-.ie6#page-mod-quiz-edit .reorder .questioncontentcontainer .randomquestioncategory label {
- width: 35%;
-}
-.qnum label {
- padding-right: 0.25em;
-}
-
/* RTL Mode */
#page-mod-quiz-mod.dir-rtl #id_reviewoptionshdr .fitem {
width: 23%;
@@ -1254,74 +893,16 @@ bank window's title is prominent enough*/
clear: right;
float: right;
}
-#page-mod-quiz-edit.dir-rtl div.quizpage span.pagetitle {
- float: right;
-}
-#page-mod-quiz-edit.dir-rtl div.quizpage .pagecontent {
- float: right;
-}
-#page-mod-quiz-edit.dir-rtl div.question {
- clear: right;
-}
-#page-mod-quiz-edit.dir-rtl div.question div.qnum {
- float: right;
-}
-#page-mod-quiz-edit.dir-rtl div.editq div.question div.content {
- float: right;
-}
-#page-mod-quiz-edit.dir-rtl div.question div.content div.points {
- left: 60px;
- right: auto;
-}
-#page-mod-quiz-edit.dir-rtl div.question div.content div.questioncontrols {
- float: left;
- left: 0.3em;
- right: auto;
-}
-#page-mod-quiz-edit.dir-rtl .editq div.question div.content .singlequestion .questioneditbutton .questionname,
-#page-mod-quiz-edit.dir-rtl .editq div.question div.content .singlequestion .questioneditbutton .questiontext {
- float: right;
- padding-right: 0.3em;
-}
-#page-mod-quiz-edit.dir-rtl div.question div.content .questiontext,
#page-mod-quiz-edit.dir-rtl #categoryquestions .questiontext {
padding-right: 0.3em;
}
-#page-mod-quiz-edit.dir-rtl .editq div.questioncontentcontainer div.singlequestion img {
- float: right;
-}
-#page-mod-quiz-edit.dir-rtl .editq div.question div.content .questionpreview {
- float: right;
-}
-#page-mod-quiz-edit.dir-rtl div.question div.content div.qorder {
- left: 60px;
- right: auto;
-}
-#page-mod-quiz-edit.dir-rtl .reorder div.question div.content {
- float: right;
-}
-#page-mod-quiz-edit.dir-rtl .quizpagedelete {
- left: 0.2em;
- right: auto;
-}
#page-mod-quiz-edit.dir-rtl div.quizcontents {
clear: right;
float: right;
}
-#page-mod-quiz-edit.dir-rtl .questionbankwindow.block {
- float: left;
-}
-#page-question-edit.dir-rtl td.creatorname, #page-question-edit.dir-rtl td.modifiername {
- text-align: center;
-}
.path-question.dir-rtl input[name="maxmark"],
.path-question-type.dir-rtl input[name="defaultmark"],
#page-mod-quiz-edit.dir-rtl div.points input {
direction: ltr;
text-align: left;
}
-#page-mod-quiz-edit.dir-rtl .pagecontrols .singlebutton {
- float: right;
- margin-left: 0;
- margin-right: 1em;
-}
diff --git a/mod/quiz/tests/behat/behat_mod_quiz.php b/mod/quiz/tests/behat/behat_mod_quiz.php
index 954d47555c0..5886d454d37 100644
--- a/mod/quiz/tests/behat/behat_mod_quiz.php
+++ b/mod/quiz/tests/behat/behat_mod_quiz.php
@@ -17,10 +17,10 @@
/**
* Steps definitions related to mod_quiz.
*
- * @package mod_quiz
- * @category test
- * @copyright 2014 Marina Glancy
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @package mod_quiz
+ * @category test
+ * @copyright 2014 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
// NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
@@ -34,10 +34,8 @@ use Behat\Behat\Context\Step\Given as Given,
/**
* Steps definitions related to mod_quiz.
*
- * @package mod_quiz
- * @category test
- * @copyright 2014 Marina Glancy
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ * @copyright 2014 Marina Glancy
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
class behat_mod_quiz extends behat_question_base {
/**
@@ -54,10 +52,162 @@ class behat_mod_quiz extends behat_question_base {
$quizname = $this->escape($quizname);
$editquiz = $this->escape(get_string('editquiz', 'quiz'));
$addaquestion = $this->escape(get_string('addaquestion', 'quiz'));
+ $menuxpath = "//div[contains(@class, ' page-add-actions ')][last()]//a[contains(@class, ' textmenu')]";
+ $itemxpath = "//div[contains(@class, ' page-add-actions ')][last()]//a[contains(@class, ' addquestion ')]";
return array_merge(array(
new Given("I follow \"$quizname\""),
new Given("I follow \"$editquiz\""),
- new Given("I press \"$addaquestion\""),
+ new Given("I click on \"$menuxpath\" \"xpath_element\""),
+ new Given("I click on \"$itemxpath\" \"xpath_element\""),
), $this->finish_adding_question($questiontype, $questiondata));
}
+
+ /**
+ * Set the max mark for a question on the Edit quiz page.
+ *
+ * @When /^I set the max mark for question "(?P(?:[^"]|\\")*)" to "(?P(?:[^"]|\\")*)"$/
+ * @param string $questionname the name of the question to set the max mark for.
+ * @param string $newmark the mark to set
+ */
+ public function i_set_the_max_mark_for_quiz_question($questionname, $newmark) {
+ return array(
+ new Given('I follow "' . $this->escape(get_string('editmaxmark', 'quiz')) . '"'),
+ new Given('I wait until "li input[name=maxmark]" "css_element" exists'),
+ new Given('I should see "' . $this->escape(get_string('edittitleinstructions')) . '"'),
+ new Given('I set the field "maxmark" to "' . $this->escape($newmark) . chr(10) . '"'),
+ );
+ }
+
+ /**
+ * Open the add menu on a given page, or at the end of the Edit quiz page.
+ * @Given /^I open the "(?P(?:[^"]|\\")*)" add to quiz menu$/
+ * @param string $pageorlast either "Page n" or "last".
+ */
+ public function i_open_the_add_to_quiz_menu_for($pageorlast) {
+
+ if (!$this->running_javascript()) {
+ throw new DriverException('Activities actions menu not available when Javascript is disabled');
+ }
+
+ if ($pageorlast == 'last') {
+ $xpath = "//div[@class = 'last-add-menu']//a[contains(@class, 'textmenu') and contains(., 'Add')]";
+ } else if (preg_match('~Page (\d+)~', $pageorlast, $matches)) {
+ $xpath = "//li[@id = 'page-{$matches[1]}']//a[contains(@class, 'textmenu') and contains(., 'Add')]";
+ } else {
+ throw new ExpectationException("The I open the add to quiz menu step must specify either 'Page N' or 'last'.");
+ }
+ $menu = $this->find('xpath', $xpath)->click();
+ }
+
+ /**
+ * Click on a given link in the moodle-actionmenu that is currently open.
+ * @Given /^I follow "(?P(?:[^"]|\\")*)" in the open menu$/
+ * @param string $linkstring the text (or id, etc.) of the link to click.
+ * @return array of steps.
+ */
+ public function i_follow_in_the_open_menu($linkstring) {
+ $openmenuxpath = "//div[contains(@class, 'moodle-actionmenu') and contains(@class, 'show')]";
+ return array(
+ new Given('I click on "' . $linkstring . '" "link" in the "' . $openmenuxpath . '" "xpath_element"'),
+ );
+ }
+
+ /**
+ * Check whether a particular question is on a particular page of the quiz on the Edit quiz page.
+ * @Given /^I should see "(?P(?:[^"]|\\")*)" on quiz page "(?P\d+)"$/
+ * @param string $questionname the name of the question we are looking for.
+ * @param number $pagenumber the page it should be found on.
+ * @return array of steps.
+ */
+ public function i_should_see_on_quiz_page($questionname, $pagenumber) {
+ $xpath = "//li[contains(., '" . $this->escape($questionname) .
+ "')][./preceding-sibling::li[contains(@class, 'pagenumber')][1][contains(., 'Page " .
+ $pagenumber . "')]]";
+ return array(
+ new Given('"' . $xpath . '" "xpath_element" should exist'),
+ );
+ }
+
+ /**
+ * Check whether one question comes before another on the Edit quiz page.
+ * The two questions must be on the same page.
+ * @Given /^I should see "(?P(?:[^"]|\\")*)" before "(?P(?:[^"]|\\")*)" on the edit quiz page$/
+ * @param string $firstquestionname the name of the question that should come first in order.
+ * @param string $secondquestionname the name of the question that should come immediately after it in order.
+ * @return array of steps.
+ */
+ public function i_should_see_before_on_the_edit_quiz_page($firstquestionname, $secondquestionname) {
+ $xpath = "//li[contains(@class, ' slot ') and contains(., '" . $this->escape($firstquestionname) .
+ "')]/following-sibling::li[contains(@class, ' slot ')][1]" .
+ "[contains(., '" . $this->escape($secondquestionname) . "')]";
+ return array(
+ new Given('"' . $xpath . '" "xpath_element" should exist'),
+ );
+ }
+
+ /**
+ * Check the number displayed alongside a question on the Edit quiz page.
+ * @Given /^"(?P(?:[^"]|\\")*)" should have number "(?P(?:[^"]|\\")*)" on the edit quiz page$/
+ * @param string $questionname the name of the question we are looking for.
+ * @param number $number the number (or 'i') that should be displayed beside that question.
+ * @return array of steps.
+ */
+ public function should_have_number_on_the_edit_quiz_page($questionname, $number) {
+ $xpath = "//li[contains(@class, ' slot ') and contains(., '" . $this->escape($questionname) .
+ "')]//span[@class = 'slotnumber' and normalize-space(text()) = '" . $this->escape($number) . "']";
+ return array(
+ new Given('"' . $xpath . '" "xpath_element" should exist'),
+ );
+ }
+
+ /**
+ * Click the add or remove page-break icon after a particular question.
+ * @When /^I click on the "(Add|Remove)" page break icon after question "(?P(?:[^"]|\\")*)"$/
+ * @param string $addorremoves 'Add' or 'Remove'.
+ * @param string $questionname the name of the question before the icon to click.
+ * @return array of steps.
+ */
+ public function i_click_on_the_page_break_icon_after_question($addorremoves, $questionname) {
+ $xpath = "//li[contains(@class, ' slot ') and contains(., '" . $this->escape($questionname) .
+ "')]//a[@class = 'page_split_join' and @title = '" . $addorremoves . " page break']";
+ return array(
+ new Given('I click on "' . $xpath . '" "xpath_element"'),
+ );
+ }
+
+ /**
+ * Move a question on the Edit quiz page by first clicking on the Move icon,
+ * then clicking one of the "After ..." links.
+ * @When /^I move "(?P(?:[^"]|\\")*)" to "(?P(?:[^"]|\\")*)" in the quiz by clicking the move icon$/
+ * @param string $questionname the name of the question we are looking for.
+ * @param string $target the target place to move to. One of the links in the pop-up like
+ * "After Page 1" or "After Question N".
+ * @return array of steps.
+ */
+ public function i_move_question_after_item_by_clicking_the_move_icon($questionname, $target) {
+ $iconxpath = "//li[contains(@class, ' slot ') and contains(., '" . $this->escape($questionname) .
+ "')]//span[contains(@class, 'editing_move')]";
+ return array(
+ new Given('I click on "' . $iconxpath . '" "xpath_element"'),
+ new Given('I click on "' . $this->escape($target) . '" "text"'),
+ );
+ }
+
+ /**
+ * Move a question on the Edit quiz page by dragging a given question on top of another item.
+ * @When /^I move "(?P(?:[^"]|\\")*)" to "(?P(?:[^"]|\\")*)" in the quiz by dragging$/
+ * @param string $questionname the name of the question we are looking for.
+ * @param string $target the target place to move to. Ether a question name, or "Page N"
+ * @return array of steps.
+ */
+ public function i_move_question_after_item_by_dragging($questionname, $target) {
+ $iconxpath = "//li[contains(@class, ' slot ') and contains(., '" . $this->escape($questionname) .
+ "')]//span[contains(@class, 'editing_move')]//img";
+ $destinationxpath = "//li[contains(@class, ' slot ') or contains(@class, 'pagenumber ')]" .
+ "[contains(., '" . $this->escape($target) . "')]";
+ return array(
+ new Given('I drag "' . $iconxpath . '" "xpath_element" ' .
+ 'and I drop it in "' . $destinationxpath . '" "xpath_element"'),
+ );
+ }
}
diff --git a/mod/quiz/tests/behat/editing_add.feature b/mod/quiz/tests/behat/editing_add.feature
new file mode 100644
index 00000000000..80eb802d434
--- /dev/null
+++ b/mod/quiz/tests/behat/editing_add.feature
@@ -0,0 +1,255 @@
+@mod @mod_quiz
+Feature: Edit quiz page - adding things
+ In order to build the quiz I want my students to attempt
+ As a teacher
+ I need to be able to add questions to the quiz.
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email |
+ | teacher1 | T1 | Teacher1 | teacher1@moodle.com |
+ And the following "courses" exist:
+ | fullname | shortname | category |
+ | Course 1 | C1 | 0 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ And the following "activities" exist:
+ | activity | name | intro | course | idnumber |
+ | quiz | Quiz 1 | Quiz 1 for testing the Add menu | C1 | quiz1 |
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I follow "Quiz 1"
+ And I navigate to "Edit quiz" node in "Quiz administration"
+ Then I should see "Editing quiz: Quiz 1"
+
+ @javascript
+ Scenario: Add some new question to the quiz using '+ a new question' options of the 'Add' menu.
+ And I open the "last" add to quiz menu
+ And I follow "a new question"
+ And I set the field "qtype_qtype_essay" to "1"
+ And I press "submitbutton"
+ Then I should see "Adding an Essay question"
+ And I set the field "Question name" to "Essay 01 new"
+ And I set the field "Question text" to "Please write 200 words about Essay 01"
+ And I press "id_submitbutton"
+ Then I should see "Editing quiz: Quiz 1"
+ And I should see "Essay 01 new" on quiz page "1"
+
+ And I open the "Page 1" add to quiz menu
+ And I follow "a new question"
+ And I set the field "qtype_qtype_essay" to "1"
+ And I press "submitbutton"
+ Then I should see "Adding an Essay question"
+ And I set the field "Question name" to "Essay 02 new"
+ And I set the field "Question text" to "Please write 200 words about Essay 02"
+ And I press "id_submitbutton"
+ Then I should see "Editing quiz: Quiz 1"
+ And I should see "Essay 01 new" on quiz page "1"
+ And I should see "Essay 02 new" on quiz page "1"
+
+ And I open the "Page 1" add to quiz menu
+ And I follow "a new question"
+ And I set the field "qtype_qtype_essay" to "1"
+ And I press "submitbutton"
+ Then I should see "Adding an Essay question"
+ And I set the field "Question name" to "Essay 03 new"
+ And I set the field "Question text" to "Please write 300 words about Essay 03"
+ And I press "id_submitbutton"
+ Then I should see "Editing quiz: Quiz 1"
+ And I should see "Essay 01 new" on quiz page "1"
+ And I should see "Essay 02 new" on quiz page "1"
+ And I should see "Essay 03 new" on quiz page "1"
+
+ And I open the "Page 1" add to quiz menu
+ And I follow "a new question"
+ And I set the field "qtype_qtype_essay" to "1"
+ And I press "submitbutton"
+ Then I should see "Adding an Essay question"
+ And I set the field "Question name" to "Essay 04 new"
+ And I set the field "Question text" to "Please write 300 words about Essay 04"
+ And I press "id_submitbutton"
+ Then I should see "Editing quiz: Quiz 1"
+ And I should see "Essay 01 new" on quiz page "1"
+ And I should see "Essay 02 new" on quiz page "1"
+ And I should see "Essay 03 new" on quiz page "1"
+ And I should see "Essay 04 new" on quiz page "1"
+
+ # Repaginate as two questions per page.
+ And I should not see "Page 2"
+ When I press "Repaginate"
+ Then I should see "Repaginate with"
+ And I set the field "menuquestionsperpage" to "2"
+ When I press "Go"
+ And I should see "Essay 01 new" on quiz page "1"
+ And I should see "Essay 02 new" on quiz page "1"
+ And I should see "Essay 03 new" on quiz page "2"
+ And I should see "Essay 04 new" on quiz page "2"
+
+ # Add a question to page 2.
+ When I open the "Page 2" add to quiz menu
+ And I follow "a new question" in the open menu
+ And I set the field "qtype_qtype_essay" to "1"
+ And I press "submitbutton"
+ Then I should see "Adding an Essay question"
+ When I set the field "Question name" to "Essay for page 2"
+ And I set the field "Question text" to "Please write 200 words about Essay for page 2"
+ And I press "id_submitbutton"
+ Then I should see "Editing quiz: Quiz 1"
+ And I should see "Essay 01 new" on quiz page "1"
+ And I should see "Essay 02 new" on quiz page "1"
+ And I should see "Essay 03 new" on quiz page "2"
+ And I should see "Essay 04 new" on quiz page "2"
+ And I should see "Essay for page 2" on quiz page "2"
+
+ @javascript
+ Scenario: Add questions from question bank to the quiz. In order to be able to
+ add questions from question bank to the quiz, first we create some new questions
+ in various categories and add them to the question bank.
+
+ # Create a couple of sub categories.
+ And I follow "Course 1"
+ And I navigate to "Categories" node in "Course administration > Question bank"
+ Then I should see "Add category"
+ Then I set the field "Parent category" to "Default for C1"
+ And I set the field "Name" to "Subcat 1"
+ And I set the field "Category info" to "This is sub category 1"
+ Then I press "id_submitbutton"
+ And I should see "Subcat 1"
+
+ Then I set the field "Parent category" to "Default for C1"
+ And I set the field "Name" to "Subcat 2"
+ And I set the field "Category info" to "This is sub category 2"
+ Then I press "id_submitbutton"
+ And I should see "Subcat 2"
+
+ And I navigate to "Questions" node in "Course administration > Question bank"
+ Then I should see "Question bank"
+ And I should see "Select a category"
+
+ # Create the Essay 01 question.
+ When I press "Create a new question ..."
+ And I set the field "qtype_qtype_essay" to "1"
+ And I press "Add"
+ Then I should see "Adding an Essay question"
+ And I set the field "Question name" to "Essay 01"
+ And I set the field "Question text" to "Please write 100 words about Essay 01"
+ And I press "id_submitbutton"
+ Then I should see "Question bank"
+ And I should see "Essay 01"
+
+ # Create the Essay 02 question.
+ And I should see "Select a category"
+ And I set the field "Select a category:" to "Subcat 1"
+ When I press "Create a new question ..."
+ And I set the field "qtype_qtype_essay" to "1"
+ And I press "Add"
+ Then I should see "Adding an Essay question"
+ And I set the field "Question name" to "Essay 02"
+ And I set the field "Question text" to "Please write 200 words about Essay 02"
+ And I press "id_submitbutton"
+ Then I should see "Question bank"
+ And I should see "Essay 02"
+
+ # Create the Essay 03 question.
+ And I set the field "Select a category" to "Default for C1"
+ When I press "Create a new question ..."
+ And I set the field "qtype_qtype_essay" to "1"
+ And I press "Add"
+ Then I should see "Adding an Essay question"
+ And I set the field "Question name" to "Essay 03"
+ And I set the field "Question text" to "Please write 300 words about Essay 03"
+ And I press "id_submitbutton"
+ Then I should see "Question bank"
+ And I should see "Essay 03"
+
+ # Create the TF 01 question.
+ When I press "Create a new question ..."
+ And I set the field "qtype_qtype_truefalse" to "1"
+ And I press "Add"
+ Then I should see "Adding a True/False question"
+ And I set the field "Question name" to "TF 01"
+ And I set the field "Question text" to "The correct answer is true"
+ And I set the field "Correct answer" to "True"
+ And I press "id_submitbutton"
+ Then I should see "Question bank"
+ And I should see "TF 01"
+
+ # Create the TF 02 question.
+ When I press "Create a new question ..."
+ And I set the field "qtype_qtype_truefalse" to "1"
+ And I press "Add"
+ Then I should see "Adding a True/False question"
+ And I set the field "Question name" to "TF 02"
+ And I set the field "Question text" to "The correct answer is false"
+ And I set the field "Correct answer" to "False"
+ And I press "id_submitbutton"
+ Then I should see "Question bank"
+ And I should see "TF 02"
+
+ # Add questions from question bank using the Add menu.
+ # Add Essay 03 from question bank.
+ And I follow "Course 1"
+ And I follow "Quiz 1"
+ And I follow "Edit quiz"
+ And I open the "last" add to quiz menu
+ And I follow "from question bank"
+ And I click on "Add to quiz" "link" in the "Essay 03" "table_row"
+ Then I should see "Editing quiz: Quiz 1"
+ And I should see "Essay 03" on quiz page "1"
+
+ # Add Essay 01 from question bank.
+ And I open the "Page 1" add to quiz menu
+ And I follow "from question bank"
+ And I click on "Add to quiz" "link" in the "Essay 01" "table_row"
+ Then I should see "Editing quiz: Quiz 1"
+ And I should see "Essay 03" on quiz page "1"
+ And I should see "Essay 01" on quiz page "1"
+
+ # Add Esay 02 from question bank.
+ And I open the "Page 1" add to quiz menu
+ And I follow "from question bank"
+ And I should see "Select a category"
+ And I set the field "Select a category" to "Subcat 1"
+ And I click on "Add to quiz" "link" in the "Essay 02" "table_row"
+ Then I should see "Editing quiz: Quiz 1"
+ And I should see "Essay 03" on quiz page "1"
+ And I should see "Essay 01" on quiz page "1"
+ And I should see "Essay 02" on quiz page "1"
+
+ # Add a random question.
+ And I open the "Page 1" add to quiz menu
+ And I follow "a random question"
+ And I press "Add random question"
+ Then I should see "Editing quiz: Quiz 1"
+ And I should see "Essay 03" on quiz page "1"
+ And I should see "Essay 01" on quiz page "1"
+ And I should see "Essay 02" on quiz page "1"
+ And I should see "Random" on quiz page "1"
+
+ # Repaginate as one question per page.
+ And I should not see "Page 2"
+ When I press "Repaginate"
+ Then I should see "Repaginate with"
+ And I set the field "menuquestionsperpage" to "1"
+ When I press "Go"
+ And I should see "Essay 03" on quiz page "1"
+ And I should see "Essay 01" on quiz page "2"
+ And I should see "Essay 02" on quiz page "3"
+ And I should see "Random" on quiz page "4"
+
+ # Add a random question to page 4.
+ And I open the "Page 4" add to quiz menu
+ And I follow "a new question" in the open menu
+ And I set the field "qtype_qtype_essay" to "1"
+ And I press "submitbutton"
+ Then I should see "Adding an Essay question"
+ And I set the field "Question name" to "Essay for page 4"
+ And I set the field "Question text" to "Please write 200 words about Essay for page 4"
+ And I press "id_submitbutton"
+ Then I should see "Editing quiz: Quiz 1"
+ And I should see "Essay 03" on quiz page "1"
+ And I should see "Essay 01" on quiz page "2"
+ And I should see "Essay 02" on quiz page "3"
+ And I should see "Random" on quiz page "4"
+ And I should see "Essay for page 4" on quiz page "4"
diff --git a/mod/quiz/tests/behat/editing_click_move_icon.feature b/mod/quiz/tests/behat/editing_click_move_icon.feature
new file mode 100644
index 00000000000..0eb6fa6a55e
--- /dev/null
+++ b/mod/quiz/tests/behat/editing_click_move_icon.feature
@@ -0,0 +1,98 @@
+@mod @mod_quiz
+Feature: Edit quiz page - drag-and-drop
+ In order to change the layout of a quiz I built
+ As a teacher
+ I need to be able to drag and drop questions to reorder them.
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email |
+ | teacher1 | T1 | Teacher1 | teacher1@moodle.com |
+ And the following "courses" exist:
+ | fullname | shortname | category |
+ | Course 1 | C1 | 0 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ And the following "activities" exist:
+ | activity | name | course | idnumber |
+ | quiz | Quiz 1 | C1 | quiz1 |
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I add a "True/False" question to the "Quiz 1" quiz with:
+ | Question name | Question A |
+ | Question text | Answer me |
+ And I add a "True/False" question to the "Quiz 1" quiz with:
+ | Question name | Question B |
+ | Question text | Answer again |
+ And I add a "True/False" question to the "Quiz 1" quiz with:
+ | Question name | Question C |
+ | Question text | And again |
+ And I click on the "Add" page break icon after question "Question B"
+
+ @javascript
+ Scenario: Re-order questions by clicking on the move icon.
+ Then I should see "Question A" on quiz page "1"
+ And I should see "Question B" on quiz page "1"
+ And I should see "Question C" on quiz page "2"
+
+ When I move "Question A" to "After Question 2" in the quiz by clicking the move icon
+ Then I should see "Question B" on quiz page "1"
+ And I should see "Question A" on quiz page "1"
+ And I should see "Question B" before "Question A" on the edit quiz page
+ And I should see "Question C" on quiz page "2"
+
+ When I move "Question A" to "After Page 2" in the quiz by clicking the move icon
+ Then I should see "Question B" on quiz page "1"
+ And I should see "Question A" on quiz page "2"
+ And I should see "Question C" on quiz page "2"
+ And I should see "Question A" before "Question C" on the edit quiz page
+
+ When I move "Question B" to "After Question 2" in the quiz by clicking the move icon
+ Then I should see "Question A" on quiz page "1"
+ And I should see "Question B" on quiz page "1"
+ And I should see "Question C" on quiz page "1"
+ And I should see "Question A" before "Question B" on the edit quiz page
+ And I should see "Question B" before "Question C" on the edit quiz page
+
+ When I move "Question B" to "After Page 1" in the quiz by clicking the move icon
+ Then I should see "Question B" on quiz page "1"
+ And I should see "Question A" on quiz page "1"
+ And I should see "Question C" on quiz page "1"
+ And I should see "Question B" before "Question A" on the edit quiz page
+ And I should see "Question A" before "Question C" on the edit quiz page
+
+ When I click on the "Add" page break icon after question "Question A"
+ When I open the "Page 2" add to quiz menu
+ And I follow "a new question" in the open menu
+ And I set the field "qtype_qtype_description" to "1"
+ And I press "submitbutton"
+ Then I should see "Adding a description"
+ And I set the following fields to these values:
+ | Question name | Question D |
+ | Question text | Useful info |
+ And I press "id_submitbutton"
+ Then I should see "Question B" on quiz page "1"
+ And I should see "Question A" on quiz page "1"
+ And I should see "Question C" on quiz page "2"
+ And I should see "Question D" on quiz page "2"
+ And I should see "Question B" before "Question A" on the edit quiz page
+ And I should see "Question C" before "Question D" on the edit quiz page
+
+ And "Question B" should have number "1" on the edit quiz page
+ And "Question A" should have number "2" on the edit quiz page
+ And "Question C" should have number "3" on the edit quiz page
+ And "Question D" should have number "i" on the edit quiz page
+
+ When I move "Question D" to "After Question 2" in the quiz by clicking the move icon
+ Then I should see "Question B" on quiz page "1"
+ And I should see "Question D" on quiz page "1"
+ And I should see "Question A" on quiz page "1"
+ And I should see "Question C" on quiz page "2"
+ And I should see "Question B" before "Question A" on the edit quiz page
+ And I should see "Question A" before "Question D" on the edit quiz page
+
+ And "Question B" should have number "1" on the edit quiz page
+ And "Question D" should have number "i" on the edit quiz page
+ And "Question A" should have number "2" on the edit quiz page
+ And "Question C" should have number "3" on the edit quiz page
diff --git a/mod/quiz/tests/behat/editing_repaginate.feature b/mod/quiz/tests/behat/editing_repaginate.feature
new file mode 100644
index 00000000000..f94cfb3d94d
--- /dev/null
+++ b/mod/quiz/tests/behat/editing_repaginate.feature
@@ -0,0 +1,154 @@
+@mod @mod_quiz
+Feature: Edit quiz page - pagination
+ In order to build a quiz laid out in pages the way I want
+ As a teacher
+ I need to be able to add and remove pages, and repaginate.
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email |
+ | teacher1 | T1 | Teacher1 | teacher1@moodle.com |
+ And the following "courses" exist:
+ | fullname | shortname | category |
+ | Course 1 | C1 | 0 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ And the following "activities" exist:
+ | activity | name | intro | course | idnumber |
+ | quiz | Quiz 1 | Quiz 1 description | C1 | quiz1 |
+
+ When I log in as "teacher1"
+ And I follow "Course 1"
+ And I follow "Quiz 1"
+ And I follow "Edit quiz"
+
+ @javascript
+ Scenario: Repaginate questions with N question(s) per page as well as clicking
+ on "add page break" or "Remove page break" icons to repaginate in any desired format.
+
+ Then I should see "Editing quiz: Quiz 1"
+
+ # Add the first Essay question.
+ And I follow "Add"
+ And I follow "a new question"
+ And I set the field "qtype_qtype_essay" to "1"
+ And I press "submitbutton"
+ Then I should see "Adding an Essay question"
+ And I set the field "Question name" to "Essay 01 new"
+ And I set the field "Question text" to "Please write 100 words about Essay 01"
+ And I press "id_submitbutton"
+ Then I should see "Editing quiz: Quiz 1"
+ And I should see "Essay 01 new" on quiz page "1"
+
+ # Add the second Essay question.
+ And I follow "Add"
+ And I follow "a new question"
+ And I set the field "qtype_qtype_essay" to "1"
+ And I press "submitbutton"
+ Then I should see "Adding an Essay question"
+ And I set the field "Question name" to "Essay 02 new"
+ And I set the field "Question text" to "Please write 200 words about Essay 02"
+ And I press "id_submitbutton"
+ Then I should see "Editing quiz: Quiz 1"
+ And I should see "Essay 01 new" on quiz page "1"
+ And I should see "Essay 02 new" on quiz page "1"
+
+ # Start repaginating.
+ And I should not see "Page 2"
+
+ When I click on the "Add" page break icon after question "Essay 01 new"
+ And I should see "Essay 01 new" on quiz page "1"
+ And I should see "Essay 02 new" on quiz page "2"
+
+ When I click on the "Remove" page break icon after question "Essay 01 new"
+ And I should see "Essay 01 new" on quiz page "1"
+ And I should see "Essay 02 new" on quiz page "1"
+ And I should not see "Page 2"
+
+ # Add the third Essay question.
+ And I follow "Add"
+ And I follow "a new question"
+ And I set the field "qtype_qtype_essay" to "1"
+ And I press "submitbutton"
+ Then I should see "Adding an Essay question"
+ And I set the field "Question name" to "Essay 03 new"
+ And I set the field "Question text" to "Please write 200 words about Essay 03"
+ And I press "id_submitbutton"
+ Then I should see "Editing quiz: Quiz 1"
+ And I should see "Essay 01 new" on quiz page "1"
+ And I should see "Essay 02 new" on quiz page "1"
+ And I should see "Essay 03 new" on quiz page "1"
+ And I should not see "Page 2"
+ And I should not see "Page 3"
+
+ When I click on the "Add" page break icon after question "Essay 02 new"
+ And I should see "Essay 01 new" on quiz page "1"
+ And I should see "Essay 02 new" on quiz page "1"
+ And I should see "Essay 03 new" on quiz page "2"
+ And I should not see "Page 3"
+
+ When I click on the "Add" page break icon after question "Essay 01 new"
+ And I should see "Essay 01 new" on quiz page "1"
+ And I should see "Essay 02 new" on quiz page "2"
+ And I should see "Essay 03 new" on quiz page "3"
+
+ When I click on the "Remove" page break icon after question "Essay 02 new"
+ And I should see "Essay 01 new" on quiz page "1"
+ And I should see "Essay 02 new" on quiz page "2"
+ And I should see "Essay 03 new" on quiz page "2"
+ And I should not see "Page 3"
+
+ When I click on the "Remove" page break icon after question "Essay 01 new"
+ And I should see "Essay 01 new" on quiz page "1"
+ And I should see "Essay 02 new" on quiz page "1"
+ And I should see "Essay 03 new" on quiz page "1"
+ And I should not see "Page 2"
+ And I should not see "Page 3"
+
+ # Repaginate one question per page.
+ When I press "Repaginate"
+ And I set the field "menuquestionsperpage" to "1"
+ And I press "Go"
+ Then I should see "Essay 01 new" on quiz page "1"
+ And I should see "Essay 02 new" on quiz page "2"
+ And I should see "Essay 03 new" on quiz page "3"
+
+ # Add the forth Essay question in a new page (Page 4).
+ When I open the "Page 3" add to quiz menu
+ And I follow "a new question" in the open menu
+ And I set the field "qtype_qtype_essay" to "1"
+ And I press "submitbutton"
+ Then I should see "Adding an Essay question"
+ When I set the field "Question name" to "Essay 04 new"
+ And I set the field "Question text" to "Please write 300 words about Essay 04"
+ And I press "id_submitbutton"
+ Then I should see "Editing quiz: Quiz 1"
+ And I should see "Essay 01 new" on quiz page "1"
+ And I should see "Essay 02 new" on quiz page "2"
+ And I should see "Essay 03 new" on quiz page "3"
+ And I should see "Essay 04 new" on quiz page "3"
+
+ When I click on the "Add" page break icon after question "Essay 03 new"
+ And I should see "Essay 01 new" on quiz page "1"
+ And I should see "Essay 02 new" on quiz page "2"
+ And I should see "Essay 03 new" on quiz page "3"
+ And I should see "Essay 04 new" on quiz page "4"
+
+ # Repaginate with 2 questions per page.
+ When I press "Repaginate"
+ And I set the field "menuquestionsperpage" to "2"
+ And I press "Go"
+ Then I should see "Essay 01 new" on quiz page "1"
+ And I should see "Essay 02 new" on quiz page "1"
+ And I should see "Essay 03 new" on quiz page "2"
+ And I should see "Essay 04 new" on quiz page "2"
+
+ # Repaginate with unlimited questions per page (All questions on Page 1).
+ When I press "Repaginate"
+ And I set the field "menuquestionsperpage" to "Unlimited"
+ And I press "Go"
+ Then I should see "Essay 01 new" on quiz page "1"
+ And I should see "Essay 02 new" on quiz page "1"
+ And I should see "Essay 03 new" on quiz page "1"
+ And I should see "Essay 04 new" on quiz page "1"
diff --git a/mod/quiz/tests/behat/editing_set_marks.feature b/mod/quiz/tests/behat/editing_set_marks.feature
new file mode 100644
index 00000000000..7e55399879d
--- /dev/null
+++ b/mod/quiz/tests/behat/editing_set_marks.feature
@@ -0,0 +1,77 @@
+@mod @mod_quiz
+Feature: In order to create a quiz that awards marks the way I want
+ As a teacher
+ I must be able to set the marks I want on the Edit quiz page.
+
+ Background:
+ Given the following "users" exist:
+ | username | firstname | lastname | email |
+ | teacher1 | T1 | Teacher1 | teacher1@moodle.com |
+ And the following "courses" exist:
+ | fullname | shortname | category |
+ | Course 1 | C1 | 0 |
+ And the following "course enrolments" exist:
+ | user | course | role |
+ | teacher1 | C1 | editingteacher |
+ And the following "activities" exist:
+ | activity | name | course | idnumber | grade | decimalpoints | questiondecimalpoints |
+ | quiz | Quiz 1 | C1 | quiz1 | 20 | 2 | -1 |
+ And I log in as "teacher1"
+ And I follow "Course 1"
+ And I add a "True/False" question to the "Quiz 1" quiz with:
+ | Question name | First question |
+ | Question text | Answer me |
+ | Default mark | 2.0 |
+ And I add a "True/False" question to the "Quiz 1" quiz with:
+ | Question name | Second question |
+ | Question text | Answer again |
+ | Default mark | 3.0 |
+
+ @javascript
+ Scenario: Set the max mark for a question.
+ When I set the max mark for question "First question" to "7.0"
+ Then I should see "7.00"
+ And I should see "3.00"
+ And I should see "Total of marks: 10.00"
+
+ When I follow "Edit maximum mark"
+ And I wait until "li input[name=maxmark]" "css_element" exists
+ And I take focus off "li input[name=maxmark]" "css_element"
+ Then I should see "7.00"
+ And I should see "3.00"
+ And I should see "Total of marks: 10.00"
+ And "li input[name=maxmark]" "css_element" should not exist
+
+ @javascript
+ Scenario: Set the overall Maximum grade.
+ When I set the field "maxgrade" to "10.0"
+ And I press "savechanges"
+ Then the field "maxgrade" matches value "10.00"
+ And I should see "2.00"
+ And I should see "3.00"
+ And I should see "Total of marks: 5.00"
+
+ @javascript
+ Scenario: Verify the number of decimal places shown is what the quiz settings say it should be.
+ # Then the field "maxgrade" matches value "20.00" -- with exact match on decimal places.
+ Then "//input[@name = 'maxgrade' and @value = '20.00']" "xpath_element" should exist
+ And I should see "2.00"
+ And I should see "3.00"
+ And I should see "Total of marks: 5.00"
+ And I should not see "2.000"
+ And I should not see "3.000"
+ And I should not see "Total of marks: 5.000"
+ When I follow "Edit settings"
+ And I set the following fields to these values:
+ | Decimal places in grades | 3 |
+ | Decimal places in question grades | 5 |
+ And I press "Save and display"
+ And I follow "Edit quiz"
+ # Then the field "maxgrade" matches value "20.000" -- with exact match on decimal places.
+ Then "//input[@name = 'maxgrade' and @value = '20.000']" "xpath_element" should exist
+ And I should see "2.00000"
+ And I should see "3.00000"
+ And I should see "Total of marks: 5.000"
+ And I should not see "2.000000"
+ And I should not see "3.000000"
+ And I should not see "Total of marks: 5.0000"
diff --git a/mod/quiz/tests/editlib_test.php b/mod/quiz/tests/editlib_test.php
deleted file mode 100644
index 2e9fe461a44..00000000000
--- a/mod/quiz/tests/editlib_test.php
+++ /dev/null
@@ -1,116 +0,0 @@
-.
-
-/**
- * Unit tests for (some of) mod/quiz/editlib.php.
- *
- * @package mod_quiz
- * @category phpunit
- * @copyright 2009 Tim Hunt
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-
-
-defined('MOODLE_INTERNAL') || die();
-
-global $CFG;
-require_once($CFG->dirroot . '/mod/quiz/editlib.php');
-
-
-/**
- * Unit tests for (some of) mod/quiz/editlib.php.
- *
- * @copyright 2009 Tim Hunt
- * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
- */
-class mod_quiz_editlib_testcase extends advanced_testcase {
- public function test_quiz_question_tostring() {
- $question = new stdClass();
- $question->qtype = 'multichoice';
- $question->name = 'The question name';
- $question->questiontext = '
What sort of inequality is x < y
';
- $question->questiontextformat = FORMAT_HTML;
-
- $summary = quiz_question_tostring($question);
- $this->assertEquals('The question name' .
- 'What sort of INEQUALITY is x < y[?]', $summary);
- }
-
- /**
- * Test removing slots from a quiz.
- */
- public function test_quiz_remove_slot() {
- global $SITE, $DB;
- $this->resetAfterTest(true);
- $this->setAdminUser();
-
- // Setup a quiz with 1 standard and 1 random question.
- $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
- $quiz = $quizgenerator->create_instance(array('course' => $SITE->id, 'questionsperpage' => 3, 'grade' => 100.0));
-
- $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
- $cat = $questiongenerator->create_question_category();
- $standardq = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
-
- quiz_add_quiz_question($standardq->id, $quiz);
- quiz_add_random_questions($quiz, 0, $cat->id, 1, false);
-
- // Get the random question.
- $randomq = $DB->get_record('question', array('qtype' => 'random'));
-
- $slotssql = "SELECT qs.*, q.qtype AS qtype
- FROM {quiz_slots} qs
- JOIN {question} q ON qs.questionid = q.id
- WHERE qs.quizid = ?
- ORDER BY qs.slot";
- $slots = $DB->get_records_sql($slotssql, array($quiz->id));
-
- // Check that the setup looks right.
- $this->assertEquals(2, count($slots));
- $slot = array_shift($slots);
- $this->assertEquals($standardq->id, $slot->questionid);
- $slot = array_shift($slots);
- $this->assertEquals($randomq->id, $slot->questionid);
- $this->assertEquals(2, $slot->slot);
-
- // Remove the standard question.
- quiz_remove_slot($quiz, 1);
-
- $slots = $DB->get_records_sql($slotssql, array($quiz->id));
-
- // Check the new ordering, and that the slot number was updated.
- $this->assertEquals(1, count($slots));
- $slot = array_shift($slots);
- $this->assertEquals($randomq->id, $slot->questionid);
- $this->assertEquals(1, $slot->slot);
-
- // Check the the standard question was not deleted.
- $count = $DB->count_records('question', array('id' => $standardq->id));
- $this->assertEquals(1, $count);
-
- // Remove the random question.
- quiz_remove_slot($quiz, 1);
-
- $slots = $DB->get_records_sql($slotssql, array($quiz->id));
-
- // Check that new ordering.
- $this->assertEquals(0, count($slots));
-
- // Check that the random question was deleted.
- $count = $DB->count_records('question', array('id' => $randomq->id));
- $this->assertEquals(0, $count);
- }
-}
diff --git a/mod/quiz/tests/lib_test.php b/mod/quiz/tests/lib_test.php
index 783f694d20f..02703ac05b0 100644
--- a/mod/quiz/tests/lib_test.php
+++ b/mod/quiz/tests/lib_test.php
@@ -18,7 +18,7 @@
* Unit tests for (some of) mod/quiz/locallib.php.
*
* @package mod_quiz
- * @category phpunit
+ * @category test
* @copyright 2008 The Open University
* @license http://www.gnu.org/copyleft/gpl.html GNU Public License
*/
@@ -58,6 +58,20 @@ class mod_quiz_lib_testcase extends advanced_testcase {
$this->assertEquals(quiz_format_grade($quiz, 0.12345678), '0');
}
+ public function test_quiz_get_grade_format() {
+ $quiz = new stdClass();
+ $quiz->decimalpoints = 2;
+ $this->assertEquals(quiz_get_grade_format($quiz), 2);
+ $this->assertEquals($quiz->questiondecimalpoints, -1);
+ $quiz->questiondecimalpoints = 2;
+ $this->assertEquals(quiz_get_grade_format($quiz), 2);
+ $quiz->decimalpoints = 3;
+ $quiz->questiondecimalpoints = -1;
+ $this->assertEquals(quiz_get_grade_format($quiz), 3);
+ $quiz->questiondecimalpoints = 4;
+ $this->assertEquals(quiz_get_grade_format($quiz), 4);
+ }
+
public function test_quiz_format_question_grade() {
$quiz = new stdClass();
$quiz->decimalpoints = 2;
diff --git a/mod/quiz/tests/locallib_test.php b/mod/quiz/tests/locallib_test.php
index e4575f5482a..07b6b44aba2 100644
--- a/mod/quiz/tests/locallib_test.php
+++ b/mod/quiz/tests/locallib_test.php
@@ -18,7 +18,7 @@
* Unit tests for (some of) mod/quiz/locallib.php.
*
* @package mod_quiz
- * @category phpunit
+ * @category test
* @copyright 2008 Tim Hunt
* @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
*/
@@ -142,4 +142,16 @@ class mod_quiz_locallib_testcase extends basic_testcase {
$this->assertEquals(mod_quiz_display_options::AFTER_CLOSE, quiz_attempt_state($quiz, $attempt));
}
+
+ public function test_quiz_question_tostring() {
+ $question = new stdClass();
+ $question->qtype = 'multichoice';
+ $question->name = 'The question name';
+ $question->questiontext = '
What sort of inequality is x < y
';
+ $question->questiontextformat = FORMAT_HTML;
+
+ $summary = quiz_question_tostring($question);
+ $this->assertEquals('The question name ' .
+ 'What sort of INEQUALITY is x < y[?]', $summary);
+ }
}
diff --git a/mod/quiz/tests/repaginate_test.php b/mod/quiz/tests/repaginate_test.php
new file mode 100644
index 00000000000..3d664cf2deb
--- /dev/null
+++ b/mod/quiz/tests/repaginate_test.php
@@ -0,0 +1,295 @@
+.
+
+/**
+ * Unit tests for the {@link \mod_quiz\repaginate} class.
+ * @package mod_quiz
+ * @category test
+ * @copyright 2014 The Open Univsersity
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/mod/quiz/editlib.php');
+require_once($CFG->dirroot . '/mod/quiz/locallib.php');
+require_once($CFG->dirroot . '/mod/quiz/classes/repaginate.php');
+
+
+/**
+ * Testable subclass, giving access to the protected methods of {@link \mod_quiz\repaginate}
+ * @copyright 2014 The Open Univsersity
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mod_quiz_repaginate_testable extends \mod_quiz\repaginate {
+
+ public function __construct($quizid = 0, $slots = null) {
+ return parent::__construct($quizid, $slots);
+ }
+ public function get_this_slot($slots, $slotnumber) {
+ return parent::get_this_slot($slots, $slotnumber);
+ }
+ public function get_slots_by_slotid($slots = null) {
+ return parent::get_slots_by_slotid($slots);
+ }
+ public function get_slots_by_slot_number($slots = null) {
+ return parent::get_slots_by_slot_number($slots);
+ }
+ public function repaginate_this_slot($slot, $newpagenumber) {
+ return parent::repaginate_this_slot($slot, $newpagenumber);
+ }
+ public function repaginate_next_slot($nextslotnumber, $type) {
+ return parent::repaginate_next_slot($nextslotnumber, $type);
+ }
+}
+
+/**
+ * Test for some parts of the repaginate class.
+ * @copyright 2014 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mod_quiz_repaginate_test extends advanced_testcase {
+
+ /** @var array stores the slots. */
+ private $quizslots;
+ /** @var mod_quiz_repaginate_testable the object being tested. */
+ private $repaginate = null;
+
+ public function setUp() {
+ $this->set_quiz_slots($this->get_quiz_object()->get_slots());
+ $this->repaginate = new mod_quiz_repaginate_testable(0, $this->quizslots);
+ }
+
+ public function tearDown() {
+ $this->repaginate = null;
+ }
+
+ /**
+ * Create a quiz, add five questions to the quiz
+ * which are all on one page and return the quiz object.
+ */
+ private function get_quiz_object() {
+ global $SITE;
+ $this->resetAfterTest(true);
+
+ // Make a quiz.
+ $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
+
+ $quiz = $quizgenerator->create_instance(array(
+ 'course' => $SITE->id, 'questionsperpage' => 0, 'grade' => 100.0, 'sumgrades' => 2));
+
+ // Create five questions.
+ $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
+ $cat = $questiongenerator->create_question_category();
+
+ $shortanswer = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
+ $numerical = $questiongenerator->create_question('numerical', null, array('category' => $cat->id));
+ $essay = $questiongenerator->create_question('essay', null, array('category' => $cat->id));
+ $truefalse = $questiongenerator->create_question('truefalse', null, array('category' => $cat->id));
+ $match = $questiongenerator->create_question('match', null, array('category' => $cat->id));
+
+ // Add them to the quiz.
+ quiz_add_quiz_question($shortanswer->id, $quiz);
+ quiz_add_quiz_question($numerical->id, $quiz);
+ quiz_add_quiz_question($essay->id, $quiz);
+ quiz_add_quiz_question($truefalse->id, $quiz);
+ quiz_add_quiz_question($match->id, $quiz);
+
+ // Return the quiz object.
+ return \mod_quiz\structure::create_for($quiz);
+ }
+
+ /**
+ * Set the quiz slots
+ * @param string $slots
+ */
+ private function set_quiz_slots($slots = null) {
+ if (!$slots) {
+ $this->quizslots = $this->get_quiz_object()->get_slots();
+ } else {
+ $this->quizslots = $slots;
+ }
+ }
+
+ /**
+ * Test the get_this_slot() method
+ */
+ public function test_get_this_slot() {
+ $this->set_quiz_slots();
+ $actual = array();
+ $expected = $this->repaginate->get_slots_by_slot_number();
+ $this->assertEquals($expected, $actual);
+
+ $slotsbyno = $this->repaginate->get_slots_by_slot_number($this->quizslots);
+ $slotnumber = 5;
+ $thisslot = $this->repaginate->get_this_slot($this->quizslots, $slotnumber);
+ $this->assertEquals($slotsbyno[$slotnumber], $thisslot);
+ }
+
+ public function test_get_slots_by_slotnumber() {
+ $this->set_quiz_slots();
+ $expected = array();
+ $actual = $this->repaginate->get_slots_by_slot_number();
+ $this->assertEquals($expected, $actual);
+
+ foreach ($this->quizslots as $slot) {
+ $expected[$slot->slot] = $slot;
+ }
+ $actual = $this->repaginate->get_slots_by_slot_number($this->quizslots);
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function test_get_slots_by_slotid() {
+ $this->set_quiz_slots();
+ $actual = $this->repaginate->get_slots_by_slotid();
+ $this->assertEquals(array(), $actual);
+
+ $slotsbyno = $this->repaginate->get_slots_by_slot_number($this->quizslots);
+ $actual = $this->repaginate->get_slots_by_slotid($slotsbyno);
+ $this->assertEquals($this->quizslots, $actual);
+ }
+
+ public function test_repaginate_n_questions_per_page() {
+ $this->set_quiz_slots();
+
+ // Expect 2 questions per page.
+ $expected = array();
+ foreach ($this->quizslots as $slot) {
+ // Page 1 contains Slots 1 and 2.
+ if ($slot->slot >= 1 && $slot->slot <= 2) {
+ $slot->page = 1;
+ }
+ // Page 2 contains slots 3 and 4.
+ if ($slot->slot >= 3 && $slot->slot <= 4) {
+ $slot->page = 2;
+ }
+ // Page 3 contains slots 5.
+ if ($slot->slot >= 5 && $slot->slot <= 6) {
+ $slot->page = 3;
+ }
+ $expected[$slot->id] = $slot;
+ }
+ $actual = $this->repaginate->repaginate_n_question_per_page($this->quizslots, 2);
+ $this->assertEquals($expected, $actual);
+
+ // Expect 3 questions per page.
+ $expected = array();
+ foreach ($this->quizslots as $slot) {
+ // Page 1 contains Slots 1, 2 and 3.
+ if ($slot->slot >= 1 && $slot->slot <= 3) {
+ $slot->page = 1;
+ }
+ // Page 2 contains slots 4 and 5.
+ if ($slot->slot >= 4 && $slot->slot <= 6) {
+ $slot->page = 2;
+ }
+ $expected[$slot->id] = $slot;
+ }
+ $actual = $this->repaginate->repaginate_n_question_per_page($this->quizslots, 3);
+ $this->assertEquals($expected, $actual);
+
+ // Expect 5 questions per page.
+ $expected = array();
+ foreach ($this->quizslots as $slot) {
+ // Page 1 contains Slots 1, 2, 3, 4 and 5.
+ if ($slot->slot > 0 && $slot->slot < 6) {
+ $slot->page = 1;
+ }
+ // Page 2 contains slots 6, 7, 8, 9 and 10.
+ if ($slot->slot > 5 && $slot->slot < 11) {
+ $slot->page = 2;
+ }
+ $expected[$slot->id] = $slot;
+ }
+ $actual = $this->repaginate->repaginate_n_question_per_page($this->quizslots, 5);
+ $this->assertEquals($expected, $actual);
+
+ // Expect 10 questions per page.
+ $expected = array();
+ foreach ($this->quizslots as $slot) {
+ // Page 1 contains Slots 1 to 10.
+ if ($slot->slot >= 1 && $slot->slot <= 10) {
+ $slot->page = 1;
+ }
+ // Page 2 contains slots 11 to 20.
+ if ($slot->slot >= 11 && $slot->slot <= 20) {
+ $slot->page = 2;
+ }
+ $expected[$slot->id] = $slot;
+ }
+ $actual = $this->repaginate->repaginate_n_question_per_page($this->quizslots, 10);
+ $this->assertEquals($expected, $actual);
+
+ // Expect 1 questions per page.
+ $expected = array();
+ $page = 1;
+ foreach ($this->quizslots as $slot) {
+ $slot->page = $page++;
+ $expected[$slot->id] = $slot;
+ }
+ $actual = $this->repaginate->repaginate_n_question_per_page($this->quizslots, 1);
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function test_repaginate_this_slot() {
+ $this->set_quiz_slots();
+ $slotsbyslotno = $this->repaginate->get_slots_by_slot_number($this->quizslots);
+ $slotnumber = 3;
+ $newpagenumber = 2;
+ $thisslot = $slotsbyslotno[3];
+ $thisslot->page = $newpagenumber;
+ $expected = $thisslot;
+ $actual = $this->repaginate->repaginate_this_slot($slotsbyslotno[3], $newpagenumber);
+ $this->assertEquals($expected, $actual);
+ }
+
+ public function test_repaginate_the_rest() {
+ $this->set_quiz_slots();
+ $slotfrom = 1;
+ $type = \mod_quiz\repaginate::LINK;
+ $expected = array();
+ foreach ($this->quizslots as $slot) {
+ if ($slot->slot > $slotfrom) {
+ $slot->page = $slot->page - 1;
+ $expected[$slot->id] = $slot;
+ }
+ }
+ $actual = $this->repaginate->repaginate_the_rest($this->quizslots, $slotfrom, $type, false);
+ $this->assertEquals($expected, $actual);
+
+ $slotfrom = 2;
+ $newslots = array();
+ foreach ($this->quizslots as $s) {
+ if ($s->slot === $slotfrom) {
+ $s->page = $s->page - 1;
+ }
+ $newslots[$s->id] = $s;
+ }
+
+ $type = \mod_quiz\repaginate::UNLINK;
+ $expected = array();
+ foreach ($this->quizslots as $slot) {
+ if ($slot->slot > ($slotfrom - 1)) {
+ $slot->page = $slot->page - 1;
+ $expected[$slot->id] = $slot;
+ }
+ }
+ $actual = $this->repaginate->repaginate_the_rest($newslots, $slotfrom, $type, false);
+ $this->assertEquals($expected, $actual);
+ }
+
+}
diff --git a/mod/quiz/tests/structure_test.php b/mod/quiz/tests/structure_test.php
new file mode 100644
index 00000000000..26f60bb03bc
--- /dev/null
+++ b/mod/quiz/tests/structure_test.php
@@ -0,0 +1,407 @@
+.
+
+/**
+ * Quiz events tests.
+ *
+ * @package mod_quiz
+ * @category test
+ * @copyright 2013 Adrian Greeve
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+defined('MOODLE_INTERNAL') || die();
+
+global $CFG;
+require_once($CFG->dirroot . '/mod/quiz/attemptlib.php');
+require_once($CFG->dirroot . '/mod/quiz/editlib.php');
+
+/**
+ * Unit tests for quiz events.
+ *
+ * @copyright 2013 Adrian Greeve
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+class mod_quiz_structure_testcase extends advanced_testcase {
+
+ /**
+ * Prepare the quiz object with standard data. Ready for testing.
+ */
+ protected function prepare_quiz_data() {
+
+ $this->resetAfterTest(true);
+
+ // Create a course.
+ $course = $this->getDataGenerator()->create_course();
+
+ // Make a quiz.
+ $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
+
+ $quiz = $quizgenerator->create_instance(array('course' => $course->id, 'questionsperpage' => 0,
+ 'grade' => 100.0, 'sumgrades' => 2));
+
+ $cm = get_coursemodule_from_instance('quiz', $quiz->id, $course->id);
+
+ return array($quiz, $cm, $course);
+ }
+
+ /**
+ * Test getting the quiz slots.
+ */
+ public function test_get_quiz_slots() {
+ // Get basic quiz.
+ list($quiz, $cm, $course) = $this->prepare_quiz_data();
+ $quizobj = new quiz($quiz, $cm, $course);
+ $structure = \mod_quiz\structure::create_for_quiz($quizobj);
+
+ // When no slots exist or slots propery is not set.
+ $slots = $structure->get_slots();
+ $this->assertInternalType('array', $slots);
+ $this->assertCount(0, $slots);
+
+ // Append slots to the quiz.
+ $this->add_eight_questions_to_the_quiz($quiz);
+ $structure = \mod_quiz\structure::create_for_quiz($quizobj);
+
+ // Are the correct slots returned?
+ $slots = $structure->get_slots();
+ $this->assertCount(8, $slots);
+ }
+
+ /**
+ * Test getting the quiz sections.
+ */
+ public function test_get_quiz_sections() {
+ // Get basic quiz.
+ list($quiz, $cm, $course) = $this->prepare_quiz_data();
+ $quizobj = new quiz($quiz, $cm, $course);
+ $structure = \mod_quiz\structure::create_for_quiz($quizobj);
+
+ // Are the correct sections returned?
+ $sections = $structure->get_quiz_sections();
+ $this->assertCount(1, $sections);
+ }
+
+ /**
+ * Verify that the given layout matches that expected.
+ * @param array $expectedlayout
+ * @param \mod_quiz\structure $structure
+ */
+ protected function assert_quiz_layout($expectedlayout, \mod_quiz\structure $structure) {
+ $slotnumber = 0;
+ foreach ($expectedlayout as $slotid => $page) {
+ $slotnumber += 1;
+ $this->assertEquals($slotid, $structure->get_question_in_slot($slotnumber)->slotid,
+ 'Wrong question in slot ' . $slotnumber);
+ $this->assertEquals($page, $structure->get_question_in_slot($slotnumber)->page,
+ 'Wrong page number for slot ' . $slotnumber);
+ }
+ }
+
+ /**
+ * Test moving slots in the quiz.
+ */
+ public function test_move_slot() {
+ // Create a test quiz with 8 questions.
+ list($quiz, $cm, $course) = $this->prepare_quiz_data();
+ $this->add_eight_questions_to_the_quiz($quiz);
+ $quizobj = new quiz($quiz, $cm, $course);
+ $structure = \mod_quiz\structure::create_for_quiz($quizobj);
+
+ // Store the original order of slots, so we can assert what has changed.
+ $originalslotids = array();
+ foreach ($structure->get_slots() as $slot) {
+ $originalslotids[$slot->slot] = $slot->id;
+ }
+
+ // Don't actually move anything. Check the layout is unchanged.
+ $idmove = $structure->get_question_in_slot(2)->slotid;
+ $idbefore = $structure->get_question_in_slot(1)->slotid;
+ $structure->move_slot($idmove, $idbefore, 2);
+
+ // Having called move, we need to reload $structure.
+ $structure = \mod_quiz\structure::create_for_quiz($quizobj);
+ $this->assert_quiz_layout(array(
+ $originalslotids[1] => 1,
+ $originalslotids[2] => 2,
+ $originalslotids[3] => 2,
+ $originalslotids[4] => 2,
+ $originalslotids[5] => 2,
+ $originalslotids[6] => 2,
+ $originalslotids[7] => 3,
+ $originalslotids[8] => 4,
+ ), $structure);
+
+ // Slots don't move. Page changed.
+ $idmove = $structure->get_question_in_slot(2)->slotid;
+ $idbefore = $structure->get_question_in_slot(1)->slotid;
+ $structure->move_slot($idmove, $idbefore, 1);
+
+ // Having called move, we need to reload $structure.
+ $structure = \mod_quiz\structure::create_for_quiz($quizobj);
+ $this->assert_quiz_layout(array(
+ $originalslotids[1] => 1,
+ $originalslotids[2] => 1,
+ $originalslotids[3] => 2,
+ $originalslotids[4] => 2,
+ $originalslotids[5] => 2,
+ $originalslotids[6] => 2,
+ $originalslotids[7] => 3,
+ $originalslotids[8] => 4,
+ ), $structure);
+
+ // Slots move 2 > 3. Page unchanged. Pages not reordered.
+ $idmove = $structure->get_question_in_slot(2)->slotid;
+ $idbefore = $structure->get_question_in_slot(3)->slotid;
+ $structure->move_slot($idmove, $idbefore, '2');
+
+ // Having called move, we need to reload $structure.
+ $structure = \mod_quiz\structure::create_for_quiz($quizobj);
+ $this->assert_quiz_layout(array(
+ $originalslotids[1] => 1,
+ $originalslotids[3] => 2,
+ $originalslotids[2] => 2,
+ $originalslotids[4] => 2,
+ $originalslotids[5] => 2,
+ $originalslotids[6] => 2,
+ $originalslotids[7] => 3,
+ $originalslotids[8] => 4,
+ ), $structure);
+
+ // Slots move 6 > 7. Page changed. Pages not reordered.
+ $idmove = $structure->get_question_in_slot(6)->slotid;
+ $idbefore = $structure->get_question_in_slot(7)->slotid;
+ $structure->move_slot($idmove, $idbefore, '3');
+
+ // Having called move, we need to reload $structure.
+ $structure = \mod_quiz\structure::create_for_quiz($quizobj);
+ $this->assert_quiz_layout(array(
+ $originalslotids[1] => 1,
+ $originalslotids[3] => 2,
+ $originalslotids[2] => 2,
+ $originalslotids[4] => 2,
+ $originalslotids[5] => 2,
+ $originalslotids[7] => 3,
+ $originalslotids[6] => 3,
+ $originalslotids[8] => 4,
+ ), $structure);
+
+ // Page changed slot 6 . Pages not reordered.
+ $idmove = $structure->get_question_in_slot(6)->slotid;
+ $idbefore = $structure->get_question_in_slot(5)->slotid;
+ $structure->move_slot($idmove, $idbefore, 2);
+
+ // Having called move, we need to reload $structure.
+ $structure = \mod_quiz\structure::create_for_quiz($quizobj);
+ $this->assert_quiz_layout(array(
+ $originalslotids[1] => 1,
+ $originalslotids[3] => 2,
+ $originalslotids[2] => 2,
+ $originalslotids[4] => 2,
+ $originalslotids[5] => 2,
+ $originalslotids[7] => 2,
+ $originalslotids[6] => 3,
+ $originalslotids[8] => 4,
+ ), $structure);
+
+ // Slots move 1 > 2. Page changed. Page 2 becomes page 1. Pages reordered.
+ $idmove = $structure->get_question_in_slot(1)->slotid;
+ $idbefore = $structure->get_question_in_slot(2)->slotid;
+ $structure->move_slot($idmove, $idbefore, 2);
+
+ // Having called move, we need to reload $structure.
+ $structure = \mod_quiz\structure::create_for_quiz($quizobj);
+ $this->assert_quiz_layout(array(
+ $originalslotids[3] => 1,
+ $originalslotids[1] => 1,
+ $originalslotids[2] => 1,
+ $originalslotids[4] => 1,
+ $originalslotids[5] => 1,
+ $originalslotids[7] => 1,
+ $originalslotids[6] => 2,
+ $originalslotids[8] => 3,
+ ), $structure);
+
+ // Slots move 7 > 3. Page changed. Page 3 becomes page 2. Pages reordered.
+ $idmove = $structure->get_question_in_slot(7)->slotid;
+ $idbefore = $structure->get_question_in_slot(2)->slotid;
+ $structure->move_slot($idmove, $idbefore, 1);
+
+ // Having called move, we need to reload $structure.
+ $structure = \mod_quiz\structure::create_for_quiz($quizobj);
+ $this->assert_quiz_layout(array(
+ $originalslotids[3] => 1,
+ $originalslotids[1] => 1,
+ $originalslotids[6] => 1,
+ $originalslotids[2] => 1,
+ $originalslotids[4] => 1,
+ $originalslotids[5] => 1,
+ $originalslotids[7] => 1,
+ $originalslotids[8] => 2,
+ ), $structure);
+
+ // Slots move 2 > top. No page changes.
+ $idmove = $structure->get_question_in_slot(2)->slotid;
+ $structure->move_slot($idmove, 0, 1);
+
+ // Having called move, we need to reload $structure.
+ $structure = \mod_quiz\structure::create_for_quiz($quizobj);
+ $this->assert_quiz_layout(array(
+ $originalslotids[1] => 1,
+ $originalslotids[3] => 1,
+ $originalslotids[6] => 1,
+ $originalslotids[2] => 1,
+ $originalslotids[4] => 1,
+ $originalslotids[5] => 1,
+ $originalslotids[7] => 1,
+ $originalslotids[8] => 2,
+ ), $structure);
+ }
+
+ /**
+ * Test removing slots from a quiz.
+ */
+ public function test_quiz_remove_slot() {
+ global $SITE, $DB;
+
+ $this->resetAfterTest(true);
+ $this->setAdminUser();
+
+ // Setup a quiz with 1 standard and 1 random question.
+ $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
+ $quiz = $quizgenerator->create_instance(array('course' => $SITE->id, 'questionsperpage' => 3, 'grade' => 100.0));
+
+ $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
+ $cat = $questiongenerator->create_question_category();
+ $standardq = $questiongenerator->create_question('shortanswer', null, array('category' => $cat->id));
+
+ quiz_add_quiz_question($standardq->id, $quiz);
+ quiz_add_random_questions($quiz, 0, $cat->id, 1, false);
+
+ // Get the random question.
+ $randomq = $DB->get_record('question', array('qtype' => 'random'));
+
+ $structure = \mod_quiz\structure::create_for($quiz);
+
+ // Check that the setup looks right.
+ $this->assertEquals(2, $structure->get_question_count());
+ $this->assertEquals($standardq->id, $structure->get_question_in_slot(1)->questionid);
+ $this->assertEquals($randomq->id, $structure->get_question_in_slot(2)->questionid);
+
+ // Remove the standard question.
+ $structure->remove_slot($quiz, 1);
+
+ $alteredstructure = \mod_quiz\structure::create_for($quiz);
+
+ // Check the new ordering, and that the slot number was updated.
+ $this->assertEquals(1, $alteredstructure->get_question_count());
+ $this->assertEquals($randomq->id, $alteredstructure->get_question_in_slot(1)->questionid);
+
+ // Check that the ordinary question was not deleted.
+ $this->assertTrue($DB->record_exists('question', array('id' => $standardq->id)));
+
+ // Remove the random question.
+ $structure->remove_slot($quiz, 1);
+ $alteredstructure = \mod_quiz\structure::create_for($quiz);
+
+ // Check that new ordering.
+ $this->assertEquals(0, $alteredstructure->get_question_count());
+
+ // Check that the random question was deleted.
+ $this->assertFalse($DB->record_exists('question', array('id' => $randomq->id)));
+ }
+
+ /**
+ * Test updating pagebreaks in the quiz.
+ */
+ public function test_update_page_break() {
+ // Create a test quiz with 8 questions.
+ list($quiz, $cm, $course) = $this->prepare_quiz_data();
+ $this->add_eight_questions_to_the_quiz($quiz);
+ $quizobj = new quiz($quiz, $cm, $course);
+ $structure = \mod_quiz\structure::create_for_quiz($quizobj);
+
+ // Store the original order of slots, so we can assert what has changed.
+ $originalslotids = array();
+ foreach ($structure->get_slots() as $slot) {
+ $originalslotids[$slot->slot] = $slot->id;
+ }
+
+ // Test removing a page break.
+ $slotid = $structure->get_question_in_slot(2)->slotid;
+ $type = \mod_quiz\repaginate::LINK;
+ $slots = $structure->update_page_break($quiz, $slotid, $type);
+
+ // Having called update page break, we need to reload $structure.
+ $structure = \mod_quiz\structure::create_for_quiz($quizobj);
+ $this->assert_quiz_layout(array(
+ $originalslotids[1] => 1,
+ $originalslotids[2] => 1,
+ $originalslotids[3] => 1,
+ $originalslotids[4] => 1,
+ $originalslotids[5] => 1,
+ $originalslotids[6] => 1,
+ $originalslotids[7] => 2,
+ $originalslotids[8] => 3,
+ ), $structure);
+
+ // Test adding a page break.
+ $slotid = $structure->get_question_in_slot(2)->slotid;
+ $type = \mod_quiz\repaginate::UNLINK;
+ $slots = $structure->update_page_break($quiz, $slotid, $type);
+
+ // Having called update page break, we need to reload $structure.
+ $structure = \mod_quiz\structure::create_for_quiz($quizobj);
+ $this->assert_quiz_layout(array(
+ $originalslotids[1] => 1,
+ $originalslotids[2] => 2,
+ $originalslotids[3] => 2,
+ $originalslotids[4] => 2,
+ $originalslotids[5] => 2,
+ $originalslotids[6] => 2,
+ $originalslotids[7] => 3,
+ $originalslotids[8] => 4,
+ ), $structure);
+ }
+
+ /**
+ * Populate quiz with eight questions.
+ * @param stdClass $quiz the quiz to add to.
+ */
+ public function add_eight_questions_to_the_quiz($quiz) {
+ // We add 8 numerical questions with this layout:
+ // Slot 1 2 3 4 5 6 7 8
+ // Page 1 2 2 2 2 2 3 4.
+
+ // Create slots.
+ $pagenumber = 1;
+ $pagenumberdefaults = array(2, 7, 8);
+
+ // Create a couple of questions.
+ $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
+
+ $cat = $questiongenerator->create_question_category();
+ for ($i = 0; $i < 8; $i ++) {
+ $numq = $questiongenerator->create_question('numerical', null, array('category' => $cat->id));
+
+ if (in_array($i + 1, $pagenumberdefaults)) {
+ $pagenumber++;
+ }
+ // Add them to the quiz.
+ quiz_add_quiz_question($numq->id, $quiz, $pagenumber);
+ }
+ }
+}
diff --git a/mod/quiz/upgrade.txt b/mod/quiz/upgrade.txt
index 808e4e00c5c..911e92ca610 100644
--- a/mod/quiz/upgrade.txt
+++ b/mod/quiz/upgrade.txt
@@ -1,5 +1,57 @@
This files describes API changes in the quiz code.
+=== 2.8 ===
+
+* Major changes to the Edit quiz page.
+
+ The goal of this work was to increase usability, and also clean up the page
+ enough that it will be possible to add new features in future.
+
+ Display of mod/quiz/edit.php is now entirely generated by
+ mod_quiz\output\edit_renderer. This uses a helper class mod_quiz\structure
+ to provide details of the structure of the quiz, and mod_quiz\repaginate to
+ alter that structure. (Actually, there are still some modification methods on
+ mod_quiz\structure. Expect that to be cleaned up in future.)
+
+ The new code uses much more ajax, and there are new scripts mod/quiz/edit_rest.php
+ and mod/quiz/repaginate.php to handle this. (Again, don't be surprised if those
+ two scripts get merged in future.) Also questionbank.ajax.php (which may, in
+ future, be made more generic, and moved into the core question bank code.)
+
+ As a result of this, mod/quiz/editlib.php is now much shorter than it was.
+ (In future, expect the remaining code in here to move into mod/quiz/classes.)
+
+ Here is a list of all the old functions or classes that have changed.
+ If you used any of these in custom code, you will need to update your code.
+ (Note that many of these functions should have been considered private internals
+ of the quiz module, and you should not have been using them!)
+
+ From editlib.php:
+ quiz_remove_slot
+ quiz_delete_empty_page
+ quiz_add_page_break_after_slot - Use methods of structure or repaginate
+ quiz_update_slot_maxmark - classes instead.
+ _quiz_move_question
+ quiz_move_question_up
+ quiz_move_question_down
+
+ quiz_print_question_list
+ quiz_print_pagecontrols
+ quiz_print_singlequestion - Use methods of edit_renderer instead.
+ quiz_print_randomquestion
+ quiz_print_singlequestion_reordertool
+ quiz_print_randomquestion_reordertool
+ print_random_option_icon
+ quiz_print_grading_form
+ quiz_print_status_bar
+
+ Moved from editlib.php to locallib.php:
+ quiz_question_tostring - now always returns a string (the only option used).
+ The $return argument has gone.
+
+ Old editing JavaScript (e.g. mod/quiz/edit.js) is gone. Replaced with YUI modules.
+
+
=== 2.7.1 ===
* The function quiz_fire_attempt_started_event has been removed. This function
diff --git a/mod/quiz/version.php b/mod/quiz/version.php
index 0d368c88427..808d627f84f 100644
--- a/mod/quiz/version.php
+++ b/mod/quiz/version.php
@@ -24,7 +24,7 @@
defined('MOODLE_INTERNAL') || die();
-$plugin->version = 2014052801; // The current module version (Date: YYYYMMDDXX).
+$plugin->version = 2014100200; // The current module version (Date: YYYYMMDDXX).
$plugin->requires = 2014050800; // Requires this Moodle version.
$plugin->component = 'mod_quiz'; // Full name of the plugin (used for diagnostics).
$plugin->cron = 60;
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-dragdrop/moodle-mod_quiz-dragdrop-debug.js b/mod/quiz/yui/build/moodle-mod_quiz-dragdrop/moodle-mod_quiz-dragdrop-debug.js
new file mode 100644
index 00000000000..b1d586c1d60
--- /dev/null
+++ b/mod/quiz/yui/build/moodle-mod_quiz-dragdrop/moodle-mod_quiz-dragdrop-debug.js
@@ -0,0 +1,562 @@
+YUI.add('moodle-mod_quiz-dragdrop', function (Y, NAME) {
+
+/**
+ * Drag and Drop for Quiz sections and slots.
+ *
+ * @module moodle-mod-quiz-dragdrop
+ */
+
+var CSS = {
+ ACTIONAREA: '.actions',
+ ACTIVITY: 'activity',
+ ACTIVITYINSTANCE: 'activityinstance',
+ CONTENT: 'content',
+ COURSECONTENT: 'mod-quiz-edit-content',
+ EDITINGMOVE: 'editing_move',
+ ICONCLASS: 'iconsmall',
+ JUMPMENU: 'jumpmenu',
+ LEFT: 'left',
+ LIGHTBOX: 'lightbox',
+ MOVEDOWN: 'movedown',
+ MOVEUP: 'moveup',
+ PAGE : 'page',
+ PAGECONTENT: 'page-content',
+ RIGHT: 'right',
+ SECTION: 'section',
+ SECTIONADDMENUS: 'section_add_menus',
+ SECTIONHANDLE: 'section-handle',
+ SLOTS: 'slots',
+ SUMMARY: 'summary',
+ SECTIONDRAGGABLE: 'sectiondraggable'
+},
+// The CSS selectors we use.
+SELECTOR = {
+ PAGE: 'li.page',
+ SLOT: 'li.slot'
+};
+/**
+ * Section drag and drop.
+ *
+ * @class M.mod_quiz.dragdrop.section
+ * @constructor
+ * @extends M.core.dragdrop
+ */
+var DRAGSECTION = function() {
+ DRAGSECTION.superclass.constructor.apply(this, arguments);
+};
+Y.extend(DRAGSECTION, M.core.dragdrop, {
+ sectionlistselector: null,
+
+ initializer: function() {
+ // Set group for parent class
+ this.groups = [ CSS.SECTIONDRAGGABLE ];
+ this.samenodeclass = M.mod_quiz.edit.get_sectionwrapperclass();
+ this.parentnodeclass = M.mod_quiz.edit.get_containerclass();
+
+ // Check if we are in single section mode
+ if (Y.Node.one('.' + CSS.JUMPMENU)) {
+ return false;
+ }
+ // Initialise sections dragging
+ this.sectionlistselector = M.mod_quiz.edit.get_section_wrapper(Y);
+ if (this.sectionlistselector) {
+ this.sectionlistselector = '.' + CSS.COURSECONTENT + ' ' + this.sectionlistselector;
+
+ this.setup_for_section(this.sectionlistselector);
+
+ // Make each li element in the lists of sections draggable
+ var del = new Y.DD.Delegate({
+ container: '.' + CSS.COURSECONTENT,
+ nodes: '.' + CSS.SECTIONDRAGGABLE,
+ target: true,
+ handles: ['.' + CSS.LEFT],
+ dragConfig: {groups: this.groups}
+ });
+ del.dd.plug(Y.Plugin.DDProxy, {
+ // Don't move the node at the end of the drag
+ moveOnEnd: false
+ });
+ del.dd.plug(Y.Plugin.DDConstrained, {
+ // Keep it inside the .mod-quiz-edit-content
+ constrain: '#' + CSS.PAGECONTENT,
+ stickY: true
+ });
+ del.dd.plug(Y.Plugin.DDWinScroll);
+ }
+ },
+
+ /**
+ * Apply dragdrop features to the specified selector or node that refers to section(s)
+ *
+ * @method setup_for_section
+ * @param {String} baseselector The CSS selector or node to limit scope to
+ */
+ setup_for_section: function(baseselector) {
+ Y.Node.all(baseselector).each(function(sectionnode) {
+ // Determine the section ID
+ var sectionid = Y.Moodle.core_course.util.section.getId(sectionnode);
+
+ // We skip the top section as it is not draggable
+ if (sectionid > 0) {
+ // Remove move icons
+ var movedown = sectionnode.one('.' + CSS.RIGHT + ' a.' + CSS.MOVEDOWN);
+ var moveup = sectionnode.one('.' + CSS.RIGHT + ' a.' + CSS.MOVEUP);
+
+ // Add dragger icon
+ var title = M.util.get_string('movesection', 'moodle', sectionid);
+ var cssleft = sectionnode.one('.' + CSS.LEFT);
+
+ if ((movedown || moveup) && cssleft) {
+ cssleft.setStyle('cursor', 'move');
+ cssleft.appendChild(this.get_drag_handle(title, CSS.SECTIONHANDLE, 'icon', true));
+
+ if (moveup) {
+ moveup.remove();
+ }
+ if (movedown) {
+ movedown.remove();
+ }
+
+ // This section can be moved - add the class to indicate this to Y.DD.
+ sectionnode.addClass(CSS.SECTIONDRAGGABLE);
+ }
+ }
+ }, this);
+ },
+
+ /*
+ * Drag-dropping related functions
+ */
+ drag_start: function(e) {
+ // Get our drag object
+ var drag = e.target;
+ // Creat a dummy structure of the outer elemnents for clean styles application
+ var containernode = Y.Node.create('<' + M.mod_quiz.edit.get_containernode() + '>' + M.mod_quiz.edit.get_containernode() + '>');
+ containernode.addClass(M.mod_quiz.edit.get_containerclass());
+ var sectionnode = Y.Node.create('<' + M.mod_quiz.edit.get_sectionwrappernode() + '>' + M.mod_quiz.edit.get_sectionwrappernode() + '>');
+ sectionnode.addClass( M.mod_quiz.edit.get_sectionwrapperclass());
+ sectionnode.setStyle('margin', 0);
+ sectionnode.setContent(drag.get('node').get('innerHTML'));
+ containernode.appendChild(sectionnode);
+ drag.get('dragNode').setContent(containernode);
+ drag.get('dragNode').addClass(CSS.COURSECONTENT);
+ },
+
+ drag_dropmiss: function(e) {
+ // Missed the target, but we assume the user intended to drop it
+ // on the last last ghost node location, e.drag and e.drop should be
+ // prepared by global_drag_dropmiss parent so simulate drop_hit(e).
+ this.drop_hit(e);
+ },
+
+ get_section_index: function(node) {
+ var sectionlistselector = '.' + CSS.COURSECONTENT + ' ' + M.mod_quiz.edit.get_section_selector(Y),
+ sectionList = Y.all(sectionlistselector),
+ nodeIndex = sectionList.indexOf(node),
+ zeroIndex = sectionList.indexOf(Y.one('#section-0'));
+
+ return (nodeIndex - zeroIndex);
+ },
+
+ drop_hit: function(e) {
+ var drag = e.drag;
+
+ // Get references to our nodes and their IDs.
+ var dragnode = drag.get('node'),
+ dragnodeid = Y.Moodle.core_course.util.section.getId(dragnode),
+ loopstart = dragnodeid,
+
+ dropnodeindex = this.get_section_index(dragnode),
+ loopend = dropnodeindex;
+
+ if (dragnodeid === dropnodeindex) {
+ Y.log("Skipping move - same location moving " + dragnodeid + " to " + dropnodeindex, 'debug', 'moodle-mod_quiz-dragdrop');
+ return;
+ }
+
+ Y.log("Moving from position " + dragnodeid + " to position " + dropnodeindex, 'debug', 'moodle-mod_quiz-dragdrop');
+
+ if (loopstart > loopend) {
+ // If we're going up, we need to swap the loop order
+ // because loops can't go backwards.
+ loopstart = dropnodeindex;
+ loopend = dragnodeid;
+ }
+
+ // Get the list of nodes.
+ drag.get('dragNode').removeClass(CSS.COURSECONTENT);
+ var sectionlist = Y.Node.all(this.sectionlistselector);
+
+ // Add a lightbox if it's not there.
+ var lightbox = M.util.add_lightbox(Y, dragnode);
+
+ // Handle any variables which we must pass via AJAX.
+ var params = {},
+ pageparams = this.get('config').pageparams,
+ varname;
+
+ for (varname in pageparams) {
+ if (!pageparams.hasOwnProperty(varname)) {
+ continue;
+ }
+ params[varname] = pageparams[varname];
+ }
+
+ // Prepare request parameters
+ params.sesskey = M.cfg.sesskey;
+ params.courseid = this.get('courseid');
+ params.quizid = this.get('quizid');
+ params['class'] = 'section';
+ params.field = 'move';
+ params.id = dragnodeid;
+ params.value = dropnodeindex;
+
+ // Perform the AJAX request.
+ var uri = M.cfg.wwwroot + this.get('ajaxurl');
+ Y.io(uri, {
+ method: 'POST',
+ data: params,
+ on: {
+ start: function() {
+ lightbox.show();
+ },
+ success: function(tid, response) {
+ // Update section titles, we can't simply swap them as
+ // they might have custom title
+ try {
+ var responsetext = Y.JSON.parse(response.responseText);
+ if (responsetext.error) {
+ new M.core.ajaxException(responsetext);
+ }
+ M.mod_quiz.edit.process_sections(Y, sectionlist, responsetext, loopstart, loopend);
+ } catch (e) {}
+
+ // Update all of the section IDs - first unset them, then set them
+ // to avoid duplicates in the DOM.
+ var index;
+
+ // Classic bubble sort algorithm is applied to the section
+ // nodes between original drag node location and the new one.
+ var swapped = false;
+ do {
+ swapped = false;
+ for (index = loopstart; index <= loopend; index++) {
+ if (Y.Moodle.core_course.util.section.getId(sectionlist.item(index - 1)) >
+ Y.Moodle.core_course.util.section.getId(sectionlist.item(index))) {
+ Y.log("Swapping " + Y.Moodle.core_course.util.section.getId(sectionlist.item(index - 1)) +
+ " with " + Y.Moodle.core_course.util.section.getId(sectionlist.item(index)),
+ "debug", "moodle-mod_quiz-dragdrop");
+ // Swap section id.
+ var sectionid = sectionlist.item(index - 1).get('id');
+ sectionlist.item(index - 1).set('id', sectionlist.item(index).get('id'));
+ sectionlist.item(index).set('id', sectionid);
+
+ // See what format needs to swap.
+ M.mod_quiz.edit.swap_sections(Y, index - 1, index);
+
+ // Update flag.
+ swapped = true;
+ }
+ }
+ loopend = loopend - 1;
+ } while (swapped);
+
+ window.setTimeout(function() {
+ lightbox.hide();
+ }, 250);
+ },
+
+ failure: function(tid, response) {
+ this.ajax_failure(response);
+ lightbox.hide();
+ }
+ },
+ context:this
+ });
+ }
+
+}, {
+ NAME: 'mod_quiz-dragdrop-section',
+ ATTRS: {
+ courseid: {
+ value: null
+ },
+ quizid: {
+ value: null
+ },
+ ajaxurl: {
+ value: 0
+ },
+ config: {
+ value: 0
+ }
+ }
+});
+
+M.mod_quiz = M.mod_quiz || {};
+M.mod_quiz.init_section_dragdrop = function(params) {
+ new DRAGSECTION(params);
+};
+/**
+ * Resource drag and drop.
+ *
+ * @class M.course.dragdrop.resource
+ * @constructor
+ * @extends M.core.dragdrop
+ */
+var DRAGRESOURCE = function() {
+ DRAGRESOURCE.superclass.constructor.apply(this, arguments);
+};
+Y.extend(DRAGRESOURCE, M.core.dragdrop, {
+ initializer: function() {
+ // Set group for parent class
+ this.groups = ['resource'];
+ this.samenodeclass = CSS.ACTIVITY;
+ this.parentnodeclass = CSS.SECTION;
+ //this.resourcedraghandle = this.get_drag_handle(M.util.get_string('movecoursemodule', 'moodle'), CSS.EDITINGMOVE, CSS.ICONCLASS, true);
+ this.resourcedraghandle = this.get_drag_handle(M.str.moodle.move, CSS.EDITINGMOVE, CSS.ICONCLASS, true);
+
+ this.samenodelabel = {
+ identifier: 'dragtoafter',
+ component: 'quiz'
+ };
+ this.parentnodelabel = {
+ identifier: 'dragtostart',
+ component: 'quiz'
+ };
+
+ // Go through all sections
+ var sectionlistselector = M.mod_quiz.edit.get_section_selector(Y);
+ if (sectionlistselector) {
+ sectionlistselector = '.' + CSS.COURSECONTENT + ' ' + sectionlistselector;
+ this.setup_for_section(sectionlistselector);
+
+ // Initialise drag & drop for all resources/activities
+ var nodeselector = sectionlistselector.slice(CSS.COURSECONTENT.length + 2) + ' li.' + CSS.ACTIVITY;
+ var del = new Y.DD.Delegate({
+ container: '.' + CSS.COURSECONTENT,
+ nodes: nodeselector,
+ target: true,
+ handles: ['.' + CSS.EDITINGMOVE],
+ dragConfig: {groups: this.groups}
+ });
+ del.dd.plug(Y.Plugin.DDProxy, {
+ // Don't move the node at the end of the drag
+ moveOnEnd: false,
+ cloneNode: true
+ });
+ del.dd.plug(Y.Plugin.DDConstrained, {
+ // Keep it inside the .mod-quiz-edit-content
+ constrain: '#' + CSS.SLOTS
+ });
+ del.dd.plug(Y.Plugin.DDWinScroll);
+
+ M.mod_quiz.quizbase.register_module(this);
+ M.mod_quiz.dragres = this;
+ }
+ },
+
+ /**
+ * Apply dragdrop features to the specified selector or node that refers to section(s)
+ *
+ * @method setup_for_section
+ * @param {String} baseselector The CSS selector or node to limit scope to
+ */
+ setup_for_section: function(baseselector) {
+ Y.Node.all(baseselector).each(function(sectionnode) {
+ var resources = sectionnode.one('.' + CSS.CONTENT + ' ul.' + CSS.SECTION);
+ // See if resources ul exists, if not create one.
+ if (!resources) {
+ resources = Y.Node.create('
');
+ resources.addClass(CSS.SECTION);
+ sectionnode.one('.' + CSS.CONTENT + ' div.' + CSS.SUMMARY).insert(resources, 'after');
+ }
+ resources.setAttribute('data-draggroups', this.groups.join(' '));
+ // Define empty ul as droptarget, so that item could be moved to empty list
+ new Y.DD.Drop({
+ node: resources,
+ groups: this.groups,
+ padding: '20 0 20 0'
+ });
+
+ // Initialise each resource/activity in this section
+ this.setup_for_resource('#' + sectionnode.get('id') + ' li.' + CSS.ACTIVITY);
+ }, this);
+ },
+
+ /**
+ * Apply dragdrop features to the specified selector or node that refers to resource(s)
+ *
+ * @method setup_for_resource
+ * @param {String} baseselector The CSS selector or node to limit scope to
+ */
+ setup_for_resource: function(baseselector) {
+ Y.Node.all(baseselector).each(function(resourcesnode) {
+ // Replace move icons
+ var move = resourcesnode.one('a.' + CSS.EDITINGMOVE);
+ if (move) {
+ move.replace(this.resourcedraghandle.cloneNode(true));
+ }
+ }, this);
+ },
+
+ drag_start: function(e) {
+ // Get our drag object
+ var drag = e.target;
+ drag.get('dragNode').setContent(drag.get('node').get('innerHTML'));
+ drag.get('dragNode').all('img.iconsmall').setStyle('vertical-align', 'baseline');
+ },
+
+ drag_dropmiss: function(e) {
+ // Missed the target, but we assume the user intended to drop it
+ // on the last ghost node location, e.drag and e.drop should be
+ // prepared by global_drag_dropmiss parent so simulate drop_hit(e).
+ this.drop_hit(e);
+ },
+
+ drop_hit: function(e) {
+ var drag = e.drag;
+ // Get a reference to our drag node
+ var dragnode = drag.get('node');
+ var dropnode = e.drop.get('node');
+
+ // Add spinner if it not there
+ var actionarea = dragnode.one(CSS.ACTIONAREA);
+ var spinner = M.util.add_spinner(Y, actionarea);
+
+ var params = {};
+
+ // Handle any variables which we must pass back through to
+ var pageparams = this.get('config').pageparams;
+ var varname;
+ for (varname in pageparams) {
+ params[varname] = pageparams[varname];
+ }
+
+ // Prepare request parameters
+ params.sesskey = M.cfg.sesskey;
+ params.courseid = this.get('courseid');
+ params.quizid = this.get('quizid');
+ params['class'] = 'resource';
+ params.field = 'move';
+ params.id = Number(Y.Moodle.mod_quiz.util.slot.getId(dragnode));
+ params.sectionId = Y.Moodle.core_course.util.section.getId(dropnode.ancestor(M.mod_quiz.edit.get_section_wrapper(Y), true));
+
+ var previousslot = dragnode.previous(SELECTOR.SLOT);
+ if (previousslot) {
+ params.previousid = Number(Y.Moodle.mod_quiz.util.slot.getId(previousslot));
+ }
+
+ var previouspage = dragnode.previous(SELECTOR.PAGE);
+ if (previouspage) {
+ params.page = Number(Y.Moodle.mod_quiz.util.page.getId(previouspage));
+ }
+
+ // Do AJAX request
+ var uri = M.cfg.wwwroot + this.get('ajaxurl');
+
+ Y.io(uri, {
+ method: 'POST',
+ data: params,
+ on: {
+ start: function() {
+ this.lock_drag_handle(drag, CSS.EDITINGMOVE);
+ spinner.show();
+ },
+ success: function(tid, response) {
+ var responsetext = Y.JSON.parse(response.responseText);
+ var params = {element: dragnode, visible: responsetext.visible};
+ M.mod_quiz.quizbase.invoke_function('set_visibility_resource_ui', params);
+ Y.Moodle.mod_quiz.util.slot.reorder_slots();
+ this.unlock_drag_handle(drag, CSS.EDITINGMOVE);
+ window.setTimeout(function() {
+ spinner.hide();
+ }, 250);
+ window.location.reload(true);
+ },
+ failure: function(tid, response) {
+ this.ajax_failure(response);
+ this.unlock_drag_handle(drag, CSS.SECTIONHANDLE);
+ spinner.hide();
+ window.location.reload(true);
+ }
+ },
+ context:this
+ });
+ },
+
+ global_drop_over: function(e) {
+ //Overriding parent method so we can stop the slots being dragged before the first page node.
+
+ // Check that drop object belong to correct group.
+ if (!e.drop || !e.drop.inGroup(this.groups)) {
+ return;
+ }
+
+ // Get a reference to our drag and drop nodes.
+ var drag = e.drag.get('node'),
+ drop = e.drop.get('node');
+
+ // Save last drop target for the case of missed target processing.
+ this.lastdroptarget = e.drop;
+
+ // Are we dropping within the same parent node?
+ if (drop.hasClass(this.samenodeclass)) {
+ var where;
+
+ if (this.goingup) {
+ where = "before";
+ } else {
+ where = "after";
+ }
+
+ drop.insert(drag, where);
+ } else if ((drop.hasClass(this.parentnodeclass) || drop.test('[data-droptarget="1"]')) && !drop.contains(drag)) {
+ // We are dropping on parent node and it is empty
+ if (this.goingup) {
+ drop.append(drag);
+ } else {
+ drop.prepend(drag);
+ }
+ }
+ this.drop_over(e);
+ }
+}, {
+ NAME: 'mod_quiz-dragdrop-resource',
+ ATTRS: {
+ courseid: {
+ value: null
+ },
+ quizid: {
+ value: null
+ },
+ ajaxurl: {
+ value: 0
+ },
+ config: {
+ value: 0
+ }
+ }
+});
+
+M.mod_quiz = M.mod_quiz || {};
+M.mod_quiz.init_resource_dragdrop = function(params) {
+ new DRAGRESOURCE(params);
+};
+
+
+}, '@VERSION@', {
+ "requires": [
+ "base",
+ "node",
+ "io",
+ "dom",
+ "dd",
+ "dd-scroll",
+ "moodle-core-dragdrop",
+ "moodle-core-notification",
+ "moodle-mod_quiz-quizbase",
+ "moodle-mod_quiz-util",
+ "moodle-course-util"
+ ]
+});
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-dragdrop/moodle-mod_quiz-dragdrop-min.js b/mod/quiz/yui/build/moodle-mod_quiz-dragdrop/moodle-mod_quiz-dragdrop-min.js
new file mode 100644
index 00000000000..bdc012617af
--- /dev/null
+++ b/mod/quiz/yui/build/moodle-mod_quiz-dragdrop/moodle-mod_quiz-dragdrop-min.js
@@ -0,0 +1,2 @@
+YUI.add("moodle-mod_quiz-dragdrop",function(e,t){var n={ACTIONAREA:".actions",ACTIVITY:"activity",ACTIVITYINSTANCE:"activityinstance",CONTENT:"content",COURSECONTENT:"mod-quiz-edit-content",EDITINGMOVE:"editing_move",ICONCLASS:"iconsmall",JUMPMENU:"jumpmenu",LEFT:"left",LIGHTBOX:"lightbox",MOVEDOWN:"movedown",MOVEUP:"moveup",PAGE:"page",PAGECONTENT:"page-content",RIGHT:"right",SECTION:"section",SECTIONADDMENUS:"section_add_menus",SECTIONHANDLE:"section-handle",SLOTS:"slots",SUMMARY:"summary",SECTIONDRAGGABLE:"sectiondraggable"},r={PAGE:"li.page",SLOT:"li.slot"},i=function(){i.superclass.constructor.apply(this,arguments)};e.extend(i,M.core.dragdrop,{sectionlistselector:null,initializer:function(){this.groups=[n.SECTIONDRAGGABLE],this.samenodeclass=M.mod_quiz.edit.get_sectionwrapperclass(),this.parentnodeclass=M.mod_quiz.edit.get_containerclass();if(e.Node.one("."+n.JUMPMENU))return!1;this.sectionlistselector=M.mod_quiz.edit.get_section_wrapper(e);if(this.sectionlistselector){this.sectionlistselector="."+n.COURSECONTENT+" "+this.sectionlistselector,this.setup_for_section(this.sectionlistselector);var t=new e.DD.Delegate({container:"."+n.COURSECONTENT,nodes:"."+n.SECTIONDRAGGABLE,target:!0,handles:["."+n.LEFT],dragConfig:{groups:this.groups}});t.dd.plug(e.Plugin.DDProxy,{moveOnEnd:!1}),t.dd.plug(e.Plugin.DDConstrained,{constrain:"#"+n.PAGECONTENT,stickY:!0}),t.dd.plug(e.Plugin.DDWinScroll)}},setup_for_section:function(t){e.Node.all(t).each(function(t){var r=e.Moodle.core_course.util.section.getId(t);if(r>0){var i=t.one("."+n.RIGHT+" a."+n.MOVEDOWN),s=t.one("."+n.RIGHT+" a."+n.MOVEUP),o=M.util.get_string("movesection","moodle",r),u=t.one("."+n.LEFT);(i||s)&&u&&(u.setStyle("cursor","move"),u.appendChild(this.get_drag_handle(o,n.SECTIONHANDLE,"icon",!0)),s&&s.remove(),i&&i.remove(),t.addClass(n.SECTIONDRAGGABLE))}},this)},drag_start:function(t){var r=t.target,i=e.Node.create("<"+M.mod_quiz.edit.get_containernode()+">"+M.mod_quiz.edit.get_containernode()+">");i.addClass(M.mod_quiz.edit.get_containerclass());var s=e.Node.create("<"+M.mod_quiz.edit.get_sectionwrappernode()+">"+M.mod_quiz.edit.get_sectionwrappernode()+">");s.addClass(M.mod_quiz.edit.get_sectionwrapperclass()),s.setStyle("margin",0),s.setContent(r.get("node").get("innerHTML")),i.appendChild(s),r.get("dragNode").setContent(i),r.get("dragNode").addClass(n.COURSECONTENT)},drag_dropmiss:function(e){this.drop_hit(e)},get_section_index:function(t){var r="."+n.COURSECONTENT+" "+M.mod_quiz.edit.get_section_selector(e),i=e.all(r),s=i.indexOf(t),o=i.indexOf(e.one("#section-0"));return s-o},drop_hit:function(t){var r=t.drag,i=r.get("node"),s=e.Moodle.core_course.util.section.getId(i),o=s,u=this.get_section_index(i),a=u;if(s===u)return;o>a&&(o=u,a=s),r.get("dragNode").removeClass(n.COURSECONTENT);var f=e.Node.all(this.sectionlistselector),l=M.util.add_lightbox(e,i),c={},h=this.get("config").pageparams,p;for(p in h){if(!h.hasOwnProperty(p))continue;c[p]=h[p]}c.sesskey=M.cfg.sesskey,c.courseid=this.get("courseid"),c.quizid=this.get("quizid"),c["class"]="section",c.field="move",c.id=s,c.value=u;var d=M.cfg.wwwroot+this.get("ajaxurl");e.io(d,{method:"POST",data:c,on:{start:function(){l.show()},success:function(t,n){try{var r=e.JSON.parse(n.responseText);r.error&&new M.core.ajaxException(r),M.mod_quiz.edit.process_sections(e,f,r,o,a)}catch(i){}var s,u=!1;do{u=!1;for(s=o;s<=a;s++)if(e.Moodle.core_course.util.section.getId(f.item(s-1))>e.Moodle.core_course.util.section.getId(f.item(s))){var c=f.item(s-1).get("id");f.item(s-1).set("id",f.item(s).get("id")),f.item(s).set("id",c),M.mod_quiz.edit.swap_sections(e,s-1,s),u=!0}a-=1}while(u);window.setTimeout(function(){l.hide()},250)},failure:function(e,t){this.ajax_failure(t),l.hide()}},context:this})}},{NAME:"mod_quiz-dragdrop-section",ATTRS:{courseid:{value:null},quizid:{value:null},ajaxurl:{value:0},config:{value:0}}}),M.mod_quiz=M.mod_quiz||{},M.mod_quiz.init_section_dragdrop=function(e){new i(e)};var s=function(){s.superclass.constructor.apply(this,arguments)};e.extend(s,M.core.dragdrop,{initializer:function(){this.groups=["resource"],this.samenodeclass=n.ACTIVITY,this.parentnodeclass=n.SECTION,this.resourcedraghandle=this.get_drag_handle(M.str.moodle.move,n.EDITINGMOVE,n.ICONCLASS,!0),this.samenodelabel={identifier:"dragtoafter",component:"quiz"},this.parentnodelabel={identifier:"dragtostart",component:"quiz"};var t=M.mod_quiz.edit.get_section_selector(e);if(t){t="."+n.COURSECONTENT+" "+t,this.setup_for_section(t);var r=t.slice(n.COURSECONTENT.length+2)+" li."+n.ACTIVITY,i=new e.DD.Delegate({container:"."+n.COURSECONTENT,nodes:r,target:!0,handles:["."+n.EDITINGMOVE],dragConfig:{groups:this.groups}});i.dd.plug(e.Plugin.DDProxy,{moveOnEnd:!1,cloneNode:!0}),i.dd.plug(e.Plugin.DDConstrained,{constrain:"#"+n.SLOTS}),i.dd.plug(e.Plugin.DDWinScroll),M.mod_quiz.quizbase.register_module(this),M.mod_quiz.dragres=this}},setup_for_section:function(t){e.Node.all(t).each(function(t){var r=t.one("."+n.CONTENT+" ul."+n.SECTION);r||(r=e.Node.create("
"),r.addClass(n.SECTION),t.one("."+n.CONTENT+" div."+n.SUMMARY).insert(r,"after")),r.setAttribute("data-draggroups",this.groups.join(" ")),new e.DD.Drop({node:r,groups:this.groups,padding:"20 0 20 0"}),this.setup_for_resource("#"+t.get("id")+" li."+n.ACTIVITY)},this)},setup_for_resource:function(t){e.Node.all(t).each(function(e){var t=e.one("a."+n.EDITINGMOVE);t&&t.replace(this.resourcedraghandle.cloneNode(!0))},this)},drag_start:function(e){var t=e.target;t.get("dragNode").setContent(t.get("node").get("innerHTML")),t.get("dragNode").all("img.iconsmall").setStyle("vertical-align","baseline")},drag_dropmiss:function(e){this.drop_hit(e)},drop_hit:function(t){var i=t.drag,s=i.get("node"),o=t.drop.get("node"),u=s.one(n.ACTIONAREA),a=M.util.add_spinner(e,u),f={},l=this.get("config").pageparams,c;for(c in l)f[c]=l[c];f.sesskey=M.cfg.sesskey,f.courseid=this.get("courseid"),f.quizid=this.get("quizid"),f["class"]="resource",f.field="move",f.id=Number(e.Moodle.mod_quiz.
+util.slot.getId(s)),f.sectionId=e.Moodle.core_course.util.section.getId(o.ancestor(M.mod_quiz.edit.get_section_wrapper(e),!0));var h=s.previous(r.SLOT);h&&(f.previousid=Number(e.Moodle.mod_quiz.util.slot.getId(h)));var p=s.previous(r.PAGE);p&&(f.page=Number(e.Moodle.mod_quiz.util.page.getId(p)));var d=M.cfg.wwwroot+this.get("ajaxurl");e.io(d,{method:"POST",data:f,on:{start:function(){this.lock_drag_handle(i,n.EDITINGMOVE),a.show()},success:function(t,r){var o=e.JSON.parse(r.responseText),u={element:s,visible:o.visible};M.mod_quiz.quizbase.invoke_function("set_visibility_resource_ui",u),e.Moodle.mod_quiz.util.slot.reorder_slots(),this.unlock_drag_handle(i,n.EDITINGMOVE),window.setTimeout(function(){a.hide()},250),window.location.reload(!0)},failure:function(e,t){this.ajax_failure(t),this.unlock_drag_handle(i,n.SECTIONHANDLE),a.hide(),window.location.reload(!0)}},context:this})},global_drop_over:function(e){if(!e.drop||!e.drop.inGroup(this.groups))return;var t=e.drag.get("node"),n=e.drop.get("node");this.lastdroptarget=e.drop;if(n.hasClass(this.samenodeclass)){var r;this.goingup?r="before":r="after",n.insert(t,r)}else(n.hasClass(this.parentnodeclass)||n.test('[data-droptarget="1"]'))&&!n.contains(t)&&(this.goingup?n.append(t):n.prepend(t));this.drop_over(e)}},{NAME:"mod_quiz-dragdrop-resource",ATTRS:{courseid:{value:null},quizid:{value:null},ajaxurl:{value:0},config:{value:0}}}),M.mod_quiz=M.mod_quiz||{},M.mod_quiz.init_resource_dragdrop=function(e){new s(e)}},"@VERSION@",{requires:["base","node","io","dom","dd","dd-scroll","moodle-core-dragdrop","moodle-core-notification","moodle-mod_quiz-quizbase","moodle-mod_quiz-util","moodle-course-util"]});
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-dragdrop/moodle-mod_quiz-dragdrop.js b/mod/quiz/yui/build/moodle-mod_quiz-dragdrop/moodle-mod_quiz-dragdrop.js
new file mode 100644
index 00000000000..76c7621c214
--- /dev/null
+++ b/mod/quiz/yui/build/moodle-mod_quiz-dragdrop/moodle-mod_quiz-dragdrop.js
@@ -0,0 +1,557 @@
+YUI.add('moodle-mod_quiz-dragdrop', function (Y, NAME) {
+
+/**
+ * Drag and Drop for Quiz sections and slots.
+ *
+ * @module moodle-mod-quiz-dragdrop
+ */
+
+var CSS = {
+ ACTIONAREA: '.actions',
+ ACTIVITY: 'activity',
+ ACTIVITYINSTANCE: 'activityinstance',
+ CONTENT: 'content',
+ COURSECONTENT: 'mod-quiz-edit-content',
+ EDITINGMOVE: 'editing_move',
+ ICONCLASS: 'iconsmall',
+ JUMPMENU: 'jumpmenu',
+ LEFT: 'left',
+ LIGHTBOX: 'lightbox',
+ MOVEDOWN: 'movedown',
+ MOVEUP: 'moveup',
+ PAGE : 'page',
+ PAGECONTENT: 'page-content',
+ RIGHT: 'right',
+ SECTION: 'section',
+ SECTIONADDMENUS: 'section_add_menus',
+ SECTIONHANDLE: 'section-handle',
+ SLOTS: 'slots',
+ SUMMARY: 'summary',
+ SECTIONDRAGGABLE: 'sectiondraggable'
+},
+// The CSS selectors we use.
+SELECTOR = {
+ PAGE: 'li.page',
+ SLOT: 'li.slot'
+};
+/**
+ * Section drag and drop.
+ *
+ * @class M.mod_quiz.dragdrop.section
+ * @constructor
+ * @extends M.core.dragdrop
+ */
+var DRAGSECTION = function() {
+ DRAGSECTION.superclass.constructor.apply(this, arguments);
+};
+Y.extend(DRAGSECTION, M.core.dragdrop, {
+ sectionlistselector: null,
+
+ initializer: function() {
+ // Set group for parent class
+ this.groups = [ CSS.SECTIONDRAGGABLE ];
+ this.samenodeclass = M.mod_quiz.edit.get_sectionwrapperclass();
+ this.parentnodeclass = M.mod_quiz.edit.get_containerclass();
+
+ // Check if we are in single section mode
+ if (Y.Node.one('.' + CSS.JUMPMENU)) {
+ return false;
+ }
+ // Initialise sections dragging
+ this.sectionlistselector = M.mod_quiz.edit.get_section_wrapper(Y);
+ if (this.sectionlistselector) {
+ this.sectionlistselector = '.' + CSS.COURSECONTENT + ' ' + this.sectionlistselector;
+
+ this.setup_for_section(this.sectionlistselector);
+
+ // Make each li element in the lists of sections draggable
+ var del = new Y.DD.Delegate({
+ container: '.' + CSS.COURSECONTENT,
+ nodes: '.' + CSS.SECTIONDRAGGABLE,
+ target: true,
+ handles: ['.' + CSS.LEFT],
+ dragConfig: {groups: this.groups}
+ });
+ del.dd.plug(Y.Plugin.DDProxy, {
+ // Don't move the node at the end of the drag
+ moveOnEnd: false
+ });
+ del.dd.plug(Y.Plugin.DDConstrained, {
+ // Keep it inside the .mod-quiz-edit-content
+ constrain: '#' + CSS.PAGECONTENT,
+ stickY: true
+ });
+ del.dd.plug(Y.Plugin.DDWinScroll);
+ }
+ },
+
+ /**
+ * Apply dragdrop features to the specified selector or node that refers to section(s)
+ *
+ * @method setup_for_section
+ * @param {String} baseselector The CSS selector or node to limit scope to
+ */
+ setup_for_section: function(baseselector) {
+ Y.Node.all(baseselector).each(function(sectionnode) {
+ // Determine the section ID
+ var sectionid = Y.Moodle.core_course.util.section.getId(sectionnode);
+
+ // We skip the top section as it is not draggable
+ if (sectionid > 0) {
+ // Remove move icons
+ var movedown = sectionnode.one('.' + CSS.RIGHT + ' a.' + CSS.MOVEDOWN);
+ var moveup = sectionnode.one('.' + CSS.RIGHT + ' a.' + CSS.MOVEUP);
+
+ // Add dragger icon
+ var title = M.util.get_string('movesection', 'moodle', sectionid);
+ var cssleft = sectionnode.one('.' + CSS.LEFT);
+
+ if ((movedown || moveup) && cssleft) {
+ cssleft.setStyle('cursor', 'move');
+ cssleft.appendChild(this.get_drag_handle(title, CSS.SECTIONHANDLE, 'icon', true));
+
+ if (moveup) {
+ moveup.remove();
+ }
+ if (movedown) {
+ movedown.remove();
+ }
+
+ // This section can be moved - add the class to indicate this to Y.DD.
+ sectionnode.addClass(CSS.SECTIONDRAGGABLE);
+ }
+ }
+ }, this);
+ },
+
+ /*
+ * Drag-dropping related functions
+ */
+ drag_start: function(e) {
+ // Get our drag object
+ var drag = e.target;
+ // Creat a dummy structure of the outer elemnents for clean styles application
+ var containernode = Y.Node.create('<' + M.mod_quiz.edit.get_containernode() + '>' + M.mod_quiz.edit.get_containernode() + '>');
+ containernode.addClass(M.mod_quiz.edit.get_containerclass());
+ var sectionnode = Y.Node.create('<' + M.mod_quiz.edit.get_sectionwrappernode() + '>' + M.mod_quiz.edit.get_sectionwrappernode() + '>');
+ sectionnode.addClass( M.mod_quiz.edit.get_sectionwrapperclass());
+ sectionnode.setStyle('margin', 0);
+ sectionnode.setContent(drag.get('node').get('innerHTML'));
+ containernode.appendChild(sectionnode);
+ drag.get('dragNode').setContent(containernode);
+ drag.get('dragNode').addClass(CSS.COURSECONTENT);
+ },
+
+ drag_dropmiss: function(e) {
+ // Missed the target, but we assume the user intended to drop it
+ // on the last last ghost node location, e.drag and e.drop should be
+ // prepared by global_drag_dropmiss parent so simulate drop_hit(e).
+ this.drop_hit(e);
+ },
+
+ get_section_index: function(node) {
+ var sectionlistselector = '.' + CSS.COURSECONTENT + ' ' + M.mod_quiz.edit.get_section_selector(Y),
+ sectionList = Y.all(sectionlistselector),
+ nodeIndex = sectionList.indexOf(node),
+ zeroIndex = sectionList.indexOf(Y.one('#section-0'));
+
+ return (nodeIndex - zeroIndex);
+ },
+
+ drop_hit: function(e) {
+ var drag = e.drag;
+
+ // Get references to our nodes and their IDs.
+ var dragnode = drag.get('node'),
+ dragnodeid = Y.Moodle.core_course.util.section.getId(dragnode),
+ loopstart = dragnodeid,
+
+ dropnodeindex = this.get_section_index(dragnode),
+ loopend = dropnodeindex;
+
+ if (dragnodeid === dropnodeindex) {
+ return;
+ }
+
+
+ if (loopstart > loopend) {
+ // If we're going up, we need to swap the loop order
+ // because loops can't go backwards.
+ loopstart = dropnodeindex;
+ loopend = dragnodeid;
+ }
+
+ // Get the list of nodes.
+ drag.get('dragNode').removeClass(CSS.COURSECONTENT);
+ var sectionlist = Y.Node.all(this.sectionlistselector);
+
+ // Add a lightbox if it's not there.
+ var lightbox = M.util.add_lightbox(Y, dragnode);
+
+ // Handle any variables which we must pass via AJAX.
+ var params = {},
+ pageparams = this.get('config').pageparams,
+ varname;
+
+ for (varname in pageparams) {
+ if (!pageparams.hasOwnProperty(varname)) {
+ continue;
+ }
+ params[varname] = pageparams[varname];
+ }
+
+ // Prepare request parameters
+ params.sesskey = M.cfg.sesskey;
+ params.courseid = this.get('courseid');
+ params.quizid = this.get('quizid');
+ params['class'] = 'section';
+ params.field = 'move';
+ params.id = dragnodeid;
+ params.value = dropnodeindex;
+
+ // Perform the AJAX request.
+ var uri = M.cfg.wwwroot + this.get('ajaxurl');
+ Y.io(uri, {
+ method: 'POST',
+ data: params,
+ on: {
+ start: function() {
+ lightbox.show();
+ },
+ success: function(tid, response) {
+ // Update section titles, we can't simply swap them as
+ // they might have custom title
+ try {
+ var responsetext = Y.JSON.parse(response.responseText);
+ if (responsetext.error) {
+ new M.core.ajaxException(responsetext);
+ }
+ M.mod_quiz.edit.process_sections(Y, sectionlist, responsetext, loopstart, loopend);
+ } catch (e) {}
+
+ // Update all of the section IDs - first unset them, then set them
+ // to avoid duplicates in the DOM.
+ var index;
+
+ // Classic bubble sort algorithm is applied to the section
+ // nodes between original drag node location and the new one.
+ var swapped = false;
+ do {
+ swapped = false;
+ for (index = loopstart; index <= loopend; index++) {
+ if (Y.Moodle.core_course.util.section.getId(sectionlist.item(index - 1)) >
+ Y.Moodle.core_course.util.section.getId(sectionlist.item(index))) {
+ // Swap section id.
+ var sectionid = sectionlist.item(index - 1).get('id');
+ sectionlist.item(index - 1).set('id', sectionlist.item(index).get('id'));
+ sectionlist.item(index).set('id', sectionid);
+
+ // See what format needs to swap.
+ M.mod_quiz.edit.swap_sections(Y, index - 1, index);
+
+ // Update flag.
+ swapped = true;
+ }
+ }
+ loopend = loopend - 1;
+ } while (swapped);
+
+ window.setTimeout(function() {
+ lightbox.hide();
+ }, 250);
+ },
+
+ failure: function(tid, response) {
+ this.ajax_failure(response);
+ lightbox.hide();
+ }
+ },
+ context:this
+ });
+ }
+
+}, {
+ NAME: 'mod_quiz-dragdrop-section',
+ ATTRS: {
+ courseid: {
+ value: null
+ },
+ quizid: {
+ value: null
+ },
+ ajaxurl: {
+ value: 0
+ },
+ config: {
+ value: 0
+ }
+ }
+});
+
+M.mod_quiz = M.mod_quiz || {};
+M.mod_quiz.init_section_dragdrop = function(params) {
+ new DRAGSECTION(params);
+};
+/**
+ * Resource drag and drop.
+ *
+ * @class M.course.dragdrop.resource
+ * @constructor
+ * @extends M.core.dragdrop
+ */
+var DRAGRESOURCE = function() {
+ DRAGRESOURCE.superclass.constructor.apply(this, arguments);
+};
+Y.extend(DRAGRESOURCE, M.core.dragdrop, {
+ initializer: function() {
+ // Set group for parent class
+ this.groups = ['resource'];
+ this.samenodeclass = CSS.ACTIVITY;
+ this.parentnodeclass = CSS.SECTION;
+ //this.resourcedraghandle = this.get_drag_handle(M.util.get_string('movecoursemodule', 'moodle'), CSS.EDITINGMOVE, CSS.ICONCLASS, true);
+ this.resourcedraghandle = this.get_drag_handle(M.str.moodle.move, CSS.EDITINGMOVE, CSS.ICONCLASS, true);
+
+ this.samenodelabel = {
+ identifier: 'dragtoafter',
+ component: 'quiz'
+ };
+ this.parentnodelabel = {
+ identifier: 'dragtostart',
+ component: 'quiz'
+ };
+
+ // Go through all sections
+ var sectionlistselector = M.mod_quiz.edit.get_section_selector(Y);
+ if (sectionlistselector) {
+ sectionlistselector = '.' + CSS.COURSECONTENT + ' ' + sectionlistselector;
+ this.setup_for_section(sectionlistselector);
+
+ // Initialise drag & drop for all resources/activities
+ var nodeselector = sectionlistselector.slice(CSS.COURSECONTENT.length + 2) + ' li.' + CSS.ACTIVITY;
+ var del = new Y.DD.Delegate({
+ container: '.' + CSS.COURSECONTENT,
+ nodes: nodeselector,
+ target: true,
+ handles: ['.' + CSS.EDITINGMOVE],
+ dragConfig: {groups: this.groups}
+ });
+ del.dd.plug(Y.Plugin.DDProxy, {
+ // Don't move the node at the end of the drag
+ moveOnEnd: false,
+ cloneNode: true
+ });
+ del.dd.plug(Y.Plugin.DDConstrained, {
+ // Keep it inside the .mod-quiz-edit-content
+ constrain: '#' + CSS.SLOTS
+ });
+ del.dd.plug(Y.Plugin.DDWinScroll);
+
+ M.mod_quiz.quizbase.register_module(this);
+ M.mod_quiz.dragres = this;
+ }
+ },
+
+ /**
+ * Apply dragdrop features to the specified selector or node that refers to section(s)
+ *
+ * @method setup_for_section
+ * @param {String} baseselector The CSS selector or node to limit scope to
+ */
+ setup_for_section: function(baseselector) {
+ Y.Node.all(baseselector).each(function(sectionnode) {
+ var resources = sectionnode.one('.' + CSS.CONTENT + ' ul.' + CSS.SECTION);
+ // See if resources ul exists, if not create one.
+ if (!resources) {
+ resources = Y.Node.create('
');
+ resources.addClass(CSS.SECTION);
+ sectionnode.one('.' + CSS.CONTENT + ' div.' + CSS.SUMMARY).insert(resources, 'after');
+ }
+ resources.setAttribute('data-draggroups', this.groups.join(' '));
+ // Define empty ul as droptarget, so that item could be moved to empty list
+ new Y.DD.Drop({
+ node: resources,
+ groups: this.groups,
+ padding: '20 0 20 0'
+ });
+
+ // Initialise each resource/activity in this section
+ this.setup_for_resource('#' + sectionnode.get('id') + ' li.' + CSS.ACTIVITY);
+ }, this);
+ },
+
+ /**
+ * Apply dragdrop features to the specified selector or node that refers to resource(s)
+ *
+ * @method setup_for_resource
+ * @param {String} baseselector The CSS selector or node to limit scope to
+ */
+ setup_for_resource: function(baseselector) {
+ Y.Node.all(baseselector).each(function(resourcesnode) {
+ // Replace move icons
+ var move = resourcesnode.one('a.' + CSS.EDITINGMOVE);
+ if (move) {
+ move.replace(this.resourcedraghandle.cloneNode(true));
+ }
+ }, this);
+ },
+
+ drag_start: function(e) {
+ // Get our drag object
+ var drag = e.target;
+ drag.get('dragNode').setContent(drag.get('node').get('innerHTML'));
+ drag.get('dragNode').all('img.iconsmall').setStyle('vertical-align', 'baseline');
+ },
+
+ drag_dropmiss: function(e) {
+ // Missed the target, but we assume the user intended to drop it
+ // on the last ghost node location, e.drag and e.drop should be
+ // prepared by global_drag_dropmiss parent so simulate drop_hit(e).
+ this.drop_hit(e);
+ },
+
+ drop_hit: function(e) {
+ var drag = e.drag;
+ // Get a reference to our drag node
+ var dragnode = drag.get('node');
+ var dropnode = e.drop.get('node');
+
+ // Add spinner if it not there
+ var actionarea = dragnode.one(CSS.ACTIONAREA);
+ var spinner = M.util.add_spinner(Y, actionarea);
+
+ var params = {};
+
+ // Handle any variables which we must pass back through to
+ var pageparams = this.get('config').pageparams;
+ var varname;
+ for (varname in pageparams) {
+ params[varname] = pageparams[varname];
+ }
+
+ // Prepare request parameters
+ params.sesskey = M.cfg.sesskey;
+ params.courseid = this.get('courseid');
+ params.quizid = this.get('quizid');
+ params['class'] = 'resource';
+ params.field = 'move';
+ params.id = Number(Y.Moodle.mod_quiz.util.slot.getId(dragnode));
+ params.sectionId = Y.Moodle.core_course.util.section.getId(dropnode.ancestor(M.mod_quiz.edit.get_section_wrapper(Y), true));
+
+ var previousslot = dragnode.previous(SELECTOR.SLOT);
+ if (previousslot) {
+ params.previousid = Number(Y.Moodle.mod_quiz.util.slot.getId(previousslot));
+ }
+
+ var previouspage = dragnode.previous(SELECTOR.PAGE);
+ if (previouspage) {
+ params.page = Number(Y.Moodle.mod_quiz.util.page.getId(previouspage));
+ }
+
+ // Do AJAX request
+ var uri = M.cfg.wwwroot + this.get('ajaxurl');
+
+ Y.io(uri, {
+ method: 'POST',
+ data: params,
+ on: {
+ start: function() {
+ this.lock_drag_handle(drag, CSS.EDITINGMOVE);
+ spinner.show();
+ },
+ success: function(tid, response) {
+ var responsetext = Y.JSON.parse(response.responseText);
+ var params = {element: dragnode, visible: responsetext.visible};
+ M.mod_quiz.quizbase.invoke_function('set_visibility_resource_ui', params);
+ Y.Moodle.mod_quiz.util.slot.reorder_slots();
+ this.unlock_drag_handle(drag, CSS.EDITINGMOVE);
+ window.setTimeout(function() {
+ spinner.hide();
+ }, 250);
+ window.location.reload(true);
+ },
+ failure: function(tid, response) {
+ this.ajax_failure(response);
+ this.unlock_drag_handle(drag, CSS.SECTIONHANDLE);
+ spinner.hide();
+ window.location.reload(true);
+ }
+ },
+ context:this
+ });
+ },
+
+ global_drop_over: function(e) {
+ //Overriding parent method so we can stop the slots being dragged before the first page node.
+
+ // Check that drop object belong to correct group.
+ if (!e.drop || !e.drop.inGroup(this.groups)) {
+ return;
+ }
+
+ // Get a reference to our drag and drop nodes.
+ var drag = e.drag.get('node'),
+ drop = e.drop.get('node');
+
+ // Save last drop target for the case of missed target processing.
+ this.lastdroptarget = e.drop;
+
+ // Are we dropping within the same parent node?
+ if (drop.hasClass(this.samenodeclass)) {
+ var where;
+
+ if (this.goingup) {
+ where = "before";
+ } else {
+ where = "after";
+ }
+
+ drop.insert(drag, where);
+ } else if ((drop.hasClass(this.parentnodeclass) || drop.test('[data-droptarget="1"]')) && !drop.contains(drag)) {
+ // We are dropping on parent node and it is empty
+ if (this.goingup) {
+ drop.append(drag);
+ } else {
+ drop.prepend(drag);
+ }
+ }
+ this.drop_over(e);
+ }
+}, {
+ NAME: 'mod_quiz-dragdrop-resource',
+ ATTRS: {
+ courseid: {
+ value: null
+ },
+ quizid: {
+ value: null
+ },
+ ajaxurl: {
+ value: 0
+ },
+ config: {
+ value: 0
+ }
+ }
+});
+
+M.mod_quiz = M.mod_quiz || {};
+M.mod_quiz.init_resource_dragdrop = function(params) {
+ new DRAGRESOURCE(params);
+};
+
+
+}, '@VERSION@', {
+ "requires": [
+ "base",
+ "node",
+ "io",
+ "dom",
+ "dd",
+ "dd-scroll",
+ "moodle-core-dragdrop",
+ "moodle-core-notification",
+ "moodle-mod_quiz-quizbase",
+ "moodle-mod_quiz-util",
+ "moodle-course-util"
+ ]
+});
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-modform/moodle-mod_quiz-modform-debug.js b/mod/quiz/yui/build/moodle-mod_quiz-modform/moodle-mod_quiz-modform-debug.js
new file mode 100644
index 00000000000..df829aa0e0f
--- /dev/null
+++ b/mod/quiz/yui/build/moodle-mod_quiz-modform/moodle-mod_quiz-modform-debug.js
@@ -0,0 +1,56 @@
+YUI.add('moodle-mod_quiz-modform', function (Y, NAME) {
+
+/**
+ * The modform class has all the JavaScript specific to mod/quiz/mod_form.php.
+ *
+ * @module moodle-mod_quiz-modform
+ */
+
+var MODFORM = function() {
+ MODFORM.superclass.constructor.apply(this, arguments);
+};
+
+/**
+ * The coursebase class to provide shared functionality to Modules within
+ * Moodle.
+ *
+ * @class M.course.coursebase
+ * @constructor
+ */
+Y.extend(MODFORM, Y.Base, {
+ repaginateCheckbox: null,
+ qppSelect: null,
+ qppInitialValue: 0,
+
+ initializer: function () {
+ this.repaginateCheckbox = Y.one('#id_repaginatenow');
+ if (!this.repaginateCheckbox) {
+ // The checkbox only appears when editing an existing quiz.
+ return;
+ }
+
+ this.qppSelect = Y.one('#id_questionsperpage');
+ this.qppInitialValue = this.qppSelect.get('value');
+ this.qppSelect.on('change', this.qppChanged, this);
+ Y.one('#id_shufflequestions').on('change', this.qppChanged, this);
+ },
+
+ qppChanged: function() {
+ Y.later(50, this, function() {
+ if (!this.repaginateCheckbox.get('disabled')) {
+ this.repaginateCheckbox.set('checked', this.qppSelect.get('value') !== this.qppInitialValue);
+ }
+ });
+ }
+
+});
+
+// Ensure that M.course exists and that coursebase is initialised correctly
+M.mod_quiz = M.mod_quiz || {};
+M.mod_quiz.modform = M.mod_quiz.modform || new MODFORM();
+M.mod_quiz.modform.init = function() {
+ return new MODFORM();
+};
+
+
+}, '@VERSION@', {"requires": ["base", "node", "event"]});
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-modform/moodle-mod_quiz-modform-min.js b/mod/quiz/yui/build/moodle-mod_quiz-modform/moodle-mod_quiz-modform-min.js
new file mode 100644
index 00000000000..fa5bb639c79
--- /dev/null
+++ b/mod/quiz/yui/build/moodle-mod_quiz-modform/moodle-mod_quiz-modform-min.js
@@ -0,0 +1 @@
+YUI.add("moodle-mod_quiz-modform",function(e,t){var n=function(){n.superclass.constructor.apply(this,arguments)};e.extend(n,e.Base,{repaginateCheckbox:null,qppSelect:null,qppInitialValue:0,initializer:function(){this.repaginateCheckbox=e.one("#id_repaginatenow");if(!this.repaginateCheckbox)return;this.qppSelect=e.one("#id_questionsperpage"),this.qppInitialValue=this.qppSelect.get("value"),this.qppSelect.on("change",this.qppChanged,this),e.one("#id_shufflequestions").on("change",this.qppChanged,this)},qppChanged:function(){e.later(50,this,function(){this.repaginateCheckbox.get("disabled")||this.repaginateCheckbox.set("checked",this.qppSelect.get("value")!==this.qppInitialValue)})}}),M.mod_quiz=M.mod_quiz||{},M.mod_quiz.modform=M.mod_quiz.modform||new n,M.mod_quiz.modform.init=function(){return new n}},"@VERSION@",{requires:["base","node","event"]});
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-modform/moodle-mod_quiz-modform.js b/mod/quiz/yui/build/moodle-mod_quiz-modform/moodle-mod_quiz-modform.js
new file mode 100644
index 00000000000..df829aa0e0f
--- /dev/null
+++ b/mod/quiz/yui/build/moodle-mod_quiz-modform/moodle-mod_quiz-modform.js
@@ -0,0 +1,56 @@
+YUI.add('moodle-mod_quiz-modform', function (Y, NAME) {
+
+/**
+ * The modform class has all the JavaScript specific to mod/quiz/mod_form.php.
+ *
+ * @module moodle-mod_quiz-modform
+ */
+
+var MODFORM = function() {
+ MODFORM.superclass.constructor.apply(this, arguments);
+};
+
+/**
+ * The coursebase class to provide shared functionality to Modules within
+ * Moodle.
+ *
+ * @class M.course.coursebase
+ * @constructor
+ */
+Y.extend(MODFORM, Y.Base, {
+ repaginateCheckbox: null,
+ qppSelect: null,
+ qppInitialValue: 0,
+
+ initializer: function () {
+ this.repaginateCheckbox = Y.one('#id_repaginatenow');
+ if (!this.repaginateCheckbox) {
+ // The checkbox only appears when editing an existing quiz.
+ return;
+ }
+
+ this.qppSelect = Y.one('#id_questionsperpage');
+ this.qppInitialValue = this.qppSelect.get('value');
+ this.qppSelect.on('change', this.qppChanged, this);
+ Y.one('#id_shufflequestions').on('change', this.qppChanged, this);
+ },
+
+ qppChanged: function() {
+ Y.later(50, this, function() {
+ if (!this.repaginateCheckbox.get('disabled')) {
+ this.repaginateCheckbox.set('checked', this.qppSelect.get('value') !== this.qppInitialValue);
+ }
+ });
+ }
+
+});
+
+// Ensure that M.course exists and that coursebase is initialised correctly
+M.mod_quiz = M.mod_quiz || {};
+M.mod_quiz.modform = M.mod_quiz.modform || new MODFORM();
+M.mod_quiz.modform.init = function() {
+ return new MODFORM();
+};
+
+
+}, '@VERSION@', {"requires": ["base", "node", "event"]});
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-questionchooser/moodle-mod_quiz-questionchooser-debug.js b/mod/quiz/yui/build/moodle-mod_quiz-questionchooser/moodle-mod_quiz-questionchooser-debug.js
new file mode 100644
index 00000000000..57c21408e2f
--- /dev/null
+++ b/mod/quiz/yui/build/moodle-mod_quiz-questionchooser/moodle-mod_quiz-questionchooser-debug.js
@@ -0,0 +1,77 @@
+YUI.add('moodle-mod_quiz-questionchooser', function (Y, NAME) {
+
+var CSS = {
+ ADDNEWQUESTIONBUTTONS: 'ul.menu a.addquestion',
+ CREATENEWQUESTION: 'div.createnewquestion',
+ CHOOSERDIALOGUE: 'div.chooserdialogue',
+ CHOOSERHEADER: 'div.choosertitle'
+};
+
+/**
+ * The questionchooser class is responsible for instantiating and displaying the question chooser
+ * when viewing a quiz in editing mode.
+ *
+ * @class questionchooser
+ * @constructor
+ * @protected
+ * @extends M.core.chooserdialogue
+ */
+var QUESTIONCHOOSER = function() {
+ QUESTIONCHOOSER.superclass.constructor.apply(this, arguments);
+};
+
+Y.extend(QUESTIONCHOOSER, M.core.chooserdialogue, {
+ initializer: function() {
+ Y.one('body').delegate('click', this.display_dialogue, CSS.ADDNEWQUESTIONBUTTONS, this);
+ },
+
+ display_dialogue: function(e) {
+ e.preventDefault();
+ var dialogue = Y.one(CSS.CREATENEWQUESTION + ' ' + CSS.CHOOSERDIALOGUE),
+ header = Y.one(CSS.CREATENEWQUESTION + ' ' + CSS.CHOOSERHEADER);
+
+ if (this.container === null) {
+ // Setup the dialogue, and then prepare the chooser if it's not already been set up.
+ this.setup_chooser_dialogue(dialogue, header, {});
+ this.prepare_chooser();
+ }
+
+ // Update all of the hidden fields within the questionbank form.
+ var parameters = Y.QueryString.parse(e.currentTarget.get('search').substring(1));
+ var form = this.container.one('form');
+ this.parameters_to_hidden_input(parameters, form, 'returnurl');
+ this.parameters_to_hidden_input(parameters, form, 'cmid');
+ this.parameters_to_hidden_input(parameters, form, 'category');
+ this.parameters_to_hidden_input(parameters, form, 'addonpage');
+ this.parameters_to_hidden_input(parameters, form, 'appendqnumstring');
+
+ // Display the chooser dialogue.
+ this.display_chooser(e);
+ },
+
+ parameters_to_hidden_input: function(parameters, form, name) {
+ var value;
+ if (parameters.hasOwnProperty(name)) {
+ value = parameters[name];
+ } else {
+ value = '';
+ }
+ var input = form.one('input[name=' + name + ']');
+ if (!input) {
+ input = form.appendChild('');
+ input.set('name', name);
+ }
+ input.set('value', value);
+ }
+}, {
+ NAME: 'mod_quiz-questionchooser'
+});
+
+M.mod_quiz = M.mod_quiz || {};
+M.mod_quiz.init_questionchooser = function() {
+ M.mod_quiz.question_chooser = new QUESTIONCHOOSER({});
+ return M.mod_quiz.question_chooser;
+};
+
+
+}, '@VERSION@', {"requires": ["moodle-core-chooserdialogue", "moodle-mod_quiz-util", "querystring-parse"]});
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-questionchooser/moodle-mod_quiz-questionchooser-min.js b/mod/quiz/yui/build/moodle-mod_quiz-questionchooser/moodle-mod_quiz-questionchooser-min.js
new file mode 100644
index 00000000000..efcd00b5a5b
--- /dev/null
+++ b/mod/quiz/yui/build/moodle-mod_quiz-questionchooser/moodle-mod_quiz-questionchooser-min.js
@@ -0,0 +1 @@
+YUI.add("moodle-mod_quiz-questionchooser",function(e,t){var n={ADDNEWQUESTIONBUTTONS:"ul.menu a.addquestion",CREATENEWQUESTION:"div.createnewquestion",CHOOSERDIALOGUE:"div.chooserdialogue",CHOOSERHEADER:"div.choosertitle"},r=function(){r.superclass.constructor.apply(this,arguments)};e.extend(r,M.core.chooserdialogue,{initializer:function(){e.one("body").delegate("click",this.display_dialogue,n.ADDNEWQUESTIONBUTTONS,this)},display_dialogue:function(t){t.preventDefault();var r=e.one(n.CREATENEWQUESTION+" "+n.CHOOSERDIALOGUE),i=e.one(n.CREATENEWQUESTION+" "+n.CHOOSERHEADER);this.container===null&&(this.setup_chooser_dialogue(r,i,{}),this.prepare_chooser());var s=e.QueryString.parse(t.currentTarget.get("search").substring(1)),o=this.container.one("form");this.parameters_to_hidden_input(s,o,"returnurl"),this.parameters_to_hidden_input(s,o,"cmid"),this.parameters_to_hidden_input(s,o,"category"),this.parameters_to_hidden_input(s,o,"addonpage"),this.parameters_to_hidden_input(s,o,"appendqnumstring"),this.display_chooser(t)},parameters_to_hidden_input:function(e,t,n){var r;e.hasOwnProperty(n)?r=e[n]:r="";var i=t.one("input[name="+n+"]");i||(i=t.appendChild(''),i.set("name",n)),i.set("value",r)}},{NAME:"mod_quiz-questionchooser"}),M.mod_quiz=M.mod_quiz||{},M.mod_quiz.init_questionchooser=function(){return M.mod_quiz.question_chooser=new r({}),M.mod_quiz.question_chooser}},"@VERSION@",{requires:["moodle-core-chooserdialogue","moodle-mod_quiz-util","querystring-parse"]});
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-questionchooser/moodle-mod_quiz-questionchooser.js b/mod/quiz/yui/build/moodle-mod_quiz-questionchooser/moodle-mod_quiz-questionchooser.js
new file mode 100644
index 00000000000..57c21408e2f
--- /dev/null
+++ b/mod/quiz/yui/build/moodle-mod_quiz-questionchooser/moodle-mod_quiz-questionchooser.js
@@ -0,0 +1,77 @@
+YUI.add('moodle-mod_quiz-questionchooser', function (Y, NAME) {
+
+var CSS = {
+ ADDNEWQUESTIONBUTTONS: 'ul.menu a.addquestion',
+ CREATENEWQUESTION: 'div.createnewquestion',
+ CHOOSERDIALOGUE: 'div.chooserdialogue',
+ CHOOSERHEADER: 'div.choosertitle'
+};
+
+/**
+ * The questionchooser class is responsible for instantiating and displaying the question chooser
+ * when viewing a quiz in editing mode.
+ *
+ * @class questionchooser
+ * @constructor
+ * @protected
+ * @extends M.core.chooserdialogue
+ */
+var QUESTIONCHOOSER = function() {
+ QUESTIONCHOOSER.superclass.constructor.apply(this, arguments);
+};
+
+Y.extend(QUESTIONCHOOSER, M.core.chooserdialogue, {
+ initializer: function() {
+ Y.one('body').delegate('click', this.display_dialogue, CSS.ADDNEWQUESTIONBUTTONS, this);
+ },
+
+ display_dialogue: function(e) {
+ e.preventDefault();
+ var dialogue = Y.one(CSS.CREATENEWQUESTION + ' ' + CSS.CHOOSERDIALOGUE),
+ header = Y.one(CSS.CREATENEWQUESTION + ' ' + CSS.CHOOSERHEADER);
+
+ if (this.container === null) {
+ // Setup the dialogue, and then prepare the chooser if it's not already been set up.
+ this.setup_chooser_dialogue(dialogue, header, {});
+ this.prepare_chooser();
+ }
+
+ // Update all of the hidden fields within the questionbank form.
+ var parameters = Y.QueryString.parse(e.currentTarget.get('search').substring(1));
+ var form = this.container.one('form');
+ this.parameters_to_hidden_input(parameters, form, 'returnurl');
+ this.parameters_to_hidden_input(parameters, form, 'cmid');
+ this.parameters_to_hidden_input(parameters, form, 'category');
+ this.parameters_to_hidden_input(parameters, form, 'addonpage');
+ this.parameters_to_hidden_input(parameters, form, 'appendqnumstring');
+
+ // Display the chooser dialogue.
+ this.display_chooser(e);
+ },
+
+ parameters_to_hidden_input: function(parameters, form, name) {
+ var value;
+ if (parameters.hasOwnProperty(name)) {
+ value = parameters[name];
+ } else {
+ value = '';
+ }
+ var input = form.one('input[name=' + name + ']');
+ if (!input) {
+ input = form.appendChild('');
+ input.set('name', name);
+ }
+ input.set('value', value);
+ }
+}, {
+ NAME: 'mod_quiz-questionchooser'
+});
+
+M.mod_quiz = M.mod_quiz || {};
+M.mod_quiz.init_questionchooser = function() {
+ M.mod_quiz.question_chooser = new QUESTIONCHOOSER({});
+ return M.mod_quiz.question_chooser;
+};
+
+
+}, '@VERSION@', {"requires": ["moodle-core-chooserdialogue", "moodle-mod_quiz-util", "querystring-parse"]});
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-quizbase/moodle-mod_quiz-quizbase-debug.js b/mod/quiz/yui/build/moodle-mod_quiz-quizbase/moodle-mod_quiz-quizbase-debug.js
new file mode 100644
index 00000000000..a035d447275
--- /dev/null
+++ b/mod/quiz/yui/build/moodle-mod_quiz-quizbase/moodle-mod_quiz-quizbase-debug.js
@@ -0,0 +1,272 @@
+YUI.add('moodle-mod_quiz-quizbase', function (Y, NAME) {
+
+/**
+ * The quizbase class to provide shared functionality to Modules within Moodle.
+ *
+ * @module moodle-mod_quiz-quizbase
+ */
+var QUIZBASENAME = 'mod_quiz-quizbase';
+
+var QUIZBASE = function() {
+ QUIZBASE.superclass.constructor.apply(this, arguments);
+};
+
+/**
+ * The coursebase class to provide shared functionality to Modules within
+ * Moodle.
+ *
+ * @class M.course.coursebase
+ * @constructor
+ */
+Y.extend(QUIZBASE, Y.Base, {
+ // Registered Modules
+ registermodules : [],
+
+ /**
+ * Register a new Javascript Module
+ *
+ * @method register_module
+ * @param {Object} The instantiated module to call functions on
+ * @chainable
+ */
+ register_module : function(object) {
+ this.registermodules.push(object);
+
+ return this;
+ },
+
+ /**
+ * Invoke the specified function in all registered modules with the given arguments
+ *
+ * @method invoke_function
+ * @param {String} functionname The name of the function to call
+ * @param {mixed} args The argument supplied to the function
+ * @chainable
+ */
+ invoke_function : function(functionname, args) {
+ var module;
+ for (module in this.registermodules) {
+ if (functionname in this.registermodules[module]) {
+ this.registermodules[module][functionname](args);
+ }
+ }
+
+ return this;
+ }
+}, {
+ NAME : QUIZBASENAME,
+ ATTRS : {}
+});
+
+// Ensure that M.course exists and that coursebase is initialised correctly
+M.mod_quiz = M.mod_quiz || {};
+M.mod_quiz.quizbase = M.mod_quiz.quizbase || new QUIZBASE();
+
+// Abstract functions that needs to be defined per format (course/format/somename/format.js)
+M.mod_quiz.edit = M.mod_quiz.edit || {};
+
+/**
+ * Swap section (should be defined in format.js if requred)
+ *
+ * @param {YUI} Y YUI3 instance
+ * @param {string} node1 node to swap to
+ * @param {string} node2 node to swap with
+ * @return {NodeList} section list
+ */
+M.mod_quiz.edit.swap_sections = function(Y, node1, node2) {
+ var CSS = {
+ COURSECONTENT : 'mod-quiz-edit-content',
+ SECTIONADDMENUS : 'section_add_menus'
+ };
+
+ var sectionlist = Y.Node.all('.'+CSS.COURSECONTENT+' '+M.mod_quiz.edit.get_section_selector(Y));
+ // Swap menus.
+ sectionlist.item(node1).one('.'+CSS.SECTIONADDMENUS).swap(sectionlist.item(node2).one('.'+CSS.SECTIONADDMENUS));
+};
+
+/**
+ * Process sections after ajax response (should be defined in format.js)
+ * If some response is expected, we pass it over to format, as it knows better
+ * hot to process it.
+ *
+ * @param {YUI} Y YUI3 instance
+ * @param {NodeList} list of sections
+ * @param {array} response ajax response
+ * @param {string} sectionfrom first affected section
+ * @param {string} sectionto last affected section
+ * @return void
+ */
+M.mod_quiz.edit.process_sections = function(Y, sectionlist, response, sectionfrom, sectionto) {
+ var CSS = {
+ SECTIONNAME : 'sectionname'
+ },
+ SELECTORS = {
+ SECTIONLEFTSIDE : '.left .section-handle img'
+ };
+
+ if (response.action === 'move') {
+ // If moving up swap around 'sectionfrom' and 'sectionto' so the that loop operates.
+ if (sectionfrom > sectionto) {
+ var temp = sectionto;
+ sectionto = sectionfrom;
+ sectionfrom = temp;
+ }
+
+ // Update titles and move icons in all affected sections.
+ var ele, str, stridx, newstr;
+
+ for (var i = sectionfrom; i <= sectionto; i++) {
+ // Update section title.
+ sectionlist.item(i).one('.'+CSS.SECTIONNAME).setContent(response.sectiontitles[i]);
+
+ // Update move icon.
+ ele = sectionlist.item(i).one(SELECTORS.SECTIONLEFTSIDE);
+ str = ele.getAttribute('alt');
+ stridx = str.lastIndexOf(' ');
+ newstr = str.substr(0, stridx + 1) + i;
+ ele.setAttribute('alt', newstr);
+ ele.setAttribute('title', newstr); // For FireFox as 'alt' is not refreshed.
+
+ // Remove the current class as section has been moved.
+ sectionlist.item(i).removeClass('current');
+ }
+ // If there is a current section, apply corresponding class in order to highlight it.
+ if (response.current !== -1) {
+ // Add current class to the required section.
+ sectionlist.item(response.current).addClass('current');
+ }
+ }
+};
+
+/**
+* Get sections config for this format, for examples see function definition
+* in the formats.
+*
+* @return {object} section list configuration
+*/
+M.mod_quiz.edit.get_config = function() {
+ return {
+ container_node : 'ul',
+ container_class : 'slots',
+ section_node : 'li',
+ section_class : 'section'
+ };
+};
+
+/**
+ * Get section list for this format (usually items inside container_node.container_class selector)
+ *
+ * @param {YUI} Y YUI3 instance
+ * @return {string} section selector
+ */
+M.mod_quiz.edit.get_section_selector = function() {
+ var config = M.mod_quiz.edit.get_config();
+ if (config.section_node && config.section_class) {
+ return config.section_node + '.' + config.section_class;
+ }
+ Y.log('section_node and section_class are not defined in M.mod_quiz.edit.get_config', 'warn', 'moodle-mod_quiz-quizbase');
+ return null;
+};
+
+/**
+ * Get section wraper for this format (only used in case when each
+ * container_node.container_class node is wrapped in some other element).
+ *
+ * @param {YUI} Y YUI3 instance
+ * @return {string} section wrapper selector or M.mod_quiz.format.get_section_selector
+ * if section_wrapper_node and section_wrapper_class are not defined in the format config.
+ */
+M.mod_quiz.edit.get_section_wrapper = function(Y) {
+ var config = M.mod_quiz.edit.get_config();
+ if (config.section_wrapper_node && config.section_wrapper_class) {
+ return config.section_wrapper_node + '.' + config.section_wrapper_class;
+ }
+ return M.mod_quiz.edit.get_section_selector(Y);
+};
+
+/**
+ * Get the tag of container node
+ *
+ * @return {string} tag of container node.
+ */
+M.mod_quiz.edit.get_containernode = function() {
+ var config = M.mod_quiz.edit.get_config();
+ if (config.container_node) {
+ return config.container_node;
+ } else {
+ Y.log('container_node is not defined in M.mod_quiz.edit.get_config', 'warn', 'moodle-mod_quiz-quizbase');
+ }
+};
+
+/**
+ * Get the class of container node
+ *
+ * @return {string} class of the container node.
+ */
+M.mod_quiz.edit.get_containerclass = function() {
+ var config = M.mod_quiz.edit.get_config();
+ if (config.container_class) {
+ return config.container_class;
+ } else {
+ Y.log('container_class is not defined in M.mod_quiz.edit.get_config', 'warn', 'moodle-mod_quiz-quizbase');
+ }
+};
+
+/**
+ * Get the tag of draggable node (section wrapper if exists, otherwise section)
+ *
+ * @return {string} tag of the draggable node.
+ */
+M.mod_quiz.edit.get_sectionwrappernode = function() {
+ var config = M.mod_quiz.edit.get_config();
+ if (config.section_wrapper_node) {
+ return config.section_wrapper_node;
+ } else {
+ return config.section_node;
+ }
+};
+
+/**
+ * Get the class of draggable node (section wrapper if exists, otherwise section)
+ *
+ * @return {string} class of the draggable node.
+ */
+M.mod_quiz.edit.get_sectionwrapperclass = function() {
+ var config = M.mod_quiz.edit.get_config();
+ if (config.section_wrapper_class) {
+ return config.section_wrapper_class;
+ } else {
+ return config.section_class;
+ }
+};
+
+/**
+ * Get the tag of section node
+ *
+ * @return {string} tag of section node.
+ */
+M.mod_quiz.edit.get_sectionnode = function() {
+ var config = M.mod_quiz.edit.get_config();
+ if (config.section_node) {
+ return config.section_node;
+ } else {
+ Y.log('section_node is not defined in M.mod_quiz.edit.get_config', 'warn', 'moodle-mod_quiz-quizbase');
+ }
+};
+
+/**
+ * Get the class of section node
+ *
+ * @return {string} class of the section node.
+ */
+M.mod_quiz.edit.get_sectionclass = function() {
+ var config = M.mod_quiz.edit.get_config();
+ if (config.section_class) {
+ return config.section_class;
+ } else {
+ Y.log('section_class is not defined in M.mod_quiz.edit.get_config', 'warn', 'moodle-mod_quiz-quizbase');
+ }
+};
+
+
+}, '@VERSION@', {"requires": ["base", "node"]});
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-quizbase/moodle-mod_quiz-quizbase-min.js b/mod/quiz/yui/build/moodle-mod_quiz-quizbase/moodle-mod_quiz-quizbase-min.js
new file mode 100644
index 00000000000..39c0265cfc1
--- /dev/null
+++ b/mod/quiz/yui/build/moodle-mod_quiz-quizbase/moodle-mod_quiz-quizbase-min.js
@@ -0,0 +1 @@
+YUI.add("moodle-mod_quiz-quizbase",function(e,t){var n="mod_quiz-quizbase",r=function(){r.superclass.constructor.apply(this,arguments)};e.extend(r,e.Base,{registermodules:[],register_module:function(e){return this.registermodules.push(e),this},invoke_function:function(e,t){var n;for(n in this.registermodules)e in this.registermodules[n]&&this.registermodules[n][e](t);return this}},{NAME:n,ATTRS:{}}),M.mod_quiz=M.mod_quiz||{},M.mod_quiz.quizbase=M.mod_quiz.quizbase||new r,M.mod_quiz.edit=M.mod_quiz.edit||{},M.mod_quiz.edit.swap_sections=function(e,t,n){var r={COURSECONTENT:"mod-quiz-edit-content",SECTIONADDMENUS:"section_add_menus"},i=e.Node.all("."+r.COURSECONTENT+" "+M.mod_quiz.edit.get_section_selector(e));i.item(t).one("."+r.SECTIONADDMENUS).swap(i.item(n).one("."+r.SECTIONADDMENUS))},M.mod_quiz.edit.process_sections=function(e,t,n,r,i){var s={SECTIONNAME:"sectionname"},o={SECTIONLEFTSIDE:".left .section-handle img"};if(n.action==="move"){if(r>i){var u=i;i=r,r=u}var a,f,l,c;for(var h=r;h<=i;h++)t.item(h).one("."+s.SECTIONNAME).setContent(n.sectiontitles[h]),a=t.item(h).one(o.SECTIONLEFTSIDE),f=a.getAttribute("alt"),l=f.lastIndexOf(" "),c=f.substr(0,l+1)+h,a.setAttribute("alt",c),a.setAttribute("title",c),t.item(h).removeClass("current");n.current!==-1&&t.item(n.current).addClass("current")}},M.mod_quiz.edit.get_config=function(){return{container_node:"ul",container_class:"slots",section_node:"li",section_class:"section"}},M.mod_quiz.edit.get_section_selector=function(){var e=M.mod_quiz.edit.get_config();return e.section_node&&e.section_class?e.section_node+"."+e.section_class:null},M.mod_quiz.edit.get_section_wrapper=function(e){var t=M.mod_quiz.edit.get_config();return t.section_wrapper_node&&t.section_wrapper_class?t.section_wrapper_node+"."+t.section_wrapper_class:M.mod_quiz.edit.get_section_selector(e)},M.mod_quiz.edit.get_containernode=function(){var e=M.mod_quiz.edit.get_config();if(e.container_node)return e.container_node},M.mod_quiz.edit.get_containerclass=function(){var e=M.mod_quiz.edit.get_config();if(e.container_class)return e.container_class},M.mod_quiz.edit.get_sectionwrappernode=function(){var e=M.mod_quiz.edit.get_config();return e.section_wrapper_node?e.section_wrapper_node:e.section_node},M.mod_quiz.edit.get_sectionwrapperclass=function(){var e=M.mod_quiz.edit.get_config();return e.section_wrapper_class?e.section_wrapper_class:e.section_class},M.mod_quiz.edit.get_sectionnode=function(){var e=M.mod_quiz.edit.get_config();if(e.section_node)return e.section_node},M.mod_quiz.edit.get_sectionclass=function(){var e=M.mod_quiz.edit.get_config();if(e.section_class)return e.section_class}},"@VERSION@",{requires:["base","node"]});
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-quizbase/moodle-mod_quiz-quizbase.js b/mod/quiz/yui/build/moodle-mod_quiz-quizbase/moodle-mod_quiz-quizbase.js
new file mode 100644
index 00000000000..eebd3435128
--- /dev/null
+++ b/mod/quiz/yui/build/moodle-mod_quiz-quizbase/moodle-mod_quiz-quizbase.js
@@ -0,0 +1,267 @@
+YUI.add('moodle-mod_quiz-quizbase', function (Y, NAME) {
+
+/**
+ * The quizbase class to provide shared functionality to Modules within Moodle.
+ *
+ * @module moodle-mod_quiz-quizbase
+ */
+var QUIZBASENAME = 'mod_quiz-quizbase';
+
+var QUIZBASE = function() {
+ QUIZBASE.superclass.constructor.apply(this, arguments);
+};
+
+/**
+ * The coursebase class to provide shared functionality to Modules within
+ * Moodle.
+ *
+ * @class M.course.coursebase
+ * @constructor
+ */
+Y.extend(QUIZBASE, Y.Base, {
+ // Registered Modules
+ registermodules : [],
+
+ /**
+ * Register a new Javascript Module
+ *
+ * @method register_module
+ * @param {Object} The instantiated module to call functions on
+ * @chainable
+ */
+ register_module : function(object) {
+ this.registermodules.push(object);
+
+ return this;
+ },
+
+ /**
+ * Invoke the specified function in all registered modules with the given arguments
+ *
+ * @method invoke_function
+ * @param {String} functionname The name of the function to call
+ * @param {mixed} args The argument supplied to the function
+ * @chainable
+ */
+ invoke_function : function(functionname, args) {
+ var module;
+ for (module in this.registermodules) {
+ if (functionname in this.registermodules[module]) {
+ this.registermodules[module][functionname](args);
+ }
+ }
+
+ return this;
+ }
+}, {
+ NAME : QUIZBASENAME,
+ ATTRS : {}
+});
+
+// Ensure that M.course exists and that coursebase is initialised correctly
+M.mod_quiz = M.mod_quiz || {};
+M.mod_quiz.quizbase = M.mod_quiz.quizbase || new QUIZBASE();
+
+// Abstract functions that needs to be defined per format (course/format/somename/format.js)
+M.mod_quiz.edit = M.mod_quiz.edit || {};
+
+/**
+ * Swap section (should be defined in format.js if requred)
+ *
+ * @param {YUI} Y YUI3 instance
+ * @param {string} node1 node to swap to
+ * @param {string} node2 node to swap with
+ * @return {NodeList} section list
+ */
+M.mod_quiz.edit.swap_sections = function(Y, node1, node2) {
+ var CSS = {
+ COURSECONTENT : 'mod-quiz-edit-content',
+ SECTIONADDMENUS : 'section_add_menus'
+ };
+
+ var sectionlist = Y.Node.all('.'+CSS.COURSECONTENT+' '+M.mod_quiz.edit.get_section_selector(Y));
+ // Swap menus.
+ sectionlist.item(node1).one('.'+CSS.SECTIONADDMENUS).swap(sectionlist.item(node2).one('.'+CSS.SECTIONADDMENUS));
+};
+
+/**
+ * Process sections after ajax response (should be defined in format.js)
+ * If some response is expected, we pass it over to format, as it knows better
+ * hot to process it.
+ *
+ * @param {YUI} Y YUI3 instance
+ * @param {NodeList} list of sections
+ * @param {array} response ajax response
+ * @param {string} sectionfrom first affected section
+ * @param {string} sectionto last affected section
+ * @return void
+ */
+M.mod_quiz.edit.process_sections = function(Y, sectionlist, response, sectionfrom, sectionto) {
+ var CSS = {
+ SECTIONNAME : 'sectionname'
+ },
+ SELECTORS = {
+ SECTIONLEFTSIDE : '.left .section-handle img'
+ };
+
+ if (response.action === 'move') {
+ // If moving up swap around 'sectionfrom' and 'sectionto' so the that loop operates.
+ if (sectionfrom > sectionto) {
+ var temp = sectionto;
+ sectionto = sectionfrom;
+ sectionfrom = temp;
+ }
+
+ // Update titles and move icons in all affected sections.
+ var ele, str, stridx, newstr;
+
+ for (var i = sectionfrom; i <= sectionto; i++) {
+ // Update section title.
+ sectionlist.item(i).one('.'+CSS.SECTIONNAME).setContent(response.sectiontitles[i]);
+
+ // Update move icon.
+ ele = sectionlist.item(i).one(SELECTORS.SECTIONLEFTSIDE);
+ str = ele.getAttribute('alt');
+ stridx = str.lastIndexOf(' ');
+ newstr = str.substr(0, stridx + 1) + i;
+ ele.setAttribute('alt', newstr);
+ ele.setAttribute('title', newstr); // For FireFox as 'alt' is not refreshed.
+
+ // Remove the current class as section has been moved.
+ sectionlist.item(i).removeClass('current');
+ }
+ // If there is a current section, apply corresponding class in order to highlight it.
+ if (response.current !== -1) {
+ // Add current class to the required section.
+ sectionlist.item(response.current).addClass('current');
+ }
+ }
+};
+
+/**
+* Get sections config for this format, for examples see function definition
+* in the formats.
+*
+* @return {object} section list configuration
+*/
+M.mod_quiz.edit.get_config = function() {
+ return {
+ container_node : 'ul',
+ container_class : 'slots',
+ section_node : 'li',
+ section_class : 'section'
+ };
+};
+
+/**
+ * Get section list for this format (usually items inside container_node.container_class selector)
+ *
+ * @param {YUI} Y YUI3 instance
+ * @return {string} section selector
+ */
+M.mod_quiz.edit.get_section_selector = function() {
+ var config = M.mod_quiz.edit.get_config();
+ if (config.section_node && config.section_class) {
+ return config.section_node + '.' + config.section_class;
+ }
+ return null;
+};
+
+/**
+ * Get section wraper for this format (only used in case when each
+ * container_node.container_class node is wrapped in some other element).
+ *
+ * @param {YUI} Y YUI3 instance
+ * @return {string} section wrapper selector or M.mod_quiz.format.get_section_selector
+ * if section_wrapper_node and section_wrapper_class are not defined in the format config.
+ */
+M.mod_quiz.edit.get_section_wrapper = function(Y) {
+ var config = M.mod_quiz.edit.get_config();
+ if (config.section_wrapper_node && config.section_wrapper_class) {
+ return config.section_wrapper_node + '.' + config.section_wrapper_class;
+ }
+ return M.mod_quiz.edit.get_section_selector(Y);
+};
+
+/**
+ * Get the tag of container node
+ *
+ * @return {string} tag of container node.
+ */
+M.mod_quiz.edit.get_containernode = function() {
+ var config = M.mod_quiz.edit.get_config();
+ if (config.container_node) {
+ return config.container_node;
+ } else {
+ }
+};
+
+/**
+ * Get the class of container node
+ *
+ * @return {string} class of the container node.
+ */
+M.mod_quiz.edit.get_containerclass = function() {
+ var config = M.mod_quiz.edit.get_config();
+ if (config.container_class) {
+ return config.container_class;
+ } else {
+ }
+};
+
+/**
+ * Get the tag of draggable node (section wrapper if exists, otherwise section)
+ *
+ * @return {string} tag of the draggable node.
+ */
+M.mod_quiz.edit.get_sectionwrappernode = function() {
+ var config = M.mod_quiz.edit.get_config();
+ if (config.section_wrapper_node) {
+ return config.section_wrapper_node;
+ } else {
+ return config.section_node;
+ }
+};
+
+/**
+ * Get the class of draggable node (section wrapper if exists, otherwise section)
+ *
+ * @return {string} class of the draggable node.
+ */
+M.mod_quiz.edit.get_sectionwrapperclass = function() {
+ var config = M.mod_quiz.edit.get_config();
+ if (config.section_wrapper_class) {
+ return config.section_wrapper_class;
+ } else {
+ return config.section_class;
+ }
+};
+
+/**
+ * Get the tag of section node
+ *
+ * @return {string} tag of section node.
+ */
+M.mod_quiz.edit.get_sectionnode = function() {
+ var config = M.mod_quiz.edit.get_config();
+ if (config.section_node) {
+ return config.section_node;
+ } else {
+ }
+};
+
+/**
+ * Get the class of section node
+ *
+ * @return {string} class of the section node.
+ */
+M.mod_quiz.edit.get_sectionclass = function() {
+ var config = M.mod_quiz.edit.get_config();
+ if (config.section_class) {
+ return config.section_class;
+ } else {
+ }
+};
+
+
+}, '@VERSION@', {"requires": ["base", "node"]});
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-quizquestionbank/moodle-mod_quiz-quizquestionbank-debug.js b/mod/quiz/yui/build/moodle-mod_quiz-quizquestionbank/moodle-mod_quiz-quizquestionbank-debug.js
new file mode 100644
index 00000000000..3e2515b364a
--- /dev/null
+++ b/mod/quiz/yui/build/moodle-mod_quiz-quizquestionbank/moodle-mod_quiz-quizquestionbank-debug.js
@@ -0,0 +1,165 @@
+YUI.add('moodle-mod_quiz-quizquestionbank', function (Y, NAME) {
+
+// 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 .
+
+
+/**
+ * Add questions from question bank functionality for a popup in quiz editing page.
+ *
+ * @package mod_quiz
+ * @copyright 2014 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+var CSS = {
+ QBANKLOADING: 'div.questionbankloading',
+ ADDQUESTIONLINKS: 'ul.menu a.questionbank',
+ ADDTOQUIZCONTAINER: 'td.addtoquizaction'
+};
+
+var PARAMS = {
+ PAGE: 'addonpage',
+ HEADER: 'header'
+};
+
+var POPUP = function() {
+ POPUP.superclass.constructor.apply(this, arguments);
+};
+
+Y.extend(POPUP, Y.Base, {
+ loadingDiv: '',
+ dialogue: null,
+ addonpage: 0,
+
+ create_dialogue: function() {
+ // Create a dialogue on the page and hide it.
+ config = {
+ headerContent : '',
+ bodyContent : Y.one(CSS.QBANKLOADING),
+ draggable : true,
+ modal : true,
+ centered: true,
+ width: null,
+ visible: false,
+ postmethod: 'form',
+ footerContent: null,
+ extraClasses: ['mod_quiz_qbank_dialogue']
+ };
+ this.dialogue = new M.core.dialogue(config);
+ this.dialogue.bodyNode.delegate('click', this.link_clicked, 'a[href]', this);
+ this.dialogue.hide();
+
+ this.loadingDiv = this.dialogue.bodyNode.getHTML();
+
+ Y.later(100, this, function() {this.load_content(window.location.search);});
+ },
+
+ initializer : function() {
+ if (!Y.one(CSS.QBANKLOADING)) {
+ return;
+ }
+ this.create_dialogue();
+ Y.one('body').delegate('click', this.display_dialogue, CSS.ADDQUESTIONLINKS, this);
+ },
+
+ display_dialogue : function (e) {
+ e.preventDefault();
+ this.dialogue.set('headerContent', e.currentTarget.getData(PARAMS.HEADER));
+
+ this.addonpage = e.currentTarget.getData(PARAMS.PAGE);
+ var controlsDiv = this.dialogue.bodyNode.one('.modulespecificbuttonscontainer');
+ if (controlsDiv) {
+ var hidden = controlsDiv.one('input[name=addonpage]');
+ if (!hidden) {
+ hidden = controlsDiv.appendChild('');
+ }
+ hidden.set('value', this.addonpage);
+ }
+
+ this.dialogue.show();
+ },
+
+ load_content : function(queryString) {
+ Y.log('Starting load.');
+ this.dialogue.bodyNode.append(this.loadingDiv);
+
+ // If to support old IE.
+ if (window.history.replaceState) {
+ window.history.replaceState(null, '', M.cfg.wwwroot + '/mod/quiz/edit.php' + queryString);
+ }
+
+ Y.io(M.cfg.wwwroot + '/mod/quiz/questionbank.ajax.php' + queryString, {
+ method: 'GET',
+ on: {
+ success: this.load_done,
+ failure: this.load_failed
+ },
+ context: this
+ });
+
+ Y.log('Load started.');
+ },
+
+ load_done: function(transactionid, response) {
+ var result = JSON.parse(response.responseText);
+ if (!result.status || result.status !== 'OK') {
+ // Because IIS is useless, Moodle can't send proper HTTP response
+ // codes, so we have to detect failures manually.
+ this.load_failed(transactionid, response);
+ return;
+ }
+
+ Y.log('Load completed.');
+
+ this.dialogue.bodyNode.setHTML(result.contents);
+ Y.use('moodle-question-chooser', function() {M.question.init_chooser({});});
+ this.dialogue.bodyNode.one('form').delegate('change', this.options_changed, '.searchoptions', this);
+
+ if (this.dialogue.visible) {
+ Y.later(0, this.dialogue, this.dialogue.centerDialogue);
+ }
+ M.question.qbankmanager.init();
+ },
+
+ load_failed: function() {
+ Y.log('Load failed.');
+ },
+
+ link_clicked: function(e) {
+ if (e.currentTarget.ancestor(CSS.ADDTOQUIZCONTAINER)) {
+ // These links need to work like normal, after we modify the URL.
+ e.currentTarget.set('href', e.currentTarget.get('href') + '&addonpage=' + this.addonpage);
+ return;
+ }
+ e.preventDefault();
+ this.load_content(e.currentTarget.get('search'));
+ },
+
+ options_changed: function(e) {
+ e.preventDefault();
+ this.load_content('?' + Y.IO.stringify(e.currentTarget.get('form')));
+ }
+});
+
+M.mod_quiz = M.mod_quiz || {};
+M.mod_quiz.quizquestionbank = M.mod_quiz.quizquestionbank || {};
+M.mod_quiz.quizquestionbank.init = function() {
+ return new POPUP();
+};
+
+
+}, '@VERSION@', {"requires": ["base", "event", "node", "io", "io-form", "yui-later", "moodle-question-qbankmanager"]});
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-quizquestionbank/moodle-mod_quiz-quizquestionbank-min.js b/mod/quiz/yui/build/moodle-mod_quiz-quizquestionbank/moodle-mod_quiz-quizquestionbank-min.js
new file mode 100644
index 00000000000..64906979541
--- /dev/null
+++ b/mod/quiz/yui/build/moodle-mod_quiz-quizquestionbank/moodle-mod_quiz-quizquestionbank-min.js
@@ -0,0 +1 @@
+YUI.add("moodle-mod_quiz-quizquestionbank",function(e,t){var n={QBANKLOADING:"div.questionbankloading",ADDQUESTIONLINKS:"ul.menu a.questionbank",ADDTOQUIZCONTAINER:"td.addtoquizaction"},r={PAGE:"addonpage",HEADER:"header"},i=function(){i.superclass.constructor.apply(this,arguments)};e.extend(i,e.Base,{loadingDiv:"",dialogue:null,addonpage:0,create_dialogue:function(){config={headerContent:"",bodyContent:e.one(n.QBANKLOADING),draggable:!0,modal:!0,centered:!0,width:null,visible:!1,postmethod:"form",footerContent:null,extraClasses:["mod_quiz_qbank_dialogue"]},this.dialogue=new M.core.dialogue(config),this.dialogue.bodyNode.delegate("click",this.link_clicked,"a[href]",this),this.dialogue.hide(),this.loadingDiv=this.dialogue.bodyNode.getHTML(),e.later(100,this,function(){this.load_content(window.location.search)})},initializer:function(){if(!e.one(n.QBANKLOADING))return;this.create_dialogue(),e.one("body").delegate("click",this.display_dialogue,n.ADDQUESTIONLINKS,this)},display_dialogue:function(e){e.preventDefault(),this.dialogue.set("headerContent",e.currentTarget.getData(r.HEADER)),this.addonpage=e.currentTarget.getData(r.PAGE);var t=this.dialogue.bodyNode.one(".modulespecificbuttonscontainer");if(t){var n=t.one("input[name=addonpage]");n||(n=t.appendChild('')),n.set("value",this.addonpage)}this.dialogue.show()},load_content:function(t){this.dialogue.bodyNode.append(this.loadingDiv),window.history.replaceState&&window.history.replaceState(null,"",M.cfg.wwwroot+"/mod/quiz/edit.php"+t),e.io(M.cfg.wwwroot+"/mod/quiz/questionbank.ajax.php"+t,{method:"GET",on:{success:this.load_done,failure:this.load_failed},context:this})},load_done:function(t,n){var r=JSON.parse(n.responseText);if(!r.status||r.status!=="OK"){this.load_failed(t,n);return}this.dialogue.bodyNode.setHTML(r.contents),e.use("moodle-question-chooser",function(){M.question.init_chooser({})}),this.dialogue.bodyNode.one("form").delegate("change",this.options_changed,".searchoptions",this),this.dialogue.visible&&e.later(0,this.dialogue,this.dialogue.centerDialogue),M.question.qbankmanager.init()},load_failed:function(){},link_clicked:function(e){if(e.currentTarget.ancestor(n.ADDTOQUIZCONTAINER)){e.currentTarget.set("href",e.currentTarget.get("href")+"&addonpage="+this.addonpage);return}e.preventDefault(),this.load_content(e.currentTarget.get("search"))},options_changed:function(t){t.preventDefault(),this.load_content("?"+e.IO.stringify(t.currentTarget.get("form")))}}),M.mod_quiz=M.mod_quiz||{},M.mod_quiz.quizquestionbank=M.mod_quiz.quizquestionbank||{},M.mod_quiz.quizquestionbank.init=function(){return new i}},"@VERSION@",{requires:["base","event","node","io","io-form","yui-later","moodle-question-qbankmanager"]});
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-quizquestionbank/moodle-mod_quiz-quizquestionbank.js b/mod/quiz/yui/build/moodle-mod_quiz-quizquestionbank/moodle-mod_quiz-quizquestionbank.js
new file mode 100644
index 00000000000..5ef76bf11d4
--- /dev/null
+++ b/mod/quiz/yui/build/moodle-mod_quiz-quizquestionbank/moodle-mod_quiz-quizquestionbank.js
@@ -0,0 +1,161 @@
+YUI.add('moodle-mod_quiz-quizquestionbank', function (Y, NAME) {
+
+// 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 .
+
+
+/**
+ * Add questions from question bank functionality for a popup in quiz editing page.
+ *
+ * @package mod_quiz
+ * @copyright 2014 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+var CSS = {
+ QBANKLOADING: 'div.questionbankloading',
+ ADDQUESTIONLINKS: 'ul.menu a.questionbank',
+ ADDTOQUIZCONTAINER: 'td.addtoquizaction'
+};
+
+var PARAMS = {
+ PAGE: 'addonpage',
+ HEADER: 'header'
+};
+
+var POPUP = function() {
+ POPUP.superclass.constructor.apply(this, arguments);
+};
+
+Y.extend(POPUP, Y.Base, {
+ loadingDiv: '',
+ dialogue: null,
+ addonpage: 0,
+
+ create_dialogue: function() {
+ // Create a dialogue on the page and hide it.
+ config = {
+ headerContent : '',
+ bodyContent : Y.one(CSS.QBANKLOADING),
+ draggable : true,
+ modal : true,
+ centered: true,
+ width: null,
+ visible: false,
+ postmethod: 'form',
+ footerContent: null,
+ extraClasses: ['mod_quiz_qbank_dialogue']
+ };
+ this.dialogue = new M.core.dialogue(config);
+ this.dialogue.bodyNode.delegate('click', this.link_clicked, 'a[href]', this);
+ this.dialogue.hide();
+
+ this.loadingDiv = this.dialogue.bodyNode.getHTML();
+
+ Y.later(100, this, function() {this.load_content(window.location.search);});
+ },
+
+ initializer : function() {
+ if (!Y.one(CSS.QBANKLOADING)) {
+ return;
+ }
+ this.create_dialogue();
+ Y.one('body').delegate('click', this.display_dialogue, CSS.ADDQUESTIONLINKS, this);
+ },
+
+ display_dialogue : function (e) {
+ e.preventDefault();
+ this.dialogue.set('headerContent', e.currentTarget.getData(PARAMS.HEADER));
+
+ this.addonpage = e.currentTarget.getData(PARAMS.PAGE);
+ var controlsDiv = this.dialogue.bodyNode.one('.modulespecificbuttonscontainer');
+ if (controlsDiv) {
+ var hidden = controlsDiv.one('input[name=addonpage]');
+ if (!hidden) {
+ hidden = controlsDiv.appendChild('');
+ }
+ hidden.set('value', this.addonpage);
+ }
+
+ this.dialogue.show();
+ },
+
+ load_content : function(queryString) {
+ this.dialogue.bodyNode.append(this.loadingDiv);
+
+ // If to support old IE.
+ if (window.history.replaceState) {
+ window.history.replaceState(null, '', M.cfg.wwwroot + '/mod/quiz/edit.php' + queryString);
+ }
+
+ Y.io(M.cfg.wwwroot + '/mod/quiz/questionbank.ajax.php' + queryString, {
+ method: 'GET',
+ on: {
+ success: this.load_done,
+ failure: this.load_failed
+ },
+ context: this
+ });
+
+ },
+
+ load_done: function(transactionid, response) {
+ var result = JSON.parse(response.responseText);
+ if (!result.status || result.status !== 'OK') {
+ // Because IIS is useless, Moodle can't send proper HTTP response
+ // codes, so we have to detect failures manually.
+ this.load_failed(transactionid, response);
+ return;
+ }
+
+
+ this.dialogue.bodyNode.setHTML(result.contents);
+ Y.use('moodle-question-chooser', function() {M.question.init_chooser({});});
+ this.dialogue.bodyNode.one('form').delegate('change', this.options_changed, '.searchoptions', this);
+
+ if (this.dialogue.visible) {
+ Y.later(0, this.dialogue, this.dialogue.centerDialogue);
+ }
+ M.question.qbankmanager.init();
+ },
+
+ load_failed: function() {
+ },
+
+ link_clicked: function(e) {
+ if (e.currentTarget.ancestor(CSS.ADDTOQUIZCONTAINER)) {
+ // These links need to work like normal, after we modify the URL.
+ e.currentTarget.set('href', e.currentTarget.get('href') + '&addonpage=' + this.addonpage);
+ return;
+ }
+ e.preventDefault();
+ this.load_content(e.currentTarget.get('search'));
+ },
+
+ options_changed: function(e) {
+ e.preventDefault();
+ this.load_content('?' + Y.IO.stringify(e.currentTarget.get('form')));
+ }
+});
+
+M.mod_quiz = M.mod_quiz || {};
+M.mod_quiz.quizquestionbank = M.mod_quiz.quizquestionbank || {};
+M.mod_quiz.quizquestionbank.init = function() {
+ return new POPUP();
+};
+
+
+}, '@VERSION@', {"requires": ["base", "event", "node", "io", "io-form", "yui-later", "moodle-question-qbankmanager"]});
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-randomquestion/moodle-mod_quiz-randomquestion-debug.js b/mod/quiz/yui/build/moodle-mod_quiz-randomquestion/moodle-mod_quiz-randomquestion-debug.js
new file mode 100644
index 00000000000..9f2c00e64dd
--- /dev/null
+++ b/mod/quiz/yui/build/moodle-mod_quiz-randomquestion/moodle-mod_quiz-randomquestion-debug.js
@@ -0,0 +1,85 @@
+YUI.add('moodle-mod_quiz-randomquestion', function (Y, NAME) {
+
+// 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 .
+
+
+/**
+ * Add a random question functionality for a popup in quiz editing page.
+ *
+ * @package mod_quiz
+ * @copyright 2014 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+var CSS = {
+ RANDOMQUESTIONFORM: 'div.randomquestionformforpopup',
+ PAGEHIDDENINPUT: 'input#rform_qpage',
+ RANDOMQUESTIONLINKS: 'ul.menu a.addarandomquestion'
+};
+
+var PARAMS = {
+ PAGE: 'addonpage',
+ HEADER: 'header',
+ FORM: 'form'
+};
+
+var POPUP = function() {
+ POPUP.superclass.constructor.apply(this, arguments);
+};
+
+Y.extend(POPUP, Y.Base, {
+
+ dialogue: function(header) {
+ // Create a dialogue on the page and hide it.
+ config = {
+ headerContent : header,
+ bodyContent : Y.one(CSS.RANDOMQUESTIONFORM),
+ draggable : true,
+ modal : true,
+ zIndex : 1000,
+ centered: false,
+ width: 'auto',
+ visible: false,
+ postmethod: 'form',
+ footerContent: null
+ };
+ var popup = { dialog: null };
+ popup.dialog = new M.core.dialogue(config);
+ popup.dialog.show();
+ },
+
+ initializer : function() {
+ Y.one('body').delegate('click', this.display_dialogue, CSS.RANDOMQUESTIONLINKS, this);
+ },
+
+ display_dialogue : function (e) {
+ e.preventDefault();
+
+ Y.one(CSS.RANDOMQUESTIONFORM + ' ' + CSS.PAGEHIDDENINPUT).set('value',
+ e.currentTarget.getData(PARAMS.PAGE));
+
+ this.dialogue(e.currentTarget.getData(PARAMS.HEADER));
+ }
+});
+
+M.mod_quiz = M.mod_quiz || {};
+M.mod_quiz.randomquestion = M.mod_quiz.randomquestion || {};
+M.mod_quiz.randomquestion.init = function() {
+ return new POPUP();
+};
+
+
+}, '@VERSION@', {"requires": ["base", "event", "node", "io", "moodle-core-notification-dialogue"]});
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-randomquestion/moodle-mod_quiz-randomquestion-min.js b/mod/quiz/yui/build/moodle-mod_quiz-randomquestion/moodle-mod_quiz-randomquestion-min.js
new file mode 100644
index 00000000000..7dfa2a9cdc0
--- /dev/null
+++ b/mod/quiz/yui/build/moodle-mod_quiz-randomquestion/moodle-mod_quiz-randomquestion-min.js
@@ -0,0 +1 @@
+YUI.add("moodle-mod_quiz-randomquestion",function(e,t){var n={RANDOMQUESTIONFORM:"div.randomquestionformforpopup",PAGEHIDDENINPUT:"input#rform_qpage",RANDOMQUESTIONLINKS:"ul.menu a.addarandomquestion"},r={PAGE:"addonpage",HEADER:"header",FORM:"form"},i=function(){i.superclass.constructor.apply(this,arguments)};e.extend(i,e.Base,{dialogue:function(t){config={headerContent:t,bodyContent:e.one(n.RANDOMQUESTIONFORM),draggable:!0,modal:!0,zIndex:1e3,centered:!1,width:"auto",visible:!1,postmethod:"form",footerContent:null};var r={dialog:null};r.dialog=new M.core.dialogue(config),r.dialog.show()},initializer:function(){e.one("body").delegate("click",this.display_dialogue,n.RANDOMQUESTIONLINKS,this)},display_dialogue:function(t){t.preventDefault(),e.one(n.RANDOMQUESTIONFORM+" "+n.PAGEHIDDENINPUT).set("value",t.currentTarget.getData(r.PAGE)),this.dialogue(t.currentTarget.getData(r.HEADER))}}),M.mod_quiz=M.mod_quiz||{},M.mod_quiz.randomquestion=M.mod_quiz.randomquestion||{},M.mod_quiz.randomquestion.init=function(){return new i}},"@VERSION@",{requires:["base","event","node","io","moodle-core-notification-dialogue"]});
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-randomquestion/moodle-mod_quiz-randomquestion.js b/mod/quiz/yui/build/moodle-mod_quiz-randomquestion/moodle-mod_quiz-randomquestion.js
new file mode 100644
index 00000000000..9f2c00e64dd
--- /dev/null
+++ b/mod/quiz/yui/build/moodle-mod_quiz-randomquestion/moodle-mod_quiz-randomquestion.js
@@ -0,0 +1,85 @@
+YUI.add('moodle-mod_quiz-randomquestion', function (Y, NAME) {
+
+// 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 .
+
+
+/**
+ * Add a random question functionality for a popup in quiz editing page.
+ *
+ * @package mod_quiz
+ * @copyright 2014 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+var CSS = {
+ RANDOMQUESTIONFORM: 'div.randomquestionformforpopup',
+ PAGEHIDDENINPUT: 'input#rform_qpage',
+ RANDOMQUESTIONLINKS: 'ul.menu a.addarandomquestion'
+};
+
+var PARAMS = {
+ PAGE: 'addonpage',
+ HEADER: 'header',
+ FORM: 'form'
+};
+
+var POPUP = function() {
+ POPUP.superclass.constructor.apply(this, arguments);
+};
+
+Y.extend(POPUP, Y.Base, {
+
+ dialogue: function(header) {
+ // Create a dialogue on the page and hide it.
+ config = {
+ headerContent : header,
+ bodyContent : Y.one(CSS.RANDOMQUESTIONFORM),
+ draggable : true,
+ modal : true,
+ zIndex : 1000,
+ centered: false,
+ width: 'auto',
+ visible: false,
+ postmethod: 'form',
+ footerContent: null
+ };
+ var popup = { dialog: null };
+ popup.dialog = new M.core.dialogue(config);
+ popup.dialog.show();
+ },
+
+ initializer : function() {
+ Y.one('body').delegate('click', this.display_dialogue, CSS.RANDOMQUESTIONLINKS, this);
+ },
+
+ display_dialogue : function (e) {
+ e.preventDefault();
+
+ Y.one(CSS.RANDOMQUESTIONFORM + ' ' + CSS.PAGEHIDDENINPUT).set('value',
+ e.currentTarget.getData(PARAMS.PAGE));
+
+ this.dialogue(e.currentTarget.getData(PARAMS.HEADER));
+ }
+});
+
+M.mod_quiz = M.mod_quiz || {};
+M.mod_quiz.randomquestion = M.mod_quiz.randomquestion || {};
+M.mod_quiz.randomquestion.init = function() {
+ return new POPUP();
+};
+
+
+}, '@VERSION@', {"requires": ["base", "event", "node", "io", "moodle-core-notification-dialogue"]});
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-repaginate/moodle-mod_quiz-repaginate-debug.js b/mod/quiz/yui/build/moodle-mod_quiz-repaginate/moodle-mod_quiz-repaginate-debug.js
new file mode 100644
index 00000000000..d6428b9aea1
--- /dev/null
+++ b/mod/quiz/yui/build/moodle-mod_quiz-repaginate/moodle-mod_quiz-repaginate-debug.js
@@ -0,0 +1,86 @@
+YUI.add('moodle-mod_quiz-repaginate', function (Y, NAME) {
+
+// 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 .
+
+
+/**
+ * Repaginate functionality for a popup in quiz editing page.
+ *
+ * @package mod_quiz
+ * @copyright 2014 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+var CSS = {
+ REPAGINATECONTAINERCLASS: '.rpcontainerclass',
+ REPAGINATECOMMAND: '#repaginatecommand'
+};
+
+var PARAMS = {
+ CMID: 'cmid',
+ HEADER: 'header',
+ FORM: 'form'
+};
+
+var POPUP = function() {
+ POPUP.superclass.constructor.apply(this, arguments);
+};
+
+Y.extend(POPUP, Y.Base, {
+ header: null,
+ body: null,
+
+ initializer : function() {
+ rpcontainerclass = Y.one(CSS.REPAGINATECONTAINERCLASS);
+
+ // Set popup header and body.
+ this.header = rpcontainerclass.getAttribute(PARAMS.HEADER);
+ this.body = rpcontainerclass.getAttribute(PARAMS.FORM);
+ Y.one(CSS.REPAGINATECOMMAND).on('click', this.display_dialog, this);
+ },
+
+ display_dialog : function (e) {
+ e.preventDefault();
+
+ // Configure the popup.
+ var config = {
+ headerContent : this.header,
+ bodyContent : this.body,
+ draggable : true,
+ modal : true,
+ zIndex : 1000,
+ context: [CSS.REPAGINATECOMMAND, 'tr', 'br', ['beforeShow']],
+ centered: false,
+ width: '30em',
+ visible: false,
+ postmethod: 'form',
+ footerContent: null
+ };
+
+ var popup = { dialog: null };
+ popup.dialog = new M.core.dialogue(config);
+ popup.dialog.show();
+ }
+});
+
+M.mod_quiz = M.mod_quiz || {};
+M.mod_quiz.repaginate = M.mod_quiz.repaginate || {};
+M.mod_quiz.repaginate.init = function() {
+ return new POPUP();
+};
+
+
+}, '@VERSION@', {"requires": ["base", "event", "node", "io", "moodle-core-notification-dialogue"]});
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-repaginate/moodle-mod_quiz-repaginate-min.js b/mod/quiz/yui/build/moodle-mod_quiz-repaginate/moodle-mod_quiz-repaginate-min.js
new file mode 100644
index 00000000000..77ab53168b4
--- /dev/null
+++ b/mod/quiz/yui/build/moodle-mod_quiz-repaginate/moodle-mod_quiz-repaginate-min.js
@@ -0,0 +1 @@
+YUI.add("moodle-mod_quiz-repaginate",function(e,t){var n={REPAGINATECONTAINERCLASS:".rpcontainerclass",REPAGINATECOMMAND:"#repaginatecommand"},r={CMID:"cmid",HEADER:"header",FORM:"form"},i=function(){i.superclass.constructor.apply(this,arguments)};e.extend(i,e.Base,{header:null,body:null,initializer:function(){rpcontainerclass=e.one(n.REPAGINATECONTAINERCLASS),this.header=rpcontainerclass.getAttribute(r.HEADER),this.body=rpcontainerclass.getAttribute(r.FORM),e.one(n.REPAGINATECOMMAND).on("click",this.display_dialog,this)},display_dialog:function(e){e.preventDefault();var t={headerContent:this.header,bodyContent:this.body,draggable:!0,modal:!0,zIndex:1e3,context:[n.REPAGINATECOMMAND,"tr","br",["beforeShow"]],centered:!1,width:"30em",visible:!1,postmethod:"form",footerContent:null},r={dialog:null};r.dialog=new M.core.dialogue(t),r.dialog.show()}}),M.mod_quiz=M.mod_quiz||{},M.mod_quiz.repaginate=M.mod_quiz.repaginate||{},M.mod_quiz.repaginate.init=function(){return new i}},"@VERSION@",{requires:["base","event","node","io","moodle-core-notification-dialogue"]});
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-repaginate/moodle-mod_quiz-repaginate.js b/mod/quiz/yui/build/moodle-mod_quiz-repaginate/moodle-mod_quiz-repaginate.js
new file mode 100644
index 00000000000..d6428b9aea1
--- /dev/null
+++ b/mod/quiz/yui/build/moodle-mod_quiz-repaginate/moodle-mod_quiz-repaginate.js
@@ -0,0 +1,86 @@
+YUI.add('moodle-mod_quiz-repaginate', function (Y, NAME) {
+
+// 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 .
+
+
+/**
+ * Repaginate functionality for a popup in quiz editing page.
+ *
+ * @package mod_quiz
+ * @copyright 2014 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+var CSS = {
+ REPAGINATECONTAINERCLASS: '.rpcontainerclass',
+ REPAGINATECOMMAND: '#repaginatecommand'
+};
+
+var PARAMS = {
+ CMID: 'cmid',
+ HEADER: 'header',
+ FORM: 'form'
+};
+
+var POPUP = function() {
+ POPUP.superclass.constructor.apply(this, arguments);
+};
+
+Y.extend(POPUP, Y.Base, {
+ header: null,
+ body: null,
+
+ initializer : function() {
+ rpcontainerclass = Y.one(CSS.REPAGINATECONTAINERCLASS);
+
+ // Set popup header and body.
+ this.header = rpcontainerclass.getAttribute(PARAMS.HEADER);
+ this.body = rpcontainerclass.getAttribute(PARAMS.FORM);
+ Y.one(CSS.REPAGINATECOMMAND).on('click', this.display_dialog, this);
+ },
+
+ display_dialog : function (e) {
+ e.preventDefault();
+
+ // Configure the popup.
+ var config = {
+ headerContent : this.header,
+ bodyContent : this.body,
+ draggable : true,
+ modal : true,
+ zIndex : 1000,
+ context: [CSS.REPAGINATECOMMAND, 'tr', 'br', ['beforeShow']],
+ centered: false,
+ width: '30em',
+ visible: false,
+ postmethod: 'form',
+ footerContent: null
+ };
+
+ var popup = { dialog: null };
+ popup.dialog = new M.core.dialogue(config);
+ popup.dialog.show();
+ }
+});
+
+M.mod_quiz = M.mod_quiz || {};
+M.mod_quiz.repaginate = M.mod_quiz.repaginate || {};
+M.mod_quiz.repaginate.init = function() {
+ return new POPUP();
+};
+
+
+}, '@VERSION@', {"requires": ["base", "event", "node", "io", "moodle-core-notification-dialogue"]});
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes-debug.js b/mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes-debug.js
new file mode 100644
index 00000000000..aece0a61743
--- /dev/null
+++ b/mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes-debug.js
@@ -0,0 +1,867 @@
+YUI.add('moodle-mod_quiz-toolboxes', function (Y, NAME) {
+
+/**
+ * Resource and activity toolbox class.
+ *
+ * This class is responsible for managing AJAX interactions with activities and resources
+ * when viewing a course in editing mode.
+ *
+ * @module moodle-course-toolboxes
+ * @namespace M.course.toolboxes
+ */
+
+// The CSS classes we use.
+ var CSS = {
+ ACTIVITYINSTANCE : 'activityinstance',
+ AVAILABILITYINFODIV : 'div.availabilityinfo',
+ CONTENTWITHOUTLINK : 'contentwithoutlink',
+ CONDITIONALHIDDEN : 'conditionalhidden',
+ DIMCLASS : 'dimmed',
+ DIMMEDTEXT : 'dimmed_text',
+ EDITINSTRUCTIONS : 'editinstructions',
+ EDITINGMAXMARK: 'editor_displayed',
+ HIDE : 'hide',
+ JOIN: 'page_join',
+ MODINDENTCOUNT : 'mod-indent-',
+ MODINDENTHUGE : 'mod-indent-huge',
+ MODULEIDPREFIX : 'slot-',
+ PAGE: 'page',
+ SECTIONHIDDENCLASS : 'hidden',
+ SECTIONIDPREFIX : 'section-',
+ SLOT : 'slot',
+ SHOW : 'editing_show',
+ TITLEEDITOR : 'titleeditor'
+ },
+ // The CSS selectors we use.
+ SELECTOR = {
+ ACTIONAREA: '.actions',
+ ACTIONLINKTEXT : '.actionlinktext',
+ ACTIVITYACTION : 'a.cm-edit-action[data-action], a.editing_maxmark',
+ ACTIVITYFORM : 'span.instancemaxmarkcontainer form',
+ ACTIVITYICON : 'img.activityicon',
+ ACTIVITYINSTANCE : '.' + CSS.ACTIVITYINSTANCE,
+ ACTIVITYLINK: '.' + CSS.ACTIVITYINSTANCE + ' > a',
+ ACTIVITYLI : 'li.activity',
+ ACTIVITYMAXMARK : 'input[name=maxmark]',
+ COMMANDSPAN : '.commands',
+ CONTENTAFTERLINK : 'div.contentafterlink',
+ CONTENTWITHOUTLINK : 'div.contentwithoutlink',
+ EDITMAXMARK: 'a.editing_maxmark',
+ HIDE : 'a.editing_hide',
+ HIGHLIGHT : 'a.editing_highlight',
+ INSTANCENAME : 'span.instancename',
+ INSTANCEMAXMARK : 'span.instancemaxmark',
+ MODINDENTDIV : '.mod-indent',
+ MODINDENTOUTER : '.mod-indent-outer',
+ PAGECONTENT : 'div#page-content',
+ PAGELI : 'li.page',
+ SECTIONUL : 'ul.section',
+ SHOW : 'a.' + CSS.SHOW,
+ SHOWHIDE : 'a.editing_showhide',
+ SLOTLI : 'li.slot',
+ SUMMARKS : '.mod_quiz_summarks'
+ },
+ BODY = Y.one(document.body);
+
+// Setup the basic namespace.
+M.mod_quiz = M.mod_quiz || {};
+
+/**
+ * The toolbox class is a generic class which should never be directly
+ * instantiated. Please extend it instead.
+ *
+ * @class toolbox
+ * @constructor
+ * @protected
+ * @extends Base
+ */
+var TOOLBOX = function() {
+ TOOLBOX.superclass.constructor.apply(this, arguments);
+};
+
+Y.extend(TOOLBOX, Y.Base, {
+ /**
+ * Send a request using the REST API
+ *
+ * @method send_request
+ * @param {Object} data The data to submit with the AJAX request
+ * @param {Node} [statusspinner] A statusspinner which may contain a section loader
+ * @param {Function} success_callback The callback to use on success
+ * @param {Object} [optionalconfig] Any additional configuration to submit
+ * @chainable
+ */
+ send_request: function(data, statusspinner, success_callback, optionalconfig) {
+ // Default data structure
+ if (!data) {
+ data = {};
+ }
+ // Handle any variables which we must pass back through to
+ var pageparams = this.get('config').pageparams,
+ varname;
+ for (varname in pageparams) {
+ data[varname] = pageparams[varname];
+ }
+
+ data.sesskey = M.cfg.sesskey;
+ data.courseid = this.get('courseid');
+ data.quizid = this.get('quizid');
+
+ var uri = M.cfg.wwwroot + this.get('ajaxurl');
+
+ // Define the configuration to send with the request
+ var responsetext = [];
+ var config = {
+ method: 'POST',
+ data: data,
+ on: {
+ success: function(tid, response) {
+ try {
+ responsetext = Y.JSON.parse(response.responseText);
+ if (responsetext.error) {
+ new M.core.ajaxException(responsetext);
+ }
+ } catch (e) {}
+
+ // Run the callback if we have one.
+ if (responsetext.newsummarks) {
+ Y.one(SELECTOR.SUMMARKS).setHTML(responsetext.newsummarks);
+ }
+ if (success_callback) {
+ Y.bind(success_callback, this, responsetext)();
+ }
+
+ if (statusspinner) {
+ window.setTimeout(function() {
+ statusspinner.hide();
+ }, 400);
+ }
+ },
+ failure: function(tid, response) {
+ if (statusspinner) {
+ statusspinner.hide();
+ }
+ new M.core.ajaxException(response);
+ }
+ },
+ context: this
+ };
+
+ // Apply optional config
+ if (optionalconfig) {
+ for (varname in optionalconfig) {
+ config[varname] = optionalconfig[varname];
+ }
+ }
+
+ if (statusspinner) {
+ statusspinner.show();
+ }
+
+ // Send the request
+ Y.io(uri, config);
+ return this;
+ }
+},
+{
+ NAME: 'mod_quiz-toolbox',
+ ATTRS: {
+ /**
+ * The ID of the Moodle Course being edited.
+ *
+ * @attribute courseid
+ * @default 0
+ * @type Number
+ */
+ courseid: {
+ 'value': 0
+ },
+
+ /**
+ * The Moodle course format.
+ *
+ * @attribute format
+ * @default 'topics'
+ * @type String
+ */
+ quizid: {
+ 'value': 0
+ },
+ /**
+ * The URL to use when submitting requests.
+ * @attribute ajaxurl
+ * @default null
+ * @type String
+ */
+ ajaxurl: {
+ 'value': null
+ },
+ /**
+ * Any additional configuration passed when creating the instance.
+ *
+ * @attribute config
+ * @default {}
+ * @type Object
+ */
+ config: {
+ 'value': {}
+ }
+ }
+}
+);
+/**
+ * Resource and activity toolbox class.
+ *
+ * This class is responsible for managing AJAX interactions with activities and resources
+ * when viewing a quiz in editing mode.
+ *
+ * @module mod_quiz-resource-toolbox
+ * @namespace M.mod_quiz.resource_toolbox
+ */
+
+/**
+ * Resource and activity toolbox class.
+ *
+ * This is a class extending TOOLBOX containing code specific to resources
+ *
+ * This class is responsible for managing AJAX interactions with activities and resources
+ * when viewing a quiz in editing mode.
+ *
+ * @class resources
+ * @constructor
+ * @extends M.course.toolboxes.toolbox
+ */
+var RESOURCETOOLBOX = function() {
+ RESOURCETOOLBOX.superclass.constructor.apply(this, arguments);
+};
+
+Y.extend(RESOURCETOOLBOX, TOOLBOX, {
+ /**
+ * An Array of events added when editing a max mark field.
+ * These should all be detached when editing is complete.
+ *
+ * @property editmaxmarkevents
+ * @protected
+ * @type Array
+ * @protected
+ */
+ editmaxmarkevents: [],
+
+ /**
+ *
+ */
+ NODE_PAGE: 1,
+ NODE_SLOT: 2,
+ NODE_JOIN: 3,
+
+ /**
+ * Initialize the resource toolbox
+ *
+ * For each activity the commands are updated and a reference to the activity is attached.
+ * This way it doesn't matter where the commands are going to called from they have a reference to the
+ * activity that they relate to.
+ * This is essential as some of the actions are displayed in an actionmenu which removes them from the
+ * page flow.
+ *
+ * This function also creates a single event delegate to manage all AJAX actions for all activities on
+ * the page.
+ *
+ * @method initializer
+ * @protected
+ */
+ initializer: function() {
+ M.mod_quiz.quizbase.register_module(this);
+ BODY.delegate('key', this.handle_data_action, 'down:enter', SELECTOR.ACTIVITYACTION, this);
+ Y.delegate('click', this.handle_data_action, BODY, SELECTOR.ACTIVITYACTION, this);
+ },
+
+ /**
+ * Handles the delegation event. When this is fired someone has triggered an action.
+ *
+ * Note not all actions will result in an AJAX enhancement.
+ *
+ * @protected
+ * @method handle_data_action
+ * @param {EventFacade} ev The event that was triggered.
+ * @returns {boolean}
+ */
+ handle_data_action: function(ev) {
+ // We need to get the anchor element that triggered this event.
+ var node = ev.target;
+ if (!node.test('a')) {
+ node = node.ancestor(SELECTOR.ACTIVITYACTION);
+ }
+
+ // From the anchor we can get both the activity (added during initialisation) and the action being
+ // performed (added by the UI as a data attribute).
+ var action = node.getData('action'),
+ activity = node.ancestor(SELECTOR.ACTIVITYLI);
+
+ if (!node.test('a') || !action || !activity) {
+ // It wasn't a valid action node.
+ return;
+ }
+
+ // Switch based upon the action and do the desired thing.
+ switch (action) {
+ case 'editmaxmark':
+ // The user wishes to edit the maxmark of the resource.
+ this.edit_maxmark(ev, node, activity, action);
+ break;
+ case 'delete':
+ // The user is deleting the activity.
+ this.delete_with_confirmation(ev, node, activity, action);
+ break;
+ case 'linkpage':
+ case 'unlinkpage':
+ // The user is linking or unlinking pages.
+ this.link_page(ev, node, activity, action);
+ break;
+ default:
+ // Nothing to do here!
+ break;
+ }
+ },
+
+ /**
+ * Add a loading icon to the specified activity.
+ *
+ * The icon is added within the action area.
+ *
+ * @method add_spinner
+ * @param {Node} activity The activity to add a loading icon to
+ * @return {Node|null} The newly created icon, or null if the action area was not found.
+ */
+ add_spinner: function(activity) {
+ var actionarea = activity.one(SELECTOR.ACTIONAREA);
+ if (actionarea) {
+ return M.util.add_spinner(Y, actionarea);
+ }
+ return null;
+ },
+
+ /**
+ * Deletes the given activity or resource after confirmation.
+ *
+ * @protected
+ * @method delete_with_confirmation
+ * @param {EventFacade} ev The event that was fired.
+ * @param {Node} button The button that triggered this action.
+ * @param {Node} activity The activity node that this action will be performed on.
+ * @chainable
+ */
+ delete_with_confirmation: function(ev, button, activity) {
+ // Prevent the default button action
+ ev.preventDefault();
+
+ // Get the element we're working on
+ var element = activity,
+ // Create confirm string (different if element has or does not have name)
+ confirmstring = '',
+ qtypename = M.util.get_string('pluginname',
+ 'qtype_' + element.getAttribute('class').match(/qtype_([^\s]*)/)[1]);
+ confirmstring = M.util.get_string('confirmremovequestion', 'quiz', qtypename);
+
+ // Create the confirmation dialogue.
+ var confirm = new M.core.confirm({
+ question: confirmstring,
+ modal: true
+ });
+
+ // If it is confirmed.
+ confirm.on('complete-yes', function() {
+
+ // Actually remove the element.
+ element.remove();
+ Y.Moodle.mod_quiz.util.slot.reorder_slots();
+ var data = {
+ 'class': 'resource',
+ 'action': 'DELETE',
+ 'id': Y.Moodle.mod_quiz.util.slot.getId(element)
+ };
+ this.send_request(data);
+ if (M.core.actionmenu && M.core.actionmenu.instance) {
+ M.core.actionmenu.instance.hideMenu();
+ }
+ window.location.reload(true);
+
+ }, this);
+
+ return this;
+ },
+
+
+ /**
+ * Edit the maxmark for the resource
+ *
+ * @protected
+ * @method edit_maxmark
+ * @param {EventFacade} ev The event that was fired.
+ * @param {Node} button The button that triggered this action.
+ * @param {Node} activity The activity node that this action will be performed on.
+ * @param {String} action The action that has been requested.
+ * @return Boolean
+ */
+ edit_maxmark : function(ev, button, activity) {
+ // Get the element we're working on
+ var activityid = Y.Moodle.mod_quiz.util.slot.getId(activity),
+ instancemaxmark = activity.one(SELECTOR.INSTANCEMAXMARK),
+ instance = activity.one(SELECTOR.ACTIVITYINSTANCE),
+ currentmaxmark = instancemaxmark.get('firstChild'),
+ oldmaxmark = currentmaxmark.get('data'),
+ maxmarktext = oldmaxmark,
+ thisevent,
+ anchor = instancemaxmark,// Grab the anchor so that we can swap it with the edit form.
+ data = {
+ 'class' : 'resource',
+ 'field' : 'getmaxmark',
+ 'id' : activityid
+ };
+
+ // Prevent the default actions.
+ ev.preventDefault();
+
+ this.send_request(data, null, function(response) {
+ if (M.core.actionmenu && M.core.actionmenu.instance) {
+ M.core.actionmenu.instance.hideMenu();
+ }
+
+ // Try to retrieve the existing string from the server
+ if (response.instancemaxmark) {
+ maxmarktext = response.instancemaxmark;
+ }
+
+ // Create the editor and submit button
+ var editform = Y.Node.create('
');
+ var editinstructions = Y.Node.create('')
+ .set('innerHTML', M.util.get_string('edittitleinstructions', 'moodle'));
+ var editor = Y.Node.create('').setAttrs({
+ 'value' : maxmarktext,
+ 'autocomplete' : 'off',
+ 'aria-describedby' : 'id_editinstructions',
+ 'maxLength' : '12',
+ 'size' : parseInt(this.get('config').questiondecimalpoints, 10) + 2
+ });
+
+ // Clear the existing content and put the editor in
+ editform.appendChild(editor);
+ editform.setData('anchor', anchor);
+ instance.insert(editinstructions, 'before');
+ anchor.replace(editform);
+
+ // Force the editing instruction to match the mod-indent position.
+ var padside = 'left';
+ if (right_to_left()) {
+ padside = 'right';
+ }
+
+ // We hide various components whilst editing:
+ activity.addClass(CSS.EDITINGMAXMARK);
+
+ // Focus and select the editor text
+ editor.focus().select();
+
+ // Cancel the edit if we lose focus or the escape key is pressed.
+ thisevent = editor.on('blur', this.edit_maxmark_cancel, this, activity, false);
+ this.editmaxmarkevents.push(thisevent);
+ thisevent = editor.on('key', this.edit_maxmark_cancel, 'esc', this, activity, true);
+ this.editmaxmarkevents.push(thisevent);
+
+ // Handle form submission.
+ thisevent = editform.on('submit', this.edit_maxmark_submit, this, activity, oldmaxmark);
+ this.editmaxmarkevents.push(thisevent);
+ });
+ },
+
+ /**
+ * Handles the submit event when editing the activity or resources maxmark.
+ *
+ * @protected
+ * @method edit_maxmark_submit
+ * @param {EventFacade} ev The event that triggered this.
+ * @param {Node} activity The activity whose maxmark we are altering.
+ * @param {String} originalmaxmark The original maxmark the activity or resource had.
+ */
+ edit_maxmark_submit : function(ev, activity, originalmaxmark) {
+ // We don't actually want to submit anything
+ ev.preventDefault();
+ var newmaxmark = Y.Lang.trim(activity.one(SELECTOR.ACTIVITYFORM + ' ' + SELECTOR.ACTIVITYMAXMARK).get('value'));
+ var spinner = this.add_spinner(activity);
+ this.edit_maxmark_clear(activity);
+ activity.one(SELECTOR.INSTANCEMAXMARK).setContent(newmaxmark);
+ if (newmaxmark !== null && newmaxmark !== "" && newmaxmark !== originalmaxmark) {
+ var data = {
+ 'class' : 'resource',
+ 'field' : 'updatemaxmark',
+ 'maxmark' : newmaxmark,
+ 'id' : Y.Moodle.mod_quiz.util.slot.getId(activity)
+ };
+ this.send_request(data, spinner, function(response) {
+ if (response.instancemaxmark) {
+ activity.one(SELECTOR.INSTANCEMAXMARK).setContent(response.instancemaxmark);
+ }
+ });
+ }
+ },
+
+ /**
+ * Handles the cancel event when editing the activity or resources maxmark.
+ *
+ * @protected
+ * @method edit_maxmark_cancel
+ * @param {EventFacade} ev The event that triggered this.
+ * @param {Node} activity The activity whose maxmark we are altering.
+ * @param {Boolean} preventdefault If true we should prevent the default action from occuring.
+ */
+ edit_maxmark_cancel : function(ev, activity, preventdefault) {
+ if (preventdefault) {
+ ev.preventDefault();
+ }
+ this.edit_maxmark_clear(activity);
+ },
+
+ /**
+ * Handles clearing the editing UI and returning things to the original state they were in.
+ *
+ * @protected
+ * @method edit_maxmark_clear
+ * @param {Node} activity The activity whose maxmark we were altering.
+ */
+ edit_maxmark_clear : function(activity) {
+ // Detach all listen events to prevent duplicate triggers
+ new Y.EventHandle(this.editmaxmarkevents).detach();
+
+ var editform = activity.one(SELECTOR.ACTIVITYFORM),
+ instructions = activity.one('#id_editinstructions');
+ if (editform) {
+ editform.replace(editform.getData('anchor'));
+ }
+ if (instructions) {
+ instructions.remove();
+ }
+
+ // Remove the editing class again to revert the display.
+ activity.removeClass(CSS.EDITINGMAXMARK);
+
+ // Refocus the link which was clicked originally so the user can continue using keyboard nav.
+ Y.later(100, this, function() {
+ activity.one(SELECTOR.EDITMAXMARK).focus();
+ });
+
+ // This hack is to keep Behat happy until they release a version of
+ // MinkSelenium2Driver that fixes
+ // https://github.com/Behat/MinkSelenium2Driver/issues/80.
+ if (!Y.one('input[name=maxmark')) {
+ Y.one('body').append('');
+ }
+ },
+
+ /**
+ * Joins or separates the given slot with the page of the previous slot. Reorders the pages of
+ * the other slots
+ *
+ * @protected
+ * @method link_page
+ * @param {EventFacade} ev The event that was fired.
+ * @param {Node} button The button that triggered this action.
+ * @param {Node} activity The activity node that this action will be performed on.
+ * @chainable
+ */
+ link_page: function(ev, button, activity, action) {
+ // Prevent the default button action
+ ev.preventDefault();
+
+ activity = activity.next('li.activity.slot');
+ var spinner = this.add_spinner(activity),
+ slotid = 0;
+ var value = action === 'linkpage' ? 1:2;
+
+ var data = {
+ 'class': 'resource',
+ 'field': 'linkslottopage',
+ 'id': slotid,
+ 'value': value
+ };
+
+ slotid = Y.Moodle.mod_quiz.util.slot.getId(activity);
+ if (slotid) {
+ data.id = Number(slotid);
+ }
+ this.send_request(data, spinner, function(response) {
+ window.location.reload(true);
+// if (response.slots) {
+// this.repaginate_slots(response.slots);
+// }
+ });
+
+ return this;
+ },
+ repaginate_slots: function(slots) {
+ this.slots = slots;
+ var section = Y.one(SELECTOR.PAGECONTENT + ' ' + SELECTOR.SECTIONUL),
+ activities = section.all(SELECTOR.ACTIVITYLI);
+ activities.each(function(node) {
+
+ // What element is it? page/slot/link
+ // what is the current slot?
+ var type;
+ var slot;
+ if(node.hasClass(CSS.PAGE)){
+ type = this.NODE_PAGE;
+ slot = node.next(SELECTOR.SLOTLI);
+ } else if (node.hasClass(CSS.SLOT)){
+ type = this.NODE_SLOT;
+ slot = node;
+ } else if (node.hasClass(CSS.JOIN)){
+ type = this.NODE_JOIN;
+ slot = node.previous(SELECTOR.SLOTLI);
+ }
+
+ // getSlotnumber() Should be a method of util.slot
+ var slotnumber = Number(Y.Moodle.mod_quiz.util.slot.getNumber(slot));
+ if(!type){
+ // Nothing we can do.
+ return;
+ }
+
+ // Is it correct?
+ if(!this.slots.hasOwnProperty(slotnumber)){
+ // An error. We should handle this.
+ return;
+ }
+
+ var slotdata = this.slots[slotnumber];
+
+ if(type === this.NODE_PAGE){
+ // Get page number
+ var pagenumber = Y.Moodle.mod_quiz.util.page.getNumber(node);
+ // Is the page number correct?
+ if (slotdata.page === pagenumber) {
+ console.log('slotdata.page == pagenumber return');
+ return;
+ }
+
+ if (pagenumber < slotdata.page) {
+ // Remove page node.
+ node.remove();
+ }
+ else {
+ // Add page node.
+ console.log('pagenumber > slotdata.page update page number');
+ }
+
+ }
+ }, this);
+ },
+
+ NAME : 'mod_quiz-resource-toolbox',
+ ATTRS : {
+ courseid : {
+ 'value' : 0
+ },
+ quizid : {
+ 'value' : 0
+ }
+ }
+});
+
+M.mod_quiz.resource_toolbox = null;
+M.mod_quiz.init_resource_toolbox = function(config) {
+ M.mod_quiz.resource_toolbox = new RESOURCETOOLBOX(config);
+ return M.mod_quiz.resource_toolbox;
+};
+/**
+ * Resource and activity toolbox class.
+ *
+ * This class is responsible for managing AJAX interactions with activities and resources
+ * when viewing a course in editing mode.
+ *
+ * @module moodle-mod_quiz-toolboxes
+ * @namespace M.mod_quiz.toolboxes
+ */
+
+/**
+ * Section toolbox class.
+ *
+ * This class is responsible for managing AJAX interactions with sections
+ * when viewing a course in editing mode.
+ *
+ * @class section
+ * @constructor
+ * @extends M.mod_quiz.toolboxes.toolbox
+ */
+var SECTIONTOOLBOX = function() {
+ SECTIONTOOLBOX.superclass.constructor.apply(this, arguments);
+};
+
+Y.extend(SECTIONTOOLBOX, TOOLBOX, {
+ /**
+ * Initialize the section toolboxes module.
+ *
+ * Updates all span.commands with relevant handlers and other required changes.
+ *
+ * @method initializer
+ * @protected
+ */
+ initializer : function() {
+ M.mod_quiz.quizbase.register_module(this);
+
+ // Section Highlighting.
+ Y.delegate('click', this.toggle_highlight, SELECTOR.PAGECONTENT, SELECTOR.SECTIONLI + ' ' + SELECTOR.HIGHLIGHT, this);
+
+ // Section Visibility.
+ Y.delegate('click', this.toggle_hide_section, SELECTOR.PAGECONTENT, SELECTOR.SECTIONLI + ' ' + SELECTOR.SHOWHIDE, this);
+ },
+
+ toggle_hide_section : function(e) {
+ // Prevent the default button action.
+ e.preventDefault();
+
+ // Get the section we're working on.
+ var section = e.target.ancestor(M.mod_quiz.format.get_section_selector(Y)),
+ button = e.target.ancestor('a', true),
+ hideicon = button.one('img'),
+
+ // The value to submit
+ value,
+
+ // The text for strings and images. Also determines the icon to display.
+ action,
+ nextaction;
+
+ if (!section.hasClass(CSS.SECTIONHIDDENCLASS)) {
+ section.addClass(CSS.SECTIONHIDDENCLASS);
+ value = 0;
+ action = 'hide';
+ nextaction = 'show';
+ } else {
+ section.removeClass(CSS.SECTIONHIDDENCLASS);
+ value = 1;
+ action = 'show';
+ nextaction = 'hide';
+ }
+
+ var newstring = M.util.get_string(nextaction + 'fromothers', 'format_' + this.get('format'));
+ hideicon.setAttrs({
+ 'alt' : newstring,
+ 'src' : M.util.image_url('i/' + nextaction)
+ });
+ button.set('title', newstring);
+
+ // Change the highlight status
+ var data = {
+ 'class' : 'section',
+ 'field' : 'visible',
+ 'id' : Y.Moodle.core_course.util.section.getId(section.ancestor(M.mod_quiz.edit.get_section_wrapper(Y), true)),
+ 'value' : value
+ };
+
+ var lightbox = M.util.add_lightbox(Y, section);
+ lightbox.show();
+
+ this.send_request(data, lightbox, function(response) {
+ var activities = section.all(SELECTOR.ACTIVITYLI);
+ activities.each(function(node) {
+ var button;
+ if (node.one(SELECTOR.SHOW)) {
+ button = node.one(SELECTOR.SHOW);
+ } else {
+ button = node.one(SELECTOR.HIDE);
+ }
+ var activityid = Y.Moodle.mod_quiz.util.slot.getId(node);
+
+ // NOTE: resourcestotoggle is returned as a string instead
+ // of a Number so we must cast our activityid to a String.
+ if (Y.Array.indexOf(response.resourcestotoggle, "" + activityid) !== -1) {
+ M.mod_quiz.resource_toolbox.handle_resource_dim(button, node, action);
+ }
+ }, this);
+ });
+ },
+
+ /**
+ * Toggle highlighting the current section.
+ *
+ * @method toggle_highlight
+ * @param {EventFacade} e
+ */
+ toggle_highlight : function(e) {
+ // Prevent the default button action.
+ e.preventDefault();
+
+ // Get the section we're working on.
+ var section = e.target.ancestor(M.mod_quiz.edit.get_section_selector(Y));
+ var button = e.target.ancestor('a', true);
+ var buttonicon = button.one('img');
+
+ // Determine whether the marker is currently set.
+ var togglestatus = section.hasClass('current');
+ var value = 0;
+
+ // Set the current highlighted item text.
+ var old_string = M.util.get_string('markthistopic', 'moodle');
+ Y.one(SELECTOR.PAGECONTENT)
+ .all(M.mod_quiz.edit.get_section_selector(Y) + '.current ' + SELECTOR.HIGHLIGHT)
+ .set('title', old_string);
+ Y.one(SELECTOR.PAGECONTENT)
+ .all(M.mod_quiz.edit.get_section_selector(Y) + '.current ' + SELECTOR.HIGHLIGHT + ' img')
+ .set('alt', old_string)
+ .set('src', M.util.image_url('i/marker'));
+
+ // Remove the highlighting from all sections.
+ Y.one(SELECTOR.PAGECONTENT).all(M.mod_quiz.edit.get_section_selector(Y))
+ .removeClass('current');
+
+ // Then add it if required to the selected section.
+ if (!togglestatus) {
+ section.addClass('current');
+ value = Y.Moodle.core_course.util.section.getId(section.ancestor(M.mod_quiz.edit.get_section_wrapper(Y), true));
+ var new_string = M.util.get_string('markedthistopic', 'moodle');
+ button
+ .set('title', new_string);
+ buttonicon
+ .set('alt', new_string)
+ .set('src', M.util.image_url('i/marked'));
+ }
+
+ // Change the highlight status.
+ var data = {
+ 'class' : 'course',
+ 'field' : 'marker',
+ 'value' : value
+ };
+ var lightbox = M.util.add_lightbox(Y, section);
+ lightbox.show();
+ this.send_request(data, lightbox);
+ }
+}, {
+ NAME : 'mod_quiz-section-toolbox',
+ ATTRS : {
+ courseid : {
+ 'value' : 0
+ },
+ quizid : {
+ 'value' : 0
+ },
+ format : {
+ 'value' : 'topics'
+ }
+ }
+});
+
+M.mod_quiz.init_section_toolbox = function(config) {
+ return new SECTIONTOOLBOX(config);
+};
+
+
+}, '@VERSION@', {
+ "requires": [
+ "base",
+ "node",
+ "event",
+ "event-key",
+ "io",
+ "moodle-mod_quiz-quizbase",
+ "moodle-mod_quiz-util-slot",
+ "moodle-core-notification-ajaxexception"
+ ]
+});
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes-min.js b/mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes-min.js
new file mode 100644
index 00000000000..4dc2ceb1f20
--- /dev/null
+++ b/mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes-min.js
@@ -0,0 +1,2 @@
+YUI.add("moodle-mod_quiz-toolboxes",function(e,t){var n={ACTIVITYINSTANCE:"activityinstance",AVAILABILITYINFODIV:"div.availabilityinfo",CONTENTWITHOUTLINK:"contentwithoutlink",CONDITIONALHIDDEN:"conditionalhidden",DIMCLASS:"dimmed",DIMMEDTEXT:"dimmed_text",EDITINSTRUCTIONS:"editinstructions",EDITINGMAXMARK:"editor_displayed",HIDE:"hide",JOIN:"page_join",MODINDENTCOUNT:"mod-indent-",MODINDENTHUGE:"mod-indent-huge",MODULEIDPREFIX:"slot-",PAGE:"page",SECTIONHIDDENCLASS:"hidden",SECTIONIDPREFIX:"section-",SLOT:"slot",SHOW:"editing_show",TITLEEDITOR:"titleeditor"},r={ACTIONAREA:".actions",ACTIONLINKTEXT:".actionlinktext",ACTIVITYACTION:"a.cm-edit-action[data-action], a.editing_maxmark",ACTIVITYFORM:"span.instancemaxmarkcontainer form",ACTIVITYICON:"img.activityicon",ACTIVITYINSTANCE:"."+n.ACTIVITYINSTANCE,ACTIVITYLINK:"."+n.ACTIVITYINSTANCE+" > a",ACTIVITYLI:"li.activity",ACTIVITYMAXMARK:"input[name=maxmark]",COMMANDSPAN:".commands",CONTENTAFTERLINK:"div.contentafterlink",CONTENTWITHOUTLINK:"div.contentwithoutlink",EDITMAXMARK:"a.editing_maxmark",HIDE:"a.editing_hide",HIGHLIGHT:"a.editing_highlight",INSTANCENAME:"span.instancename",INSTANCEMAXMARK:"span.instancemaxmark",MODINDENTDIV:".mod-indent",MODINDENTOUTER:".mod-indent-outer",PAGECONTENT:"div#page-content",PAGELI:"li.page",SECTIONUL:"ul.section",SHOW:"a."+n.SHOW,SHOWHIDE:"a.editing_showhide",SLOTLI:"li.slot",SUMMARKS:".mod_quiz_summarks"},i=e.one(document.body);M.mod_quiz=M.mod_quiz||{};var s=function(){s.superclass.constructor.apply(this,arguments)};e.extend(s,e.Base,{send_request:function(t,n,i,s){t||(t={});var o=this.get("config").pageparams,u;for(u in o)t[u]=o[u];t.sesskey=M.cfg.sesskey,t.courseid=this.get("courseid"),t.quizid=this.get("quizid");var a=M.cfg.wwwroot+this.get("ajaxurl"),f=[],l={method:"POST",data:t,on:{success:function(t,s){try{f=e.JSON.parse(s.responseText),f.error&&new M.core.ajaxException(f)}catch(o){}f.newsummarks&&e.one(r.SUMMARKS).setHTML(f.newsummarks),i&&e.bind(i,this,f)(),n&&window.setTimeout(function(){n.hide()},400)},failure:function(e,t){n&&n.hide(),new M.core.ajaxException(t)}},context:this};if(s)for(u in s)l[u]=s[u];return n&&n.show(),e.io(a,l),this}},{NAME:"mod_quiz-toolbox",ATTRS:{courseid:{value:0},quizid:{value:0},ajaxurl:{value:null},config:{value:{}}}});var o=function(){o.superclass.constructor.apply(this,arguments)};e.extend(o,s,{editmaxmarkevents:[],NODE_PAGE:1,NODE_SLOT:2,NODE_JOIN:3,initializer:function(){M.mod_quiz.quizbase.register_module(this),i.delegate("key",this.handle_data_action,"down:enter",r.ACTIVITYACTION,this),e.delegate("click",this.handle_data_action,i,r.ACTIVITYACTION,this)},handle_data_action:function(e){var t=e.target;t.test("a")||(t=t.ancestor(r.ACTIVITYACTION));var n=t.getData("action"),i=t.ancestor(r.ACTIVITYLI);if(!t.test("a")||!n||!i)return;switch(n){case"editmaxmark":this.edit_maxmark(e,t,i,n);break;case"delete":this.delete_with_confirmation(e,t,i,n);break;case"linkpage":case"unlinkpage":this.link_page(e,t,i,n);break;default:}},add_spinner:function(t){var n=t.one(r.ACTIONAREA);return n?M.util.add_spinner(e,n):null},delete_with_confirmation:function(t,n,r){t.preventDefault();var i=r,s="",o=M.util.get_string("pluginname","qtype_"+i.getAttribute("class").match(/qtype_([^\s]*)/)[1]);s=M.util.get_string("confirmremovequestion","quiz",o);var u=new M.core.confirm({question:s,modal:!0});return u.on("complete-yes",function(){i.remove(),e.Moodle.mod_quiz.util.slot.reorder_slots();var t={"class":"resource",action:"DELETE",id:e.Moodle.mod_quiz.util.slot.getId(i)};this.send_request(t),M.core.actionmenu&&M.core.actionmenu.instance&&M.core.actionmenu.instance.hideMenu(),window.location.reload(!0)},this),this},edit_maxmark:function(t,i,s){var o=e.Moodle.mod_quiz.util.slot.getId(s),u=s.one(r.INSTANCEMAXMARK),a=s.one(r.ACTIVITYINSTANCE),f=u.get("firstChild"),l=f.get("data"),c=l,h,p=u,d={"class":"resource",field:"getmaxmark",id:o};t.preventDefault(),this.send_request(d,null,function(t){M.core.actionmenu&&M.core.actionmenu.instance&&M.core.actionmenu.instance.hideMenu(),t.instancemaxmark&&(c=t.instancemaxmark);var r=e.Node.create('
'),i=e.Node.create('').set("innerHTML",M.util.get_string("edittitleinstructions","moodle")),o=e.Node.create('').setAttrs({value:c,autocomplete:"off","aria-describedby":"id_editinstructions",maxLength:"12",size:parseInt(this.get("config").questiondecimalpoints,10)+2});r.appendChild(o),r.setData("anchor",p),a.insert(i,"before"),p.replace(r);var u="left";right_to_left()&&(u="right"),s.addClass(n.EDITINGMAXMARK),o.focus().select(),h=o.on("blur",this.edit_maxmark_cancel,this,s,!1),this.editmaxmarkevents.push(h),h=o.on("key",this.edit_maxmark_cancel,"esc",this,s,!0),this.editmaxmarkevents.push(h),h=r.on("submit",this.edit_maxmark_submit,this,s,l),this.editmaxmarkevents.push(h)})},edit_maxmark_submit:function(t,n,i){t.preventDefault();var s=e.Lang.trim(n.one(r.ACTIVITYFORM+" "+r.ACTIVITYMAXMARK).get("value")),o=this.add_spinner(n);this.edit_maxmark_clear(n),n.one(r.INSTANCEMAXMARK).setContent(s);if(s!==null&&s!==""&&s!==i){var u={"class":"resource",field:"updatemaxmark",maxmark:s,id:e.Moodle.mod_quiz.util.slot.getId(n)};this.send_request(u,o,function(e){e.instancemaxmark&&n.one(r.INSTANCEMAXMARK).setContent(e.instancemaxmark)})}},edit_maxmark_cancel:function(e,t,n){n&&e.preventDefault(),this.edit_maxmark_clear(t)},edit_maxmark_clear:function(t){(new e.EventHandle(this.editmaxmarkevents)).detach();var i=t.one(r.ACTIVITYFORM),s=t.one("#id_editinstructions");i&&i.replace(i.getData("anchor")),s&&s.remove(),t.removeClass(n.EDITINGMAXMARK),e.later(100,this,function(){t.one(r.EDITMAXMARK).focus()}),e.one("input[name=maxmark")||e.one("body").append('')},link_page:function(t,n,r,i){t.preventDefault(),r=r.next("li.activity.slot");var s=this.add_spinner(r),o=0,u=i==="linkpage"?1:2,a={"class"
+:"resource",field:"linkslottopage",id:o,value:u};return o=e.Moodle.mod_quiz.util.slot.getId(r),o&&(a.id=Number(o)),this.send_request(a,s,function(e){window.location.reload(!0)}),this},repaginate_slots:function(t){this.slots=t;var i=e.one(r.PAGECONTENT+" "+r.SECTIONUL),s=i.all(r.ACTIVITYLI);s.each(function(t){var i,s;t.hasClass(n.PAGE)?(i=this.NODE_PAGE,s=t.next(r.SLOTLI)):t.hasClass(n.SLOT)?(i=this.NODE_SLOT,s=t):t.hasClass(n.JOIN)&&(i=this.NODE_JOIN,s=t.previous(r.SLOTLI));var o=Number(e.Moodle.mod_quiz.util.slot.getNumber(s));if(!i)return;if(!this.slots.hasOwnProperty(o))return;var u=this.slots[o];if(i===this.NODE_PAGE){var a=e.Moodle.mod_quiz.util.page.getNumber(t);if(u.page===a){console.log("slotdata.page == pagenumber return");return}a slotdata.page update page number")}},this)},NAME:"mod_quiz-resource-toolbox",ATTRS:{courseid:{value:0},quizid:{value:0}}}),M.mod_quiz.resource_toolbox=null,M.mod_quiz.init_resource_toolbox=function(e){return M.mod_quiz.resource_toolbox=new o(e),M.mod_quiz.resource_toolbox};var u=function(){u.superclass.constructor.apply(this,arguments)};e.extend(u,s,{initializer:function(){M.mod_quiz.quizbase.register_module(this),e.delegate("click",this.toggle_highlight,r.PAGECONTENT,r.SECTIONLI+" "+r.HIGHLIGHT,this),e.delegate("click",this.toggle_hide_section,r.PAGECONTENT,r.SECTIONLI+" "+r.SHOWHIDE,this)},toggle_hide_section:function(t){t.preventDefault();var i=t.target.ancestor(M.mod_quiz.format.get_section_selector(e)),s=t.target.ancestor("a",!0),o=s.one("img"),u,a,f;i.hasClass(n.SECTIONHIDDENCLASS)?(i.removeClass(n.SECTIONHIDDENCLASS),u=1,a="show",f="hide"):(i.addClass(n.SECTIONHIDDENCLASS),u=0,a="hide",f="show");var l=M.util.get_string(f+"fromothers","format_"+this.get("format"));o.setAttrs({alt:l,src:M.util.image_url("i/"+f)}),s.set("title",l);var c={"class":"section",field:"visible",id:e.Moodle.core_course.util.section.getId(i.ancestor(M.mod_quiz.edit.get_section_wrapper(e),!0)),value:u},h=M.util.add_lightbox(e,i);h.show(),this.send_request(c,h,function(t){var n=i.all(r.ACTIVITYLI);n.each(function(n){var i;n.one(r.SHOW)?i=n.one(r.SHOW):i=n.one(r.HIDE);var s=e.Moodle.mod_quiz.util.slot.getId(n);e.Array.indexOf(t.resourcestotoggle,""+s)!==-1&&M.mod_quiz.resource_toolbox.handle_resource_dim(i,n,a)},this)})},toggle_highlight:function(t){t.preventDefault();var n=t.target.ancestor(M.mod_quiz.edit.get_section_selector(e)),i=t.target.ancestor("a",!0),s=i.one("img"),o=n.hasClass("current"),u=0,a=M.util.get_string("markthistopic","moodle");e.one(r.PAGECONTENT).all(M.mod_quiz.edit.get_section_selector(e)+".current "+r.HIGHLIGHT).set("title",a),e.one(r.PAGECONTENT).all(M.mod_quiz.edit.get_section_selector(e)+".current "+r.HIGHLIGHT+" img").set("alt",a).set("src",M.util.image_url("i/marker")),e.one(r.PAGECONTENT).all(M.mod_quiz.edit.get_section_selector(e)).removeClass("current");if(!o){n.addClass("current"),u=e.Moodle.core_course.util.section.getId(n.ancestor(M.mod_quiz.edit.get_section_wrapper(e),!0));var f=M.util.get_string("markedthistopic","moodle");i.set("title",f),s.set("alt",f).set("src",M.util.image_url("i/marked"))}var l={"class":"course",field:"marker",value:u},c=M.util.add_lightbox(e,n);c.show(),this.send_request(l,c)}},{NAME:"mod_quiz-section-toolbox",ATTRS:{courseid:{value:0},quizid:{value:0},format:{value:"topics"}}}),M.mod_quiz.init_section_toolbox=function(e){return new u(e)}},"@VERSION@",{requires:["base","node","event","event-key","io","moodle-mod_quiz-quizbase","moodle-mod_quiz-util-slot","moodle-core-notification-ajaxexception"]});
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes.js b/mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes.js
new file mode 100644
index 00000000000..aece0a61743
--- /dev/null
+++ b/mod/quiz/yui/build/moodle-mod_quiz-toolboxes/moodle-mod_quiz-toolboxes.js
@@ -0,0 +1,867 @@
+YUI.add('moodle-mod_quiz-toolboxes', function (Y, NAME) {
+
+/**
+ * Resource and activity toolbox class.
+ *
+ * This class is responsible for managing AJAX interactions with activities and resources
+ * when viewing a course in editing mode.
+ *
+ * @module moodle-course-toolboxes
+ * @namespace M.course.toolboxes
+ */
+
+// The CSS classes we use.
+ var CSS = {
+ ACTIVITYINSTANCE : 'activityinstance',
+ AVAILABILITYINFODIV : 'div.availabilityinfo',
+ CONTENTWITHOUTLINK : 'contentwithoutlink',
+ CONDITIONALHIDDEN : 'conditionalhidden',
+ DIMCLASS : 'dimmed',
+ DIMMEDTEXT : 'dimmed_text',
+ EDITINSTRUCTIONS : 'editinstructions',
+ EDITINGMAXMARK: 'editor_displayed',
+ HIDE : 'hide',
+ JOIN: 'page_join',
+ MODINDENTCOUNT : 'mod-indent-',
+ MODINDENTHUGE : 'mod-indent-huge',
+ MODULEIDPREFIX : 'slot-',
+ PAGE: 'page',
+ SECTIONHIDDENCLASS : 'hidden',
+ SECTIONIDPREFIX : 'section-',
+ SLOT : 'slot',
+ SHOW : 'editing_show',
+ TITLEEDITOR : 'titleeditor'
+ },
+ // The CSS selectors we use.
+ SELECTOR = {
+ ACTIONAREA: '.actions',
+ ACTIONLINKTEXT : '.actionlinktext',
+ ACTIVITYACTION : 'a.cm-edit-action[data-action], a.editing_maxmark',
+ ACTIVITYFORM : 'span.instancemaxmarkcontainer form',
+ ACTIVITYICON : 'img.activityicon',
+ ACTIVITYINSTANCE : '.' + CSS.ACTIVITYINSTANCE,
+ ACTIVITYLINK: '.' + CSS.ACTIVITYINSTANCE + ' > a',
+ ACTIVITYLI : 'li.activity',
+ ACTIVITYMAXMARK : 'input[name=maxmark]',
+ COMMANDSPAN : '.commands',
+ CONTENTAFTERLINK : 'div.contentafterlink',
+ CONTENTWITHOUTLINK : 'div.contentwithoutlink',
+ EDITMAXMARK: 'a.editing_maxmark',
+ HIDE : 'a.editing_hide',
+ HIGHLIGHT : 'a.editing_highlight',
+ INSTANCENAME : 'span.instancename',
+ INSTANCEMAXMARK : 'span.instancemaxmark',
+ MODINDENTDIV : '.mod-indent',
+ MODINDENTOUTER : '.mod-indent-outer',
+ PAGECONTENT : 'div#page-content',
+ PAGELI : 'li.page',
+ SECTIONUL : 'ul.section',
+ SHOW : 'a.' + CSS.SHOW,
+ SHOWHIDE : 'a.editing_showhide',
+ SLOTLI : 'li.slot',
+ SUMMARKS : '.mod_quiz_summarks'
+ },
+ BODY = Y.one(document.body);
+
+// Setup the basic namespace.
+M.mod_quiz = M.mod_quiz || {};
+
+/**
+ * The toolbox class is a generic class which should never be directly
+ * instantiated. Please extend it instead.
+ *
+ * @class toolbox
+ * @constructor
+ * @protected
+ * @extends Base
+ */
+var TOOLBOX = function() {
+ TOOLBOX.superclass.constructor.apply(this, arguments);
+};
+
+Y.extend(TOOLBOX, Y.Base, {
+ /**
+ * Send a request using the REST API
+ *
+ * @method send_request
+ * @param {Object} data The data to submit with the AJAX request
+ * @param {Node} [statusspinner] A statusspinner which may contain a section loader
+ * @param {Function} success_callback The callback to use on success
+ * @param {Object} [optionalconfig] Any additional configuration to submit
+ * @chainable
+ */
+ send_request: function(data, statusspinner, success_callback, optionalconfig) {
+ // Default data structure
+ if (!data) {
+ data = {};
+ }
+ // Handle any variables which we must pass back through to
+ var pageparams = this.get('config').pageparams,
+ varname;
+ for (varname in pageparams) {
+ data[varname] = pageparams[varname];
+ }
+
+ data.sesskey = M.cfg.sesskey;
+ data.courseid = this.get('courseid');
+ data.quizid = this.get('quizid');
+
+ var uri = M.cfg.wwwroot + this.get('ajaxurl');
+
+ // Define the configuration to send with the request
+ var responsetext = [];
+ var config = {
+ method: 'POST',
+ data: data,
+ on: {
+ success: function(tid, response) {
+ try {
+ responsetext = Y.JSON.parse(response.responseText);
+ if (responsetext.error) {
+ new M.core.ajaxException(responsetext);
+ }
+ } catch (e) {}
+
+ // Run the callback if we have one.
+ if (responsetext.newsummarks) {
+ Y.one(SELECTOR.SUMMARKS).setHTML(responsetext.newsummarks);
+ }
+ if (success_callback) {
+ Y.bind(success_callback, this, responsetext)();
+ }
+
+ if (statusspinner) {
+ window.setTimeout(function() {
+ statusspinner.hide();
+ }, 400);
+ }
+ },
+ failure: function(tid, response) {
+ if (statusspinner) {
+ statusspinner.hide();
+ }
+ new M.core.ajaxException(response);
+ }
+ },
+ context: this
+ };
+
+ // Apply optional config
+ if (optionalconfig) {
+ for (varname in optionalconfig) {
+ config[varname] = optionalconfig[varname];
+ }
+ }
+
+ if (statusspinner) {
+ statusspinner.show();
+ }
+
+ // Send the request
+ Y.io(uri, config);
+ return this;
+ }
+},
+{
+ NAME: 'mod_quiz-toolbox',
+ ATTRS: {
+ /**
+ * The ID of the Moodle Course being edited.
+ *
+ * @attribute courseid
+ * @default 0
+ * @type Number
+ */
+ courseid: {
+ 'value': 0
+ },
+
+ /**
+ * The Moodle course format.
+ *
+ * @attribute format
+ * @default 'topics'
+ * @type String
+ */
+ quizid: {
+ 'value': 0
+ },
+ /**
+ * The URL to use when submitting requests.
+ * @attribute ajaxurl
+ * @default null
+ * @type String
+ */
+ ajaxurl: {
+ 'value': null
+ },
+ /**
+ * Any additional configuration passed when creating the instance.
+ *
+ * @attribute config
+ * @default {}
+ * @type Object
+ */
+ config: {
+ 'value': {}
+ }
+ }
+}
+);
+/**
+ * Resource and activity toolbox class.
+ *
+ * This class is responsible for managing AJAX interactions with activities and resources
+ * when viewing a quiz in editing mode.
+ *
+ * @module mod_quiz-resource-toolbox
+ * @namespace M.mod_quiz.resource_toolbox
+ */
+
+/**
+ * Resource and activity toolbox class.
+ *
+ * This is a class extending TOOLBOX containing code specific to resources
+ *
+ * This class is responsible for managing AJAX interactions with activities and resources
+ * when viewing a quiz in editing mode.
+ *
+ * @class resources
+ * @constructor
+ * @extends M.course.toolboxes.toolbox
+ */
+var RESOURCETOOLBOX = function() {
+ RESOURCETOOLBOX.superclass.constructor.apply(this, arguments);
+};
+
+Y.extend(RESOURCETOOLBOX, TOOLBOX, {
+ /**
+ * An Array of events added when editing a max mark field.
+ * These should all be detached when editing is complete.
+ *
+ * @property editmaxmarkevents
+ * @protected
+ * @type Array
+ * @protected
+ */
+ editmaxmarkevents: [],
+
+ /**
+ *
+ */
+ NODE_PAGE: 1,
+ NODE_SLOT: 2,
+ NODE_JOIN: 3,
+
+ /**
+ * Initialize the resource toolbox
+ *
+ * For each activity the commands are updated and a reference to the activity is attached.
+ * This way it doesn't matter where the commands are going to called from they have a reference to the
+ * activity that they relate to.
+ * This is essential as some of the actions are displayed in an actionmenu which removes them from the
+ * page flow.
+ *
+ * This function also creates a single event delegate to manage all AJAX actions for all activities on
+ * the page.
+ *
+ * @method initializer
+ * @protected
+ */
+ initializer: function() {
+ M.mod_quiz.quizbase.register_module(this);
+ BODY.delegate('key', this.handle_data_action, 'down:enter', SELECTOR.ACTIVITYACTION, this);
+ Y.delegate('click', this.handle_data_action, BODY, SELECTOR.ACTIVITYACTION, this);
+ },
+
+ /**
+ * Handles the delegation event. When this is fired someone has triggered an action.
+ *
+ * Note not all actions will result in an AJAX enhancement.
+ *
+ * @protected
+ * @method handle_data_action
+ * @param {EventFacade} ev The event that was triggered.
+ * @returns {boolean}
+ */
+ handle_data_action: function(ev) {
+ // We need to get the anchor element that triggered this event.
+ var node = ev.target;
+ if (!node.test('a')) {
+ node = node.ancestor(SELECTOR.ACTIVITYACTION);
+ }
+
+ // From the anchor we can get both the activity (added during initialisation) and the action being
+ // performed (added by the UI as a data attribute).
+ var action = node.getData('action'),
+ activity = node.ancestor(SELECTOR.ACTIVITYLI);
+
+ if (!node.test('a') || !action || !activity) {
+ // It wasn't a valid action node.
+ return;
+ }
+
+ // Switch based upon the action and do the desired thing.
+ switch (action) {
+ case 'editmaxmark':
+ // The user wishes to edit the maxmark of the resource.
+ this.edit_maxmark(ev, node, activity, action);
+ break;
+ case 'delete':
+ // The user is deleting the activity.
+ this.delete_with_confirmation(ev, node, activity, action);
+ break;
+ case 'linkpage':
+ case 'unlinkpage':
+ // The user is linking or unlinking pages.
+ this.link_page(ev, node, activity, action);
+ break;
+ default:
+ // Nothing to do here!
+ break;
+ }
+ },
+
+ /**
+ * Add a loading icon to the specified activity.
+ *
+ * The icon is added within the action area.
+ *
+ * @method add_spinner
+ * @param {Node} activity The activity to add a loading icon to
+ * @return {Node|null} The newly created icon, or null if the action area was not found.
+ */
+ add_spinner: function(activity) {
+ var actionarea = activity.one(SELECTOR.ACTIONAREA);
+ if (actionarea) {
+ return M.util.add_spinner(Y, actionarea);
+ }
+ return null;
+ },
+
+ /**
+ * Deletes the given activity or resource after confirmation.
+ *
+ * @protected
+ * @method delete_with_confirmation
+ * @param {EventFacade} ev The event that was fired.
+ * @param {Node} button The button that triggered this action.
+ * @param {Node} activity The activity node that this action will be performed on.
+ * @chainable
+ */
+ delete_with_confirmation: function(ev, button, activity) {
+ // Prevent the default button action
+ ev.preventDefault();
+
+ // Get the element we're working on
+ var element = activity,
+ // Create confirm string (different if element has or does not have name)
+ confirmstring = '',
+ qtypename = M.util.get_string('pluginname',
+ 'qtype_' + element.getAttribute('class').match(/qtype_([^\s]*)/)[1]);
+ confirmstring = M.util.get_string('confirmremovequestion', 'quiz', qtypename);
+
+ // Create the confirmation dialogue.
+ var confirm = new M.core.confirm({
+ question: confirmstring,
+ modal: true
+ });
+
+ // If it is confirmed.
+ confirm.on('complete-yes', function() {
+
+ // Actually remove the element.
+ element.remove();
+ Y.Moodle.mod_quiz.util.slot.reorder_slots();
+ var data = {
+ 'class': 'resource',
+ 'action': 'DELETE',
+ 'id': Y.Moodle.mod_quiz.util.slot.getId(element)
+ };
+ this.send_request(data);
+ if (M.core.actionmenu && M.core.actionmenu.instance) {
+ M.core.actionmenu.instance.hideMenu();
+ }
+ window.location.reload(true);
+
+ }, this);
+
+ return this;
+ },
+
+
+ /**
+ * Edit the maxmark for the resource
+ *
+ * @protected
+ * @method edit_maxmark
+ * @param {EventFacade} ev The event that was fired.
+ * @param {Node} button The button that triggered this action.
+ * @param {Node} activity The activity node that this action will be performed on.
+ * @param {String} action The action that has been requested.
+ * @return Boolean
+ */
+ edit_maxmark : function(ev, button, activity) {
+ // Get the element we're working on
+ var activityid = Y.Moodle.mod_quiz.util.slot.getId(activity),
+ instancemaxmark = activity.one(SELECTOR.INSTANCEMAXMARK),
+ instance = activity.one(SELECTOR.ACTIVITYINSTANCE),
+ currentmaxmark = instancemaxmark.get('firstChild'),
+ oldmaxmark = currentmaxmark.get('data'),
+ maxmarktext = oldmaxmark,
+ thisevent,
+ anchor = instancemaxmark,// Grab the anchor so that we can swap it with the edit form.
+ data = {
+ 'class' : 'resource',
+ 'field' : 'getmaxmark',
+ 'id' : activityid
+ };
+
+ // Prevent the default actions.
+ ev.preventDefault();
+
+ this.send_request(data, null, function(response) {
+ if (M.core.actionmenu && M.core.actionmenu.instance) {
+ M.core.actionmenu.instance.hideMenu();
+ }
+
+ // Try to retrieve the existing string from the server
+ if (response.instancemaxmark) {
+ maxmarktext = response.instancemaxmark;
+ }
+
+ // Create the editor and submit button
+ var editform = Y.Node.create('
');
+ var editinstructions = Y.Node.create('')
+ .set('innerHTML', M.util.get_string('edittitleinstructions', 'moodle'));
+ var editor = Y.Node.create('').setAttrs({
+ 'value' : maxmarktext,
+ 'autocomplete' : 'off',
+ 'aria-describedby' : 'id_editinstructions',
+ 'maxLength' : '12',
+ 'size' : parseInt(this.get('config').questiondecimalpoints, 10) + 2
+ });
+
+ // Clear the existing content and put the editor in
+ editform.appendChild(editor);
+ editform.setData('anchor', anchor);
+ instance.insert(editinstructions, 'before');
+ anchor.replace(editform);
+
+ // Force the editing instruction to match the mod-indent position.
+ var padside = 'left';
+ if (right_to_left()) {
+ padside = 'right';
+ }
+
+ // We hide various components whilst editing:
+ activity.addClass(CSS.EDITINGMAXMARK);
+
+ // Focus and select the editor text
+ editor.focus().select();
+
+ // Cancel the edit if we lose focus or the escape key is pressed.
+ thisevent = editor.on('blur', this.edit_maxmark_cancel, this, activity, false);
+ this.editmaxmarkevents.push(thisevent);
+ thisevent = editor.on('key', this.edit_maxmark_cancel, 'esc', this, activity, true);
+ this.editmaxmarkevents.push(thisevent);
+
+ // Handle form submission.
+ thisevent = editform.on('submit', this.edit_maxmark_submit, this, activity, oldmaxmark);
+ this.editmaxmarkevents.push(thisevent);
+ });
+ },
+
+ /**
+ * Handles the submit event when editing the activity or resources maxmark.
+ *
+ * @protected
+ * @method edit_maxmark_submit
+ * @param {EventFacade} ev The event that triggered this.
+ * @param {Node} activity The activity whose maxmark we are altering.
+ * @param {String} originalmaxmark The original maxmark the activity or resource had.
+ */
+ edit_maxmark_submit : function(ev, activity, originalmaxmark) {
+ // We don't actually want to submit anything
+ ev.preventDefault();
+ var newmaxmark = Y.Lang.trim(activity.one(SELECTOR.ACTIVITYFORM + ' ' + SELECTOR.ACTIVITYMAXMARK).get('value'));
+ var spinner = this.add_spinner(activity);
+ this.edit_maxmark_clear(activity);
+ activity.one(SELECTOR.INSTANCEMAXMARK).setContent(newmaxmark);
+ if (newmaxmark !== null && newmaxmark !== "" && newmaxmark !== originalmaxmark) {
+ var data = {
+ 'class' : 'resource',
+ 'field' : 'updatemaxmark',
+ 'maxmark' : newmaxmark,
+ 'id' : Y.Moodle.mod_quiz.util.slot.getId(activity)
+ };
+ this.send_request(data, spinner, function(response) {
+ if (response.instancemaxmark) {
+ activity.one(SELECTOR.INSTANCEMAXMARK).setContent(response.instancemaxmark);
+ }
+ });
+ }
+ },
+
+ /**
+ * Handles the cancel event when editing the activity or resources maxmark.
+ *
+ * @protected
+ * @method edit_maxmark_cancel
+ * @param {EventFacade} ev The event that triggered this.
+ * @param {Node} activity The activity whose maxmark we are altering.
+ * @param {Boolean} preventdefault If true we should prevent the default action from occuring.
+ */
+ edit_maxmark_cancel : function(ev, activity, preventdefault) {
+ if (preventdefault) {
+ ev.preventDefault();
+ }
+ this.edit_maxmark_clear(activity);
+ },
+
+ /**
+ * Handles clearing the editing UI and returning things to the original state they were in.
+ *
+ * @protected
+ * @method edit_maxmark_clear
+ * @param {Node} activity The activity whose maxmark we were altering.
+ */
+ edit_maxmark_clear : function(activity) {
+ // Detach all listen events to prevent duplicate triggers
+ new Y.EventHandle(this.editmaxmarkevents).detach();
+
+ var editform = activity.one(SELECTOR.ACTIVITYFORM),
+ instructions = activity.one('#id_editinstructions');
+ if (editform) {
+ editform.replace(editform.getData('anchor'));
+ }
+ if (instructions) {
+ instructions.remove();
+ }
+
+ // Remove the editing class again to revert the display.
+ activity.removeClass(CSS.EDITINGMAXMARK);
+
+ // Refocus the link which was clicked originally so the user can continue using keyboard nav.
+ Y.later(100, this, function() {
+ activity.one(SELECTOR.EDITMAXMARK).focus();
+ });
+
+ // This hack is to keep Behat happy until they release a version of
+ // MinkSelenium2Driver that fixes
+ // https://github.com/Behat/MinkSelenium2Driver/issues/80.
+ if (!Y.one('input[name=maxmark')) {
+ Y.one('body').append('');
+ }
+ },
+
+ /**
+ * Joins or separates the given slot with the page of the previous slot. Reorders the pages of
+ * the other slots
+ *
+ * @protected
+ * @method link_page
+ * @param {EventFacade} ev The event that was fired.
+ * @param {Node} button The button that triggered this action.
+ * @param {Node} activity The activity node that this action will be performed on.
+ * @chainable
+ */
+ link_page: function(ev, button, activity, action) {
+ // Prevent the default button action
+ ev.preventDefault();
+
+ activity = activity.next('li.activity.slot');
+ var spinner = this.add_spinner(activity),
+ slotid = 0;
+ var value = action === 'linkpage' ? 1:2;
+
+ var data = {
+ 'class': 'resource',
+ 'field': 'linkslottopage',
+ 'id': slotid,
+ 'value': value
+ };
+
+ slotid = Y.Moodle.mod_quiz.util.slot.getId(activity);
+ if (slotid) {
+ data.id = Number(slotid);
+ }
+ this.send_request(data, spinner, function(response) {
+ window.location.reload(true);
+// if (response.slots) {
+// this.repaginate_slots(response.slots);
+// }
+ });
+
+ return this;
+ },
+ repaginate_slots: function(slots) {
+ this.slots = slots;
+ var section = Y.one(SELECTOR.PAGECONTENT + ' ' + SELECTOR.SECTIONUL),
+ activities = section.all(SELECTOR.ACTIVITYLI);
+ activities.each(function(node) {
+
+ // What element is it? page/slot/link
+ // what is the current slot?
+ var type;
+ var slot;
+ if(node.hasClass(CSS.PAGE)){
+ type = this.NODE_PAGE;
+ slot = node.next(SELECTOR.SLOTLI);
+ } else if (node.hasClass(CSS.SLOT)){
+ type = this.NODE_SLOT;
+ slot = node;
+ } else if (node.hasClass(CSS.JOIN)){
+ type = this.NODE_JOIN;
+ slot = node.previous(SELECTOR.SLOTLI);
+ }
+
+ // getSlotnumber() Should be a method of util.slot
+ var slotnumber = Number(Y.Moodle.mod_quiz.util.slot.getNumber(slot));
+ if(!type){
+ // Nothing we can do.
+ return;
+ }
+
+ // Is it correct?
+ if(!this.slots.hasOwnProperty(slotnumber)){
+ // An error. We should handle this.
+ return;
+ }
+
+ var slotdata = this.slots[slotnumber];
+
+ if(type === this.NODE_PAGE){
+ // Get page number
+ var pagenumber = Y.Moodle.mod_quiz.util.page.getNumber(node);
+ // Is the page number correct?
+ if (slotdata.page === pagenumber) {
+ console.log('slotdata.page == pagenumber return');
+ return;
+ }
+
+ if (pagenumber < slotdata.page) {
+ // Remove page node.
+ node.remove();
+ }
+ else {
+ // Add page node.
+ console.log('pagenumber > slotdata.page update page number');
+ }
+
+ }
+ }, this);
+ },
+
+ NAME : 'mod_quiz-resource-toolbox',
+ ATTRS : {
+ courseid : {
+ 'value' : 0
+ },
+ quizid : {
+ 'value' : 0
+ }
+ }
+});
+
+M.mod_quiz.resource_toolbox = null;
+M.mod_quiz.init_resource_toolbox = function(config) {
+ M.mod_quiz.resource_toolbox = new RESOURCETOOLBOX(config);
+ return M.mod_quiz.resource_toolbox;
+};
+/**
+ * Resource and activity toolbox class.
+ *
+ * This class is responsible for managing AJAX interactions with activities and resources
+ * when viewing a course in editing mode.
+ *
+ * @module moodle-mod_quiz-toolboxes
+ * @namespace M.mod_quiz.toolboxes
+ */
+
+/**
+ * Section toolbox class.
+ *
+ * This class is responsible for managing AJAX interactions with sections
+ * when viewing a course in editing mode.
+ *
+ * @class section
+ * @constructor
+ * @extends M.mod_quiz.toolboxes.toolbox
+ */
+var SECTIONTOOLBOX = function() {
+ SECTIONTOOLBOX.superclass.constructor.apply(this, arguments);
+};
+
+Y.extend(SECTIONTOOLBOX, TOOLBOX, {
+ /**
+ * Initialize the section toolboxes module.
+ *
+ * Updates all span.commands with relevant handlers and other required changes.
+ *
+ * @method initializer
+ * @protected
+ */
+ initializer : function() {
+ M.mod_quiz.quizbase.register_module(this);
+
+ // Section Highlighting.
+ Y.delegate('click', this.toggle_highlight, SELECTOR.PAGECONTENT, SELECTOR.SECTIONLI + ' ' + SELECTOR.HIGHLIGHT, this);
+
+ // Section Visibility.
+ Y.delegate('click', this.toggle_hide_section, SELECTOR.PAGECONTENT, SELECTOR.SECTIONLI + ' ' + SELECTOR.SHOWHIDE, this);
+ },
+
+ toggle_hide_section : function(e) {
+ // Prevent the default button action.
+ e.preventDefault();
+
+ // Get the section we're working on.
+ var section = e.target.ancestor(M.mod_quiz.format.get_section_selector(Y)),
+ button = e.target.ancestor('a', true),
+ hideicon = button.one('img'),
+
+ // The value to submit
+ value,
+
+ // The text for strings and images. Also determines the icon to display.
+ action,
+ nextaction;
+
+ if (!section.hasClass(CSS.SECTIONHIDDENCLASS)) {
+ section.addClass(CSS.SECTIONHIDDENCLASS);
+ value = 0;
+ action = 'hide';
+ nextaction = 'show';
+ } else {
+ section.removeClass(CSS.SECTIONHIDDENCLASS);
+ value = 1;
+ action = 'show';
+ nextaction = 'hide';
+ }
+
+ var newstring = M.util.get_string(nextaction + 'fromothers', 'format_' + this.get('format'));
+ hideicon.setAttrs({
+ 'alt' : newstring,
+ 'src' : M.util.image_url('i/' + nextaction)
+ });
+ button.set('title', newstring);
+
+ // Change the highlight status
+ var data = {
+ 'class' : 'section',
+ 'field' : 'visible',
+ 'id' : Y.Moodle.core_course.util.section.getId(section.ancestor(M.mod_quiz.edit.get_section_wrapper(Y), true)),
+ 'value' : value
+ };
+
+ var lightbox = M.util.add_lightbox(Y, section);
+ lightbox.show();
+
+ this.send_request(data, lightbox, function(response) {
+ var activities = section.all(SELECTOR.ACTIVITYLI);
+ activities.each(function(node) {
+ var button;
+ if (node.one(SELECTOR.SHOW)) {
+ button = node.one(SELECTOR.SHOW);
+ } else {
+ button = node.one(SELECTOR.HIDE);
+ }
+ var activityid = Y.Moodle.mod_quiz.util.slot.getId(node);
+
+ // NOTE: resourcestotoggle is returned as a string instead
+ // of a Number so we must cast our activityid to a String.
+ if (Y.Array.indexOf(response.resourcestotoggle, "" + activityid) !== -1) {
+ M.mod_quiz.resource_toolbox.handle_resource_dim(button, node, action);
+ }
+ }, this);
+ });
+ },
+
+ /**
+ * Toggle highlighting the current section.
+ *
+ * @method toggle_highlight
+ * @param {EventFacade} e
+ */
+ toggle_highlight : function(e) {
+ // Prevent the default button action.
+ e.preventDefault();
+
+ // Get the section we're working on.
+ var section = e.target.ancestor(M.mod_quiz.edit.get_section_selector(Y));
+ var button = e.target.ancestor('a', true);
+ var buttonicon = button.one('img');
+
+ // Determine whether the marker is currently set.
+ var togglestatus = section.hasClass('current');
+ var value = 0;
+
+ // Set the current highlighted item text.
+ var old_string = M.util.get_string('markthistopic', 'moodle');
+ Y.one(SELECTOR.PAGECONTENT)
+ .all(M.mod_quiz.edit.get_section_selector(Y) + '.current ' + SELECTOR.HIGHLIGHT)
+ .set('title', old_string);
+ Y.one(SELECTOR.PAGECONTENT)
+ .all(M.mod_quiz.edit.get_section_selector(Y) + '.current ' + SELECTOR.HIGHLIGHT + ' img')
+ .set('alt', old_string)
+ .set('src', M.util.image_url('i/marker'));
+
+ // Remove the highlighting from all sections.
+ Y.one(SELECTOR.PAGECONTENT).all(M.mod_quiz.edit.get_section_selector(Y))
+ .removeClass('current');
+
+ // Then add it if required to the selected section.
+ if (!togglestatus) {
+ section.addClass('current');
+ value = Y.Moodle.core_course.util.section.getId(section.ancestor(M.mod_quiz.edit.get_section_wrapper(Y), true));
+ var new_string = M.util.get_string('markedthistopic', 'moodle');
+ button
+ .set('title', new_string);
+ buttonicon
+ .set('alt', new_string)
+ .set('src', M.util.image_url('i/marked'));
+ }
+
+ // Change the highlight status.
+ var data = {
+ 'class' : 'course',
+ 'field' : 'marker',
+ 'value' : value
+ };
+ var lightbox = M.util.add_lightbox(Y, section);
+ lightbox.show();
+ this.send_request(data, lightbox);
+ }
+}, {
+ NAME : 'mod_quiz-section-toolbox',
+ ATTRS : {
+ courseid : {
+ 'value' : 0
+ },
+ quizid : {
+ 'value' : 0
+ },
+ format : {
+ 'value' : 'topics'
+ }
+ }
+});
+
+M.mod_quiz.init_section_toolbox = function(config) {
+ return new SECTIONTOOLBOX(config);
+};
+
+
+}, '@VERSION@', {
+ "requires": [
+ "base",
+ "node",
+ "event",
+ "event-key",
+ "io",
+ "moodle-mod_quiz-quizbase",
+ "moodle-mod_quiz-util-slot",
+ "moodle-core-notification-ajaxexception"
+ ]
+});
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-util-base/moodle-mod_quiz-util-base-debug.js b/mod/quiz/yui/build/moodle-mod_quiz-util-base/moodle-mod_quiz-util-base-debug.js
new file mode 100644
index 00000000000..314c21fef2f
--- /dev/null
+++ b/mod/quiz/yui/build/moodle-mod_quiz-util-base/moodle-mod_quiz-util-base-debug.js
@@ -0,0 +1,20 @@
+YUI.add('moodle-mod_quiz-util-base', function (Y, NAME) {
+
+/**
+ * The Moodle.mod_quiz.util classes provide quiz-related utility functions.
+ *
+ * @module moodle-mod_quiz-util
+ * @main
+ */
+
+Y.namespace('Moodle.mod_quiz.util');
+
+/**
+ * A collection of general utility functions for use in quiz.
+ *
+ * @class Moodle.mod_quiz.util
+ * @static
+ */
+
+
+}, '@VERSION@');
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-util-base/moodle-mod_quiz-util-base-min.js b/mod/quiz/yui/build/moodle-mod_quiz-util-base/moodle-mod_quiz-util-base-min.js
new file mode 100644
index 00000000000..91e96e72d9c
--- /dev/null
+++ b/mod/quiz/yui/build/moodle-mod_quiz-util-base/moodle-mod_quiz-util-base-min.js
@@ -0,0 +1 @@
+YUI.add("moodle-mod_quiz-util-base",function(e,t){e.namespace("Moodle.mod_quiz.util")},"@VERSION@");
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-util-base/moodle-mod_quiz-util-base.js b/mod/quiz/yui/build/moodle-mod_quiz-util-base/moodle-mod_quiz-util-base.js
new file mode 100644
index 00000000000..314c21fef2f
--- /dev/null
+++ b/mod/quiz/yui/build/moodle-mod_quiz-util-base/moodle-mod_quiz-util-base.js
@@ -0,0 +1,20 @@
+YUI.add('moodle-mod_quiz-util-base', function (Y, NAME) {
+
+/**
+ * The Moodle.mod_quiz.util classes provide quiz-related utility functions.
+ *
+ * @module moodle-mod_quiz-util
+ * @main
+ */
+
+Y.namespace('Moodle.mod_quiz.util');
+
+/**
+ * A collection of general utility functions for use in quiz.
+ *
+ * @class Moodle.mod_quiz.util
+ * @static
+ */
+
+
+}, '@VERSION@');
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-util-page/moodle-mod_quiz-util-page-debug.js b/mod/quiz/yui/build/moodle-mod_quiz-util-page/moodle-mod_quiz-util-page-debug.js
new file mode 100644
index 00000000000..7882b6f5ae0
--- /dev/null
+++ b/mod/quiz/yui/build/moodle-mod_quiz-util-page/moodle-mod_quiz-util-page-debug.js
@@ -0,0 +1,96 @@
+YUI.add('moodle-mod_quiz-util-page', function (Y, NAME) {
+
+/**
+ * A collection of utility classes for use with pages.
+ *
+ * @module moodle-mod_quiz-util
+ * @submodule moodle-mod_quiz-util-page
+ */
+
+Y.namespace('Moodle.mod_quiz.util.page');
+
+/**
+ * A collection of utility classes for use with pages.
+ *
+ * @class Moodle.mod_quiz.util.page
+ * @static
+ */
+Y.Moodle.mod_quiz.util.page = {
+ CONSTANTS: {
+ PAGEIDPREFIX : 'page-',
+ PAGENUMBERPREFIX : 'Page '
+ },
+ SELECTORS: {
+ PAGE: 'li.page',
+ INSTANCENAME: '.instancename'
+ },
+
+ /**
+ * Retrieve the page item from one of it's child Nodes.
+ *
+ * @method getPageFromComponent
+ * @param pagecomponent {Node} The component Node.
+ * @return {Node|null} The Page Node.
+ */
+ getPageFromComponent: function(pagecomponent) {
+ return Y.one(pagecomponent).ancestor(this.SELECTORS.PAGE, true);
+ },
+
+ /**
+ * Determines the page ID for the provided page.
+ *
+ * @method getId
+ * @param page {Node} The page to find an ID for.
+ * @return {Number|false} The ID of the page in question or false if no ID was found.
+ */
+ getId: function(page) {
+ // We perform a simple substitution operation to get the ID.
+ var id = page.get('id').replace(
+ this.CONSTANTS.PAGEIDPREFIX, '');
+
+ // Attempt to validate the ID.
+ id = parseInt(id, 10);
+ if (typeof id === 'number' && isFinite(id)) {
+ return id;
+ }
+ return false;
+ },
+
+ /**
+ * Determines the page name for the provided page.
+ *
+ * @method getName
+ * @param page {Node} The page to find a name for.
+ * @return {string|false} The name of the page in question or false if no ID was found.
+ */
+ getName: function(page) {
+ var instance = page.one(this.SELECTORS.INSTANCENAME);
+ if (instance) {
+ return instance.get('firstChild').get('data');
+ }
+ return null;
+ },
+
+ /**
+ * Determines the page number for the provided page.
+ *
+ * @method getNumber
+ * @param page {Node} The page to find a number for.
+ * @return {Number|false} The number of the page in question or false if no number was found.
+ */
+ getNumber: function(page) {
+ // We perform a simple substitution operation to get the ID.
+ var number = page.get('text').replace(
+ this.CONSTANTS.PAGENUMBERPREFIX, '');
+
+ // Attempt to validate the ID.
+ number = parseInt(number, 10);
+ if (typeof number === 'number' && isFinite(number)) {
+ return number;
+ }
+ return false;
+ }
+};
+
+
+}, '@VERSION@', {"requires": ["node", "moodle-mod_quiz-util-base"]});
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-util-page/moodle-mod_quiz-util-page-min.js b/mod/quiz/yui/build/moodle-mod_quiz-util-page/moodle-mod_quiz-util-page-min.js
new file mode 100644
index 00000000000..24de48ea012
--- /dev/null
+++ b/mod/quiz/yui/build/moodle-mod_quiz-util-page/moodle-mod_quiz-util-page-min.js
@@ -0,0 +1 @@
+YUI.add("moodle-mod_quiz-util-page",function(e,t){e.namespace("Moodle.mod_quiz.util.page"),e.Moodle.mod_quiz.util.page={CONSTANTS:{PAGEIDPREFIX:"page-",PAGENUMBERPREFIX:"Page "},SELECTORS:{PAGE:"li.page",INSTANCENAME:".instancename"},getPageFromComponent:function(t){return e.one(t).ancestor(this.SELECTORS.PAGE,!0)},getId:function(e){var t=e.get("id").replace(this.CONSTANTS.PAGEIDPREFIX,"");return t=parseInt(t,10),typeof t=="number"&&isFinite(t)?t:!1},getName:function(e){var t=e.one(this.SELECTORS.INSTANCENAME);return t?t.get("firstChild").get("data"):null},getNumber:function(e){var t=e.get("text").replace(this.CONSTANTS.PAGENUMBERPREFIX,"");return t=parseInt(t,10),typeof t=="number"&&isFinite(t)?t:!1}}},"@VERSION@",{requires:["node","moodle-mod_quiz-util-base"]});
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-util-page/moodle-mod_quiz-util-page.js b/mod/quiz/yui/build/moodle-mod_quiz-util-page/moodle-mod_quiz-util-page.js
new file mode 100644
index 00000000000..7882b6f5ae0
--- /dev/null
+++ b/mod/quiz/yui/build/moodle-mod_quiz-util-page/moodle-mod_quiz-util-page.js
@@ -0,0 +1,96 @@
+YUI.add('moodle-mod_quiz-util-page', function (Y, NAME) {
+
+/**
+ * A collection of utility classes for use with pages.
+ *
+ * @module moodle-mod_quiz-util
+ * @submodule moodle-mod_quiz-util-page
+ */
+
+Y.namespace('Moodle.mod_quiz.util.page');
+
+/**
+ * A collection of utility classes for use with pages.
+ *
+ * @class Moodle.mod_quiz.util.page
+ * @static
+ */
+Y.Moodle.mod_quiz.util.page = {
+ CONSTANTS: {
+ PAGEIDPREFIX : 'page-',
+ PAGENUMBERPREFIX : 'Page '
+ },
+ SELECTORS: {
+ PAGE: 'li.page',
+ INSTANCENAME: '.instancename'
+ },
+
+ /**
+ * Retrieve the page item from one of it's child Nodes.
+ *
+ * @method getPageFromComponent
+ * @param pagecomponent {Node} The component Node.
+ * @return {Node|null} The Page Node.
+ */
+ getPageFromComponent: function(pagecomponent) {
+ return Y.one(pagecomponent).ancestor(this.SELECTORS.PAGE, true);
+ },
+
+ /**
+ * Determines the page ID for the provided page.
+ *
+ * @method getId
+ * @param page {Node} The page to find an ID for.
+ * @return {Number|false} The ID of the page in question or false if no ID was found.
+ */
+ getId: function(page) {
+ // We perform a simple substitution operation to get the ID.
+ var id = page.get('id').replace(
+ this.CONSTANTS.PAGEIDPREFIX, '');
+
+ // Attempt to validate the ID.
+ id = parseInt(id, 10);
+ if (typeof id === 'number' && isFinite(id)) {
+ return id;
+ }
+ return false;
+ },
+
+ /**
+ * Determines the page name for the provided page.
+ *
+ * @method getName
+ * @param page {Node} The page to find a name for.
+ * @return {string|false} The name of the page in question or false if no ID was found.
+ */
+ getName: function(page) {
+ var instance = page.one(this.SELECTORS.INSTANCENAME);
+ if (instance) {
+ return instance.get('firstChild').get('data');
+ }
+ return null;
+ },
+
+ /**
+ * Determines the page number for the provided page.
+ *
+ * @method getNumber
+ * @param page {Node} The page to find a number for.
+ * @return {Number|false} The number of the page in question or false if no number was found.
+ */
+ getNumber: function(page) {
+ // We perform a simple substitution operation to get the ID.
+ var number = page.get('text').replace(
+ this.CONSTANTS.PAGENUMBERPREFIX, '');
+
+ // Attempt to validate the ID.
+ number = parseInt(number, 10);
+ if (typeof number === 'number' && isFinite(number)) {
+ return number;
+ }
+ return false;
+ }
+};
+
+
+}, '@VERSION@', {"requires": ["node", "moodle-mod_quiz-util-base"]});
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-util-slot/moodle-mod_quiz-util-slot-debug.js b/mod/quiz/yui/build/moodle-mod_quiz-util-slot/moodle-mod_quiz-util-slot-debug.js
new file mode 100644
index 00000000000..1f0927c301b
--- /dev/null
+++ b/mod/quiz/yui/build/moodle-mod_quiz-util-slot/moodle-mod_quiz-util-slot-debug.js
@@ -0,0 +1,150 @@
+YUI.add('moodle-mod_quiz-util-slot', function (Y, NAME) {
+
+/**
+ * A collection of utility classes for use with slots.
+ *
+ * @module moodle-mod_quiz-util
+ * @submodule moodle-mod_quiz-util-slot
+ */
+
+Y.namespace('Moodle.mod_quiz.util.slot');
+
+/**
+ * A collection of utility classes for use with slots.
+ *
+ * @class Moodle.mod_quiz.util.slot
+ * @static
+ */
+Y.Moodle.mod_quiz.util.slot = {
+ CONSTANTS: {
+ SLOTIDPREFIX : 'slot-'
+ },
+ SELECTORS: {
+ SLOT: 'li.slot',
+ INSTANCENAME: '.instancename',
+ NUMBER: 'span.slotnumber',
+ PAGECONTENT : 'div#page-content',
+ SECTIONUL : 'ul.section'
+ },
+
+ /**
+ * Retrieve the slot item from one of it's child Nodes.
+ *
+ * @method getSlotFromComponent
+ * @param slotcomponent {Node} The component Node.
+ * @return {Node|null} The Slot Node.
+ */
+ getSlotFromComponent: function(slotcomponent) {
+ return Y.one(slotcomponent).ancestor(this.SELECTORS.SLOT, true);
+ },
+
+ /**
+ * Determines the slot ID for the provided slot.
+ *
+ * @method getId
+ * @param slot {Node} The slot to find an ID for.
+ * @return {Number|false} The ID of the slot in question or false if no ID was found.
+ */
+ getId: function(slot) {
+ // We perform a simple substitution operation to get the ID.
+ var id = slot.get('id').replace(
+ this.CONSTANTS.SLOTIDPREFIX, '');
+
+ // Attempt to validate the ID.
+ id = parseInt(id, 10);
+ if (typeof id === 'number' && isFinite(id)) {
+ return id;
+ }
+ return false;
+ },
+
+ /**
+ * Determines the slot name for the provided slot.
+ *
+ * @method getName
+ * @param slot {Node} The slot to find a name for.
+ * @return {string|false} The name of the slot in question or false if no ID was found.
+ */
+ getName: function(slot) {
+ var instance = slot.one(this.SELECTORS.INSTANCENAME);
+ if (instance) {
+ return instance.get('firstChild').get('data');
+ }
+ return null;
+ },
+
+ /**
+ * Determines the slot number for the provided slot.
+ *
+ * @method getNumber
+ * @param slot {Node} The slot to find the number for.
+ * @return {Number|false} The number of the slot in question or false if no number was found.
+ */
+ getNumber: function(slot) {
+ var number = slot.one(this.SELECTORS.NUMBER).get('text');
+ // Attempt to validate the ID.
+ number = parseInt(number, 10);
+ if (typeof number === 'number' && isFinite(number)) {
+ return number;
+ }
+ return false;
+ },
+
+ /**
+ * Updates the slot number for the provided slot.
+ *
+ * @method setNumber
+ * @param slot {Node} The slot to update the number for.
+ * @return void
+ */
+ setNumber: function(slot, number) {
+ slot.one(this.SELECTORS.NUMBER).set('text', number);
+ },
+
+ /**
+ * Returns a list of all slot elements on the page.
+ *
+ * @method getSlots
+ * @return {node[]} An array containing slot nodes.
+ */
+ getSlots: function() {
+ return Y.all(this.SELECTORS.PAGECONTENT + ' ' + this.SELECTORS.SECTIONUL + ' ' + this.SELECTORS.SLOT);
+ },
+
+ /**
+ * Returns the previous slot to the give slot.
+ *
+ * @method getPrevious
+ * @param slot Slot node
+ * @return {node|false} The previous slot node or false.
+ */
+ getPrevious: function(slot) {
+ return slot.previous(this.SELECTORS.SLOT);
+ },
+
+ /**
+ * Reset the order of the numbers given to each slot.
+ *
+ * @method reorder_slots
+ * @return void
+ */
+ reorder_slots: function() {
+ // Get list of slot nodes.
+ var slots = this.getSlots();
+ // Loop through slots incrementing the number each time.
+ slots.each(function(slot) {
+ var previousSlot = this.getPrevious(slot),
+ previousslotnumber = 0;
+ if(previousSlot){
+ previousslotnumber = this.getNumber(previousSlot);
+ }
+
+ // Set slot number.
+ this.setNumber(slot, previousslotnumber + 1);
+ }, this);
+
+ }
+};
+
+
+}, '@VERSION@', {"requires": ["node", "moodle-mod_quiz-util-base"]});
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-util-slot/moodle-mod_quiz-util-slot-min.js b/mod/quiz/yui/build/moodle-mod_quiz-util-slot/moodle-mod_quiz-util-slot-min.js
new file mode 100644
index 00000000000..ff750afa6c0
--- /dev/null
+++ b/mod/quiz/yui/build/moodle-mod_quiz-util-slot/moodle-mod_quiz-util-slot-min.js
@@ -0,0 +1 @@
+YUI.add("moodle-mod_quiz-util-slot",function(e,t){e.namespace("Moodle.mod_quiz.util.slot"),e.Moodle.mod_quiz.util.slot={CONSTANTS:{SLOTIDPREFIX:"slot-"},SELECTORS:{SLOT:"li.slot",INSTANCENAME:".instancename",NUMBER:"span.slotnumber",PAGECONTENT:"div#page-content",SECTIONUL:"ul.section"},getSlotFromComponent:function(t){return e.one(t).ancestor(this.SELECTORS.SLOT,!0)},getId:function(e){var t=e.get("id").replace(this.CONSTANTS.SLOTIDPREFIX,"");return t=parseInt(t,10),typeof t=="number"&&isFinite(t)?t:!1},getName:function(e){var t=e.one(this.SELECTORS.INSTANCENAME);return t?t.get("firstChild").get("data"):null},getNumber:function(e){var t=e.one(this.SELECTORS.NUMBER).get("text");return t=parseInt(t,10),typeof t=="number"&&isFinite(t)?t:!1},setNumber:function(e,t){e.one(this.SELECTORS.NUMBER).set("text",t)},getSlots:function(){return e.all(this.SELECTORS.PAGECONTENT+" "+this.SELECTORS.SECTIONUL+" "+this.SELECTORS.SLOT)},getPrevious:function(e){return e.previous(this.SELECTORS.SLOT)},reorder_slots:function(){var e=this.getSlots();e.each(function(e){var t=this.getPrevious(e),n=0;t&&(n=this.getNumber(t)),this.setNumber(e,n+1)},this)}}},"@VERSION@",{requires:["node","moodle-mod_quiz-util-base"]});
diff --git a/mod/quiz/yui/build/moodle-mod_quiz-util-slot/moodle-mod_quiz-util-slot.js b/mod/quiz/yui/build/moodle-mod_quiz-util-slot/moodle-mod_quiz-util-slot.js
new file mode 100644
index 00000000000..1f0927c301b
--- /dev/null
+++ b/mod/quiz/yui/build/moodle-mod_quiz-util-slot/moodle-mod_quiz-util-slot.js
@@ -0,0 +1,150 @@
+YUI.add('moodle-mod_quiz-util-slot', function (Y, NAME) {
+
+/**
+ * A collection of utility classes for use with slots.
+ *
+ * @module moodle-mod_quiz-util
+ * @submodule moodle-mod_quiz-util-slot
+ */
+
+Y.namespace('Moodle.mod_quiz.util.slot');
+
+/**
+ * A collection of utility classes for use with slots.
+ *
+ * @class Moodle.mod_quiz.util.slot
+ * @static
+ */
+Y.Moodle.mod_quiz.util.slot = {
+ CONSTANTS: {
+ SLOTIDPREFIX : 'slot-'
+ },
+ SELECTORS: {
+ SLOT: 'li.slot',
+ INSTANCENAME: '.instancename',
+ NUMBER: 'span.slotnumber',
+ PAGECONTENT : 'div#page-content',
+ SECTIONUL : 'ul.section'
+ },
+
+ /**
+ * Retrieve the slot item from one of it's child Nodes.
+ *
+ * @method getSlotFromComponent
+ * @param slotcomponent {Node} The component Node.
+ * @return {Node|null} The Slot Node.
+ */
+ getSlotFromComponent: function(slotcomponent) {
+ return Y.one(slotcomponent).ancestor(this.SELECTORS.SLOT, true);
+ },
+
+ /**
+ * Determines the slot ID for the provided slot.
+ *
+ * @method getId
+ * @param slot {Node} The slot to find an ID for.
+ * @return {Number|false} The ID of the slot in question or false if no ID was found.
+ */
+ getId: function(slot) {
+ // We perform a simple substitution operation to get the ID.
+ var id = slot.get('id').replace(
+ this.CONSTANTS.SLOTIDPREFIX, '');
+
+ // Attempt to validate the ID.
+ id = parseInt(id, 10);
+ if (typeof id === 'number' && isFinite(id)) {
+ return id;
+ }
+ return false;
+ },
+
+ /**
+ * Determines the slot name for the provided slot.
+ *
+ * @method getName
+ * @param slot {Node} The slot to find a name for.
+ * @return {string|false} The name of the slot in question or false if no ID was found.
+ */
+ getName: function(slot) {
+ var instance = slot.one(this.SELECTORS.INSTANCENAME);
+ if (instance) {
+ return instance.get('firstChild').get('data');
+ }
+ return null;
+ },
+
+ /**
+ * Determines the slot number for the provided slot.
+ *
+ * @method getNumber
+ * @param slot {Node} The slot to find the number for.
+ * @return {Number|false} The number of the slot in question or false if no number was found.
+ */
+ getNumber: function(slot) {
+ var number = slot.one(this.SELECTORS.NUMBER).get('text');
+ // Attempt to validate the ID.
+ number = parseInt(number, 10);
+ if (typeof number === 'number' && isFinite(number)) {
+ return number;
+ }
+ return false;
+ },
+
+ /**
+ * Updates the slot number for the provided slot.
+ *
+ * @method setNumber
+ * @param slot {Node} The slot to update the number for.
+ * @return void
+ */
+ setNumber: function(slot, number) {
+ slot.one(this.SELECTORS.NUMBER).set('text', number);
+ },
+
+ /**
+ * Returns a list of all slot elements on the page.
+ *
+ * @method getSlots
+ * @return {node[]} An array containing slot nodes.
+ */
+ getSlots: function() {
+ return Y.all(this.SELECTORS.PAGECONTENT + ' ' + this.SELECTORS.SECTIONUL + ' ' + this.SELECTORS.SLOT);
+ },
+
+ /**
+ * Returns the previous slot to the give slot.
+ *
+ * @method getPrevious
+ * @param slot Slot node
+ * @return {node|false} The previous slot node or false.
+ */
+ getPrevious: function(slot) {
+ return slot.previous(this.SELECTORS.SLOT);
+ },
+
+ /**
+ * Reset the order of the numbers given to each slot.
+ *
+ * @method reorder_slots
+ * @return void
+ */
+ reorder_slots: function() {
+ // Get list of slot nodes.
+ var slots = this.getSlots();
+ // Loop through slots incrementing the number each time.
+ slots.each(function(slot) {
+ var previousSlot = this.getPrevious(slot),
+ previousslotnumber = 0;
+ if(previousSlot){
+ previousslotnumber = this.getNumber(previousSlot);
+ }
+
+ // Set slot number.
+ this.setNumber(slot, previousslotnumber + 1);
+ }, this);
+
+ }
+};
+
+
+}, '@VERSION@', {"requires": ["node", "moodle-mod_quiz-util-base"]});
diff --git a/mod/quiz/yui/src/dragdrop/build.json b/mod/quiz/yui/src/dragdrop/build.json
new file mode 100644
index 00000000000..b005fabbeba
--- /dev/null
+++ b/mod/quiz/yui/src/dragdrop/build.json
@@ -0,0 +1,12 @@
+{
+ "name": "moodle-mod_quiz-dragdrop",
+ "builds": {
+ "moodle-mod_quiz-dragdrop": {
+ "jsfiles": [
+ "dragdrop.js",
+ "section.js",
+ "resource.js"
+ ]
+ }
+ }
+}
diff --git a/mod/quiz/yui/src/dragdrop/js/dragdrop.js b/mod/quiz/yui/src/dragdrop/js/dragdrop.js
new file mode 100644
index 00000000000..bcd66831e0a
--- /dev/null
+++ b/mod/quiz/yui/src/dragdrop/js/dragdrop.js
@@ -0,0 +1,34 @@
+/**
+ * Drag and Drop for Quiz sections and slots.
+ *
+ * @module moodle-mod-quiz-dragdrop
+ */
+
+var CSS = {
+ ACTIONAREA: '.actions',
+ ACTIVITY: 'activity',
+ ACTIVITYINSTANCE: 'activityinstance',
+ CONTENT: 'content',
+ COURSECONTENT: 'mod-quiz-edit-content',
+ EDITINGMOVE: 'editing_move',
+ ICONCLASS: 'iconsmall',
+ JUMPMENU: 'jumpmenu',
+ LEFT: 'left',
+ LIGHTBOX: 'lightbox',
+ MOVEDOWN: 'movedown',
+ MOVEUP: 'moveup',
+ PAGE : 'page',
+ PAGECONTENT: 'page-content',
+ RIGHT: 'right',
+ SECTION: 'section',
+ SECTIONADDMENUS: 'section_add_menus',
+ SECTIONHANDLE: 'section-handle',
+ SLOTS: 'slots',
+ SUMMARY: 'summary',
+ SECTIONDRAGGABLE: 'sectiondraggable'
+},
+// The CSS selectors we use.
+SELECTOR = {
+ PAGE: 'li.page',
+ SLOT: 'li.slot'
+};
diff --git a/mod/quiz/yui/src/dragdrop/js/resource.js b/mod/quiz/yui/src/dragdrop/js/resource.js
new file mode 100644
index 00000000000..f4fdd460742
--- /dev/null
+++ b/mod/quiz/yui/src/dragdrop/js/resource.js
@@ -0,0 +1,246 @@
+/**
+ * Resource drag and drop.
+ *
+ * @class M.course.dragdrop.resource
+ * @constructor
+ * @extends M.core.dragdrop
+ */
+var DRAGRESOURCE = function() {
+ DRAGRESOURCE.superclass.constructor.apply(this, arguments);
+};
+Y.extend(DRAGRESOURCE, M.core.dragdrop, {
+ initializer: function() {
+ // Set group for parent class
+ this.groups = ['resource'];
+ this.samenodeclass = CSS.ACTIVITY;
+ this.parentnodeclass = CSS.SECTION;
+ //this.resourcedraghandle = this.get_drag_handle(M.util.get_string('movecoursemodule', 'moodle'), CSS.EDITINGMOVE, CSS.ICONCLASS, true);
+ this.resourcedraghandle = this.get_drag_handle(M.str.moodle.move, CSS.EDITINGMOVE, CSS.ICONCLASS, true);
+
+ this.samenodelabel = {
+ identifier: 'dragtoafter',
+ component: 'quiz'
+ };
+ this.parentnodelabel = {
+ identifier: 'dragtostart',
+ component: 'quiz'
+ };
+
+ // Go through all sections
+ var sectionlistselector = M.mod_quiz.edit.get_section_selector(Y);
+ if (sectionlistselector) {
+ sectionlistselector = '.' + CSS.COURSECONTENT + ' ' + sectionlistselector;
+ this.setup_for_section(sectionlistselector);
+
+ // Initialise drag & drop for all resources/activities
+ var nodeselector = sectionlistselector.slice(CSS.COURSECONTENT.length + 2) + ' li.' + CSS.ACTIVITY;
+ var del = new Y.DD.Delegate({
+ container: '.' + CSS.COURSECONTENT,
+ nodes: nodeselector,
+ target: true,
+ handles: ['.' + CSS.EDITINGMOVE],
+ dragConfig: {groups: this.groups}
+ });
+ del.dd.plug(Y.Plugin.DDProxy, {
+ // Don't move the node at the end of the drag
+ moveOnEnd: false,
+ cloneNode: true
+ });
+ del.dd.plug(Y.Plugin.DDConstrained, {
+ // Keep it inside the .mod-quiz-edit-content
+ constrain: '#' + CSS.SLOTS
+ });
+ del.dd.plug(Y.Plugin.DDWinScroll);
+
+ M.mod_quiz.quizbase.register_module(this);
+ M.mod_quiz.dragres = this;
+ }
+ },
+
+ /**
+ * Apply dragdrop features to the specified selector or node that refers to section(s)
+ *
+ * @method setup_for_section
+ * @param {String} baseselector The CSS selector or node to limit scope to
+ */
+ setup_for_section: function(baseselector) {
+ Y.Node.all(baseselector).each(function(sectionnode) {
+ var resources = sectionnode.one('.' + CSS.CONTENT + ' ul.' + CSS.SECTION);
+ // See if resources ul exists, if not create one.
+ if (!resources) {
+ resources = Y.Node.create('
');
+ resources.addClass(CSS.SECTION);
+ sectionnode.one('.' + CSS.CONTENT + ' div.' + CSS.SUMMARY).insert(resources, 'after');
+ }
+ resources.setAttribute('data-draggroups', this.groups.join(' '));
+ // Define empty ul as droptarget, so that item could be moved to empty list
+ new Y.DD.Drop({
+ node: resources,
+ groups: this.groups,
+ padding: '20 0 20 0'
+ });
+
+ // Initialise each resource/activity in this section
+ this.setup_for_resource('#' + sectionnode.get('id') + ' li.' + CSS.ACTIVITY);
+ }, this);
+ },
+
+ /**
+ * Apply dragdrop features to the specified selector or node that refers to resource(s)
+ *
+ * @method setup_for_resource
+ * @param {String} baseselector The CSS selector or node to limit scope to
+ */
+ setup_for_resource: function(baseselector) {
+ Y.Node.all(baseselector).each(function(resourcesnode) {
+ // Replace move icons
+ var move = resourcesnode.one('a.' + CSS.EDITINGMOVE);
+ if (move) {
+ move.replace(this.resourcedraghandle.cloneNode(true));
+ }
+ }, this);
+ },
+
+ drag_start: function(e) {
+ // Get our drag object
+ var drag = e.target;
+ drag.get('dragNode').setContent(drag.get('node').get('innerHTML'));
+ drag.get('dragNode').all('img.iconsmall').setStyle('vertical-align', 'baseline');
+ },
+
+ drag_dropmiss: function(e) {
+ // Missed the target, but we assume the user intended to drop it
+ // on the last ghost node location, e.drag and e.drop should be
+ // prepared by global_drag_dropmiss parent so simulate drop_hit(e).
+ this.drop_hit(e);
+ },
+
+ drop_hit: function(e) {
+ var drag = e.drag;
+ // Get a reference to our drag node
+ var dragnode = drag.get('node');
+ var dropnode = e.drop.get('node');
+
+ // Add spinner if it not there
+ var actionarea = dragnode.one(CSS.ACTIONAREA);
+ var spinner = M.util.add_spinner(Y, actionarea);
+
+ var params = {};
+
+ // Handle any variables which we must pass back through to
+ var pageparams = this.get('config').pageparams;
+ var varname;
+ for (varname in pageparams) {
+ params[varname] = pageparams[varname];
+ }
+
+ // Prepare request parameters
+ params.sesskey = M.cfg.sesskey;
+ params.courseid = this.get('courseid');
+ params.quizid = this.get('quizid');
+ params['class'] = 'resource';
+ params.field = 'move';
+ params.id = Number(Y.Moodle.mod_quiz.util.slot.getId(dragnode));
+ params.sectionId = Y.Moodle.core_course.util.section.getId(dropnode.ancestor(M.mod_quiz.edit.get_section_wrapper(Y), true));
+
+ var previousslot = dragnode.previous(SELECTOR.SLOT);
+ if (previousslot) {
+ params.previousid = Number(Y.Moodle.mod_quiz.util.slot.getId(previousslot));
+ }
+
+ var previouspage = dragnode.previous(SELECTOR.PAGE);
+ if (previouspage) {
+ params.page = Number(Y.Moodle.mod_quiz.util.page.getId(previouspage));
+ }
+
+ // Do AJAX request
+ var uri = M.cfg.wwwroot + this.get('ajaxurl');
+
+ Y.io(uri, {
+ method: 'POST',
+ data: params,
+ on: {
+ start: function() {
+ this.lock_drag_handle(drag, CSS.EDITINGMOVE);
+ spinner.show();
+ },
+ success: function(tid, response) {
+ var responsetext = Y.JSON.parse(response.responseText);
+ var params = {element: dragnode, visible: responsetext.visible};
+ M.mod_quiz.quizbase.invoke_function('set_visibility_resource_ui', params);
+ Y.Moodle.mod_quiz.util.slot.reorder_slots();
+ this.unlock_drag_handle(drag, CSS.EDITINGMOVE);
+ window.setTimeout(function() {
+ spinner.hide();
+ }, 250);
+ window.location.reload(true);
+ },
+ failure: function(tid, response) {
+ this.ajax_failure(response);
+ this.unlock_drag_handle(drag, CSS.SECTIONHANDLE);
+ spinner.hide();
+ window.location.reload(true);
+ }
+ },
+ context:this
+ });
+ },
+
+ global_drop_over: function(e) {
+ //Overriding parent method so we can stop the slots being dragged before the first page node.
+
+ // Check that drop object belong to correct group.
+ if (!e.drop || !e.drop.inGroup(this.groups)) {
+ return;
+ }
+
+ // Get a reference to our drag and drop nodes.
+ var drag = e.drag.get('node'),
+ drop = e.drop.get('node');
+
+ // Save last drop target for the case of missed target processing.
+ this.lastdroptarget = e.drop;
+
+ // Are we dropping within the same parent node?
+ if (drop.hasClass(this.samenodeclass)) {
+ var where;
+
+ if (this.goingup) {
+ where = "before";
+ } else {
+ where = "after";
+ }
+
+ drop.insert(drag, where);
+ } else if ((drop.hasClass(this.parentnodeclass) || drop.test('[data-droptarget="1"]')) && !drop.contains(drag)) {
+ // We are dropping on parent node and it is empty
+ if (this.goingup) {
+ drop.append(drag);
+ } else {
+ drop.prepend(drag);
+ }
+ }
+ this.drop_over(e);
+ }
+}, {
+ NAME: 'mod_quiz-dragdrop-resource',
+ ATTRS: {
+ courseid: {
+ value: null
+ },
+ quizid: {
+ value: null
+ },
+ ajaxurl: {
+ value: 0
+ },
+ config: {
+ value: 0
+ }
+ }
+});
+
+M.mod_quiz = M.mod_quiz || {};
+M.mod_quiz.init_resource_dragdrop = function(params) {
+ new DRAGRESOURCE(params);
+};
diff --git a/mod/quiz/yui/src/dragdrop/js/section.js b/mod/quiz/yui/src/dragdrop/js/section.js
new file mode 100644
index 00000000000..9fdb109811b
--- /dev/null
+++ b/mod/quiz/yui/src/dragdrop/js/section.js
@@ -0,0 +1,263 @@
+/**
+ * Section drag and drop.
+ *
+ * @class M.mod_quiz.dragdrop.section
+ * @constructor
+ * @extends M.core.dragdrop
+ */
+var DRAGSECTION = function() {
+ DRAGSECTION.superclass.constructor.apply(this, arguments);
+};
+Y.extend(DRAGSECTION, M.core.dragdrop, {
+ sectionlistselector: null,
+
+ initializer: function() {
+ // Set group for parent class
+ this.groups = [ CSS.SECTIONDRAGGABLE ];
+ this.samenodeclass = M.mod_quiz.edit.get_sectionwrapperclass();
+ this.parentnodeclass = M.mod_quiz.edit.get_containerclass();
+
+ // Check if we are in single section mode
+ if (Y.Node.one('.' + CSS.JUMPMENU)) {
+ return false;
+ }
+ // Initialise sections dragging
+ this.sectionlistselector = M.mod_quiz.edit.get_section_wrapper(Y);
+ if (this.sectionlistselector) {
+ this.sectionlistselector = '.' + CSS.COURSECONTENT + ' ' + this.sectionlistselector;
+
+ this.setup_for_section(this.sectionlistselector);
+
+ // Make each li element in the lists of sections draggable
+ var del = new Y.DD.Delegate({
+ container: '.' + CSS.COURSECONTENT,
+ nodes: '.' + CSS.SECTIONDRAGGABLE,
+ target: true,
+ handles: ['.' + CSS.LEFT],
+ dragConfig: {groups: this.groups}
+ });
+ del.dd.plug(Y.Plugin.DDProxy, {
+ // Don't move the node at the end of the drag
+ moveOnEnd: false
+ });
+ del.dd.plug(Y.Plugin.DDConstrained, {
+ // Keep it inside the .mod-quiz-edit-content
+ constrain: '#' + CSS.PAGECONTENT,
+ stickY: true
+ });
+ del.dd.plug(Y.Plugin.DDWinScroll);
+ }
+ },
+
+ /**
+ * Apply dragdrop features to the specified selector or node that refers to section(s)
+ *
+ * @method setup_for_section
+ * @param {String} baseselector The CSS selector or node to limit scope to
+ */
+ setup_for_section: function(baseselector) {
+ Y.Node.all(baseselector).each(function(sectionnode) {
+ // Determine the section ID
+ var sectionid = Y.Moodle.core_course.util.section.getId(sectionnode);
+
+ // We skip the top section as it is not draggable
+ if (sectionid > 0) {
+ // Remove move icons
+ var movedown = sectionnode.one('.' + CSS.RIGHT + ' a.' + CSS.MOVEDOWN);
+ var moveup = sectionnode.one('.' + CSS.RIGHT + ' a.' + CSS.MOVEUP);
+
+ // Add dragger icon
+ var title = M.util.get_string('movesection', 'moodle', sectionid);
+ var cssleft = sectionnode.one('.' + CSS.LEFT);
+
+ if ((movedown || moveup) && cssleft) {
+ cssleft.setStyle('cursor', 'move');
+ cssleft.appendChild(this.get_drag_handle(title, CSS.SECTIONHANDLE, 'icon', true));
+
+ if (moveup) {
+ moveup.remove();
+ }
+ if (movedown) {
+ movedown.remove();
+ }
+
+ // This section can be moved - add the class to indicate this to Y.DD.
+ sectionnode.addClass(CSS.SECTIONDRAGGABLE);
+ }
+ }
+ }, this);
+ },
+
+ /*
+ * Drag-dropping related functions
+ */
+ drag_start: function(e) {
+ // Get our drag object
+ var drag = e.target;
+ // Creat a dummy structure of the outer elemnents for clean styles application
+ var containernode = Y.Node.create('<' + M.mod_quiz.edit.get_containernode() + '>' + M.mod_quiz.edit.get_containernode() + '>');
+ containernode.addClass(M.mod_quiz.edit.get_containerclass());
+ var sectionnode = Y.Node.create('<' + M.mod_quiz.edit.get_sectionwrappernode() + '>' + M.mod_quiz.edit.get_sectionwrappernode() + '>');
+ sectionnode.addClass( M.mod_quiz.edit.get_sectionwrapperclass());
+ sectionnode.setStyle('margin', 0);
+ sectionnode.setContent(drag.get('node').get('innerHTML'));
+ containernode.appendChild(sectionnode);
+ drag.get('dragNode').setContent(containernode);
+ drag.get('dragNode').addClass(CSS.COURSECONTENT);
+ },
+
+ drag_dropmiss: function(e) {
+ // Missed the target, but we assume the user intended to drop it
+ // on the last last ghost node location, e.drag and e.drop should be
+ // prepared by global_drag_dropmiss parent so simulate drop_hit(e).
+ this.drop_hit(e);
+ },
+
+ get_section_index: function(node) {
+ var sectionlistselector = '.' + CSS.COURSECONTENT + ' ' + M.mod_quiz.edit.get_section_selector(Y),
+ sectionList = Y.all(sectionlistselector),
+ nodeIndex = sectionList.indexOf(node),
+ zeroIndex = sectionList.indexOf(Y.one('#section-0'));
+
+ return (nodeIndex - zeroIndex);
+ },
+
+ drop_hit: function(e) {
+ var drag = e.drag;
+
+ // Get references to our nodes and their IDs.
+ var dragnode = drag.get('node'),
+ dragnodeid = Y.Moodle.core_course.util.section.getId(dragnode),
+ loopstart = dragnodeid,
+
+ dropnodeindex = this.get_section_index(dragnode),
+ loopend = dropnodeindex;
+
+ if (dragnodeid === dropnodeindex) {
+ Y.log("Skipping move - same location moving " + dragnodeid + " to " + dropnodeindex, 'debug', 'moodle-mod_quiz-dragdrop');
+ return;
+ }
+
+ Y.log("Moving from position " + dragnodeid + " to position " + dropnodeindex, 'debug', 'moodle-mod_quiz-dragdrop');
+
+ if (loopstart > loopend) {
+ // If we're going up, we need to swap the loop order
+ // because loops can't go backwards.
+ loopstart = dropnodeindex;
+ loopend = dragnodeid;
+ }
+
+ // Get the list of nodes.
+ drag.get('dragNode').removeClass(CSS.COURSECONTENT);
+ var sectionlist = Y.Node.all(this.sectionlistselector);
+
+ // Add a lightbox if it's not there.
+ var lightbox = M.util.add_lightbox(Y, dragnode);
+
+ // Handle any variables which we must pass via AJAX.
+ var params = {},
+ pageparams = this.get('config').pageparams,
+ varname;
+
+ for (varname in pageparams) {
+ if (!pageparams.hasOwnProperty(varname)) {
+ continue;
+ }
+ params[varname] = pageparams[varname];
+ }
+
+ // Prepare request parameters
+ params.sesskey = M.cfg.sesskey;
+ params.courseid = this.get('courseid');
+ params.quizid = this.get('quizid');
+ params['class'] = 'section';
+ params.field = 'move';
+ params.id = dragnodeid;
+ params.value = dropnodeindex;
+
+ // Perform the AJAX request.
+ var uri = M.cfg.wwwroot + this.get('ajaxurl');
+ Y.io(uri, {
+ method: 'POST',
+ data: params,
+ on: {
+ start: function() {
+ lightbox.show();
+ },
+ success: function(tid, response) {
+ // Update section titles, we can't simply swap them as
+ // they might have custom title
+ try {
+ var responsetext = Y.JSON.parse(response.responseText);
+ if (responsetext.error) {
+ new M.core.ajaxException(responsetext);
+ }
+ M.mod_quiz.edit.process_sections(Y, sectionlist, responsetext, loopstart, loopend);
+ } catch (e) {}
+
+ // Update all of the section IDs - first unset them, then set them
+ // to avoid duplicates in the DOM.
+ var index;
+
+ // Classic bubble sort algorithm is applied to the section
+ // nodes between original drag node location and the new one.
+ var swapped = false;
+ do {
+ swapped = false;
+ for (index = loopstart; index <= loopend; index++) {
+ if (Y.Moodle.core_course.util.section.getId(sectionlist.item(index - 1)) >
+ Y.Moodle.core_course.util.section.getId(sectionlist.item(index))) {
+ Y.log("Swapping " + Y.Moodle.core_course.util.section.getId(sectionlist.item(index - 1)) +
+ " with " + Y.Moodle.core_course.util.section.getId(sectionlist.item(index)),
+ "debug", "moodle-mod_quiz-dragdrop");
+ // Swap section id.
+ var sectionid = sectionlist.item(index - 1).get('id');
+ sectionlist.item(index - 1).set('id', sectionlist.item(index).get('id'));
+ sectionlist.item(index).set('id', sectionid);
+
+ // See what format needs to swap.
+ M.mod_quiz.edit.swap_sections(Y, index - 1, index);
+
+ // Update flag.
+ swapped = true;
+ }
+ }
+ loopend = loopend - 1;
+ } while (swapped);
+
+ window.setTimeout(function() {
+ lightbox.hide();
+ }, 250);
+ },
+
+ failure: function(tid, response) {
+ this.ajax_failure(response);
+ lightbox.hide();
+ }
+ },
+ context:this
+ });
+ }
+
+}, {
+ NAME: 'mod_quiz-dragdrop-section',
+ ATTRS: {
+ courseid: {
+ value: null
+ },
+ quizid: {
+ value: null
+ },
+ ajaxurl: {
+ value: 0
+ },
+ config: {
+ value: 0
+ }
+ }
+});
+
+M.mod_quiz = M.mod_quiz || {};
+M.mod_quiz.init_section_dragdrop = function(params) {
+ new DRAGSECTION(params);
+};
diff --git a/mod/quiz/yui/src/dragdrop/meta/dragdrop.json b/mod/quiz/yui/src/dragdrop/meta/dragdrop.json
new file mode 100644
index 00000000000..f80f8ed6387
--- /dev/null
+++ b/mod/quiz/yui/src/dragdrop/meta/dragdrop.json
@@ -0,0 +1,17 @@
+{
+ "moodle-mod_quiz-dragdrop": {
+ "requires": [
+ "base",
+ "node",
+ "io",
+ "dom",
+ "dd",
+ "dd-scroll",
+ "moodle-core-dragdrop",
+ "moodle-core-notification",
+ "moodle-mod_quiz-quizbase",
+ "moodle-mod_quiz-util",
+ "moodle-course-util"
+ ]
+ }
+}
diff --git a/mod/quiz/yui/src/modform/build.json b/mod/quiz/yui/src/modform/build.json
new file mode 100644
index 00000000000..e40f536fd5b
--- /dev/null
+++ b/mod/quiz/yui/src/modform/build.json
@@ -0,0 +1,10 @@
+{
+ "name": "moodle-mod_quiz-modform",
+ "builds": {
+ "moodle-mod_quiz-modform": {
+ "jsfiles": [
+ "modform.js"
+ ]
+ }
+ }
+}
diff --git a/mod/quiz/yui/src/modform/js/modform.js b/mod/quiz/yui/src/modform/js/modform.js
new file mode 100644
index 00000000000..6a524cfb7ae
--- /dev/null
+++ b/mod/quiz/yui/src/modform/js/modform.js
@@ -0,0 +1,51 @@
+/**
+ * The modform class has all the JavaScript specific to mod/quiz/mod_form.php.
+ *
+ * @module moodle-mod_quiz-modform
+ */
+
+var MODFORM = function() {
+ MODFORM.superclass.constructor.apply(this, arguments);
+};
+
+/**
+ * The coursebase class to provide shared functionality to Modules within
+ * Moodle.
+ *
+ * @class M.course.coursebase
+ * @constructor
+ */
+Y.extend(MODFORM, Y.Base, {
+ repaginateCheckbox: null,
+ qppSelect: null,
+ qppInitialValue: 0,
+
+ initializer: function () {
+ this.repaginateCheckbox = Y.one('#id_repaginatenow');
+ if (!this.repaginateCheckbox) {
+ // The checkbox only appears when editing an existing quiz.
+ return;
+ }
+
+ this.qppSelect = Y.one('#id_questionsperpage');
+ this.qppInitialValue = this.qppSelect.get('value');
+ this.qppSelect.on('change', this.qppChanged, this);
+ Y.one('#id_shufflequestions').on('change', this.qppChanged, this);
+ },
+
+ qppChanged: function() {
+ Y.later(50, this, function() {
+ if (!this.repaginateCheckbox.get('disabled')) {
+ this.repaginateCheckbox.set('checked', this.qppSelect.get('value') !== this.qppInitialValue);
+ }
+ });
+ }
+
+});
+
+// Ensure that M.course exists and that coursebase is initialised correctly
+M.mod_quiz = M.mod_quiz || {};
+M.mod_quiz.modform = M.mod_quiz.modform || new MODFORM();
+M.mod_quiz.modform.init = function() {
+ return new MODFORM();
+};
diff --git a/mod/quiz/yui/src/modform/meta/modform.json b/mod/quiz/yui/src/modform/meta/modform.json
new file mode 100644
index 00000000000..360e6ee43d4
--- /dev/null
+++ b/mod/quiz/yui/src/modform/meta/modform.json
@@ -0,0 +1,9 @@
+{
+ "moodle-mod_quiz-modform": {
+ "requires": [
+ "base",
+ "node",
+ "event"
+ ]
+ }
+}
diff --git a/mod/quiz/yui/src/questionchooser/build.json b/mod/quiz/yui/src/questionchooser/build.json
new file mode 100644
index 00000000000..0cc9bc0feea
--- /dev/null
+++ b/mod/quiz/yui/src/questionchooser/build.json
@@ -0,0 +1,10 @@
+{
+ "name": "moodle-mod_quiz-questionchooser",
+ "builds": {
+ "moodle-mod_quiz-questionchooser": {
+ "jsfiles": [
+ "questionchooser.js"
+ ]
+ }
+ }
+}
diff --git a/mod/quiz/yui/src/questionchooser/js/questionchooser.js b/mod/quiz/yui/src/questionchooser/js/questionchooser.js
new file mode 100644
index 00000000000..b940570d52a
--- /dev/null
+++ b/mod/quiz/yui/src/questionchooser/js/questionchooser.js
@@ -0,0 +1,72 @@
+var CSS = {
+ ADDNEWQUESTIONBUTTONS: 'ul.menu a.addquestion',
+ CREATENEWQUESTION: 'div.createnewquestion',
+ CHOOSERDIALOGUE: 'div.chooserdialogue',
+ CHOOSERHEADER: 'div.choosertitle'
+};
+
+/**
+ * The questionchooser class is responsible for instantiating and displaying the question chooser
+ * when viewing a quiz in editing mode.
+ *
+ * @class questionchooser
+ * @constructor
+ * @protected
+ * @extends M.core.chooserdialogue
+ */
+var QUESTIONCHOOSER = function() {
+ QUESTIONCHOOSER.superclass.constructor.apply(this, arguments);
+};
+
+Y.extend(QUESTIONCHOOSER, M.core.chooserdialogue, {
+ initializer: function() {
+ Y.one('body').delegate('click', this.display_dialogue, CSS.ADDNEWQUESTIONBUTTONS, this);
+ },
+
+ display_dialogue: function(e) {
+ e.preventDefault();
+ var dialogue = Y.one(CSS.CREATENEWQUESTION + ' ' + CSS.CHOOSERDIALOGUE),
+ header = Y.one(CSS.CREATENEWQUESTION + ' ' + CSS.CHOOSERHEADER);
+
+ if (this.container === null) {
+ // Setup the dialogue, and then prepare the chooser if it's not already been set up.
+ this.setup_chooser_dialogue(dialogue, header, {});
+ this.prepare_chooser();
+ }
+
+ // Update all of the hidden fields within the questionbank form.
+ var parameters = Y.QueryString.parse(e.currentTarget.get('search').substring(1));
+ var form = this.container.one('form');
+ this.parameters_to_hidden_input(parameters, form, 'returnurl');
+ this.parameters_to_hidden_input(parameters, form, 'cmid');
+ this.parameters_to_hidden_input(parameters, form, 'category');
+ this.parameters_to_hidden_input(parameters, form, 'addonpage');
+ this.parameters_to_hidden_input(parameters, form, 'appendqnumstring');
+
+ // Display the chooser dialogue.
+ this.display_chooser(e);
+ },
+
+ parameters_to_hidden_input: function(parameters, form, name) {
+ var value;
+ if (parameters.hasOwnProperty(name)) {
+ value = parameters[name];
+ } else {
+ value = '';
+ }
+ var input = form.one('input[name=' + name + ']');
+ if (!input) {
+ input = form.appendChild('');
+ input.set('name', name);
+ }
+ input.set('value', value);
+ }
+}, {
+ NAME: 'mod_quiz-questionchooser'
+});
+
+M.mod_quiz = M.mod_quiz || {};
+M.mod_quiz.init_questionchooser = function() {
+ M.mod_quiz.question_chooser = new QUESTIONCHOOSER({});
+ return M.mod_quiz.question_chooser;
+};
diff --git a/mod/quiz/yui/src/questionchooser/meta/questionchooser.json b/mod/quiz/yui/src/questionchooser/meta/questionchooser.json
new file mode 100644
index 00000000000..5a69c90b178
--- /dev/null
+++ b/mod/quiz/yui/src/questionchooser/meta/questionchooser.json
@@ -0,0 +1,9 @@
+{
+ "moodle-mod_quiz-questionchooser": {
+ "requires": [
+ "moodle-core-chooserdialogue",
+ "moodle-mod_quiz-util",
+ "querystring-parse"
+ ]
+ }
+}
diff --git a/mod/quiz/yui/src/quizbase/build.json b/mod/quiz/yui/src/quizbase/build.json
new file mode 100644
index 00000000000..62f4c3c955f
--- /dev/null
+++ b/mod/quiz/yui/src/quizbase/build.json
@@ -0,0 +1,10 @@
+{
+ "name": "moodle-mod_quiz-quizbase",
+ "builds": {
+ "moodle-mod_quiz-quizbase": {
+ "jsfiles": [
+ "quizbase.js"
+ ]
+ }
+ }
+}
diff --git a/mod/quiz/yui/src/quizbase/js/quizbase.js b/mod/quiz/yui/src/quizbase/js/quizbase.js
new file mode 100644
index 00000000000..db05ce194be
--- /dev/null
+++ b/mod/quiz/yui/src/quizbase/js/quizbase.js
@@ -0,0 +1,267 @@
+/**
+ * The quizbase class to provide shared functionality to Modules within Moodle.
+ *
+ * @module moodle-mod_quiz-quizbase
+ */
+var QUIZBASENAME = 'mod_quiz-quizbase';
+
+var QUIZBASE = function() {
+ QUIZBASE.superclass.constructor.apply(this, arguments);
+};
+
+/**
+ * The coursebase class to provide shared functionality to Modules within
+ * Moodle.
+ *
+ * @class M.course.coursebase
+ * @constructor
+ */
+Y.extend(QUIZBASE, Y.Base, {
+ // Registered Modules
+ registermodules : [],
+
+ /**
+ * Register a new Javascript Module
+ *
+ * @method register_module
+ * @param {Object} The instantiated module to call functions on
+ * @chainable
+ */
+ register_module : function(object) {
+ this.registermodules.push(object);
+
+ return this;
+ },
+
+ /**
+ * Invoke the specified function in all registered modules with the given arguments
+ *
+ * @method invoke_function
+ * @param {String} functionname The name of the function to call
+ * @param {mixed} args The argument supplied to the function
+ * @chainable
+ */
+ invoke_function : function(functionname, args) {
+ var module;
+ for (module in this.registermodules) {
+ if (functionname in this.registermodules[module]) {
+ this.registermodules[module][functionname](args);
+ }
+ }
+
+ return this;
+ }
+}, {
+ NAME : QUIZBASENAME,
+ ATTRS : {}
+});
+
+// Ensure that M.course exists and that coursebase is initialised correctly
+M.mod_quiz = M.mod_quiz || {};
+M.mod_quiz.quizbase = M.mod_quiz.quizbase || new QUIZBASE();
+
+// Abstract functions that needs to be defined per format (course/format/somename/format.js)
+M.mod_quiz.edit = M.mod_quiz.edit || {};
+
+/**
+ * Swap section (should be defined in format.js if requred)
+ *
+ * @param {YUI} Y YUI3 instance
+ * @param {string} node1 node to swap to
+ * @param {string} node2 node to swap with
+ * @return {NodeList} section list
+ */
+M.mod_quiz.edit.swap_sections = function(Y, node1, node2) {
+ var CSS = {
+ COURSECONTENT : 'mod-quiz-edit-content',
+ SECTIONADDMENUS : 'section_add_menus'
+ };
+
+ var sectionlist = Y.Node.all('.'+CSS.COURSECONTENT+' '+M.mod_quiz.edit.get_section_selector(Y));
+ // Swap menus.
+ sectionlist.item(node1).one('.'+CSS.SECTIONADDMENUS).swap(sectionlist.item(node2).one('.'+CSS.SECTIONADDMENUS));
+};
+
+/**
+ * Process sections after ajax response (should be defined in format.js)
+ * If some response is expected, we pass it over to format, as it knows better
+ * hot to process it.
+ *
+ * @param {YUI} Y YUI3 instance
+ * @param {NodeList} list of sections
+ * @param {array} response ajax response
+ * @param {string} sectionfrom first affected section
+ * @param {string} sectionto last affected section
+ * @return void
+ */
+M.mod_quiz.edit.process_sections = function(Y, sectionlist, response, sectionfrom, sectionto) {
+ var CSS = {
+ SECTIONNAME : 'sectionname'
+ },
+ SELECTORS = {
+ SECTIONLEFTSIDE : '.left .section-handle img'
+ };
+
+ if (response.action === 'move') {
+ // If moving up swap around 'sectionfrom' and 'sectionto' so the that loop operates.
+ if (sectionfrom > sectionto) {
+ var temp = sectionto;
+ sectionto = sectionfrom;
+ sectionfrom = temp;
+ }
+
+ // Update titles and move icons in all affected sections.
+ var ele, str, stridx, newstr;
+
+ for (var i = sectionfrom; i <= sectionto; i++) {
+ // Update section title.
+ sectionlist.item(i).one('.'+CSS.SECTIONNAME).setContent(response.sectiontitles[i]);
+
+ // Update move icon.
+ ele = sectionlist.item(i).one(SELECTORS.SECTIONLEFTSIDE);
+ str = ele.getAttribute('alt');
+ stridx = str.lastIndexOf(' ');
+ newstr = str.substr(0, stridx + 1) + i;
+ ele.setAttribute('alt', newstr);
+ ele.setAttribute('title', newstr); // For FireFox as 'alt' is not refreshed.
+
+ // Remove the current class as section has been moved.
+ sectionlist.item(i).removeClass('current');
+ }
+ // If there is a current section, apply corresponding class in order to highlight it.
+ if (response.current !== -1) {
+ // Add current class to the required section.
+ sectionlist.item(response.current).addClass('current');
+ }
+ }
+};
+
+/**
+* Get sections config for this format, for examples see function definition
+* in the formats.
+*
+* @return {object} section list configuration
+*/
+M.mod_quiz.edit.get_config = function() {
+ return {
+ container_node : 'ul',
+ container_class : 'slots',
+ section_node : 'li',
+ section_class : 'section'
+ };
+};
+
+/**
+ * Get section list for this format (usually items inside container_node.container_class selector)
+ *
+ * @param {YUI} Y YUI3 instance
+ * @return {string} section selector
+ */
+M.mod_quiz.edit.get_section_selector = function() {
+ var config = M.mod_quiz.edit.get_config();
+ if (config.section_node && config.section_class) {
+ return config.section_node + '.' + config.section_class;
+ }
+ Y.log('section_node and section_class are not defined in M.mod_quiz.edit.get_config', 'warn', 'moodle-mod_quiz-quizbase');
+ return null;
+};
+
+/**
+ * Get section wraper for this format (only used in case when each
+ * container_node.container_class node is wrapped in some other element).
+ *
+ * @param {YUI} Y YUI3 instance
+ * @return {string} section wrapper selector or M.mod_quiz.format.get_section_selector
+ * if section_wrapper_node and section_wrapper_class are not defined in the format config.
+ */
+M.mod_quiz.edit.get_section_wrapper = function(Y) {
+ var config = M.mod_quiz.edit.get_config();
+ if (config.section_wrapper_node && config.section_wrapper_class) {
+ return config.section_wrapper_node + '.' + config.section_wrapper_class;
+ }
+ return M.mod_quiz.edit.get_section_selector(Y);
+};
+
+/**
+ * Get the tag of container node
+ *
+ * @return {string} tag of container node.
+ */
+M.mod_quiz.edit.get_containernode = function() {
+ var config = M.mod_quiz.edit.get_config();
+ if (config.container_node) {
+ return config.container_node;
+ } else {
+ Y.log('container_node is not defined in M.mod_quiz.edit.get_config', 'warn', 'moodle-mod_quiz-quizbase');
+ }
+};
+
+/**
+ * Get the class of container node
+ *
+ * @return {string} class of the container node.
+ */
+M.mod_quiz.edit.get_containerclass = function() {
+ var config = M.mod_quiz.edit.get_config();
+ if (config.container_class) {
+ return config.container_class;
+ } else {
+ Y.log('container_class is not defined in M.mod_quiz.edit.get_config', 'warn', 'moodle-mod_quiz-quizbase');
+ }
+};
+
+/**
+ * Get the tag of draggable node (section wrapper if exists, otherwise section)
+ *
+ * @return {string} tag of the draggable node.
+ */
+M.mod_quiz.edit.get_sectionwrappernode = function() {
+ var config = M.mod_quiz.edit.get_config();
+ if (config.section_wrapper_node) {
+ return config.section_wrapper_node;
+ } else {
+ return config.section_node;
+ }
+};
+
+/**
+ * Get the class of draggable node (section wrapper if exists, otherwise section)
+ *
+ * @return {string} class of the draggable node.
+ */
+M.mod_quiz.edit.get_sectionwrapperclass = function() {
+ var config = M.mod_quiz.edit.get_config();
+ if (config.section_wrapper_class) {
+ return config.section_wrapper_class;
+ } else {
+ return config.section_class;
+ }
+};
+
+/**
+ * Get the tag of section node
+ *
+ * @return {string} tag of section node.
+ */
+M.mod_quiz.edit.get_sectionnode = function() {
+ var config = M.mod_quiz.edit.get_config();
+ if (config.section_node) {
+ return config.section_node;
+ } else {
+ Y.log('section_node is not defined in M.mod_quiz.edit.get_config', 'warn', 'moodle-mod_quiz-quizbase');
+ }
+};
+
+/**
+ * Get the class of section node
+ *
+ * @return {string} class of the section node.
+ */
+M.mod_quiz.edit.get_sectionclass = function() {
+ var config = M.mod_quiz.edit.get_config();
+ if (config.section_class) {
+ return config.section_class;
+ } else {
+ Y.log('section_class is not defined in M.mod_quiz.edit.get_config', 'warn', 'moodle-mod_quiz-quizbase');
+ }
+};
diff --git a/mod/quiz/yui/src/quizbase/meta/quizbase.json b/mod/quiz/yui/src/quizbase/meta/quizbase.json
new file mode 100644
index 00000000000..24deb8f0a11
--- /dev/null
+++ b/mod/quiz/yui/src/quizbase/meta/quizbase.json
@@ -0,0 +1,8 @@
+{
+ "moodle-mod_quiz-quizbase": {
+ "requires": [
+ "base",
+ "node"
+ ]
+ }
+}
diff --git a/mod/quiz/yui/src/quizquestionbank/build.json b/mod/quiz/yui/src/quizquestionbank/build.json
new file mode 100644
index 00000000000..5ba44a3c807
--- /dev/null
+++ b/mod/quiz/yui/src/quizquestionbank/build.json
@@ -0,0 +1,10 @@
+{
+ "name": "moodle-mod_quiz-quizquestionbank",
+ "builds": {
+ "moodle-mod_quiz-quizquestionbank": {
+ "jsfiles": [
+ "quizquestionbank.js"
+ ]
+ }
+ }
+}
diff --git a/mod/quiz/yui/src/quizquestionbank/js/quizquestionbank.js b/mod/quiz/yui/src/quizquestionbank/js/quizquestionbank.js
new file mode 100644
index 00000000000..8c6841e5d44
--- /dev/null
+++ b/mod/quiz/yui/src/quizquestionbank/js/quizquestionbank.js
@@ -0,0 +1,160 @@
+// 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 .
+
+
+/**
+ * Add questions from question bank functionality for a popup in quiz editing page.
+ *
+ * @package mod_quiz
+ * @copyright 2014 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+
+var CSS = {
+ QBANKLOADING: 'div.questionbankloading',
+ ADDQUESTIONLINKS: 'ul.menu a.questionbank',
+ ADDTOQUIZCONTAINER: 'td.addtoquizaction'
+};
+
+var PARAMS = {
+ PAGE: 'addonpage',
+ HEADER: 'header'
+};
+
+var POPUP = function() {
+ POPUP.superclass.constructor.apply(this, arguments);
+};
+
+Y.extend(POPUP, Y.Base, {
+ loadingDiv: '',
+ dialogue: null,
+ addonpage: 0,
+
+ create_dialogue: function() {
+ // Create a dialogue on the page and hide it.
+ config = {
+ headerContent : '',
+ bodyContent : Y.one(CSS.QBANKLOADING),
+ draggable : true,
+ modal : true,
+ centered: true,
+ width: null,
+ visible: false,
+ postmethod: 'form',
+ footerContent: null,
+ extraClasses: ['mod_quiz_qbank_dialogue']
+ };
+ this.dialogue = new M.core.dialogue(config);
+ this.dialogue.bodyNode.delegate('click', this.link_clicked, 'a[href]', this);
+ this.dialogue.hide();
+
+ this.loadingDiv = this.dialogue.bodyNode.getHTML();
+
+ Y.later(100, this, function() {this.load_content(window.location.search);});
+ },
+
+ initializer : function() {
+ if (!Y.one(CSS.QBANKLOADING)) {
+ return;
+ }
+ this.create_dialogue();
+ Y.one('body').delegate('click', this.display_dialogue, CSS.ADDQUESTIONLINKS, this);
+ },
+
+ display_dialogue : function (e) {
+ e.preventDefault();
+ this.dialogue.set('headerContent', e.currentTarget.getData(PARAMS.HEADER));
+
+ this.addonpage = e.currentTarget.getData(PARAMS.PAGE);
+ var controlsDiv = this.dialogue.bodyNode.one('.modulespecificbuttonscontainer');
+ if (controlsDiv) {
+ var hidden = controlsDiv.one('input[name=addonpage]');
+ if (!hidden) {
+ hidden = controlsDiv.appendChild('');
+ }
+ hidden.set('value', this.addonpage);
+ }
+
+ this.dialogue.show();
+ },
+
+ load_content : function(queryString) {
+ Y.log('Starting load.', 'debug', 'moodle-mod_quiz-quizquestionbank');
+ this.dialogue.bodyNode.append(this.loadingDiv);
+
+ // If to support old IE.
+ if (window.history.replaceState) {
+ window.history.replaceState(null, '', M.cfg.wwwroot + '/mod/quiz/edit.php' + queryString);
+ }
+
+ Y.io(M.cfg.wwwroot + '/mod/quiz/questionbank.ajax.php' + queryString, {
+ method: 'GET',
+ on: {
+ success: this.load_done,
+ failure: this.load_failed
+ },
+ context: this
+ });
+
+ Y.log('Load request sent.', 'debug', 'moodle-mod_quiz-quizquestionbank');
+ },
+
+ load_done: function(transactionid, response) {
+ var result = JSON.parse(response.responseText);
+ if (!result.status || result.status !== 'OK') {
+ // Because IIS is useless, Moodle can't send proper HTTP response
+ // codes, so we have to detect failures manually.
+ this.load_failed(transactionid, response);
+ return;
+ }
+
+ Y.log('Load completed.', 'debug', 'moodle-mod_quiz-quizquestionbank');
+
+ this.dialogue.bodyNode.setHTML(result.contents);
+ Y.use('moodle-question-chooser', function() {M.question.init_chooser({});});
+ this.dialogue.bodyNode.one('form').delegate('change', this.options_changed, '.searchoptions', this);
+
+ if (this.dialogue.visible) {
+ Y.later(0, this.dialogue, this.dialogue.centerDialogue);
+ }
+ M.question.qbankmanager.init();
+ },
+
+ load_failed: function() {
+ Y.log('Load failed.', 'debug', 'moodle-mod_quiz-quizquestionbank');
+ },
+
+ link_clicked: function(e) {
+ if (e.currentTarget.ancestor(CSS.ADDTOQUIZCONTAINER)) {
+ // These links need to work like normal, after we modify the URL.
+ e.currentTarget.set('href', e.currentTarget.get('href') + '&addonpage=' + this.addonpage);
+ return;
+ }
+ e.preventDefault();
+ this.load_content(e.currentTarget.get('search'));
+ },
+
+ options_changed: function(e) {
+ e.preventDefault();
+ this.load_content('?' + Y.IO.stringify(e.currentTarget.get('form')));
+ }
+});
+
+M.mod_quiz = M.mod_quiz || {};
+M.mod_quiz.quizquestionbank = M.mod_quiz.quizquestionbank || {};
+M.mod_quiz.quizquestionbank.init = function() {
+ return new POPUP();
+};
diff --git a/mod/quiz/yui/src/quizquestionbank/meta/quizquestionbank.json b/mod/quiz/yui/src/quizquestionbank/meta/quizquestionbank.json
new file mode 100644
index 00000000000..5bb0c64b609
--- /dev/null
+++ b/mod/quiz/yui/src/quizquestionbank/meta/quizquestionbank.json
@@ -0,0 +1,14 @@
+{
+ "moodle-mod_quiz-quizquestionbank": {
+ "requires": [
+ "base",
+ "event",
+ "node",
+ "io",
+ "io-form",
+ "yui-later",
+ "moodle-question-qbankmanager"
+ "moodle-core-notification-dialogue"
+ ]
+ }
+}
diff --git a/mod/quiz/yui/src/randomquestion/build.json b/mod/quiz/yui/src/randomquestion/build.json
new file mode 100644
index 00000000000..279667b23ec
--- /dev/null
+++ b/mod/quiz/yui/src/randomquestion/build.json
@@ -0,0 +1,10 @@
+{
+ "name": "moodle-mod_quiz-randomquestion",
+ "builds": {
+ "moodle-mod_quiz-randomquestion": {
+ "jsfiles": [
+ "randomquestion.js"
+ ]
+ }
+ }
+}
diff --git a/mod/quiz/yui/src/randomquestion/js/randomquestion.js b/mod/quiz/yui/src/randomquestion/js/randomquestion.js
new file mode 100644
index 00000000000..65faedc1d26
--- /dev/null
+++ b/mod/quiz/yui/src/randomquestion/js/randomquestion.js
@@ -0,0 +1,80 @@
+// 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 .
+
+
+/**
+ * Add a random question functionality for a popup in quiz editing page.
+ *
+ * @package mod_quiz
+ * @copyright 2014 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+var CSS = {
+ RANDOMQUESTIONFORM: 'div.randomquestionformforpopup',
+ PAGEHIDDENINPUT: 'input#rform_qpage',
+ RANDOMQUESTIONLINKS: 'ul.menu a.addarandomquestion'
+};
+
+var PARAMS = {
+ PAGE: 'addonpage',
+ HEADER: 'header',
+ FORM: 'form'
+};
+
+var POPUP = function() {
+ POPUP.superclass.constructor.apply(this, arguments);
+};
+
+Y.extend(POPUP, Y.Base, {
+
+ dialogue: function(header) {
+ // Create a dialogue on the page and hide it.
+ config = {
+ headerContent : header,
+ bodyContent : Y.one(CSS.RANDOMQUESTIONFORM),
+ draggable : true,
+ modal : true,
+ zIndex : 1000,
+ centered: false,
+ width: 'auto',
+ visible: false,
+ postmethod: 'form',
+ footerContent: null
+ };
+ var popup = { dialog: null };
+ popup.dialog = new M.core.dialogue(config);
+ popup.dialog.show();
+ },
+
+ initializer : function() {
+ Y.one('body').delegate('click', this.display_dialogue, CSS.RANDOMQUESTIONLINKS, this);
+ },
+
+ display_dialogue : function (e) {
+ e.preventDefault();
+
+ Y.one(CSS.RANDOMQUESTIONFORM + ' ' + CSS.PAGEHIDDENINPUT).set('value',
+ e.currentTarget.getData(PARAMS.PAGE));
+
+ this.dialogue(e.currentTarget.getData(PARAMS.HEADER));
+ }
+});
+
+M.mod_quiz = M.mod_quiz || {};
+M.mod_quiz.randomquestion = M.mod_quiz.randomquestion || {};
+M.mod_quiz.randomquestion.init = function() {
+ return new POPUP();
+};
diff --git a/mod/quiz/yui/src/randomquestion/meta/randomquestion.json b/mod/quiz/yui/src/randomquestion/meta/randomquestion.json
new file mode 100644
index 00000000000..a15a4393a84
--- /dev/null
+++ b/mod/quiz/yui/src/randomquestion/meta/randomquestion.json
@@ -0,0 +1,11 @@
+{
+ "moodle-mod_quiz-randomquestion": {
+ "requires": [
+ "base",
+ "event",
+ "node",
+ "io",
+ "moodle-core-notification-dialogue"
+ ]
+ }
+}
diff --git a/mod/quiz/yui/src/repaginate/build.json b/mod/quiz/yui/src/repaginate/build.json
new file mode 100644
index 00000000000..fc6e1f7d235
--- /dev/null
+++ b/mod/quiz/yui/src/repaginate/build.json
@@ -0,0 +1,10 @@
+{
+ "name": "moodle-mod_quiz-repaginate",
+ "builds": {
+ "moodle-mod_quiz-repaginate": {
+ "jsfiles": [
+ "repaginate.js"
+ ]
+ }
+ }
+}
diff --git a/mod/quiz/yui/src/repaginate/js/repaginate.js b/mod/quiz/yui/src/repaginate/js/repaginate.js
new file mode 100644
index 00000000000..2a52ba37ba5
--- /dev/null
+++ b/mod/quiz/yui/src/repaginate/js/repaginate.js
@@ -0,0 +1,81 @@
+// 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 .
+
+
+/**
+ * Repaginate functionality for a popup in quiz editing page.
+ *
+ * @package mod_quiz
+ * @copyright 2014 The Open University
+ * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
+ */
+
+var CSS = {
+ REPAGINATECONTAINERCLASS: '.rpcontainerclass',
+ REPAGINATECOMMAND: '#repaginatecommand'
+};
+
+var PARAMS = {
+ CMID: 'cmid',
+ HEADER: 'header',
+ FORM: 'form'
+};
+
+var POPUP = function() {
+ POPUP.superclass.constructor.apply(this, arguments);
+};
+
+Y.extend(POPUP, Y.Base, {
+ header: null,
+ body: null,
+
+ initializer : function() {
+ rpcontainerclass = Y.one(CSS.REPAGINATECONTAINERCLASS);
+
+ // Set popup header and body.
+ this.header = rpcontainerclass.getAttribute(PARAMS.HEADER);
+ this.body = rpcontainerclass.getAttribute(PARAMS.FORM);
+ Y.one(CSS.REPAGINATECOMMAND).on('click', this.display_dialog, this);
+ },
+
+ display_dialog : function (e) {
+ e.preventDefault();
+
+ // Configure the popup.
+ var config = {
+ headerContent : this.header,
+ bodyContent : this.body,
+ draggable : true,
+ modal : true,
+ zIndex : 1000,
+ context: [CSS.REPAGINATECOMMAND, 'tr', 'br', ['beforeShow']],
+ centered: false,
+ width: '30em',
+ visible: false,
+ postmethod: 'form',
+ footerContent: null
+ };
+
+ var popup = { dialog: null };
+ popup.dialog = new M.core.dialogue(config);
+ popup.dialog.show();
+ }
+});
+
+M.mod_quiz = M.mod_quiz || {};
+M.mod_quiz.repaginate = M.mod_quiz.repaginate || {};
+M.mod_quiz.repaginate.init = function() {
+ return new POPUP();
+};
diff --git a/mod/quiz/yui/src/repaginate/meta/repaginate.json b/mod/quiz/yui/src/repaginate/meta/repaginate.json
new file mode 100644
index 00000000000..c97b768fab6
--- /dev/null
+++ b/mod/quiz/yui/src/repaginate/meta/repaginate.json
@@ -0,0 +1,11 @@
+{
+ "moodle-mod_quiz-repaginate": {
+ "requires": [
+ "base",
+ "event",
+ "node",
+ "io",
+ "moodle-core-notification-dialogue"
+ ]
+ }
+}
diff --git a/mod/quiz/yui/src/toolboxes/build.json b/mod/quiz/yui/src/toolboxes/build.json
new file mode 100644
index 00000000000..3adc6198383
--- /dev/null
+++ b/mod/quiz/yui/src/toolboxes/build.json
@@ -0,0 +1,12 @@
+{
+ "name": "moodle-mod_quiz-toolboxes",
+ "builds": {
+ "moodle-mod_quiz-toolboxes": {
+ "jsfiles": [
+ "toolbox.js",
+ "resource.js",
+ "section.js"
+ ]
+ }
+ }
+}
diff --git a/mod/quiz/yui/src/toolboxes/js/resource.js b/mod/quiz/yui/src/toolboxes/js/resource.js
new file mode 100644
index 00000000000..ba27dfdde94
--- /dev/null
+++ b/mod/quiz/yui/src/toolboxes/js/resource.js
@@ -0,0 +1,461 @@
+/**
+ * Resource and activity toolbox class.
+ *
+ * This class is responsible for managing AJAX interactions with activities and resources
+ * when viewing a quiz in editing mode.
+ *
+ * @module mod_quiz-resource-toolbox
+ * @namespace M.mod_quiz.resource_toolbox
+ */
+
+/**
+ * Resource and activity toolbox class.
+ *
+ * This is a class extending TOOLBOX containing code specific to resources
+ *
+ * This class is responsible for managing AJAX interactions with activities and resources
+ * when viewing a quiz in editing mode.
+ *
+ * @class resources
+ * @constructor
+ * @extends M.course.toolboxes.toolbox
+ */
+var RESOURCETOOLBOX = function() {
+ RESOURCETOOLBOX.superclass.constructor.apply(this, arguments);
+};
+
+Y.extend(RESOURCETOOLBOX, TOOLBOX, {
+ /**
+ * An Array of events added when editing a max mark field.
+ * These should all be detached when editing is complete.
+ *
+ * @property editmaxmarkevents
+ * @protected
+ * @type Array
+ * @protected
+ */
+ editmaxmarkevents: [],
+
+ /**
+ *
+ */
+ NODE_PAGE: 1,
+ NODE_SLOT: 2,
+ NODE_JOIN: 3,
+
+ /**
+ * Initialize the resource toolbox
+ *
+ * For each activity the commands are updated and a reference to the activity is attached.
+ * This way it doesn't matter where the commands are going to called from they have a reference to the
+ * activity that they relate to.
+ * This is essential as some of the actions are displayed in an actionmenu which removes them from the
+ * page flow.
+ *
+ * This function also creates a single event delegate to manage all AJAX actions for all activities on
+ * the page.
+ *
+ * @method initializer
+ * @protected
+ */
+ initializer: function() {
+ M.mod_quiz.quizbase.register_module(this);
+ BODY.delegate('key', this.handle_data_action, 'down:enter', SELECTOR.ACTIVITYACTION, this);
+ Y.delegate('click', this.handle_data_action, BODY, SELECTOR.ACTIVITYACTION, this);
+ },
+
+ /**
+ * Handles the delegation event. When this is fired someone has triggered an action.
+ *
+ * Note not all actions will result in an AJAX enhancement.
+ *
+ * @protected
+ * @method handle_data_action
+ * @param {EventFacade} ev The event that was triggered.
+ * @returns {boolean}
+ */
+ handle_data_action: function(ev) {
+ // We need to get the anchor element that triggered this event.
+ var node = ev.target;
+ if (!node.test('a')) {
+ node = node.ancestor(SELECTOR.ACTIVITYACTION);
+ }
+
+ // From the anchor we can get both the activity (added during initialisation) and the action being
+ // performed (added by the UI as a data attribute).
+ var action = node.getData('action'),
+ activity = node.ancestor(SELECTOR.ACTIVITYLI);
+
+ if (!node.test('a') || !action || !activity) {
+ // It wasn't a valid action node.
+ return;
+ }
+
+ // Switch based upon the action and do the desired thing.
+ switch (action) {
+ case 'editmaxmark':
+ // The user wishes to edit the maxmark of the resource.
+ this.edit_maxmark(ev, node, activity, action);
+ break;
+ case 'delete':
+ // The user is deleting the activity.
+ this.delete_with_confirmation(ev, node, activity, action);
+ break;
+ case 'linkpage':
+ case 'unlinkpage':
+ // The user is linking or unlinking pages.
+ this.link_page(ev, node, activity, action);
+ break;
+ default:
+ // Nothing to do here!
+ break;
+ }
+ },
+
+ /**
+ * Add a loading icon to the specified activity.
+ *
+ * The icon is added within the action area.
+ *
+ * @method add_spinner
+ * @param {Node} activity The activity to add a loading icon to
+ * @return {Node|null} The newly created icon, or null if the action area was not found.
+ */
+ add_spinner: function(activity) {
+ var actionarea = activity.one(SELECTOR.ACTIONAREA);
+ if (actionarea) {
+ return M.util.add_spinner(Y, actionarea);
+ }
+ return null;
+ },
+
+ /**
+ * Deletes the given activity or resource after confirmation.
+ *
+ * @protected
+ * @method delete_with_confirmation
+ * @param {EventFacade} ev The event that was fired.
+ * @param {Node} button The button that triggered this action.
+ * @param {Node} activity The activity node that this action will be performed on.
+ * @chainable
+ */
+ delete_with_confirmation: function(ev, button, activity) {
+ // Prevent the default button action
+ ev.preventDefault();
+
+ // Get the element we're working on
+ var element = activity,
+ // Create confirm string (different if element has or does not have name)
+ confirmstring = '',
+ qtypename = M.util.get_string('pluginname',
+ 'qtype_' + element.getAttribute('class').match(/qtype_([^\s]*)/)[1]);
+ confirmstring = M.util.get_string('confirmremovequestion', 'quiz', qtypename);
+
+ // Create the confirmation dialogue.
+ var confirm = new M.core.confirm({
+ question: confirmstring,
+ modal: true
+ });
+
+ // If it is confirmed.
+ confirm.on('complete-yes', function() {
+
+ // Actually remove the element.
+ element.remove();
+ Y.Moodle.mod_quiz.util.slot.reorder_slots();
+ var data = {
+ 'class': 'resource',
+ 'action': 'DELETE',
+ 'id': Y.Moodle.mod_quiz.util.slot.getId(element)
+ };
+ this.send_request(data);
+ if (M.core.actionmenu && M.core.actionmenu.instance) {
+ M.core.actionmenu.instance.hideMenu();
+ }
+ window.location.reload(true);
+
+ }, this);
+
+ return this;
+ },
+
+
+ /**
+ * Edit the maxmark for the resource
+ *
+ * @protected
+ * @method edit_maxmark
+ * @param {EventFacade} ev The event that was fired.
+ * @param {Node} button The button that triggered this action.
+ * @param {Node} activity The activity node that this action will be performed on.
+ * @param {String} action The action that has been requested.
+ * @return Boolean
+ */
+ edit_maxmark : function(ev, button, activity) {
+ // Get the element we're working on
+ var activityid = Y.Moodle.mod_quiz.util.slot.getId(activity),
+ instancemaxmark = activity.one(SELECTOR.INSTANCEMAXMARK),
+ instance = activity.one(SELECTOR.ACTIVITYINSTANCE),
+ currentmaxmark = instancemaxmark.get('firstChild'),
+ oldmaxmark = currentmaxmark.get('data'),
+ maxmarktext = oldmaxmark,
+ thisevent,
+ anchor = instancemaxmark,// Grab the anchor so that we can swap it with the edit form.
+ data = {
+ 'class' : 'resource',
+ 'field' : 'getmaxmark',
+ 'id' : activityid
+ };
+
+ // Prevent the default actions.
+ ev.preventDefault();
+
+ this.send_request(data, null, function(response) {
+ if (M.core.actionmenu && M.core.actionmenu.instance) {
+ M.core.actionmenu.instance.hideMenu();
+ }
+
+ // Try to retrieve the existing string from the server
+ if (response.instancemaxmark) {
+ maxmarktext = response.instancemaxmark;
+ }
+
+ // Create the editor and submit button
+ var editform = Y.Node.create('
');
+ var editinstructions = Y.Node.create('')
+ .set('innerHTML', M.util.get_string('edittitleinstructions', 'moodle'));
+ var editor = Y.Node.create('').setAttrs({
+ 'value' : maxmarktext,
+ 'autocomplete' : 'off',
+ 'aria-describedby' : 'id_editinstructions',
+ 'maxLength' : '12',
+ 'size' : parseInt(this.get('config').questiondecimalpoints, 10) + 2
+ });
+
+ // Clear the existing content and put the editor in
+ editform.appendChild(editor);
+ editform.setData('anchor', anchor);
+ instance.insert(editinstructions, 'before');
+ anchor.replace(editform);
+
+ // Force the editing instruction to match the mod-indent position.
+ var padside = 'left';
+ if (right_to_left()) {
+ padside = 'right';
+ }
+
+ // We hide various components whilst editing:
+ activity.addClass(CSS.EDITINGMAXMARK);
+
+ // Focus and select the editor text
+ editor.focus().select();
+
+ // Cancel the edit if we lose focus or the escape key is pressed.
+ thisevent = editor.on('blur', this.edit_maxmark_cancel, this, activity, false);
+ this.editmaxmarkevents.push(thisevent);
+ thisevent = editor.on('key', this.edit_maxmark_cancel, 'esc', this, activity, true);
+ this.editmaxmarkevents.push(thisevent);
+
+ // Handle form submission.
+ thisevent = editform.on('submit', this.edit_maxmark_submit, this, activity, oldmaxmark);
+ this.editmaxmarkevents.push(thisevent);
+ });
+ },
+
+ /**
+ * Handles the submit event when editing the activity or resources maxmark.
+ *
+ * @protected
+ * @method edit_maxmark_submit
+ * @param {EventFacade} ev The event that triggered this.
+ * @param {Node} activity The activity whose maxmark we are altering.
+ * @param {String} originalmaxmark The original maxmark the activity or resource had.
+ */
+ edit_maxmark_submit : function(ev, activity, originalmaxmark) {
+ // We don't actually want to submit anything
+ ev.preventDefault();
+ var newmaxmark = Y.Lang.trim(activity.one(SELECTOR.ACTIVITYFORM + ' ' + SELECTOR.ACTIVITYMAXMARK).get('value'));
+ var spinner = this.add_spinner(activity);
+ this.edit_maxmark_clear(activity);
+ activity.one(SELECTOR.INSTANCEMAXMARK).setContent(newmaxmark);
+ if (newmaxmark !== null && newmaxmark !== "" && newmaxmark !== originalmaxmark) {
+ var data = {
+ 'class' : 'resource',
+ 'field' : 'updatemaxmark',
+ 'maxmark' : newmaxmark,
+ 'id' : Y.Moodle.mod_quiz.util.slot.getId(activity)
+ };
+ this.send_request(data, spinner, function(response) {
+ if (response.instancemaxmark) {
+ activity.one(SELECTOR.INSTANCEMAXMARK).setContent(response.instancemaxmark);
+ }
+ });
+ }
+ },
+
+ /**
+ * Handles the cancel event when editing the activity or resources maxmark.
+ *
+ * @protected
+ * @method edit_maxmark_cancel
+ * @param {EventFacade} ev The event that triggered this.
+ * @param {Node} activity The activity whose maxmark we are altering.
+ * @param {Boolean} preventdefault If true we should prevent the default action from occuring.
+ */
+ edit_maxmark_cancel : function(ev, activity, preventdefault) {
+ if (preventdefault) {
+ ev.preventDefault();
+ }
+ this.edit_maxmark_clear(activity);
+ },
+
+ /**
+ * Handles clearing the editing UI and returning things to the original state they were in.
+ *
+ * @protected
+ * @method edit_maxmark_clear
+ * @param {Node} activity The activity whose maxmark we were altering.
+ */
+ edit_maxmark_clear : function(activity) {
+ // Detach all listen events to prevent duplicate triggers
+ new Y.EventHandle(this.editmaxmarkevents).detach();
+
+ var editform = activity.one(SELECTOR.ACTIVITYFORM),
+ instructions = activity.one('#id_editinstructions');
+ if (editform) {
+ editform.replace(editform.getData('anchor'));
+ }
+ if (instructions) {
+ instructions.remove();
+ }
+
+ // Remove the editing class again to revert the display.
+ activity.removeClass(CSS.EDITINGMAXMARK);
+
+ // Refocus the link which was clicked originally so the user can continue using keyboard nav.
+ Y.later(100, this, function() {
+ activity.one(SELECTOR.EDITMAXMARK).focus();
+ });
+
+ // This hack is to keep Behat happy until they release a version of
+ // MinkSelenium2Driver that fixes
+ // https://github.com/Behat/MinkSelenium2Driver/issues/80.
+ if (!Y.one('input[name=maxmark')) {
+ Y.one('body').append('');
+ }
+ },
+
+ /**
+ * Joins or separates the given slot with the page of the previous slot. Reorders the pages of
+ * the other slots
+ *
+ * @protected
+ * @method link_page
+ * @param {EventFacade} ev The event that was fired.
+ * @param {Node} button The button that triggered this action.
+ * @param {Node} activity The activity node that this action will be performed on.
+ * @chainable
+ */
+ link_page: function(ev, button, activity, action) {
+ // Prevent the default button action
+ ev.preventDefault();
+
+ activity = activity.next('li.activity.slot');
+ var spinner = this.add_spinner(activity),
+ slotid = 0;
+ var value = action === 'linkpage' ? 1:2;
+
+ var data = {
+ 'class': 'resource',
+ 'field': 'linkslottopage',
+ 'id': slotid,
+ 'value': value
+ };
+
+ slotid = Y.Moodle.mod_quiz.util.slot.getId(activity);
+ if (slotid) {
+ data.id = Number(slotid);
+ }
+ this.send_request(data, spinner, function(response) {
+ window.location.reload(true);
+// if (response.slots) {
+// this.repaginate_slots(response.slots);
+// }
+ });
+
+ return this;
+ },
+ repaginate_slots: function(slots) {
+ this.slots = slots;
+ var section = Y.one(SELECTOR.PAGECONTENT + ' ' + SELECTOR.SECTIONUL),
+ activities = section.all(SELECTOR.ACTIVITYLI);
+ activities.each(function(node) {
+
+ // What element is it? page/slot/link
+ // what is the current slot?
+ var type;
+ var slot;
+ if(node.hasClass(CSS.PAGE)){
+ type = this.NODE_PAGE;
+ slot = node.next(SELECTOR.SLOTLI);
+ } else if (node.hasClass(CSS.SLOT)){
+ type = this.NODE_SLOT;
+ slot = node;
+ } else if (node.hasClass(CSS.JOIN)){
+ type = this.NODE_JOIN;
+ slot = node.previous(SELECTOR.SLOTLI);
+ }
+
+ // getSlotnumber() Should be a method of util.slot
+ var slotnumber = Number(Y.Moodle.mod_quiz.util.slot.getNumber(slot));
+ if(!type){
+ // Nothing we can do.
+ return;
+ }
+
+ // Is it correct?
+ if(!this.slots.hasOwnProperty(slotnumber)){
+ // An error. We should handle this.
+ return;
+ }
+
+ var slotdata = this.slots[slotnumber];
+
+ if(type === this.NODE_PAGE){
+ // Get page number
+ var pagenumber = Y.Moodle.mod_quiz.util.page.getNumber(node);
+ // Is the page number correct?
+ if (slotdata.page === pagenumber) {
+ console.log('slotdata.page == pagenumber return');
+ return;
+ }
+
+ if (pagenumber < slotdata.page) {
+ // Remove page node.
+ node.remove();
+ }
+ else {
+ // Add page node.
+ console.log('pagenumber > slotdata.page update page number');
+ }
+
+ }
+ }, this);
+ },
+
+ NAME : 'mod_quiz-resource-toolbox',
+ ATTRS : {
+ courseid : {
+ 'value' : 0
+ },
+ quizid : {
+ 'value' : 0
+ }
+ }
+});
+
+M.mod_quiz.resource_toolbox = null;
+M.mod_quiz.init_resource_toolbox = function(config) {
+ M.mod_quiz.resource_toolbox = new RESOURCETOOLBOX(config);
+ return M.mod_quiz.resource_toolbox;
+};
diff --git a/mod/quiz/yui/src/toolboxes/js/section.js b/mod/quiz/yui/src/toolboxes/js/section.js
new file mode 100644
index 00000000000..19051ebd2c7
--- /dev/null
+++ b/mod/quiz/yui/src/toolboxes/js/section.js
@@ -0,0 +1,182 @@
+/**
+ * Resource and activity toolbox class.
+ *
+ * This class is responsible for managing AJAX interactions with activities and resources
+ * when viewing a course in editing mode.
+ *
+ * @module moodle-mod_quiz-toolboxes
+ * @namespace M.mod_quiz.toolboxes
+ */
+
+/**
+ * Section toolbox class.
+ *
+ * This class is responsible for managing AJAX interactions with sections
+ * when viewing a course in editing mode.
+ *
+ * @class section
+ * @constructor
+ * @extends M.mod_quiz.toolboxes.toolbox
+ */
+var SECTIONTOOLBOX = function() {
+ SECTIONTOOLBOX.superclass.constructor.apply(this, arguments);
+};
+
+Y.extend(SECTIONTOOLBOX, TOOLBOX, {
+ /**
+ * Initialize the section toolboxes module.
+ *
+ * Updates all span.commands with relevant handlers and other required changes.
+ *
+ * @method initializer
+ * @protected
+ */
+ initializer : function() {
+ M.mod_quiz.quizbase.register_module(this);
+
+ // Section Highlighting.
+ Y.delegate('click', this.toggle_highlight, SELECTOR.PAGECONTENT, SELECTOR.SECTIONLI + ' ' + SELECTOR.HIGHLIGHT, this);
+
+ // Section Visibility.
+ Y.delegate('click', this.toggle_hide_section, SELECTOR.PAGECONTENT, SELECTOR.SECTIONLI + ' ' + SELECTOR.SHOWHIDE, this);
+ },
+
+ toggle_hide_section : function(e) {
+ // Prevent the default button action.
+ e.preventDefault();
+
+ // Get the section we're working on.
+ var section = e.target.ancestor(M.mod_quiz.format.get_section_selector(Y)),
+ button = e.target.ancestor('a', true),
+ hideicon = button.one('img'),
+
+ // The value to submit
+ value,
+
+ // The text for strings and images. Also determines the icon to display.
+ action,
+ nextaction;
+
+ if (!section.hasClass(CSS.SECTIONHIDDENCLASS)) {
+ section.addClass(CSS.SECTIONHIDDENCLASS);
+ value = 0;
+ action = 'hide';
+ nextaction = 'show';
+ } else {
+ section.removeClass(CSS.SECTIONHIDDENCLASS);
+ value = 1;
+ action = 'show';
+ nextaction = 'hide';
+ }
+
+ var newstring = M.util.get_string(nextaction + 'fromothers', 'format_' + this.get('format'));
+ hideicon.setAttrs({
+ 'alt' : newstring,
+ 'src' : M.util.image_url('i/' + nextaction)
+ });
+ button.set('title', newstring);
+
+ // Change the highlight status
+ var data = {
+ 'class' : 'section',
+ 'field' : 'visible',
+ 'id' : Y.Moodle.core_course.util.section.getId(section.ancestor(M.mod_quiz.edit.get_section_wrapper(Y), true)),
+ 'value' : value
+ };
+
+ var lightbox = M.util.add_lightbox(Y, section);
+ lightbox.show();
+
+ this.send_request(data, lightbox, function(response) {
+ var activities = section.all(SELECTOR.ACTIVITYLI);
+ activities.each(function(node) {
+ var button;
+ if (node.one(SELECTOR.SHOW)) {
+ button = node.one(SELECTOR.SHOW);
+ } else {
+ button = node.one(SELECTOR.HIDE);
+ }
+ var activityid = Y.Moodle.mod_quiz.util.slot.getId(node);
+
+ // NOTE: resourcestotoggle is returned as a string instead
+ // of a Number so we must cast our activityid to a String.
+ if (Y.Array.indexOf(response.resourcestotoggle, "" + activityid) !== -1) {
+ M.mod_quiz.resource_toolbox.handle_resource_dim(button, node, action);
+ }
+ }, this);
+ });
+ },
+
+ /**
+ * Toggle highlighting the current section.
+ *
+ * @method toggle_highlight
+ * @param {EventFacade} e
+ */
+ toggle_highlight : function(e) {
+ // Prevent the default button action.
+ e.preventDefault();
+
+ // Get the section we're working on.
+ var section = e.target.ancestor(M.mod_quiz.edit.get_section_selector(Y));
+ var button = e.target.ancestor('a', true);
+ var buttonicon = button.one('img');
+
+ // Determine whether the marker is currently set.
+ var togglestatus = section.hasClass('current');
+ var value = 0;
+
+ // Set the current highlighted item text.
+ var old_string = M.util.get_string('markthistopic', 'moodle');
+ Y.one(SELECTOR.PAGECONTENT)
+ .all(M.mod_quiz.edit.get_section_selector(Y) + '.current ' + SELECTOR.HIGHLIGHT)
+ .set('title', old_string);
+ Y.one(SELECTOR.PAGECONTENT)
+ .all(M.mod_quiz.edit.get_section_selector(Y) + '.current ' + SELECTOR.HIGHLIGHT + ' img')
+ .set('alt', old_string)
+ .set('src', M.util.image_url('i/marker'));
+
+ // Remove the highlighting from all sections.
+ Y.one(SELECTOR.PAGECONTENT).all(M.mod_quiz.edit.get_section_selector(Y))
+ .removeClass('current');
+
+ // Then add it if required to the selected section.
+ if (!togglestatus) {
+ section.addClass('current');
+ value = Y.Moodle.core_course.util.section.getId(section.ancestor(M.mod_quiz.edit.get_section_wrapper(Y), true));
+ var new_string = M.util.get_string('markedthistopic', 'moodle');
+ button
+ .set('title', new_string);
+ buttonicon
+ .set('alt', new_string)
+ .set('src', M.util.image_url('i/marked'));
+ }
+
+ // Change the highlight status.
+ var data = {
+ 'class' : 'course',
+ 'field' : 'marker',
+ 'value' : value
+ };
+ var lightbox = M.util.add_lightbox(Y, section);
+ lightbox.show();
+ this.send_request(data, lightbox);
+ }
+}, {
+ NAME : 'mod_quiz-section-toolbox',
+ ATTRS : {
+ courseid : {
+ 'value' : 0
+ },
+ quizid : {
+ 'value' : 0
+ },
+ format : {
+ 'value' : 'topics'
+ }
+ }
+});
+
+M.mod_quiz.init_section_toolbox = function(config) {
+ return new SECTIONTOOLBOX(config);
+};
diff --git a/mod/quiz/yui/src/toolboxes/js/toolbox.js b/mod/quiz/yui/src/toolboxes/js/toolbox.js
new file mode 100644
index 00000000000..2e1eb753bbe
--- /dev/null
+++ b/mod/quiz/yui/src/toolboxes/js/toolbox.js
@@ -0,0 +1,208 @@
+/**
+ * Resource and activity toolbox class.
+ *
+ * This class is responsible for managing AJAX interactions with activities and resources
+ * when viewing a course in editing mode.
+ *
+ * @module moodle-course-toolboxes
+ * @namespace M.course.toolboxes
+ */
+
+// The CSS classes we use.
+ var CSS = {
+ ACTIVITYINSTANCE : 'activityinstance',
+ AVAILABILITYINFODIV : 'div.availabilityinfo',
+ CONTENTWITHOUTLINK : 'contentwithoutlink',
+ CONDITIONALHIDDEN : 'conditionalhidden',
+ DIMCLASS : 'dimmed',
+ DIMMEDTEXT : 'dimmed_text',
+ EDITINSTRUCTIONS : 'editinstructions',
+ EDITINGMAXMARK: 'editor_displayed',
+ HIDE : 'hide',
+ JOIN: 'page_join',
+ MODINDENTCOUNT : 'mod-indent-',
+ MODINDENTHUGE : 'mod-indent-huge',
+ MODULEIDPREFIX : 'slot-',
+ PAGE: 'page',
+ SECTIONHIDDENCLASS : 'hidden',
+ SECTIONIDPREFIX : 'section-',
+ SLOT : 'slot',
+ SHOW : 'editing_show',
+ TITLEEDITOR : 'titleeditor'
+ },
+ // The CSS selectors we use.
+ SELECTOR = {
+ ACTIONAREA: '.actions',
+ ACTIONLINKTEXT : '.actionlinktext',
+ ACTIVITYACTION : 'a.cm-edit-action[data-action], a.editing_maxmark',
+ ACTIVITYFORM : 'span.instancemaxmarkcontainer form',
+ ACTIVITYICON : 'img.activityicon',
+ ACTIVITYINSTANCE : '.' + CSS.ACTIVITYINSTANCE,
+ ACTIVITYLINK: '.' + CSS.ACTIVITYINSTANCE + ' > a',
+ ACTIVITYLI : 'li.activity',
+ ACTIVITYMAXMARK : 'input[name=maxmark]',
+ COMMANDSPAN : '.commands',
+ CONTENTAFTERLINK : 'div.contentafterlink',
+ CONTENTWITHOUTLINK : 'div.contentwithoutlink',
+ EDITMAXMARK: 'a.editing_maxmark',
+ HIDE : 'a.editing_hide',
+ HIGHLIGHT : 'a.editing_highlight',
+ INSTANCENAME : 'span.instancename',
+ INSTANCEMAXMARK : 'span.instancemaxmark',
+ MODINDENTDIV : '.mod-indent',
+ MODINDENTOUTER : '.mod-indent-outer',
+ PAGECONTENT : 'div#page-content',
+ PAGELI : 'li.page',
+ SECTIONUL : 'ul.section',
+ SHOW : 'a.' + CSS.SHOW,
+ SHOWHIDE : 'a.editing_showhide',
+ SLOTLI : 'li.slot',
+ SUMMARKS : '.mod_quiz_summarks'
+ },
+ BODY = Y.one(document.body);
+
+// Setup the basic namespace.
+M.mod_quiz = M.mod_quiz || {};
+
+/**
+ * The toolbox class is a generic class which should never be directly
+ * instantiated. Please extend it instead.
+ *
+ * @class toolbox
+ * @constructor
+ * @protected
+ * @extends Base
+ */
+var TOOLBOX = function() {
+ TOOLBOX.superclass.constructor.apply(this, arguments);
+};
+
+Y.extend(TOOLBOX, Y.Base, {
+ /**
+ * Send a request using the REST API
+ *
+ * @method send_request
+ * @param {Object} data The data to submit with the AJAX request
+ * @param {Node} [statusspinner] A statusspinner which may contain a section loader
+ * @param {Function} success_callback The callback to use on success
+ * @param {Object} [optionalconfig] Any additional configuration to submit
+ * @chainable
+ */
+ send_request: function(data, statusspinner, success_callback, optionalconfig) {
+ // Default data structure
+ if (!data) {
+ data = {};
+ }
+ // Handle any variables which we must pass back through to
+ var pageparams = this.get('config').pageparams,
+ varname;
+ for (varname in pageparams) {
+ data[varname] = pageparams[varname];
+ }
+
+ data.sesskey = M.cfg.sesskey;
+ data.courseid = this.get('courseid');
+ data.quizid = this.get('quizid');
+
+ var uri = M.cfg.wwwroot + this.get('ajaxurl');
+
+ // Define the configuration to send with the request
+ var responsetext = [];
+ var config = {
+ method: 'POST',
+ data: data,
+ on: {
+ success: function(tid, response) {
+ try {
+ responsetext = Y.JSON.parse(response.responseText);
+ if (responsetext.error) {
+ new M.core.ajaxException(responsetext);
+ }
+ } catch (e) {}
+
+ // Run the callback if we have one.
+ if (responsetext.newsummarks) {
+ Y.one(SELECTOR.SUMMARKS).setHTML(responsetext.newsummarks);
+ }
+ if (success_callback) {
+ Y.bind(success_callback, this, responsetext)();
+ }
+
+ if (statusspinner) {
+ window.setTimeout(function() {
+ statusspinner.hide();
+ }, 400);
+ }
+ },
+ failure: function(tid, response) {
+ if (statusspinner) {
+ statusspinner.hide();
+ }
+ new M.core.ajaxException(response);
+ }
+ },
+ context: this
+ };
+
+ // Apply optional config
+ if (optionalconfig) {
+ for (varname in optionalconfig) {
+ config[varname] = optionalconfig[varname];
+ }
+ }
+
+ if (statusspinner) {
+ statusspinner.show();
+ }
+
+ // Send the request
+ Y.io(uri, config);
+ return this;
+ }
+},
+{
+ NAME: 'mod_quiz-toolbox',
+ ATTRS: {
+ /**
+ * The ID of the Moodle Course being edited.
+ *
+ * @attribute courseid
+ * @default 0
+ * @type Number
+ */
+ courseid: {
+ 'value': 0
+ },
+
+ /**
+ * The Moodle course format.
+ *
+ * @attribute format
+ * @default 'topics'
+ * @type String
+ */
+ quizid: {
+ 'value': 0
+ },
+ /**
+ * The URL to use when submitting requests.
+ * @attribute ajaxurl
+ * @default null
+ * @type String
+ */
+ ajaxurl: {
+ 'value': null
+ },
+ /**
+ * Any additional configuration passed when creating the instance.
+ *
+ * @attribute config
+ * @default {}
+ * @type Object
+ */
+ config: {
+ 'value': {}
+ }
+ }
+}
+);
diff --git a/mod/quiz/yui/src/toolboxes/meta/toolboxes.json b/mod/quiz/yui/src/toolboxes/meta/toolboxes.json
new file mode 100644
index 00000000000..02242bf1b8f
--- /dev/null
+++ b/mod/quiz/yui/src/toolboxes/meta/toolboxes.json
@@ -0,0 +1,14 @@
+{
+ "moodle-mod_quiz-toolboxes": {
+ "requires": [
+ "base",
+ "node",
+ "event",
+ "event-key",
+ "io",
+ "moodle-mod_quiz-quizbase",
+ "moodle-mod_quiz-util-slot",
+ "moodle-core-notification-ajaxexception"
+ ]
+ }
+}
diff --git a/mod/quiz/yui/src/util/build.json b/mod/quiz/yui/src/util/build.json
new file mode 100644
index 00000000000..007196d8af0
--- /dev/null
+++ b/mod/quiz/yui/src/util/build.json
@@ -0,0 +1,21 @@
+{
+ "name": "moodle-mod_quiz-util",
+ "builds": {
+ "moodle-mod_quiz-util-base": {
+ "jsfiles": [
+ "base.js"
+ ]
+ },
+ "moodle-mod_quiz-util-slot": {
+ "jsfiles": [
+ "slot.js"
+ ]
+ }
+ ,
+ "moodle-mod_quiz-util-page": {
+ "jsfiles": [
+ "page.js"
+ ]
+ }
+ }
+}
diff --git a/mod/quiz/yui/src/util/js/base.js b/mod/quiz/yui/src/util/js/base.js
new file mode 100644
index 00000000000..c5e3aa7d670
--- /dev/null
+++ b/mod/quiz/yui/src/util/js/base.js
@@ -0,0 +1,15 @@
+/**
+ * The Moodle.mod_quiz.util classes provide quiz-related utility functions.
+ *
+ * @module moodle-mod_quiz-util
+ * @main
+ */
+
+Y.namespace('Moodle.mod_quiz.util');
+
+/**
+ * A collection of general utility functions for use in quiz.
+ *
+ * @class Moodle.mod_quiz.util
+ * @static
+ */
diff --git a/mod/quiz/yui/src/util/js/page.js b/mod/quiz/yui/src/util/js/page.js
new file mode 100644
index 00000000000..39f49db5017
--- /dev/null
+++ b/mod/quiz/yui/src/util/js/page.js
@@ -0,0 +1,91 @@
+/**
+ * A collection of utility classes for use with pages.
+ *
+ * @module moodle-mod_quiz-util
+ * @submodule moodle-mod_quiz-util-page
+ */
+
+Y.namespace('Moodle.mod_quiz.util.page');
+
+/**
+ * A collection of utility classes for use with pages.
+ *
+ * @class Moodle.mod_quiz.util.page
+ * @static
+ */
+Y.Moodle.mod_quiz.util.page = {
+ CONSTANTS: {
+ PAGEIDPREFIX : 'page-',
+ PAGENUMBERPREFIX : 'Page '
+ },
+ SELECTORS: {
+ PAGE: 'li.page',
+ INSTANCENAME: '.instancename'
+ },
+
+ /**
+ * Retrieve the page item from one of it's child Nodes.
+ *
+ * @method getPageFromComponent
+ * @param pagecomponent {Node} The component Node.
+ * @return {Node|null} The Page Node.
+ */
+ getPageFromComponent: function(pagecomponent) {
+ return Y.one(pagecomponent).ancestor(this.SELECTORS.PAGE, true);
+ },
+
+ /**
+ * Determines the page ID for the provided page.
+ *
+ * @method getId
+ * @param page {Node} The page to find an ID for.
+ * @return {Number|false} The ID of the page in question or false if no ID was found.
+ */
+ getId: function(page) {
+ // We perform a simple substitution operation to get the ID.
+ var id = page.get('id').replace(
+ this.CONSTANTS.PAGEIDPREFIX, '');
+
+ // Attempt to validate the ID.
+ id = parseInt(id, 10);
+ if (typeof id === 'number' && isFinite(id)) {
+ return id;
+ }
+ return false;
+ },
+
+ /**
+ * Determines the page name for the provided page.
+ *
+ * @method getName
+ * @param page {Node} The page to find a name for.
+ * @return {string|false} The name of the page in question or false if no ID was found.
+ */
+ getName: function(page) {
+ var instance = page.one(this.SELECTORS.INSTANCENAME);
+ if (instance) {
+ return instance.get('firstChild').get('data');
+ }
+ return null;
+ },
+
+ /**
+ * Determines the page number for the provided page.
+ *
+ * @method getNumber
+ * @param page {Node} The page to find a number for.
+ * @return {Number|false} The number of the page in question or false if no number was found.
+ */
+ getNumber: function(page) {
+ // We perform a simple substitution operation to get the ID.
+ var number = page.get('text').replace(
+ this.CONSTANTS.PAGENUMBERPREFIX, '');
+
+ // Attempt to validate the ID.
+ number = parseInt(number, 10);
+ if (typeof number === 'number' && isFinite(number)) {
+ return number;
+ }
+ return false;
+ }
+};
diff --git a/mod/quiz/yui/src/util/js/slot.js b/mod/quiz/yui/src/util/js/slot.js
new file mode 100644
index 00000000000..4fc216e3406
--- /dev/null
+++ b/mod/quiz/yui/src/util/js/slot.js
@@ -0,0 +1,145 @@
+/**
+ * A collection of utility classes for use with slots.
+ *
+ * @module moodle-mod_quiz-util
+ * @submodule moodle-mod_quiz-util-slot
+ */
+
+Y.namespace('Moodle.mod_quiz.util.slot');
+
+/**
+ * A collection of utility classes for use with slots.
+ *
+ * @class Moodle.mod_quiz.util.slot
+ * @static
+ */
+Y.Moodle.mod_quiz.util.slot = {
+ CONSTANTS: {
+ SLOTIDPREFIX : 'slot-'
+ },
+ SELECTORS: {
+ SLOT: 'li.slot',
+ INSTANCENAME: '.instancename',
+ NUMBER: 'span.slotnumber',
+ PAGECONTENT : 'div#page-content',
+ SECTIONUL : 'ul.section'
+ },
+
+ /**
+ * Retrieve the slot item from one of it's child Nodes.
+ *
+ * @method getSlotFromComponent
+ * @param slotcomponent {Node} The component Node.
+ * @return {Node|null} The Slot Node.
+ */
+ getSlotFromComponent: function(slotcomponent) {
+ return Y.one(slotcomponent).ancestor(this.SELECTORS.SLOT, true);
+ },
+
+ /**
+ * Determines the slot ID for the provided slot.
+ *
+ * @method getId
+ * @param slot {Node} The slot to find an ID for.
+ * @return {Number|false} The ID of the slot in question or false if no ID was found.
+ */
+ getId: function(slot) {
+ // We perform a simple substitution operation to get the ID.
+ var id = slot.get('id').replace(
+ this.CONSTANTS.SLOTIDPREFIX, '');
+
+ // Attempt to validate the ID.
+ id = parseInt(id, 10);
+ if (typeof id === 'number' && isFinite(id)) {
+ return id;
+ }
+ return false;
+ },
+
+ /**
+ * Determines the slot name for the provided slot.
+ *
+ * @method getName
+ * @param slot {Node} The slot to find a name for.
+ * @return {string|false} The name of the slot in question or false if no ID was found.
+ */
+ getName: function(slot) {
+ var instance = slot.one(this.SELECTORS.INSTANCENAME);
+ if (instance) {
+ return instance.get('firstChild').get('data');
+ }
+ return null;
+ },
+
+ /**
+ * Determines the slot number for the provided slot.
+ *
+ * @method getNumber
+ * @param slot {Node} The slot to find the number for.
+ * @return {Number|false} The number of the slot in question or false if no number was found.
+ */
+ getNumber: function(slot) {
+ var number = slot.one(this.SELECTORS.NUMBER).get('text');
+ // Attempt to validate the ID.
+ number = parseInt(number, 10);
+ if (typeof number === 'number' && isFinite(number)) {
+ return number;
+ }
+ return false;
+ },
+
+ /**
+ * Updates the slot number for the provided slot.
+ *
+ * @method setNumber
+ * @param slot {Node} The slot to update the number for.
+ * @return void
+ */
+ setNumber: function(slot, number) {
+ slot.one(this.SELECTORS.NUMBER).set('text', number);
+ },
+
+ /**
+ * Returns a list of all slot elements on the page.
+ *
+ * @method getSlots
+ * @return {node[]} An array containing slot nodes.
+ */
+ getSlots: function() {
+ return Y.all(this.SELECTORS.PAGECONTENT + ' ' + this.SELECTORS.SECTIONUL + ' ' + this.SELECTORS.SLOT);
+ },
+
+ /**
+ * Returns the previous slot to the give slot.
+ *
+ * @method getPrevious
+ * @param slot Slot node
+ * @return {node|false} The previous slot node or false.
+ */
+ getPrevious: function(slot) {
+ return slot.previous(this.SELECTORS.SLOT);
+ },
+
+ /**
+ * Reset the order of the numbers given to each slot.
+ *
+ * @method reorder_slots
+ * @return void
+ */
+ reorder_slots: function() {
+ // Get list of slot nodes.
+ var slots = this.getSlots();
+ // Loop through slots incrementing the number each time.
+ slots.each(function(slot) {
+ var previousSlot = this.getPrevious(slot),
+ previousslotnumber = 0;
+ if(previousSlot){
+ previousslotnumber = this.getNumber(previousSlot);
+ }
+
+ // Set slot number.
+ this.setNumber(slot, previousslotnumber + 1);
+ }, this);
+
+ }
+};
diff --git a/mod/quiz/yui/src/util/meta/util.json b/mod/quiz/yui/src/util/meta/util.json
new file mode 100644
index 00000000000..4dca54b340b
--- /dev/null
+++ b/mod/quiz/yui/src/util/meta/util.json
@@ -0,0 +1,26 @@
+{
+ "moodle-mod_quiz-util": {
+ "requires": [
+ "node"
+ ],
+ "use": [
+ "moodle-mod_quiz-util-base"
+ ],
+ "submodules": {
+ "moodle-mod_quiz-util-base": {
+ },
+ "moodle-mod_quiz-util-slot": {
+ "requires": [
+ "node",
+ "moodle-mod_quiz-util-base"
+ ]
+ },
+ "moodle-mod_quiz-util-page": {
+ "requires": [
+ "node",
+ "moodle-mod_quiz-util-base"
+ ]
+ }
+ }
+ }
+}
diff --git a/pix/e/insert_page_break.png b/pix/e/insert_page_break.png
new file mode 100644
index 00000000000..d59d79341b1
Binary files /dev/null and b/pix/e/insert_page_break.png differ
diff --git a/pix/e/insert_page_break.svg b/pix/e/insert_page_break.svg
new file mode 100644
index 00000000000..dfa1dafac73
--- /dev/null
+++ b/pix/e/insert_page_break.svg
@@ -0,0 +1,161 @@
+
+
+
+
diff --git a/pix/e/remove_page_break.png b/pix/e/remove_page_break.png
new file mode 100644
index 00000000000..a87dc1e0ba0
Binary files /dev/null and b/pix/e/remove_page_break.png differ
diff --git a/pix/e/remove_page_break.svg b/pix/e/remove_page_break.svg
new file mode 100644
index 00000000000..b7ae7e7df81
--- /dev/null
+++ b/pix/e/remove_page_break.svg
@@ -0,0 +1,159 @@
+
+
+
+
diff --git a/question/addquestion.php b/question/addquestion.php
index 7cff336b73a..6c32374ea58 100644
--- a/question/addquestion.php
+++ b/question/addquestion.php
@@ -93,7 +93,7 @@ if ($cm !== null) {
// Display a form to choose the question type.
echo $OUTPUT->notification(get_string('youmustselectaqtype', 'question'));
echo $OUTPUT->box_start('generalbox boxwidthnormal boxaligncenter', 'chooseqtypebox');
-print_choose_qtype_to_add_form($hiddenparams, null, false);
+echo print_choose_qtype_to_add_form($hiddenparams, null, false);
echo $OUTPUT->box_end();
echo $OUTPUT->footer();
diff --git a/question/classes/bank/checkbox_column.php b/question/classes/bank/checkbox_column.php
index 9ba0ac35e0d..289aaba5e02 100644
--- a/question/classes/bank/checkbox_column.php
+++ b/question/classes/bank/checkbox_column.php
@@ -24,7 +24,6 @@ namespace core_question\bank;
*/
class checkbox_column extends column_base {
protected $strselect;
- protected $firstrow = true;
public function init() {
$this->strselect = get_string('select');
@@ -39,19 +38,17 @@ class checkbox_column extends column_base {
}
protected function get_title_tip() {
+ global $PAGE;
+ $PAGE->requires->strings_for_js(array('selectall', 'deselectall'), 'moodle');
+ $PAGE->requires->yui_module('moodle-question-qbankmanager', 'M.question.qbankmanager.init');
return get_string('selectquestionsforbulk', 'question');
+
}
protected function display_content($question, $rowclasses) {
global $PAGE;
echo '';
- if ($this->firstrow) {
- $PAGE->requires->strings_for_js(array('selectall', 'deselectall'), 'moodle');
- $PAGE->requires->yui_module('moodle-question-qbankmanager', 'M.question.qbankmanager.init',
- array('checkq' . $question->id));
- $this->firstrow = false;
- }
}
public function get_required_fields() {
diff --git a/question/classes/bank/view.php b/question/classes/bank/view.php
index 579bd314537..688ead03497 100644
--- a/question/classes/bank/view.php
+++ b/question/classes/bank/view.php
@@ -666,20 +666,13 @@ class view {
$category = $this->get_current_category($categoryandcontext);
- $cmoptions = new \stdClass();
- $cmoptions->hasattempts = !empty($this->quizhasattempts);
-
$strselectall = get_string('selectall');
$strselectnone = get_string('deselectall');
- $strdelete = get_string('delete');
list($categoryid, $contextid) = explode(',', $categoryandcontext);
$catcontext = \context::instance_by_id($contextid);
$canadd = has_capability('moodle/question:add', $catcontext);
- $caneditall = has_capability('moodle/question:editall', $catcontext);
- $canuseall = has_capability('moodle/question:useall', $catcontext);
- $canmoveall = has_capability('moodle/question:moveall', $catcontext);
$this->create_new_question_form($category, $canadd);
@@ -728,35 +721,40 @@ class view {
}
echo '
';
+ $this->display_bottom_controls($totalnumber, $recurse, $category, $catcontext, $addcontexts);
+
+ echo '';
+ echo "\n";
+ }
+
+ /**
+ * Display the controls at the bottom of the list of questions.
+ * @param int $totalnumber Total number of questions that might be shown (if it was not for paging).
+ * @param bool $recurse Whether to include subcategories.
+ * @param stdClass $category The question_category row from the database.
+ * @param context $catcontext The context of the category being displayed.
+ * @param array $addcontexts contexts where the user is allowed to add new questions.
+ */
+ protected function display_bottom_controls($totalnumber, $recurse, $category, \context $catcontext, array $addcontexts) {
+ $caneditall = has_capability('moodle/question:editall', $catcontext);
+ $canuseall = has_capability('moodle/question:useall', $catcontext);
+ $canmoveall = has_capability('moodle/question:moveall', $catcontext);
+
echo '