MDL-16106 report - fix old double-column trick causing problems in backup logs. Thank...
[moodle.git] / mod / hotpot / hotpot-full.js
blobf3bcc734fd1be000e06f3b745c9b574fa5bf79c7
1 <!--
2 // PLEASE NOTE that this version is more recent than the incorrectly
3 // numbered v6.1, dated 2003.11.17. From now on, version numbers will
4 // follow those of Hot Potatoes.
5 /* hot-potatoes.js (v6.0.4.0 - 2005.02.18)
6  * =======================================
7  * by Gordon Bateson, February 2003
8  * Copyright (c) 2003 Gordon Bateson. All Rights Reserved.
9  *
10  * You are hereby granted a royalty free license to use or modify this
11  * software provided that this copyright notice appears on all copies.
12  *
13  * This software is provided "AS IS" without a warranty of any kind.
14  *
15  * Documentation and downloads may be available from:
16  * http://www.kanazawa-gu.ac.jp/~gordon/research/hot-potatoes/
17  */
18 // This JavaScript library modifies the SendResults and StartUp functions
19 // used by hotpot v5 and v6, so that more (or less!) details about the
20 // student can be input, and more details of a quiz's questions and answers
21 // can be submitted to the server when the quiz is finished
22 // If the arrays below (Login, DB, JBC, ...) are set BEFORE calling this
23 // script, they will NOT be overwritten. Any array that is not set, will
24 // use the defaults below. This is useful if you want to use different
25 // settings for different quizzes.
26 // ************
27 //  Login Screen
28 // ************
29 if (window.Login==null) {
30         Login = new Array();
31         Login[0] = true;        // Show prompt for user name
32                                 // This can also be a string of user names ...
33                                 // Login[0] = "Guest,Peter,Paul,Mary,Webmaster";
34                                 // or an array of user names (and on-screen texts) (and passwords) ...
35                                 // Login[0] = new Array("Guest", "001,Peter,xxxx", "002,Paul,yyyy", "003,Mary,zzzz", "Webmaster");
36                                 // and can also be  written as ...
37                                 // Login[0] = new Array(
38                                 //      new Array("Guest"),
39                                 //      new Array("001", "Peter", "xxxx"),
40                                 //      new Array("002", "Paul", "yyyy"),
41                                 //      new Array("003", "Mary", "zzzz"),
42                                 //      new Array("Webmaster")
43                                 // );
44         Login[1] = true;        // Show prompt for student's UserID
45                                 // If there is no password prompt (i.e. Logon[3] is false), this value
46                                 // will be checked against the password information, if any, in Login[0]
47         Login[2] = false;       // Show prompt for student's email
48         Login[3] = false;       // Show prompt for quiz password, and check this value against
49                                 // the password information, if any, in Login[0]
50                                 // This can also be a string required to start the quiz ...
51                                 // Login[3] = "password";
52         Login[4] = true;        // Show prompt for the cookie expiry date
53                                 // If false, cookies expire at the end of the current session
54         Login[5] = "guest,webmaster"
55                                 // guest user names (case insensitive) ...
56                                 // Login[5] = "guest,webmaster";
57                                 // These users do NOT need to fill in other login fields
58                                 // and their quiz results are NOT added to the database
59         // the Login prompts and error messages
60         // are defined in the MSG array (see below)
62 // *********
63 //  Database (for use with BFormMail)
64 // *********
65 if (window.DB==null) {
66         DB = new Array();
67         DB[0] = true; // append form fields to database on server
68                         // If you are NOT using BFormMail's database feature,
69                         // set DB[0]=false, and you can then safely ignore DB[1 to 5]
70         DB[1] = "/home/gordon/public_html/cgi/hot-potatoes-data";
71                         // append_db folder path (no trailing slash)
72                         // Can be either an absolute path  e.g. "/home/gordon/public_html/cgi/hot-potatoes-data"
73                         // or a relative (to CGI bin) path  e.g. "hot-potatoes-data"
74         DB[2] = "hot-potatoes";
75                         // append_db file name (no extension)
76                         // If left blank, the quiz file name, without extension, will be used
77                         // i.e. each quiz will have its results stored in a different file.
78                         // If filled in, this file will store the results for ALL quizzes.
79                         // Database files and folders must be set up BEFORE running the quiz
80                         // must have appropriate access privileges (on Unix, use "chmod 666").
81         DB[3] = ""; // append_db extension (if left blank, ".txt" will be used)
82         DB[4] = ""; // db_fields (if left blank, ALL quiz fields will be sent)
83         DB[5] = ""; // db_delimiter (if left blank, tab will be used)
84         DB[6] = "REMOTE_ADDR,HTTP_USER_AGENT";
85                         // env_report ('REMOTE_ADDR','HTTP_USER_AGENT' and a few others)
86         // for a complete description of these fields are, see ...
87         // http://www.infosheet.com/stuff/BFormMail.readme
88         // Switches DB[7] and DB[8] force the settings in the ResultForm
89         // In v5 and v6 quizzes, these settings wil be override those in the original quiz
90         // If the quiz results are to be sent to an LMS (via the "store" form)
91         // then switches DB[7] and DB[8] are not used
92         DB[7] = '';     // URL of form processing script
93                         // e.g. http://www.kanazawa-gu.ac.jp/~gordon/cgi/bformmail.cgi
94         DB[8] = '';     // email address to which results should be sent
95                         // e.g. gordon@kanazawa-gu.ac.jp
97 // By default the quiz's question's scores will be returned.
98 // If you want more detailed information, set the flags below:
99 // ********
100 //  JBC
101 // ********
102 if (window.JBC==null) {
103         JBC = new Array();
104         JBC[0] = true;  // show separator line between answers on email
105         JBC[1] = true;  // show number of attempts to answer question
106         JBC[2] = true;  // show question texts
107         JBC[3] = true;  // show right answer(s)
108         JBC[4] = true;  // show wrong answer(s)
109         JBC[5] = true;  // show ignored answer(s)
110         JBC[6] = false; // show answer as text (false) or number (true)
112 // JBC quizzes use the global variables 'I' and 'Status'
113 // I : an array of JBC_QUESTIONs (one for each question)
114 // JBC_QUESTION :
115 //      [0] : question text
116 //      [1] : array of JBC_ANSWERs (one for each answer)
117 //      [2] : single/multi flag
118 //              0 : single answer (using 'button')
119 //              1 : multiple answers (using 'checkbox')
120 // JBC_ANSWER :
121 //      [0] : answer text
122 //      [1] : answer feedback
123 //      [2] : correct answer flag
124 //              0 : this is NOT the correct answer
125 //              1 : this is the correct answer
126 // Status : an array of JBC_QUESTION_STATUSes
127 // JBC_QUESTION_STATUS:
128 //      [0] : correctly answered yet flag
129 //              0 : this question has NOT been correctly answered
130 //              1 : this question has been correctly answered
131 //      [1] : array of JBC_ANSWER_STATUSes (one for each answer)
132 //              '0' : initial value
133 //              'R' : single answer question was answered 'R'ight
134 //              'W' : single answer question was answered 'W'rong
135 //              'C' : multiple answer question's checkbox was 'C'hecked
136 //              'U' : multiple answer question's checkbox was 'U'nchecked
137 //      [2] : number of times this question has been wrongly answered
138 //      [3] : score (out of 1) for this question (maybe undefined on HP<5.5)
139 //              0 : not correct yet
140 //              0<[3]<1 : correct but only after [2] wrong attempts
141 //              1 : correct first time (bravo!)
142 //      N.B. score = (numberOfAnswers - numberofWrongTries) / numberOfAnswers
143 // ********
144 //  JCloze
145 // ********
146 if (window.JCloze==null) {
147         JCloze = new Array();
148         JCloze[0] = true;       // show separator line between answers on email
149         JCloze[1] = true;       // show student's correct answer
150         JCloze[2] = true;       // show other correct answer(s), if any
151         JCloze[3] = true;       // show wrong answer(s), if any (NOT available for v5)
152         JCloze[4] = false;      // show number of hints + checks (legacy field, replaced by [7]+[9])
153         JCloze[5] = false;      // show if clue was asked for or not (legacy field, replaced by [8])
154         JCloze[6] = true;       // show clue
155         JCloze[7] = true;       // show number of hints (=next letter requests)
156         JCloze[8] = true;       // show number of clues
157         JCloze[9] = true;       // show number of checks
159 // JCloze quizzes use the global variables 'I' and 'State'
160 // I : array of JCLOZE_ANSWERs
161 // JCLOZE_ANSWER :
162 //      [0] : (unused)
163 //      [1] : array of JCLOZE_ANSWER_TEXTs
164 //      [2] : clue for this answer
165 // JCLOZE_ANSWER_TEXT :
166 //      [0] : array (seems unnecessary, just the text would be enough?)
167 //              [0] : text of possible answer
168 // State : array of JCLOZE_ANSWER_STATEs
169 // JCLOZE_ANSWER_STATE (v5) :
170 //      [0] : clue asked for or not
171 //      [1] : number of hints (show next letter) and penalties ('check' an incorrect answer)
172 //      [2] : length of answer matched
173 //      [3] : score for this item
174 //      [4] : already answered correctly
175 //      [5] : answer entered in text box (right or not)
176 // JCLOZE_ANSWER_STATE (v6)
177 //      this.ClueGiven = false;
178 //      this.HintsAndChecks = 0;
179 //      this.MatchedAnswerLength = 0;
180 //      this.ItemScore = 0;
181 //      this.AnsweredCorrectly = false;
182 //      this.Guesses = new Array(); last guess is correct answer
183 // ********
184 //  JCross
185 // ********
186 if (window.JCross==null) {
187         JCross = new Array();
188         JCross[0] = true;       // show separator line between answers on email
189         JCross[1] = true;       // show number of penalties (hints or checks before complete)
190         JCross[2] = true;       // show number of letters
191         JCross[3] = true;       // show correct answers
192         JCross[4] = true;       // show clues
193         JCross[5] = true;       // show wrong answers
194         JCross[6] = true;       // show if clue was asked for or not
195         JCross[7] = true;       // show number of hints (=next letter requests)
196         JCross[8] = true;       // show number of checks
197         // there are no "ignored" answers for JCross quizzes
199 // JCross quizzes use the following global variables:
200 //      L : letters (of correct answers)
201 //      C : clue numbers (CL in v6)
202 //      G : guesses
203 // 'L', 'C' ('CL') and 'G' are all 2-dimensional arrays (rows x cols)
205 // v5 quizzes additionally use the following single-dimension arrays
206 //      A : clues for across (horizontal) words
207 //      D : clues for down (vertical) words
208 // N.B. form is only sent when all answers are correct so
209 // you can't find out what 'wrong' answers were entered
210 // ********
211 //  JMatch
212 // ********
213 if (window.JMatch==null) {
214         JMatch = new Array();
215         JMatch[0] = true;       // show separator line between answers on email
216         JMatch[1] = false;      // show number of penalties (= total number of checks)
217         JMatch[2] = true;       // show LHS texts (the question)
218         JMatch[3] = true;       // show correct answers
219         JMatch[4] = true;       // show wrong answers
220         JMatch[5] = true;       // show checks (per match) [empty or unchanged RHS are not counted]
221         // JMatch has no "clue" or "hint" buttons
222         // there cannot be any "ignored" answers
224 // v5 JMatch quizzes use the global variables 'I' and 'Status' (and 'RItems')
225 // v6 JMatch quizzes use only 'Status'
226 // v6+ JMatch quizzes use 'F' and 'D' (see below)
227 // I : an array of JMATCH_PAIRs (one for each pair)
228 // JMATCH_PAIR :
229 //      [0] : LHS text
230 //      [1] : RHS text
231 //      [2] : fixed (=not jumbled) flag
232 //              0 : not fixed
233 //              1 : fixed
234 //      [3] : index in drop down list selection
235 // Status : an array of JMATCH_PAIR_STATUSes
236 // JMATCH_PAIR_STATUS:
237 //      [0] : correctly matched yet flag
238 //              0 : this pair has NOT been correctly matched
239 //              1 : this pair has been correctly matched
240 //      [1] : number of times this item has been wrongly matched
241 //      v6 quizzes only
242 //      [2] : id of original SELECT element containing possible matches
243 //      Note that after matching, this SELECT is removed, so don't try looking for it :-)
244 // v6+ JMatch quizzes use the global variables 'F' and 'D'
245 // F : array of JMATCH_FIXED_ITEMs
246 // JMATCH_FIXED_ITEM:
247 //      [0] : text
248 //      [1] : tag
249 // D : array of JMATCH_DRAGGABLE_ITEMs
250 // JMATCH_DRAGGABLE_ITEM
251 //      [0] : text
252 //      [1] : tag of the F item to which it SHOULD be dragged
253 //      [2] : tag of the F item to which it was dragged (initally 0)
254 // N.B. form is only sent when all answers are correct so
255 // you can't find out what 'wrong' answers were entered
256 // ********
257 //  JMix
258 // ********
259 if (window.JMix==null) {
260         JMix = new Array();
261         JMix[0] = true;         // show separator line between answers on email
262         JMix[1] = false;        // show number of wrong guesses (replaced by JMix[5])
263         JMix[2] = true;         // show right answer
264         JMix[3] = true;         // show wrong answer, if any
265         JMix[4] = false;        // show answer as text (false) or number (true)
266         JMix[5] = true;         // show number of checks
267         JMix[6] = true;         // show number of hints (=show next word)
269 // JMix quizzes use the global variables
270 // 'Segments', 'GuessSequence' and 'Penalties'
271 // Segments : array of JMix_QUESTIONs
272 // JMix_QUESTION:
273 //      [0] : text
274 //      [1] : order in sequence
275 //      [2] : used flag
276 // GuessSequence : array of 'order in sequence' numbers
277 // Penalties : number of incorrect guesses
278 // ********
279 //  JQuiz
280 // ********
281 if (window.JQuiz==null) {
282         JQuiz = new Array();
283         JQuiz[0] = true;        // show separator line between answers on email
284         JQuiz[1] = true;        // show question text
285         JQuiz[2] = true;        // show student's correct answer(s)
286         JQuiz[3] = false;       // show wrong and ignored answer(s) (legacy field superceded by [8] & [9])
287         JQuiz[4] = true;        // show number of hints requested
288         JQuiz[5] = false;       // show number of checks of incorrect answers (legacy field superceded by [12])
289         // HP6 v6 quizzes only
290         JQuiz[6] = false;       // show answer value (false) or A,B,C... index (true)
291         JQuiz[7] = false;       // show all students answers
292         JQuiz[8] = true;        // show student's wrong answers
293         JQuiz[9] = true;        // show ignored answers (not relevant for multi-select questions)
294         JQuiz[10] = true;       // show score weightings
295         JQuiz[11] = true;       // show question type
296         JQuiz[12] = true;       // show number of checks (if true, then JQuiz[5] of will be ignored)
297         JQuiz[13] = true;       // show number of times ShowAnswer button was pressed (usually 0 or 1)
299 // v5 JQuiz quizzes use the global variables 'I' and 'Status'
300 // I : array of JQUIZ_ANSWERs
301 // JQUIZ_ANSWER :
302 //      [0] : question text
303 //      [1] : array of JQUIZ_ANSWER_TEXTs (one for each answer)
304 // JQUIZ_ANSWER_TEXT :
305 //      [0] : array (seems unnecessary, just the text would be enough?)
306 //              [0] : text of possible answer
307 // Status : array of JQUIZ_ANSWER_STATEs
308 // JQUIZ_ANSWER_STATE :
309 //      [0] : question done or not
310 //      [1] : number of wrong checks
311 //      [2] : number of hints asked for
312 //      [3] : student's answer
313 //      [4] : score for this question
314 // v6 JQuiz quizzes use the global variables 'I' and 'State'
315 // I : array of JQUIZ_QUESTIONs
316 // JQUIZ_QUESTION :
317 //      [0] : weighting
318 //      [1] : ?? (always set to '')
319 //      [2] : question type
320 //              '0'=multiple-choice, '1'=short-answer, '2'=hybrid, '3'=multi-select
321 //      [3] : array of JQUIZ_ANSWERSs (one for each possible answer)
322 // JQUIZ_ANSWER :
323 //      [0] : answer value
324 //      [1] : feedback text
325 //      [2] : correct answer flag (1=a correct answer, 0=a wrong answer)
326 //      [3] : weighted score (as percentage) if correct
327 //      [4] : flag (usually set to 1, but for hybrid answers that are not
328 //              to be included in multiple choice options, it is set to 0)
329 // State : array of JQUIZ_QUESTION_STATEs
330 // JQUIZ_QUESTION_STATE :
331 //      [0] : score (-1 shows not done yet)
332 //      [1] : array showing on which number try each JQUIZ_ANSWER was selected
333 //      [2] : number of attempts at this question
334 //      [3] : total of weighted scores of correct answers that were selected
335 //              i.e. each time a correct answer is selected,
336 //              its JQUIZ_ANSWER[3] weighting is added to this total
337 //              so when all the correct answers have been selected, this will be 100
338 //      [4] : penalties incurred for hints (score is set to zero if >= 1)
339 //      [5] :   - for multiple choice, short-answer and hybrid questions, this is a
340 //              comma-delimited list showing order in which answers were chosen
341 //              - for multi-select fields, this is bar-delimted ('|') list of settings
342 //              showing whether each checkbox was selected ('Y') on not ('N') when the
343 //              'Check' button was clicked. The final item in the list will be the
344 //              settings for the correct answer.
345 // N.B. JBC, JMatch(v5) and JQuiz(v5) all use global variables 'I' and 'Status'
346 //      JBC : I[0].length==3 && !window.RItems
347 //      JQuiz(v5) : I[0].length==2
348 //      JMatch(v5) : I[0].length==4 && window.RItems
349 // N.B. JCloze(v5+6) and JQuiz(v6) both use global variables 'I' and 'State'
350 //      JCloze (v5) : I[0].length==3 && State[0].Guesses==null
351 //      JCloze (v6) : I[0].length==3 && State[0].Guesses!=null
352 //      JQuiz  (v6) : I[0].length==4
353 // **********
354 //  Rhubarb
355 // **********
356 if (window.Rhubarb==null) {
357         Rhubarb = new Array();
358         Rhubarb[0] = true;  // show correct words (so far)
359         Rhubarb[1] = true;  // show correct words as count (true) or list (false)
360         Rhubarb[2] = true;  // show wrong words
361         Rhubarb[3] = false; // show wrong words as count (true) or list (false)
362         Rhubarb[4] = false; // show ignored words (not implemented yet)
363         Rhubarb[5] = true;  // show hints
365 // **********
366 //  Sequitur
367 // **********
368 if (window.Sequitur==null) {
369         Sequitur = new Array();
370         Sequitur[0] = true;  // show count of correct button clicks
371         Sequitur[1] = true;  // show count of wrong button clicks
373 // **********
374 //  Messages
375 // **********
376 if (window.MSG==null) {
377         MSG = new Array();
378         // Login prompts
379         MSG[0] = 'Name';
380         MSG[1] = 'ID';
381         MSG[2] = 'Email';
382         MSG[3] = 'Password';
383         MSG[4] = 'Cookies';
384         // Login buttons
385         MSG[5] = 'Start the Quiz';
386         MSG[6] = 'Cancel';
387         // Cookie menu options (only used if Login[4] is true)
388         MSG[7] = 'keep for this session only';
389         MSG[8] = 'keep for one day';
390         MSG[9] = 'keep for one month';
391         MSG[10] = 'do NOT keep cookies';
392         // Login error messages
393         MSG[11] = 'Sorry, you were unable to login. Please try again later.';
394         MSG[12] = 'Please fill in all the information.';
395         MSG[13] = 'Incorrect Password. Please try again.';
396         MSG[14] = 'Incorrect ID. Please try again.';
397         MSG[15] = 'Email address does not appear to be valid.';
398         // day and month names (used in Start_Time and End_Time)
399         MSG[16] = new Array('Sun','Mon','Tue','Wed','Thu','Fri','Sat');
400         MSG[17] = new Array('Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
401         // enable popups
402         MSG[18] = 'Please enable pop-up windows on your browser.';
403         // browser specific instuctions on how to enable popup windows
404         var n = navigator;
405         var s = n.userAgent.toLowerCase();
406         if (n.appName=='Netscape' && s.indexOf('gecko')>=0) {
407                 // Netscape 6 and 7
408                 MSG[18] += '\n\n' + 'Edit->Preferences, ' + (s.indexOf('mac')>=0 ? 'Advanced->Scripts & Plugins' : 'Privacy & Security->Popup Window Controls');
409         } else if (s.indexOf('safari')>=0) {
410                 // Safari
411                 MSG[18] += '\n\n' + 'on Safari menu, uncheck "Block Pop-Up Windows"';
412         } else if (s.indexOf('firebird')>=0) {
413                 // Firebird
414                 MSG[18] += '\n\n' + 'Preferences->Web Features, uncheck "Block Pop-Up Windows"';
415         } else if (s.indexOf('msie 6')>=0) {
416                 // IE 6 (WinXP.SP2)
417                 MSG[18] += '\n\n' + 'Tools->Pop-up Blocker->Turn Off Pop-up Blocker';
418         }
420 //if (window.FEEDBACK==null) {
421 //      FEEDBACK = new Array();
422 //      FEEDBACK[0] = ''; // url of feedback page/script
423 //      FEEDBACK[1] = ''; // array of array('teachername', 'value');
424 //      FEEDBACK[2] = ''; // 'student name' [formmail only]
425 //      FEEDBACK[3] = ''; // 'email@somewhere.com>' [formmail only]
426 //      FEEDBACK[4] = ''; // window width
427 //      FEEDBACK[5] = ''; // window height
428 //      FEEDBACK[6] = ''; // 'Send a message to teacher' [prompt/button text]
429 //      FEEDBACK[7] = ''; // 'Title'
430 //      FEEDBACK[8] = ''; // 'Teacher'
431 //      FEEDBACK[8] = ''; // 'Message'
432 //      FEEDBACK[10] = ''; // 'Close this window' [formmail only]
434 // **********
435 //  HP array
436 // **********
437 HP = new Array();
438 for (var i=0; i<=8; i++) {
439         HP[i] = new Array();
441 // indexes for the HP array (makes the code further down easier to read)
442 _score   = 0;
443 _weight  = 1;
444 _correct = 2;
445 _wrong   = 3;
446 _unused  = 4;
447 _hints   = 5;
448 _clues   = 6;
449 _checks  = 7;
450 _guesses = 8;
451 // *************
452 //  Server Fields
453 // *************
454 if (window.ServerFields==null) {
455         ServerFields = new Array();
456         // these fields will be added to the ResultForm and submitted to the CGI script on the server.
457         // 'Sort', 'return_link_title', 'return_link_url' and 'print_blank_fields' are useful for formmail
458         // override the HP setting of sort fields (forces ALL fields to be displayed)
459         ServerFields[0] = new Array('sort', '');
460         // add link to close pop-up results window
461         ServerFields[1] = new Array('return_link_title', 'Close this window');
462         ServerFields[2] = new Array('return_link_url', 'javascript:self.close()');
463         // make sure zero values are printed
464         ServerFields[3] = new Array('print_blank_fields', 'yes');
465         // you can also set other fields for your customized CGI script
466         // e.g. adding a server defined start time (instead of a client defined start time)
467         // ServerFields[4] = new Array('serverStartTime', '<?php echo date("Y-m-d H:i:s") ?>');
469 // *********************
470 //      Login screen
471 //  (not required by LMS)
472 // *********************
473 function QuizLogin(LoginPrompt) {
474         if (!is_LMS() && (Login[0] || Login[1] || Login[2] || Login[3])) {
475                 var html = ''
476                         + '<html>'
477                         + '<head></head>'
478                         + '<body bgColor="#cccccc" onLoad="opener.setFocus(self)">'
479                         + '<form onSubmit="'
480                         +       'self.ok=true;'
481                         +       'self.expiry=null;'
482                 ;
483                 if (Login[4]) { // cookie expiry
484                         html += "opener.checkOK(self,'CookieExpiry');";
485                 }
486                 if (Login[0]) { // user name
487                         html += "opener.checkOK(self,'UserName');";
488                 }
489                 if (Login[1]) { // user ID
490                         html += "opener.checkOK(self,'UserID');";
491                 }
492                 if (Login[2]) { // user email
493                         html += "opener.checkOK(self,'UserEmail');";
494                 }
495                 if (Login[3]) { // quiz password
496                         html += "opener.checkOK(self,'Password');";
497                 }
498                 html +=          'if(ok){'
499                         +               'opener.StartQuiz();'
500                         +               'self.close();'
501                         +         '}else{'
502                         +               'if(isNaN(self.tries))self.tries=0;'
503                         +               'self.tries++;'
504                         +               'if(self.tries<3){'
505                         +                       'opener.setFocus(self);'
506                         +               '}else{'
507                         +                       "alert(opener.MSG[11]);"
508                         +                       'opener.goBack();'
509                         +                       'self.close();'
510                         +               '}'
511                         +         '}'
512                         +         'return false;'
513                         + '">'
514                 ;
515                 html += '<table>'
516                         +       '<caption>' + LoginPrompt + '</caption>';
517                 ;
518                 if (Login[0]) { // user name
519                         var v = getCookie(self, 'UserName');
520                         html += '<tr>'
521                                 +       '<th align=right nowrap>' + MSG[0] + ' :</th>'
522                                 +       '<td>'
523                         ;
524                         if (typeof(Login[0])=='boolean') { // text box
525                                 html += '<input type=text name=UserName value="' + v + '">';
526                         } else { // drop down menu of names
527                                 // pattern to match commas and white space
528                                 var comma = (window.RegExp) ? new RegExp('\\s*,\\s*') : ',';
529                                 // convert list of names to array, if necessary
530                                 if (typeof(Login[0])=='string') {
531                                         Login[0] = Login[0].split(comma);
532                                 }
533                                 html += '<select name=UserName size=1>'
534                                         + '<option value=""></option>'
535                                 ;
536                                 for(var i=0; i<Login[0].length; i++) {
537                                         // convert name details to array if nececount_cary
538                                         if (typeof(Login[0][i])=='string') {
539                                                 Login[0][i] = Login[0][i].split(comma);
540                                         }
541                                         html += makeOption(Login[0][i][0], v, Login[0][i][1]);
542                                 }
543                                 html += '</select>';
544                         }
545                         html +=         '</td>'
546                                 + '</tr>'
547                         ;
548                 }
549                 if (Login[1]) { // user ID
550                         var v = getCookie(self, 'UserID');
551                         html += '<tr><th align=right nowrap>' + MSG[1] + ' :</th><td><input type=text name=UserID value="' + v + '"></td></tr>';
552                 }
553                 if (Login[2]) { // user email
554                         var v = getCookie(self, 'UserEmail');
555                         html += '<tr><th align=right nowrap>' + MSG[2] +' :</th><td><input type=text name=UserEmail value="' + v + '"></td></tr>';
556                 }
557                 if (Login[3]) { // quiz password
558                         var v = getCookie(self, 'Password');
559                         html += '<tr><th align=right nowrap>' + MSG[3] + ' :</th><td><input type=password name=Password value="' + v + '"></td></tr>';
560                 }
561                 if (Login[4]) { // cookie lifespan
562                         var v = getCookie(self, 'CookieExpiry');
563                         html += '<tr>'
564                                 +       '<th align=right nowrap>' + MSG[4] + ' :</th>'
565                                 +       '<td>'
566                                 +               '<select name="CookieExpiry" size=1>'
567                                 +                       makeOption('session', v, MSG[7])
568                                 +                       makeOption('day', v, MSG[8])
569                                 +                       makeOption('month', v, MSG[9])
570                                 +                       makeOption('never', v, MSG[10])
571                                 +               '</select>'
572                                 +       '</td>'
573                                 + '</tr>'
574                         ;
575                 }
576                 html +=         '<tr>'
577                         +               '<th>&nbsp;</th>'
578                         +               '<td nowrap>'
579                         +                       '<input type=submit value="' + MSG[5] + '"> '
580                         +                       '<input type=button value="' + MSG[6] + '" onClick="opener.goBack();self.close();">'
581                         +               '</td>'
582                         +       '</tr>'
583                         + '</table></form></body></html>'
584                 ;
585                 // set height of Login Window
586                 var m = navigator.userAgent.indexOf('Mac')>=0;
587                 var h = (m ? 80 : 100);
588                 for (var i=0; i<5; i++) h += (Login[i] ? (m ? 20 : 25) : 0);
589                 // open up a new window
590                 if (!openWindow('', '', (m ? 320 : 300), h, 'RESIZABLE', html)) {
591                         alert(MSG[18]); // unable to open popup window
592                 }
593         } else { // no Login required
594                 window.UserName = window.UserID = window.UserEmail = window.Password = '';
595                 window.StartQuiz();
596         }
597         return true;
599 function makeOption(value, v, txt) {
600         return '<option value="' + value + '"' + (value==v ? ' SELECTED' : '') + '>' + (txt ? txt : value) + '</option>';
602 function setFocus(w) {
603         w.focus(); // bring window to the front
604         var obj = w.document.forms[0].elements;
605         for(var i=0; i<obj.length; i++) {
606                 var v = getValue(w, i);
607                 if (v=='' || obj[i].type=='submit') {
608                         obj[i].focus();
609                         break;
610                 }
611         }
613 function checkOK(w, n){
614         var v = getValue(w, n, true);
615         if (v || (n!='UserName' && isGuest())) {
616                 if (n=='CookieExpiry') setCookieExpiry(w, v);
617                 setCookie(self, n, v, w.expiry);
618                 if (n!='CookieExpiry') eval('self.' + n + '=v');
619         } else {
620                 if (w.ok) alert(MSG[12]);
621                 w.ok = false;
622         }
624 function getValue(w, n, flag) {
625         var obj = w.document.forms[0].elements[n];
626         var TYPE = obj.type.toUpperCase(); // required for ns4 (win)
627         if (obj.options && TYPE.indexOf('SELECT')>=0){
628                 var v = obj.options[obj.selectedIndex].value;
629         } else {
630                 var v = obj.value;
631         }
632         if (flag) {
633                 var msg = '';
634                 if (n=='Password' || (n=='UserID' && !Login[3])) {
635                         var pwd = getPassword(w);
636                         if (pwd && v!=pwd) msg = MSG[n=='Password' ? 13 : 14];
637                 }
638                 if (n=='UserEmail' && window.RegExp) {
639                         var r = '(\\w|-)+';
640                         r = r + '(\\.' + r + ')';
641                         r = new RegExp('^(' + r + '*)@(' + r + '+)$');
642                         if (v.match(r)==null) msg = MSG[15];
643                 }
644                 if (msg) {
645                         obj.value = v = '';
646                         if (w.ok) alert(msg);
647                         w.ok = false;
648                 }
649         }
650         return v;
652 function getPassword(w) {
653         var pwd = '';
654         if (Login[3] && typeof(Login[3])=='string') {
655                 pwd = Login[3];
656         } else if ((Login[3] || Login[1]) && typeof(Login[0])=='object') {
657                 var username = getValue(w, 'UserName');
658                 for(var i=0; i<Login[0].length; i++) {
659                         if (username==Login[0][i][0]) {
660                                 pwd = Login[0][i][2];
661                                 break;
662                         }
663                 }
664         }
665         return pwd;
667 function setCookieExpiry(w, v) {
668         if (v=='never'){
669                 w.expiry = new Date('Thu, 01-Jan-70 00:00:01 GMT');
670         } else if (v=='day' || v=='month') {
671                 var ms = (v=='month' ? 31 : 1) * 60 * 60 * 24 * 1000;
672                 w.expiry = new Date((new Date()).getTime() + ms);
673         }
675 function setCookie(w, name, value, expires, path, domain, secure) {
676         if (name) w.document.cookie = ''
677                 + 'HP_' + name + "=" + escape(value)
678                 + (expires ? "; expires=" + expires.toGMTString() : "")
679                 + (path ? "; path=" + path : "")
680                 + (domain ? "; domain=" + domain : "")
681                 + (secure ? "; secure" : "")
682         ;
684 function getCookie(w, n) {
685         var c = w.document.cookie;
686         var i = c.indexOf('HP_' + n + '=');
687         var j = (i<0) ? -1 : c.indexOf(';', (i += n.length + 4));
688         return (i<0) ? '' : unescape(c.substring(i, ((j<0) ? c.length : j)));
690 function goBack(w) {
691         if (w==null) w = self; // default
692         if (w.history.length) w.history.back();
694 function openWindow(url, name, width, height, attributes, html) {
695         // set height, width and attributes
696         if (window.screen && width && height) {
697                 var W = screen.availWidth;
698                 var H = screen.availHeight;
699                 width = Math.min(width, W);
700                 height = Math.min(height, H);
701                 attributes = ''
702                         + (attributes ? (attributes+',') : '')
703                         + 'WIDTH='+width+',HEIGHT='+height
704                 ;
705         }
706         // create global hpWindows object, if necessary
707         if (!window.hpWindows) window.hpWindows = new Array();
708         // initialize window object
709         var w = null;
710         // has a window with this name been opened before?
711         if (name && hpWindows[name]) {
712                 // http://www.webreference.com/js/tutorial1/exist.html
713                 if (hpWindows[name].open && !hpWindows[name].closed) {
714                         w = hpWindows[name];
715                         w.focus();
716                 } else {
717                         hpWindows[name] = null;
718                 }
719         }
720         // check window is not already open
721         if (w==null) {
722                 // workaround for "Access is denied" errors in IE when offline
723                 // based on an idea seen at http://www.devshed.com/Client_Side/JavaScript/Mini_FAQ
724                 var ie_offline = (document.all && location.protocol=='file:');
725                 // try and open the new window
726                 w = window.open((ie_offline ? '' : url), name, attributes);
727                 // check window opened OK (user may have prevented popups)
728                 if (w) {
729                         // center the window
730                         if (window.screen && width && height) {
731                                 w.moveTo((W-width)/2, (H-height)/2);
732                         }
733                         // add content, if required
734                         if (html) {
735                                 with (w.document) {
736                                         clear();
737                                         open();
738                                         write(html);
739                                         close();
740                                 }
741                         } else if (url && ie_offline) {
742                                 w.location = url;
743                         }
744                         if (name) hpWindows[name] = w;
745                 }
746         }
747         return w;
749 // *********************
750 //  Send results by email
751 //  (not required by LMS)
752 // *********************
753 function SendAllResults(Score) {
754         // check this quiz is not generated by a LMS
755         if (!is_LMS()) {
756                 // add flat file database details to the results form
757                 AddDatabaseDetailsToResultForm();
758                 // add student details to the results form
759                 AddStudentDetailsToResultForm();
760                 // add question details to the results form
761                 AddQuestionDetailsToResultForm();
762                 // add server fields, if any, to results form
763                 AddServerFieldsToResultForm();
764                 // change "method" of form, because "get" only allows 512 byts of data
765                 ResultForm = replaceLast('method="get"', 'method="post"', ResultForm);
766                 // create results window and form
767                 var w = openWindow('', '', 500, 400, 'RESIZABLE,SCROLLBARS,LOCATION', ResultForm);
768                 // check window opened OK (user may have prevented popups)
769                 if (w) {
770                         // get shortcut to form object
771                         var form = w.document.forms[0];
772                         // update some important field values
773                         form.Score.value = Score + '%';
774                         form.realname.value = UserName;
775                         form.Start_Time.value = getTime(Start_Time);
776                         form.End_Time.value = getTime();
777                         // force email subject and Exercise title
778                         form.subject.value = document.title;
779                         form. Exercise.value = document.title;
780                         // update DB fields, if required
781                         if (DB[0] && !isGuest()) set_db_fields(form);
782                         if (DB[7]) form.action = DB[7];
783                         if (DB[8]) form.recipient.value = DB[8];
784                         // if this is a Netscape browser, check if the referer will be set OK
785                         if (navigator.appName=='Netscape' && (location.protocol=='file:' || navigator.userAgent.indexOf('Netscape6')>=0)) {
786                                 // ns4 and ns7 set referer to 'file:// ...' when running a quiz offline
787                                 // ns6.2 (at least) always sets referer to 'about:blank'
788                                 // Netscape's setting of referer can cause BFormMail
789                                 // to reject the form, so encode the form data as a URL
790                                 var url = form.action;
791                                 var obj = form.elements;
792                                 for (var i=0; i<obj.length; i++) {
793                                         var  v = escape(obj[i].value);
794                                         v = v.replace((new RegExp('\\+', 'g')), '%2B');
795                                         url += (i==0 ? '?' : '&') + obj[i].name + '=' + v;
796                                 }
797                                 w.location.href = url;
798                         } else { // browser can POST form ok
799                                 form.submit();
800                         }
801                 } else { // unable to open popup window
802                         alert(MSG[18]);
803                 }
804         } // end if LMS
806 function isGuest() {
807         // check username is not a "guest" user
808         var flag = false;
809         var n = getCookie(self, 'UserName').toLowerCase();
810         if (n) {
811                 // convert list of user names to array, if necessary
812                 if(typeof(Login[5])=='string') {
813                         Login[5] = Login[5].split(',');
814                 }
815                 for(var i=0; i<Login[5].length; i++) {
816                         if (n==Login[5][i].toLowerCase()) {
817                                 flag = true;
818                                 break;
819                         }
820                 }
821         }
822         return flag;
824 function set_db_fields(form) {
825         // update list of DB fields, if required
826         if (DB[4]=='' && window.RegExp) {
827                 // add administration fields
828                 var db_fields = ''
829                         + 'subject,realname'
830                         + (Login[1] ? ',ID' : '')
831                         + (Login[2] ? ',email' : '')
832                         + (Login[3] ? ',password' : '')
833                         + ',Score,Start_Time,End_Time'
834                 ;
835                 // add answer fields (except separators)
836                 var r = new RegExp('^[^_]+_q\\d\\d_\\w+$');
837                 for(var i=0; i<form.elements.length; i++) {
838                         var n = form.elements[i].name;
839                         if (r.test(n)) db_fields += ',' + n;
840                 }
841                 form.db_fields.value = db_fields;
842         }
843         // make sure delimiter is set (NS6+ requires this be set here, not any earlier)
844         form.db_delimiter.value = (DB[5] ? DB[5] : '\t');
846 function AddStudentDetailsToResultForm() {
847         var sDetails = '';
848         if (Login[0]) { // user name
849                 // use 'realname' instead of a separate 'Name' field
850                 // sDetails += hpHiddenField('Name', window.UserName);
851         }
852         if (Login[1]) { // user ID
853                 sDetails += hpHiddenField('ID', window.UserID);
854         }
855         if (Login[2]) { // user email
856                 sDetails += hpHiddenField('email', window.UserEmail);
857         }
858         if (sDetails && window.RegExp) {
859                 // insert sDetails before '<input...Score...></input>'
860                 var r = new RegExp('<input[^>]*Score[^>]*><\\/input>', 'i');
861                 var m = r.exec(ResultForm);
862                 if (m) {
863                         ResultForm = ResultForm.replace(m[0], sDetails + m[0] + makeSeparator('Time_'));
864                         sDetails = '';
865                 }
866         }
867         if (Login[3]) { // quiz password
868                 sDetails += hpHiddenField('Password', window.Password);
869                 ResultForm = replaceLast('</form>', sDetails + '</form>', ResultForm);
870         }
872 function AddQuestionDetailsToResultForm() {
873         var qDetails = GetQuestionDetails();
874         if (qDetails) {
875                 // insert qDetails before the final </form> tag in the ResultForm
876                 ResultForm = replaceLast('</form>', qDetails + '</form>', ResultForm);
877         }
879 function AddDatabaseDetailsToResultForm() {
880         if (window.DB && DB[0] && !isGuest()) {
881                 var dbDetails = '';
882                 var folder = DB[1];
883                 if (folder && folder.charAt(folder.length-1)!='/') folder += '/';
884                 var file = DB[2];
885                 if (file=='') {
886                         file = location.href;
887                         file= file.substring(file.lastIndexOf('/')+1);
888                         var i = file.indexOf('?');
889                         if (i >= 0) file = file.substring(0, i);
890                         var i = file.lastIndexOf('.');
891                         if (i >= 0) file = file.substring(0, i);
892                 }
893                 var ext = (DB[3] ? DB[3] : 'txt');
894                 if (ext.charAt(0)!='.') ext = '.' + ext;
895                 dbDetails += hpHiddenField('append_db', folder + file + ext);
896                 dbDetails += hpHiddenField('db_fields', DB[4]);
897                 dbDetails += hpHiddenField('db_delimiter', ''); // NS6+ requires this be set later
898                 if (DB[6]) dbDetails += hpHiddenField('env_report', DB[6]);
899                 // insert dbDetails before the final </form> tag in the ResultForm
900                 ResultForm = replaceLast('</form>', dbDetails + '</form>', ResultForm);
901         }
903 function AddServerFieldsToResultForm() {
904         if (window.ServerFields) {
905                 var s = ''; // input tags for s(erver fields)
906                 for (var i=0; i<ServerFields.length; i++) {
907                         if (ServerFields[i][0] && window.RegExp) {
908                                 // remove previous field value, if any
909                                 var r = new RegExp('<input[^>]*name\\s*=\\s*["\']\\s*' + ServerFields[i][0] + '[^>]*>(\\s*<\\/input>)?', 'i');
910                                 if (r.test(ResultForm)) {
911                                         ResultForm = ResultForm.replace(r, '');
912                                 }
913                         }
914                         if (ServerFields[i][1]) {
915                                 s += hpHiddenField(ServerFields[i][0], ServerFields[i][1]);
916                         }
917                 } // end for
918                 if (s) ResultForm = replaceLast('</form>', s + '</form>', ResultForm);
919         }
921 function replaceLast(a, b, c) {
922         // replace last occurrence of 'a' in 'c' with 'b'
923         var l = a.length;
924         var i = c.lastIndexOf(a);
925         return (i<0 || l==0) ? c : (c.substring(0, i) + b + c.substring(i+l));
927 // *************************
928 //  Extract question details
929 // *************************
930 function GetQuestionDetails() {
931         var hp = hpVersion();
932         var t = hpQuizType();
933         var v = hpQuizVersion();
934         return  (t==1) ? GetJbcQuestionDetails(hp, v) :
935                 (t==2) ? GetJClozeQuestionDetails(hp, v) :
936                 (t==3) ? GetJCrossQuestionDetails(hp, v) :
937                 (t==4) ? GetJMatchQuestionDetails(hp, v) :
938                 (t==5) ? GetJMixQuestionDetails(hp, v) :
939                 (t==6) ? GetJQuizQuestionDetails(hp, v) :
940                 (t==7) ? GetRhubarbDetails(hp, v) :
941                 (t==8) ? GetSequiturDetails(hp, v) : '';
943 function GetJbcQuestionDetails(hp, v) {
944         qDetails = '';
945         // check the quiz version
946         if (hp==5 || hp==6) {
947                 // get question details
948                 for(var q=0; q<I.length; q++) {
949                         // initialize strings to hold answer details
950                         var aDetails = new Array();
951                         aDetails[0] = new Array(); // right
952                         aDetails[1] = new Array(); // wrong
953                         aDetails[2] = new Array(); // ignored
954                         // get answer details
955                         for(var a=0; a<I[q][1].length; a++) {
956                                 var i = (Status[q][1][a]=='R') ? 0 : (Status[q][1][a]=='0') ? 2 : 1;
957                                 aDetails[i][aDetails[i].length] = (JBC[6] ? a : I[q][1][a][0]);
958                         }
959                         // format 'Q' (a padded, two-digit version of 'q')
960                         var Q = getQ('JBC', q);
961                         // add separator, if required
962                         if (JBC[0]) qDetails += makeSeparator(Q);
963                         if (JBC[1]) { // number of attempts to answer question
964                                 qDetails += hpHiddenField(Q+'attempts', Status[q][2] + (Status[q][0]==1 ? 1 : 0));
965                         }
966                         if (JBC[2]) { // question text
967                                 qDetails += hpHiddenField(Q+'text', I[q][0]);
968                         }
969                         if (JBC[3] && (DB[0] || aDetails[0].length>0)) { // right
970                                 qDetails += hpHiddenField(Q+'right', aDetails[0]);
971                         }
972                         if (JBC[4] && (DB[0] || aDetails[1].length>0)) { // wrong
973                                 qDetails += hpHiddenField(Q+'wrong', aDetails[1]);
974                         }
975                         if (JBC[5] && (DB[0] || aDetails[2].length>0)) { // ignored
976                                 qDetails += hpHiddenField(Q+'ignored', aDetails[2]);
977                         }
978                         // calculate score for this question, if required (for HP version < 5.5)
979                         if (isNaN(Status[q][3])) {
980                                 var a1 = Status[q][1].length; // answers
981                                 var a2 = Status[q][2]; // attempts
982                                 Status[q][3] =  (a1<1 || a1<(a2-1)) ? 0 : ((a1 - (a2-1)) / a1);
983                         }
984                         // add 'score' for this question
985                         qDetails += hpHiddenField(Q+'score', Math.floor(Status[q][3]*100)+'%');
986                 } // end for
987         }
988         return qDetails;
990 function GetJClozeQuestionDetails(hp, v) {
991         var qDetails = '';
992         // check the quiz version
993         if (hp==5 || hp==6) {
994                 var r = hpRottmeier();
995                 if (parseInt(r)==2) { // Rottmeier Find-It 3a+3b
996                         qDetails += hpHiddenField('JCloze_penalties', window.TotWrongChoices);
997                 }
998                 // get details for each question
999                 var q_max = (r==0) ? State.length :  GapList.length; // could use I.length for both
1000                 for (var q=0; q<q_max; q++) {
1001                         // format 'Q' (a padded, two-digit version of 'q')
1002                         var Q = getQ('JCloze', q);
1003                         // add separator, if required
1004                         if (JCloze[0]) qDetails += makeSeparator(Q);
1005                         // score (as %)
1006                         var x = (hp==5) ? State[q][3] : (r==0) ? State[q].ItemScore : GapList[q][1].Score;
1007                         qDetails += hpHiddenField(Q+'score', Math.floor(x*100)+'%');
1008                         var correct = (HP[_correct][q] ? HP[_correct][q] : '');
1009                         if (JCloze[1]) { // student's correct answer
1010                                 qDetails += hpHiddenField(Q+'correct', correct);
1011                         }
1012                         if (JCloze[2]) { // ignored answers
1013                                 var x = new Array();
1014                                 if (r!=2.1) { // exclude Find-It 3a
1015                                         for (var i=0, ii=0; i<I[q][1].length; i++) {
1016                                                 var s = I[q][1][i][0];
1017                         if (typeof(s)=='string' && s!='') {
1018                             if (s.toUpperCase() == correct.toUpperCase()) {
1019                                 var is_ignored = false;
1020                             } else {
1021                                 // DropDown 2.4
1022                                 var is_ignored = true;
1023                                 var iii_max = HP[_wrong][q] ? HP[_wrong][q].length : 0;
1024                                 for (var iii=0; iii<iii_max; iii++) {
1025                                     if (s.toUpperCase() == HP[_wrong][q][iii].toUpperCase()) {
1026                                         var is_ignored = false;
1027                                     }
1028                                 }
1029                             }
1030                                                 if (is_ignored) {
1031                                                         x[ii++] = s;
1032                                                 }
1033                         }
1034                                         }
1035                                 }
1036                                 qDetails += hpHiddenField(Q+'ignored', x);
1037                         }
1038                         if (JCloze[3]) {
1039                                 var x = (HP[_wrong][q] ? HP[_wrong][q] : '');
1040                                 qDetails += hpHiddenField(Q+'wrong', x);
1041                         }
1042                         if (JCloze[4]) { // number of penalties (Hints + Checks)
1043                                 var x = (hp==5) ? State[q][1] : (r==0) ? State[q].HintsAndChecks : (r==1) ?  GapList[q][1].NumOfTrials : (r==2.2) ?  GapList[q][1].HintsAndChecks : 0;
1044                                 qDetails += hpHiddenField(Q+'penalties', x);
1045                         }
1046                         if (JCloze[5]) { // clue shown?
1047                                 var x = (hp==5) ? State[q][0] : (r==0) ? State[q].ClueGiven: (r==1) ? GapList[q][1].ClueAskedFor : false;
1048                                 qDetails += hpHiddenField(Q+'clue_shown', (x ? 'YES' : 'NO'));
1049                         }
1050                         if (JCloze[6]) { // clue text
1051                                 qDetails += hpHiddenField(Q+'clue_text', I[q][2]);
1052                         }
1053                         if (JCloze[7]) { // number of hints
1054                                 var x = (HP[_hints][q] ? HP[_hints][q] : 0);
1055                                 qDetails += hpHiddenField(Q+'hints', x);
1056                         }
1057                         if (JCloze[8]) { // number of clues
1058                                 var x = HP[_clues][q] ? HP[_clues][q] : 0;
1059                                 qDetails += hpHiddenField(Q+'clues', x);
1060                         }
1061                         if (JCloze[9]) { // number of checks (including the final one for the correct answer)
1062                                 var x = (HP[_checks][q] ? HP[_checks][q] : 0);
1063                                 qDetails += hpHiddenField(Q+'checks', x);
1064                         }
1065                 } // end for
1066         }
1067         return qDetails;
1069 function GetJCrossQuestionDetails(hp, v) {
1070         var qDetails = '';
1071         // check the quiz version
1072         if (hp==5 || hp==6) {
1073                 // inialize letter count
1074                 var letters = 0;
1075                 // get details for each question
1076                 for (var row=0; row<L.length; row++) {
1077                         for (var col=0; col<L[row].length; col++) {
1078                                 // increment letter count, if required
1079                                 if (L[row][col]) letters++;
1080                                 // show answers and clues, if required
1081                                 var q = (hp==5) ? C[row][col] : CL[row][col];
1082                                 if (q) {
1083                                         for (var i=0; i<2; i++) { // 0==across, 1==down
1084                                                 var AD = (i==0) ? 'A' : 'D';
1085                                                 var acrossdown = (i==0) ? 'across' : 'down';
1087                                                 var clue = (hp==5) ? eval(AD+'['+q+']') : GetJCrossClue('Clue_'+AD+'_'+q);
1088                                                 if (clue) {
1089                                                         // format 'Q' (a padded, two-digit version of 'q')
1090                                                         var Q = getQ('JCross', q) + acrossdown + '_'; // e.g. JCross_01_across_
1092                                                         if (JCross[0]) {
1093                                                                 qDetails += makeSeparator(Q);
1094                                                         }
1095                                                         if (JCross[3]) {
1096                                                                 var x = (HP[_correct][AD] && HP[_correct][AD][q]) ? HP[_correct][AD][q] : '';
1097                                                                 qDetails += hpHiddenField(Q+'correct', x);
1098                                                         }
1099                                                         if (JCross[4]) qDetails += hpHiddenField(Q+'clue', clue);
1100                                                         if (JCross[5]) {
1101                                                                 var x = (HP[_wrong][AD] && HP[_wrong][AD][q]) ? HP[_wrong][AD][q] : '';
1102                                                                 qDetails += hpHiddenField(Q+'wrong', x);
1103                                                         }
1104                                                         if (JCross[6]) {
1105                                                                 var x = HP[_clues][q] ? HP[_clues][q] : 0;
1106                                                                 qDetails += hpHiddenField(Q+'clues', x);
1107                                                         }
1108                                                         if (JCross[7]) {
1109                                                                 var x = (HP[_hints][AD] && HP[_hints][AD][q]) ? HP[_hints][AD][q] : 0;
1110                                                                 qDetails += hpHiddenField(Q+'hints', x);
1111                                                         }
1112                                                         if (JCross[8]) {
1113                                                                 var x = (HP[_checks][AD] && HP[_checks][AD][q]) ? HP[_checks][AD][q] : '';
1114                                                                 qDetails += hpHiddenField(Q+'checks', x);
1115                                                         }
1116                                                 } // end for i
1117                                         } // end if clue
1118                                 } // end if q
1119                         } // end for col
1120                 } // end for row
1121                 if (JCross[2]) { // show number of letters
1122                         qDetails = hpHiddenField('JCross_letters', letters) + qDetails;
1123                 }
1124                 if (JCross[1]) { // show penalties
1125                         var x = (window.Penalties) ? Penalties : 0;
1126                         qDetails = hpHiddenField('JCross_penalties', x) + qDetails;
1127                 }
1128         }
1129         return qDetails;
1131 function GetJCrossClue(id) {
1132         var obj = (document.getElementById) ? document.getElementById(id) : null;
1133         return (obj) ? GetTextFromNodeN(obj, 'Clue') : '';
1135 function GetJCrossWord(a, r, c, goDown) {
1136         // a is a 2-dimensional array of letters, r is a row number, c is a column number
1137         var s = '';
1138         while (r<a.length && c<a[r].length && a[r][c]) {
1139                 s += a[r][c];
1140                 if (goDown) {
1141                         r++;
1142                 } else {
1143                         c++;
1144                 }
1145         }
1146         return s;
1148 function GetJMatchText(q, className) {
1149         var obj = (document.getElementById) ? document.getElementById('Questions') : null;
1150         return (obj) ? GetTextFromNodeN(obj, className, q) : '';
1152 function GetJMatchRHS(v, q, getCorrect) {
1153         var rhs = '';
1154         if (v==5.1 || v==6.1) { // Drag-and-drop
1155                 var max_i = (window.F && window.D) ? D.length : 0;
1156                 for (var i=0; i<max_i; i++) {
1157                         if (F[q][1]==D[i][getCorrect ? 1 : 2]) break;
1158                 }
1159                 if (i<max_i) rhs = D[i][0];
1160         } else if (v==5 || v==6) { // drop-down list of options
1161                 var obj=document.getElementById(Status[q][2]);
1162                 if (obj) { // not correct yet
1163                         if (getCorrect) {
1164                                 var k = GetKeyFromSelect(obj);
1165                                 var i_max = obj.options.length;
1166                                 for (var i=0; i<i_max; i++) {
1167                                         if (obj.options[i].value==k) break;
1168                                 }
1169                                 if (i>=i_max) i = 0; // shouldn't happen
1170                         } else {
1171                                 // get current guess, if any
1172                                 var i = obj.selectedIndex;
1173                         }
1174                         if (i) rhs = obj.options[i].innerHTML;
1175                 } else { // correct
1176                         rhs = GetJMatchText(q, 'RightItem');
1177                 }
1178         }
1179         return rhs;
1181 function GetJMixQuestionDetails(hp, v) {
1182         qDetails = '';
1183         // check the quiz version
1184         if (hp==5 || hp==6) {
1185                 var q = 0; // question number
1186                 // format 'Q' (a padded, two-digit version of 'q')
1187                 var Q = getQ('JMix', q);
1188                 // add separator, if required
1189                 if (JMix[0]) qDetails += makeSeparator(Q);
1190                 // add 'score' for this question
1191                 var score = HP[_correct]==null ? 0 : ((Segments.length-Penalties)/Segments.length);
1192                 qDetails += hpHiddenField(Q+'score', Math.floor(score*100)+'%');
1193                 if (JMix[1]) { // number of wrong guesses
1194                         qDetails += hpHiddenField(Q+'wrongGuesses', Penalties);
1195                 }
1196                 if (JMix[2]) { // right answer
1197                         var x = (HP[_correct][q]) ? HP[_correct][q] : '';
1198                         qDetails += hpHiddenField(Q+'correct', x);
1199                 }
1200                 if (JMix[3]) { // wrong answer(s)
1201                         var x = (HP[_wrong][q]) ? HP[_wrong][q] : '';
1202                         qDetails += hpHiddenField(Q+'wrong', x);
1203                 }
1204                 if (JMix[5]) { // checks
1205                         var x = (HP[_checks][q]) ? HP[_checks][q] : 0;
1206                         qDetails += hpHiddenField(Q+'checks', x);
1207                 }
1208                 if (JMix[6]) { // hints
1209                         var x = (HP[_hints][q]) ? HP[_hints][q] : 0;
1210                         qDetails += hpHiddenField(Q+'hints', x);
1211                 }
1212         }
1213         return qDetails;
1215 function GetJMixSequence(indexes) {
1216         var s = new Array();
1217         for (var i=0; i<indexes.length; i++) {
1218                 s[i] = JMix[4] ? indexes[i] : GetJMixSegmentText(indexes[i]);
1219         }
1220         return s;
1222 function GetJMixSegmentText(index){
1223         var i_max = Segments.length;
1224         for (var i=0; i<i_max; i++) {
1225                 if (Segments[i][1] == index) break;
1226         }
1227         return (i<i_max) ? Segments[i][0] : '';
1229 function GetJQuizQuestionDetails(hp, v) {
1230         var qDetails = '';
1231         // HP5.5 uses "Status" for v5 and v6 JMatch quizzes (HP6 uses "State")
1232         // var hp =  (window.Status) ? 5 : (window.State) ? 6 : 0;
1233         // check the quiz version
1234         if (hp==5 || hp==6) {
1235                 // get details for each question
1236                 var max_q = (hp==5) ? Status.length : State.length;
1237                 for (var q=0; q<max_q; q++) {
1238                         // skip this question if it was not used (HP6 v6 only)
1239                         if (hp==6 && !State[q]) continue;
1240                         // format 'Q' (a padded, two-digit version of 'q')
1241                         var Q = getQ('JQuiz', q);
1242                         // add separator
1243                         if (JQuiz[0]) qDetails += makeSeparator(Q);
1244                         if (hp==6 && JQuiz[11]) { // question type
1245                                 var x = parseInt(I[q][2]);
1246                                 x = (x==0) ? 'multiple-choice' : (x==1) ? 'short-answer' : (x==2) ? 'hybrid' : (x==3) ? 'multi-select' : 'n/a';
1247                                 qDetails += hpHiddenField(Q+'type', x);
1248                         }
1249                         // score (as %)
1250                         var x = (hp==5) ? Status[q][4]*10 : I[q][0]*State[q][0];
1251                         if (x<0) x = 0;
1252                         qDetails += hpHiddenField(Q+'score', Math.floor(x)+'%');
1253                         if (hp==6 && JQuiz[10]) { // weighting
1254                                 qDetails += hpHiddenField(Q+'weighting', I[q][0]);
1255                         }
1256                         if (JQuiz[1]) { // question text
1257                                 var x = (hp==5) ? I[q][0] : (document.getElementById) ? GetTextFromNodeN(document.getElementById('Q_'+q), 'QuestionText') : '';
1258                                 qDetails += hpHiddenField(Q+'question', x);
1259                         }
1260                         if (JQuiz[2]) { // student's correct answers
1261                                 var x = (HP[_correct][q]) ? HP[_correct][q] : '';
1262                                 qDetails += hpHiddenField(Q+'correct', x);
1263                         }
1264                         if (JQuiz[3]) { // ignored and wrong answers
1265                                 var x = (hp==5) ? new Array() : GetJQuizAnswerDetails(q, 1);
1266                                 if (hp==5) {
1267                                         for (var i=0; i<I[q][1].length; i++) {
1268                                                 var correct = HP[_correct][q] ? HP[_correct][q] : '';
1269                                                 if (I[q][1][i][0] && I[q][1][i][0].toUpperCase()!=correct.toUpperCase()) {
1270                                                         x[x.length] = I[q][1][i][0];
1271                                                 }
1272                                         }
1273                                 }
1274                                 if (DB[0] || x) qDetails += hpHiddenField(Q+'other', x);
1275                         }
1276                         if (hp==6 && JQuiz[7]) { // all selected answers
1277                                 var x = GetJQuizAnswerDetails(q, 0);
1278                                 qDetails += hpHiddenField(Q+'selected', x);
1279                         }
1280                         if (JQuiz[8]) { // wrong answers
1281                                 var x = (HP[_wrong][q]) ? HP[_wrong][q] : '';
1282                                 qDetails += hpHiddenField(Q+'wrong', x);
1283                         }
1284                         if (hp==6 && JQuiz[9]) { // ignored answers
1285                                 var x = GetJQuizAnswerDetails(q, 4);
1286                                 qDetails += hpHiddenField(Q+'ignored', x);
1287                         }
1288                         if (JQuiz[4]) { // number of hints
1289                                 var x = (HP[_hints][q]) ? HP[_hints][q] : 0;
1290                                 qDetails += hpHiddenField(Q+'hints', x);
1291                         }
1292                         if (JQuiz[5] || JQuiz[12]) { // number of checks
1293                                 if (JQuiz[12]) { // strictly checks only
1294                                         var x = (HP[_checks][q]) ? HP[_checks][q] : 0;
1295                                 } else { // checks (+ hints in HP6)
1296                                         var x = (hp==5) ? Status[q][1] : (State[q][2]-1);
1297                                 }
1298                                 qDetails += hpHiddenField(Q+'checks', x);
1299                         }
1300                         if (JQuiz[13]) { // ShowAnswer button
1301                                 var x = (HP[_clues][q]) ? HP[_clues][q] : 0;
1302                                 qDetails += hpHiddenField(Q+'clues', x);
1303                         }
1304                 } // end for
1305         } // end if
1306         return qDetails;
1308 function GetTextFromNodeN(obj, className, n) {
1309         // returns the text under the nth node of obj with the target class name
1310         var txt = '';
1311         if (obj && className) {
1312                 if (typeof(n)=='undefined') {
1313                         n = 0;
1314                 }
1315                 var nodes = GetNodesByClassName(obj, className);
1316                 if (n<nodes.length) {
1317                         txt += GetTextFromNode(nodes[n]);
1318                 }
1319         }
1320         return txt;
1322 function GetNodesByClassName(obj, className) {
1323         // returns an array of nodes with the target classname
1324         var nodes = new Array();
1325         if (obj) {
1326                 if (className && obj.className==className) {
1327                         nodes.push(obj);
1328                 } else if (obj.childNodes) {
1329                         for (var i=0; i<obj.childNodes.length; i++) {
1330                                 nodes = nodes.concat(GetNodesByClassName(obj.childNodes[i], className));
1331                         }
1332                 }
1333         }
1334         return nodes;
1336 function GetTextFromNode(obj) {
1337         // return text in (and under) a single DOM node
1338         var txt = '';
1339         if (obj) {
1340                 if (obj.nodeType==3) {
1341                         txt = obj.nodeValue + ' ';
1342                 }
1343                 if (obj.childNodes) {
1344                         for (var i=0; i<obj.childNodes.length; i++) {
1345                                 txt += GetTextFromNode(obj.childNodes[i]);
1346                         }
1347                 }
1348         }
1349         return txt;
1351 function GetJQuizAnswerDetails(q, flag) {
1352         // flag : the type of information required about the student's answers
1353         //      0 : all student's answers
1354         //      1 : student's wrong and ignored answers
1355         //      2 : student's correct answers
1356         //      3 : student's wrong answers
1357         //      4 : ignored answers
1358         var x = State[q][5]; //Sequence of answers chosen by number
1359         if (I[q][2]=='3') { // multi-select
1360                 if (flag==4) {
1361                         var x = new Array();
1362                 } else {
1363                         // get required part of 'x' and convert to array
1364             if (x.charAt(0)=='|') {
1365                 // HP 6.0 and 6.1 (always has leading bar)
1366                         var i = x.lastIndexOf('|');
1367                         var x = x.substring((flag==2 ? (i+1) : 1), ((flag==0 || flag==2) ? x.length : i)).split('|');
1368             } else {
1369                 // HP 6.2 (no leading delimiter)
1370                         var i = x.lastIndexOf(' | ');
1371                         var x = x.substring((flag==2 ? (i+3) : 0), ((flag==0 || flag==2) ? x.length : i)).split(' | ');
1372             }
1373                 }
1374                 for (var i=0; i<x.length; i++) {
1375                         var a = new Array();
1376                         for (var ii=0; ii<x[i].length; ii++) {
1377                                 if (x[i].charAt(ii)=='Y') {
1378                                         var s = JQuiz[6] ? String.fromCharCode(97+ii) : I[q][3][ii][0];
1379                                         if (s && s.replace && window.RegExp) {
1380                                                 s = s.replace(new RegExp('\\+', 'g'), '&#43;');
1381                                         }
1382                                         a.push(s);
1383                                 }
1384                         }
1385                         x[i] = a.join('+');
1386                 }
1387         } else if (x) { // multiple-choice, short-answer and hybrid
1388         if (x.charAt(x.length-1)==',') {
1389             // HP 6.0 and 6.1 (always has trailing comma)
1390             x = x.substring(0, x.length-1).split(',');
1391         } else {
1392             // HP 6.2 (short answer also contains student entered text)
1393             x = x.split(' | ');
1394         }
1395                 if (flag) {
1396                         var a = new Array();
1397                         if (flag==1 || flag==2 || flag==3) {
1398                                 for (var i=0; i<x.length; i++) {
1399                     var is_correct = false;
1400                     if (x[i].length==1) { // single letter
1401                         var ii = x[i].charCodeAt(0) - 65;
1402                         if (I[q][3] && I[q][3][ii] && I[q][3][ii][2]) {
1403                             var is_correct = true;
1404                         }
1405                     }
1406                     if (is_correct) {
1407                         if (flag==2) {
1408                             a.push(x[i]);
1409                         }
1410                     } else {
1411                         if (flag==1 || flag==3) {
1412                             a.push(x[i]);
1413                         }
1414                     }
1415                                 }
1416                         }
1417                         if (flag==1) {
1418                                 x = a;
1419                                 a = new Array();
1420                         }
1421                         if (flag==1 || flag==4) {
1422                                 for (var i=0; i<I[q][3].length; i++) {
1423                                         var s = String.fromCharCode(65+i);
1424                                         for (var ii=0; ii<x.length; ii++) {
1425                                                 if (x[ii]==s) break;
1426                                         }
1427                                         if (ii==x.length) a.push(s);
1428                                 }
1429                         }
1430                         x = a;
1431                 }
1432                 // convert answer indexes to values, if required
1433                 if (JQuiz[6]==false) {
1434                         for (var i=0; i<x.length; i++) {
1435                 if (x[i].length==1) { // single letter
1436                     var ii = x[i].charCodeAt(0) - 65;
1437                     if (I[q][3] && I[q][3][ii]) {
1438                                         x[i] = I[q][3][ii][0];
1439                     }
1440                 }
1441                         }
1442                 }
1443         } else {
1444                 x = new Array();
1445         }
1446         return x;
1448 function GetRhubarbDetails(v) {
1449         qDetails = '';
1450         if (v==6) {
1451                 var q = 0; // always zero
1452                 var Q = getQ('Rhubarb', q);
1453                 if (document.title) { // use quiz title as question name
1454                         qDetails += hpHiddenField(Q+'name', document.title);
1455                 }
1456                 if (Rhubarb[0]) { // correct words
1457                         var x = (HP[_correct][q]) ? HP[_correct][q] : '';
1458                         if (Rhubarb[1]) { // count of correct words
1459                                 for (var i=0,ii=0; i<x.length; i++) {
1460                                         if (x[i]) ii++;
1461                                 }
1462                                 x = ii;
1463                         }
1464                         qDetails += hpHiddenField(Q+'correct', x);
1465                 }
1466                 if (Rhubarb[2]) { // wrong words
1467                         var x = (HP[_wrong][q]) ? HP[_wrong][q] : '';
1468                         if (Rhubarb[3]) { // count of wrong words
1469                                 x = x.length;
1470                         }
1471                         qDetails += hpHiddenField(Q+'wrong', x);
1472                 }
1473                 if (Rhubarb[4]) { // ignored
1474                         var x = '';
1475                         qDetails += hpHiddenField(Q+'ignored', x);
1476                 }
1477                 if (Rhubarb[5]) { // hints
1478                         var x = (HP[_hints][q]) ? HP[_hints][q] : '';
1479                         qDetails += hpHiddenField(Q+'hints', x);
1480                 }
1481         }
1482         return qDetails;
1484 function GetSequiturDetails(v) {
1485         qDetails = '';
1486         if (v==6) {
1487                 var q = 0; // always zero
1488                 var Q = getQ('Sequitur', q);
1489                 if (document.title) { // use quiz title as question name
1490                         qDetails += hpHiddenField(Q+'name', document.title);
1491                 }
1492                 if (Sequitur[0]) { // number of correct buttons chosen
1493                         var x = (HP[_correct][q]) ? HP[_correct][q] : '';
1494                         qDetails += hpHiddenField(Q+'correct', x);
1495                 }
1496                 if (Sequitur[1]) { // number of wrong buttons chosen
1497                         var x = (HP[_wrong][q]) ? HP[_wrong][q] : '';
1498                         qDetails += hpHiddenField(Q+'wrong', x);
1499                 }
1500         }
1501         return qDetails;
1503 // *********************
1504 //      click event handlers
1505 // *********************
1506 function hpClick(x, args) {
1507         // x is the button type
1508         // args is either empty, a single argument, or an array of arguments
1509         var btn = (x==1) ? 'Hint' : (x==2) ? 'Clue' : (x==3) ? 'Check' : (x==4)  ? 'Enter' : '';
1510         if (btn) {
1511                 // convert args to array, if necessary
1512                 var t = typeof(args);
1513                 if (t=='object') {
1514                         // do nothing (args is already an array)
1515                 } else if (t=='undefined') {
1516                         args = new Array();
1517                 } else {
1518                         args = new Array(''+args);
1519                 }
1520                 // call handler for this kind of button
1521                 var x = eval('hpClick'+btn+'('+hpVersion()+','+hpQuizType()+','+hpQuizVersion()+',args);');
1522         }
1524 function hpClickHint(hp, t, v, args) {
1525         if (t==2 || t==5 || t==6 || t==7) { // JCloze, JMix, JQuiz, Rhubarb
1526                 var q = args[0]; // clue/question number
1527                 if (!HP[_hints][q]) HP[_hints][q] = 0;
1528                 HP[_hints][q]++;
1529         }
1530         if (t==3) { // JCross
1531                 if (v==6 || v==5) {
1532                         var q = args[0]; // clue/question number
1533                         var AD = args[1]; // direction ('A' or 'D')
1534                         if (!HP[_hints][AD]) HP[_hints][AD] = new Array();
1535                         if (!HP[_hints][AD][q]) HP[_hints][AD][q] = 0;
1536                         HP[_hints][AD][q]++;
1537                 }
1538         }
1539         return true;
1541 function hpClickClue(hp, t, v, args) {
1542         if (t==2 || t==3 || t==6) { // JCloze or JCross, or JQuiz (ShowAnswer button)
1543                 var q = args[0]; // clue/question number
1544                 if (!HP[_clues][q]) HP[_clues][q] = 0;
1545                 HP[_clues][q]++;
1546         }
1547         return true;
1549 function hpClickCheck(hp, t, v, args) {
1550         if (t==2) { // JCloze
1551         if (v==5 || v==6) {
1552                         var r = hpRottmeier();
1553                         var already_correct = 'true';
1554                         if (r==0) {
1555                                 already_correct = (hp==5) ? 'State[i][4]==1' : 'State[i].AnsweredCorrectly==true';
1556                         } else if (r==1) { // DropDown
1557                                 already_correct = 'GapList[i][1].GapLocked==true';
1558                         } else if (r==2.1) { // Find-It 3a
1559                                 already_correct = 'GapList[i][1].ErrorFound==true';
1560                         } else if (r==2.2) { // Find-It 3b
1561                                 already_correct = 'GapList[i][1].GapSolved==true';
1562                         }
1563                         var i_max = I.length;
1564                         for (var i=0; i<i_max; i++) {
1565                                 if (eval(already_correct)) continue;
1566                                 var g = '';
1567                                 if (r==0 || r==2.2) {
1568                                         g = GetGapValue(i);
1569                                 } else if (r==1) { // DropDown
1570                                         if (hp==5) {
1571                                                 g = eval('document.Cloze.Gap'+i+'.value');
1572                                         } else if (hp==6) {
1573                                                 var ii = Get_SelectedDropValue(i);
1574                         if (isNaN(ii) || ii<0) { // 'null' || -1
1575                             g = ''; // no guess yet
1576                         } else {
1577                                         if (window.MakeIndividualDropdowns) {
1578                                 var is_wrong = (ii!=0);
1579                                 g = I[i][1][ii][0];
1580                             } else {
1581                                 var is_wrong = (ii!=i);
1582                                 g = I[ii][1][0][0];
1583                             }
1584                         }
1585                                         }
1586                                 } else if (r==2.1 && i==args[0]) { // Find-It 3a
1587                                         g = I[i][1][0][0];
1588                                 }
1589                                 if (g) {
1590                                         if (!HP[_checks][i]) HP[_checks][i] = 0;
1591                                         HP[_checks][i]++;
1592                                         if (!HP[_guesses][i]) HP[_guesses][i] = new Array();
1593                                         var ii = HP[_guesses][i].length;
1594                                         // is this a new guess at this gap?
1595                                         if (ii==0 || g!=HP[_guesses][i][ii-1]) {
1596                                                 HP[_guesses][i][ii] = g;
1597                         if (r==1) {
1598                             // Rottmeier DropDown 2.4
1599                             // do nothing
1600                         } else {
1601                                                 var G = g.toUpperCase();
1602                                                 var ii_max = I[i][1].length;
1603                                                 for (var ii=0; ii<ii_max; ii++) {
1604                                                         if (window.CaseSensitive) {
1605                                                                 if (g==I[i][1][ii][0]) break;
1606                                                         } else {
1607                                                                 if (G==I[i][1][ii][0].toUpperCase()) break;
1608                                                         }
1609                                                 }
1610                             var is_wrong = (ii==ii_max);
1611                         }
1612                                                 if (is_wrong) { // guess is wrong
1613                                                         if (!HP[_wrong][i]) HP[_wrong][i] = new Array();
1614                                                         var ii_max = HP[_wrong][i].length;
1615                                                         for (var ii=0; ii<ii_max; ii++) {
1616                                                                 if (HP[_wrong][i][ii]==g) break;
1617                                                         }
1618                                                         if (ii==ii_max) {
1619                                                                 HP[_wrong][i][ii] = g;
1620                                                         }
1621                                                 } else { // guess is correct
1622                                                         HP[_correct][i] = g;
1623                                                 }
1624                                         }
1625                                 }
1626                         }
1627                 }
1628         }
1629         if (t==3) { // JCross
1630                 if (v==5 || v==6) {
1631                         var q = args[0]; // clue/question number
1632                         for (var row=0; row<L.length; row++) {
1633                                 for (var col=0; col<L[row].length; col++) {
1634                                         var q = (v==5) ? C[row][col] : CL[row][col];
1635                                         if (q) {
1636                                                 hpClickCheckJCrossV5V6(hp, v, 'A', q, row, col);
1637                                                 hpClickCheckJCrossV5V6(hp, v, 'D', q, row, col);
1638                                         }
1639                                 }
1640                         }
1641                 }
1642         }
1643         if (t==4) { // JMatch
1644                 var a = new Array();
1645                 var extra = ''; // extra js code to eval(uate)
1646                 var guess = ''; // js code to eval(uate) guess
1647                 var correct = ''; // js code to eval(uate) correct answer
1648                 if (window.D && window.F) {
1649                         // drag-and-drop, i.e. v5+ and v6+ (HP5 and HP6)
1650                         a = F;
1651                         guess = 'GetJMatchRHS(v,i)';
1652                         correct = 'GetJMatchRHS(v,i,true)';
1653                 } else  if (window.GetKeyFromSelect) {
1654                         // HP6 v6
1655                         a = Status;
1656                         guess = 'GetJMatchRHS(v,i)';
1657                         correct = 'GetJMatchRHS(v,i,true)';
1658                 } else if (window.GetAnswer) {
1659                         // HP5 v6,v5
1660                         a = I;
1661                         guess = "(I[i][2]==0||I[i][0]=='')?'':GetAnswer(i)";
1662                         correct = 'I[i][3])';
1663                 } else if (window.Draggables) {
1664                         // HP5 v4
1665                         a = Draggables;
1666                         s = "Draggables[i].correct=='1'";
1667                 } else if (window.CorrectAnswers) {
1668                         // HP5 v3
1669                         a = CorrectAnswers;
1670                         guess = 'document.QuizForm.elements[i*2].selectedIndex';
1671                         correct = 'CorrectAnswers[i]';
1672                 }
1673                 for (var i=0; i<a.length; i++) {
1674                         // check this match has not already been finished
1675                         if (!HP[_correct][i]) {
1676                                 // do extra setup, if necessary
1677                                 if (extra) eval(extra);
1678                                 // get the guess, if any
1679                                 var g = ''+eval(guess);
1680                                 if (g) {
1681                                         // is the guess correct?
1682                                         if (g==eval(correct)) {
1683                                                 HP[_correct][i] = g;
1684                                         } else { // wrong answer
1685                                                 // initialize wrong guess array if necessary
1686                                                 if (!HP[_wrong][i]) HP[_wrong][i] = new Array();
1687                                                 // check to see if the guess is already in the guess array
1688                                                 var i_max = HP[_wrong][i].length;
1689                                                 for (var ii=0; ii<i_max; ii++) {
1690                                                         if (HP[_wrong][i][ii]==g) break;
1691                                                 }
1692                                                 // add the guess if it was not found
1693                                                 if (ii==i_max) {
1694                                                         HP[_wrong][i][ii]=g;
1695                                                 } else {
1696                                                         g = null; // this is not a new answer
1697                                                 }
1698                                         }
1699                                         // increment checks for this question, if necessary
1700                                         if (g) {
1701                                                 if (!HP[_checks][i]) HP[_checks][i] = 0;
1702                                                 HP[_checks][i]++;
1703                                         }
1704                                 }
1705                         }
1706                 }
1707         } // end if JMatch
1708         if (t==5) { // JMix
1709                 // get question number (always 0)
1710                 var q = args[0];
1711                 // check question has not already been answered correctly
1712                 if (!HP[_correct][q]) {
1713                         // match current guess against possible correct answers
1714                         var a_max = Answers.length;
1715                         for (var a=0; a<a_max; a++) {
1716                                 var i_max = Answers[a].length;
1717                                 for (var i=0; i<i_max; i++) {
1718                                         if (Answers[a][i] != GuessSequence[i]) break;
1719                                 }
1720                                 if (i==i_max) break; // correct answer was found
1721                         }
1722                         // at this point, (a==a_max) means guess is wrong
1723                         // get array of segment texts in this g(uess)
1724                         var g = GetJMixSequence(GuessSequence);
1725                         // convert g(uess) array and to a s(tring)
1726                         var s = '';
1727                         var i_max = g.length;
1728                         for (var i=0; i<i_max; i++) {
1729                                 g[i] = trim(g[i]);
1730                                 if (g[i]!='') {
1731                                         s += (s=='' ? '' : '+') +  g[i];
1732                                 }
1733                         }
1734                         if (s) {
1735                                 if (a==a_max) { // wrong
1736                                         if (!HP[_wrong][q]) HP[_wrong][q] = new Array();
1737                                         var i = HP[_wrong][q].length;
1738                                         HP[_wrong][q][i] = s;
1739                                 } else { // correct
1740                                         HP[_correct][q] = s;
1741                                 }
1742                                 // increment checks for this question
1743                                 if (!HP[_checks][q]) HP[_checks][q] = 0;
1744                                 HP[_checks][q]++;
1745                         }
1746                 }
1747         }
1748         if (t==6) { // JQuiz
1749                 if (hp==5 || hp==6) {
1750                         var q = args[0]; // clue/question number
1751                         if (hp==5) {
1752                                 if (v==5) {
1753                                         var g = TrimString(eval('BottomFrame.document.QForm' + q + '.Guess').value);
1754                                 } else if (v==6) {
1755                                         var g = TrimString(eval('document.QForm.Guess').value);
1756                                 }
1757                         } else  { // HP 6
1758                                 var g = args[1];
1759                         }
1760                         // increment check count
1761                         if (!HP[_checks][q]) HP[_checks][q] = 0;
1762                         HP[_checks][q]++;
1763                         if (g) {
1764                                 var G = g.toUpperCase(); // used for shortanswer only
1765                                 var correct_answer = ''; // used for multiselect only
1766                                 // set index of answer array in I (the question array)
1767                                 var ans = (hp==5) ? 1 : 3;
1768                                 var i_max = I[q][ans].length;
1769                                 for (var i=0; i<i_max; i++) {
1770                                         // is this a (possible) correct answer?
1771                                         if (hp==5 || (hp==6 && I[q][ans][i][2])) {
1772                                                 if (hp==6 && I[q][2]==3) { // multiselect
1773                                                         correct_answer += (correct_answer  ? '&#43;' : '') + I[q][ans][i][0];
1774                                                 } else { // multichoice, shortanswer
1775                                                         if (window.CaseSensitive) {
1776                                                                 if (g==I[q][ans][i][0]) break;
1777                                                         } else {
1778                                                                 if (G==I[q][ans][i][0].toUpperCase()) break;
1779                                                         }
1780                                                 }
1781                                         }
1782                                 }
1783                                 if (i==i_max && g!=correct_answer) { // wrong
1784                                         if (!HP[_wrong][q]) HP[_wrong][q] = new Array();
1785                                         var i_max = HP[_wrong][q].length;
1786                                         for (var i=0; i<i_max; i++) {
1787                                                 if (HP[_wrong][q][i]==g) break;
1788                                         }
1789                                         if (i==i_max) HP[_wrong][q][i] = g;
1790                                 } else {
1791                                         HP[_correct][q] = g;
1792                                 }
1793                         }
1794                 }
1795         }
1796         if (t==7) { // Rhubarb
1797                 if (hp==6) {
1798                         var q = 0; // question number (always zero)
1799                         var g = args[0]; // InputWord from CheckGuess()
1800                         if (g) {
1801                                 var G = g.toUpperCase();
1802                                 var i_max = Words.length;
1803                                 for (var i=0; i<i_max; i++) {
1804                                         if (G==Words[i].toUpperCase()) break;
1805                                 }
1806                                 if (i<i_max) { // correct
1807                                         if (!HP[_correct][q]) HP[_correct][q] = new Array();
1808                                         HP[_correct][q][i] = g;
1809                                 } else { // wrong
1810                                         if (!HP[_wrong][q]) HP[_wrong][q] = new Array();
1811                                         var i_max = HP[_wrong][q].length;
1812                                         for (var i=0; i<i_max; i++) {
1813                                                 if (G==HP[_wrong][q][i].toUpperCase()) break;
1814                                         }
1815                                         if (i==i_max) HP[_wrong][q][i] = g;
1816                                 }
1817                         }
1818                 }
1819         }
1820         if (t==8) { // Sequitur
1821                 if (hp==6) {
1822                         var q = 0; // question number (always zero)
1823                         if (CurrentCorrect==args[0]) { // correct button chosen
1824                                 if (!HP[_correct][q]) HP[_correct][q] = 0;
1825                                 HP[_correct][q]++;
1826                         } else {
1827                                 if (!HP[_wrong][q]) HP[_wrong][q] = 0;
1828                                 HP[_wrong][q]++;
1829                         }
1830                 }
1831         }
1832         //return true;
1834 function hpClickCheckJCrossV5V6(hp, v, AD, q, row, col) {
1835         // v is the version of Hot Potatoes
1836         // AD is the direction ('A' or 'D')
1837         // make sure HP[_checks] and HP[_correct] are initialized
1838         if (!HP[_checks][AD]) HP[_checks][AD] = new Array();
1839         if (!HP[_correct][AD]) HP[_correct][AD] = new Array();
1840         // get clue, if any
1841         var clue = (hp==5) ? eval('window.'+AD) : GetJCrossClue('Clue_'+AD+'_' + q);
1842         // is this a question that has not been answered correctly yet?
1843         if (clue && !HP[_correct][AD][q]) {
1844                 var check = false;
1845                 var guess = GetJCrossWord(G, row, col, (AD=='D'));
1846                 var correct = GetJCrossWord(L, row, col, (AD=='D'));
1847         if (window.CaseSensitive) {
1848             var is_correct = (guess==correct);
1849         } else {
1850             var is_correct = (guess.toUpperCase()==correct.toUpperCase());
1851         }
1852                 if (is_correct) {
1853                         HP[_correct][AD][q] = correct;
1854                         check = true;
1855                 } else if (guess) {
1856                         // make sure HP[_wrong] is initialized
1857                         if (!HP[_wrong][AD]) HP[_wrong][AD] = new Array();
1858                         if (!HP[_wrong][AD][q]) HP[_wrong][AD][q] = new Array();
1859                         // check this guess has not been entered before
1860                         var i_max = HP[_wrong][AD][q].length;
1861                         for (var i=0; i<i_max; i++) {
1862                                 if (HP[_wrong][AD][q]==guess) break;
1863                         }
1864                         // add the guess if it has not been entered before
1865                         if (i>=i_max) {
1866                                 HP[_wrong][AD][q][i] = guess;
1867                                 check = true;
1868                         }
1869                 }
1870                 // update HP[_checks], if necessary
1871                 if (check) {
1872                         if (!HP[_checks][AD]) HP[_checks][AD] = new Array();
1873                         if (!HP[_checks][AD][q]) HP[_checks][AD][q] = 0;
1874                         HP[_checks][AD][q]++;
1875                 }
1876         }
1878 function hpClickEnter(hp, t, v, args) {
1879         if (t==3) { // JCross
1880                 var q = args[0]; // clue/question number
1881                 if (!HP[_enter][q]) HP[_enter][q] = 0;
1882                 HP[_enter][q]++;
1883         }
1884         return true;
1886 function GetJMatchQuestionDetails(hp, v) {
1887         var qDetails = '';
1888         // HP5.5 uses "I" for v5 and v6 JMatch quizzes
1889         // var hp5 = (window.I) ? true : false;
1890         // check the quiz version
1891         if (hp==5 || hp==6) {
1892                 if (JMatch[1] && v==6.1) { // attempts
1893                         qDetails += hpHiddenField('JMatch_attempts', Penalties+1);
1894                 }
1895                 // get number of questions
1896                 var max_q = (hp==5 || v==6) ? Status.length : F.length;
1897                 // get details for each question
1898                 for (var q=0; q<max_q; q++) {
1899                         // format 'Q' (a padded, two-digit version of 'q')
1900                         var Q = getQ('JMatch', q);
1901                         // add separator, if required
1902                         if (JMatch[0] && (JMatch[1] || JMatch[2] || JMatch[3])) {
1903                                 qDetails += makeSeparator(Q);
1904                         }
1905                         if (JMatch[1] && (hp==5 || v==6)) { // attempts
1906                                 qDetails += hpHiddenField(Q+'attempts', Status[q][1]);
1907                         }
1908                         if (JMatch[2]) { // LHS text (the question)
1909                                 var x = (v==5) ? I[q][0] : (v==6) ? GetJMatchText(q, 'LeftItem') : F[q][0];
1910                                 qDetails += hpHiddenField(Q+'lhs', x);
1911                         }
1912                         if (JMatch[3]) { // correct answer (if any)
1913                                 var x = HP[_correct][q] ? HP[_correct][q] : '';
1914                                 qDetails += hpHiddenField(Q+'correct', x);
1915                         }
1916                         if (JMatch[4]) { // wrong answers (if any)
1917                                 var x = HP[_wrong][q] ? HP[_wrong][q] : '';
1918                                 qDetails += hpHiddenField(Q+'wrong', x);
1919                         }
1920                         if (JMatch[5]) { // checks
1921                                 var x = HP[_checks][q] ? HP[_checks][q] : 0;
1922                                 qDetails += hpHiddenField(Q+'checks', x);
1923                         }
1924                 } // end for
1925         }
1926         return qDetails;
1928 // *********************
1929 //   library functions
1930 // *********************
1931 function pad(i, l) {
1932         var s = (i+'');
1933         while (s.length<l) s = '0' + s;
1934         return s;
1936 function getQ(section, q) {
1937         // Q is a padded, two-digit version of the question number, 'q', prefixed by 'section'
1938         return section + '_q' + (q<9 ? '0' : '') + (q+1) + '_';
1940 function makeSeparator(Q) {
1941         return  is_LMS() ? '' : hpHiddenField(Q.substring(0, Q.length-1), '---------------------------------');
1943 function hpHiddenField(name, value, comma, forceHTML) {
1944         var field = '';
1945         var t = typeof(value);
1946         if (t=='string') {
1947                 value = encode_entities(value);
1948         } else if (t=='object') { // array
1949                 var values = value;
1950                 var i_max = values.length;
1951                 value = '';
1952                 if (comma==null) comma = ',';
1953                 for (var i=0; i<i_max; i++) {
1954                         values[i] = trim(values[i]);
1955                         if (values[i]!=null && values[i]!='') {
1956                                 value += (i==0 ? '' : comma) +  encode_entities(values[i]);
1957                         }
1958                 }
1959         }
1960         if (is_LMS() && !forceHTML) {
1961                 if (value && value.indexOf && value.indexOf('<')>=0 && value.indexOf('>')>=0) {
1962                         value = '<![CDATA[' + value + ']]>';
1963                 }
1964                 field = '<field><fieldname>' + name + '</fieldname><fielddata>' + value + '</fielddata></field>';
1965         } else {
1966                 field = '<input type=hidden name="' + name + '" value="' + value + '">';
1967         }
1968         return field;
1970 function trim(s) {
1971         if (s==null) s = '';
1972         var i = 0;
1973         var ii = s.length;
1974         while (i<ii && s.charAt(i)==' ') {
1975                 i++;
1976         }
1977         while (ii>i && s.charAt(ii-1)==' ') {
1978                 ii--;
1979         }
1980         return s.substring(i, ii);
1982 function encode_entities(s_in) {
1983         var i_max = (s_in) ? s_in.length : 0;
1984         var s_out = '';
1985         for (var i=0; i<i_max; i++) {
1986                 var c = s_in.charCodeAt(i);
1987                 // 34 : double quote .......["] &amp;
1988                 // 38 : single quote .......['] &apos;
1989                 // 43 : plus sign ..........[+]
1990                 // 44 : comma ..............[,]
1991                 // 60 : left angle bracket .[<] &lt;
1992                 // 62 : right angle bracket [>] &gt;
1993                 // >=128 multibyte character
1994                 s_out += (c==43 || c==44 || c>=128) ? ('&#x' + pad(c.toString(16), 4) + ';') : s_in.charAt(i);
1995         }
1996         return s_out;
1998 // *********************
1999 //      initialization
2000 //        functions
2001 // *********************
2002 function getTime(obj) {
2003         obj = obj ? obj : new Date();
2004         // get year, month and day
2005         //      for an LMS : yyyy-mm-dd
2006         //      for email  : DayName MonthName dd yyyy
2007         var s = is_LMS() ?
2008                 obj.getFullYear() + '-' + pad(obj.getMonth()+1, 2) + '-' + pad(obj.getDate(), 2) :
2009                 MSG[16][obj.getDay()] + ' ' + MSG[17][obj.getMonth()] + ' ' + pad(obj.getDate(), 2) + ' ' + obj.getFullYear()
2010         ;
2011         // get hours, minutes and seconds (hh:mm:ss)
2012         s += ' ' + pad(obj.getHours(), 2) + ':' + pad(obj.getMinutes(), 2) + ':' + pad(obj.getSeconds(), 2);
2013         // get time difference
2014         //      for an LMS : +xxxx
2015         //      for email  : GMT+xxxx
2016         var x = obj.getTimezoneOffset(); // e.g. -540
2017         if (!isNaN(x)) {
2018                 s += ' ' + (is_LMS() ? '' : 'GMT') + (x<0 ? '+' : '-');
2019                 x = Math.abs(x);
2020                 s += pad(parseInt(x/60), 2) + pad(x - (parseInt(x/60)*60), 2);
2021         }
2022         return s;
2024 function getFunc(fn) {
2025         if (typeof(fn)=='string') {
2026                 fn = eval('window.' + fn);
2027         }
2028         return (typeof(fn)=='function') ? fn : null;
2030 function getFuncCode(fn, extraCode, anchorCode, beforeAnchor) {
2031         var s = '';
2032         var obj = getFunc(fn);
2033         if (obj) {
2034                 s = obj.toString();
2035                 var i1 = s.indexOf('{')+1;
2036                 var i2 = s.lastIndexOf('}');
2037                 if (i1>0 && i1<i2) {
2038                         s = s.substring(i1, i2);
2039                 }
2040         }
2041         if (extraCode) {
2042                 if (anchorCode) {
2043                         if (beforeAnchor) {
2044                                 s = replaceLast(anchorCode, extraCode + anchorCode, s);
2045                         } else {
2046                                 s = replaceLast(anchorCode, anchorCode + extraCode, s);
2047                         }
2048                 } else {
2049                         if (beforeAnchor) {
2050                                 s = extraCode + s;
2051                         } else {
2052                                 s = s + extraCode;
2053                         }
2054                 }
2055         }
2056         return s;
2058 function getArgsStr(args, addQuotes) {
2059         // make s(tring) version of function args array
2060         var s = '';
2061         var i_max = args.length;
2062         for (var i=0; i<i_max; i++) {
2063         if (addQuotes) {
2064             s += '"' + args[i] + '",';
2065         } else {
2066             if (s) {
2067                 s += ',';
2068             }
2069             s += args[i];
2070         }
2071         }
2072         return s;
2074 function getFuncArgs(fn, flag) {
2075         // flag==0 : return args as string
2076         // flag==1 ; return args as array
2077         var i = 0;
2078         var a = new Array();
2079         var obj = getFunc(fn);
2080         if (obj) {
2081                 var s = obj.toString();
2082                 var i1 = s.indexOf('(') + 1;
2083                 var i2 = s.indexOf(')', i1);
2084                 // add args to a(rray)
2085                 while (i1>0 && i1<i2) {
2086                         var i3 = s.indexOf(',', i1); // next comma
2087                         if (i3<0 || i3>i2) i3 = i2;
2088                         a[i++] = trim(s.substring(i1, i3));
2089                         i1 = i3+1;
2090                 }
2091         }
2092         return flag ? a : getArgsStr(a);
2094 function getPrompt(fn) {
2095         // the LoginPrompt is the text string in the first prompt(...) statement
2096         //      v5 : in the StartUp function
2097         //      v6 : in the GetUserName function
2098         // Note: netscape uses double-quote as delimiter, others use single quote
2099         var s = getFuncCode(fn);
2100         var i1 = s.indexOf('prompt') + 8;
2101         var i2 = s.indexOf(s.charAt(i1-1), i1);
2102         var p = (i1>=8 && i2>i1) ? s.substring(i1, i2) : '';
2103         // make sure browser has decoded the unicode prompt properly
2104         // this check is mainly for ns4, but there may be others
2105         if (window.RegExp) {
2106                 var r = new RegExp('u([0-9A-F]{4})');
2107                 var m = r.exec(p);
2108                 while (m) {
2109                         p = p.replace(m[0], '&#' + parseInt(m[1], 16) + ';');
2110                         m = r.exec(p);
2111                 }
2112         }
2113         return p;
2115 function getStartUpCode(fn) {
2116         // the main initialization code comes from the StartUp function
2117         //      v5 : the code before "UserName", if any,
2118         //           and the code after the 2nd subsequent '}'
2119         //      v6 : the code before and after 'GetUserName();'
2120         //           i.e. all the code except the call to 'GetUserName();'
2121         var s = getFuncCode(fn);
2122         var i1 = s.indexOf('GetUserName();');
2123         if (i1>=0) { // v6
2124                 var i2 = i1 + 14;
2125         } else { // v5
2126                 var i1 = s.indexOf('UserName');
2127                 var i2 = s.indexOf('}', s.indexOf('}', i1+8)+1)+1;
2128         }
2129         return (0<i1 && i1<i2) ? s.substring(0, i1) + s.substring(i2) : '';
2131 function is_LMS() {
2132         if (!window.hpCheckedForm) {
2133                 window.hpCheckedForm = true;
2134                 window.hpFoundForm = hpFindForm('store') ? true : false;
2135         }
2136         return hpFoundForm;
2138 function hpFeedback() {
2139         if (FEEDBACK[0]) {
2140                 var url = '';
2141                 var html = '';
2142                 if (FEEDBACK[1] && FEEDBACK[2]) { // formmail
2143                         html += '<html><body>'
2144                                 + '<form action="' + FEEDBACK[0] + '" method="POST">'
2145                                 + '<table border="0">'
2146                                 + '<tr><th valign="top" align="right">' + FEEDBACK[7] + ':</th><td>' + document.title + '</td></tr>'
2147                                 + '<tr><th valign="top" align="right">' + FEEDBACK[8] + ': </th><td>'
2148                         ;
2149                         if (typeof(FEEDBACK[1])=='string') {
2150                                 html += FEEDBACK[1] + hpHiddenField('recipient', FEEDBACK[1], ',', true);
2151                         } else if (typeof(FEEDBACK[1])=='object') {
2152                                 var i_max = FEEDBACK[1].length;
2153                                 if (i_max==1) { // one teacher
2154                                         html += FEEDBACK[1][0][0] + hpHiddenField('recipient', FEEDBACK[1][0][0]+' &lt;'+FEEDBACK[1][0][1]+'&gt;', ',', true);
2155                                 } else if (i_max>1) { // several teachers
2156                                         html += '<select name="recipient">';
2157                                         for (var i=0; i<i_max; i++) {
2158                                                 html += '<option value="'+FEEDBACK[1][i][1]+'">' + FEEDBACK[1][i][0] + '</option>';
2159                                         }
2160                                         html += '</select>';
2161                                 }
2162                         }
2163                         html += '</td></tr>'
2164                                 +       '<tr><th valign="top" align="right">' + FEEDBACK[9] + ':</th>'
2165                                 +       '<td><TEXTAREA name="message" rows="10" cols="40"></TEXTAREA></td></tr>'
2166                                 +       '<tr><td>&nbsp;</td><td><input type="submit" value="' + FEEDBACK[6] + '">'
2167                                 +       hpHiddenField('realname', FEEDBACK[2], ',', true)
2168                                 +       hpHiddenField('email', FEEDBACK[3], ',', true)
2169                                 +       hpHiddenField('subject', document.title, ',', true)
2170                                 +       hpHiddenField('title', document.title, ',', true)
2171                                 +       hpHiddenField('return_link_title', FEEDBACK[10], ',', true)
2172                                 +       hpHiddenField('return_link_url', 'javascript:self.close()', ',', true)
2173                                 +       '</td></tr></table></form></body></html>'
2174                         ;
2175                 } else if (FEEDBACK[1]) { // url only
2176                         if (typeof(FEEDBACK[1])=='object') {
2177                                 var i_max = FEEDBACK[1].length;
2178                                 if (i_max>1) { // several teachers
2179                                         html += '<html><body>'
2180                                                 + '<form action="' + FEEDBACK[0] + '" method="POST" onsubmit="this.action+=this.recipient.options[this.recipient.selectedIndex].value">'
2181                                                 + '<table border="0">'
2182                                                 + '<tr><th valign="top" align="right">' + FEEDBACK[7] + ':</th><td>' + document.title + '</td></tr>'
2183                                                 + '<tr><th valign="top" align="right">' + FEEDBACK[8] + ': </th><td>'
2184                                         ;
2185                                         html += '<select name="recipient">';
2186                                         for (var i=0; i<i_max; i++) {
2187                                                 html += '<option value="'+FEEDBACK[1][i][1]+'">' + FEEDBACK[1][i][0] + '</option>';
2188                                         }
2189                                         html += '</select>';
2190                                         html += '</td></tr>'
2191                                                 +       '<tr><td>&nbsp;</td><td><input type="submit" value="' + FEEDBACK[6] + '">'
2192                                                 +       '</td></tr></table></form></body></html>'
2193                                         ;
2194                                 } else if (i_max==1) { // one teacher
2195                                         url = FEEDBACK[0] + FEEDBACK[1][0][1];
2196                                 }
2197                         } else if (typeof(FEEDBACK[1])=='string') {
2198                                 url = FEEDBACK[0] + FEEDBACK[1];
2199                         }
2200                 } else {
2201                         url = FEEDBACK[0];
2202                 }
2203                 if (url || html) {
2204                         var w = openWindow(url, 'feedback', FEEDBACK[4], FEEDBACK[5], 'RESIZABLE,SCROLLBARS', html);
2205                         if (!w) {
2206                                  // unable to open popup window
2207                                 alert(MSG[18]);
2208                         }
2209                 }
2210         }
2212 // ********************
2213 //      intercept clicks
2214 // ********************
2215 function hpNewFunction(f, a, s) {
2216     if (window.C && C.safari) {
2217         if (f=='CheckAnswers') {
2218             if (s.indexOf('TotalChars-State[i].HintsAndChecks/')>=0) {
2219                 // special fix for "CheckAnswers" in JCloze
2220                 s = s.replace(/TotalChars-State\[i\]\.HintsAndChecks/g, '(TotalChars-State[i].HintsAndChecks)');
2221             }
2222             if (s.indexOf('TotalChars-GapList[x][1].HintsAndChecks/')>=0) {
2223                 // special fix for "CheckAnswers" in JCloze (Find-It)
2224                 s = s.replace(/TotalChars-GapList\[x\]\[1\]\.HintsAndChecks/g, '(TotalChars-GapList[x][1].HintsAndChecks)');
2225             }
2226             if (s.indexOf('CorrectLetters-Penalties/')>=0) {
2227                 // special fix for "CheckAnswers" in JMatch
2228                 s = s.replace(/CorrectLetters-Penalties/g, '(CorrectLetters-Penalties)');
2229             }
2230             if (s.indexOf('TotCorrectChoices-Penalties/')>=0) {
2231                 // special fix for "CheckAnswers" in JMix (v6)
2232                 s = s.replace(/TotCorrectChoices-Penalties/g, '(TotCorrectChoices-Penalties)');
2233             }
2234             if (s.indexOf('TotalCorrect-Penalties/')>=0) {
2235                 // special fix for "CheckAnswers" in JMix (v6+) Drag-and_Drop
2236                 s = s.replace(/TotalCorrect-Penalties/g, '(TotalCorrect-Penalties)');
2237             }
2238         }
2239         if (s.indexOf('replace(\\[')>=0) {
2240             s = s.replace(/\\\[/g, '/\\[');
2241             s = s.replace(/\\\]/g, '\\]/g');
2242         }
2243         if (s.indexOf('for (i')>=0 || s.indexOf('for (x')>=0) {
2244             s = s.replace(/for \(/g, 'for (var ');
2245         }
2246         eval('window.' + f + '=function(' + getArgsStr(a) + '){' + s + '}');
2247     } else {
2248         eval('window.' + f + '=new Function(' + getArgsStr(a, true) + 's);');
2249     }
2251 function hpInterceptFeedback() {
2252         // modify the function which writes feedback
2253         //      v6: ShowMessage(Feedback)
2254         //              but Rhubarb prints score in other functions, so use 'CheckFinished'
2255         //      v5: WriteFeedback(Feedback)
2256         //      v4: WriteFeedback(Stuff)
2257         //      v3: WriteFeedback(Feedback) [except JMatch]
2258         //      v3: CheckAnswer()           [JMatch only]
2259         var f = '';
2260         if (window.CheckWord) { // Rhubarb
2261                 f = 'CheckFinished';
2262                 window.FEEDBACK = null;
2263         } else if (window.ShowText) { // Sequitur
2264                 f = 'CheckAnswer';
2265                 window.FEEDBACK = null;
2266         } else { // JBC, JCloze, JCross, JMatch, JMix, JQuiz
2267                 f = window.ShowMessage ? 'ShowMessage' : window.WriteFeedback ? 'WriteFeedback' : 'CheckAnswer';
2268         }
2269         if (f) {
2270                 var s = getFuncCode(f) + 'Finish();';
2271                 var a = getFuncArgs(f, true);
2272                 if (a[0] && window.FEEDBACK && FEEDBACK[0]) {
2273                         s = a[0] + "+='<br /><br />" + '<a href="javascript:hpFeedback();">' + FEEDBACK[6] + "</A>';" + s;
2274                 }
2275         hpNewFunction(f, a, s);
2276         }
2278 function hpInterceptHints() {
2279         // modify the function which shows hints
2280         //      JBC:    none
2281         //      JCloze  v3-v6: ShowHint()
2282         //      JCross  v3: Cheat(), v4: ShowHint(), v5-v6[HP5]: ShowHint(Across,x,y,BoxName), v6[HP6]: ShowHint(Across,ClueNum,x,y,BoxId)
2283         //      JMatch: none
2284         //      JMix    v5-v6: CheckAnswer(CheckType=1)
2285         //      JQuiz   v3: CheckAnswer(ShowHint=true, QNum), v4: CheckAnswer(ShowHint=true), v5-v6[HP5]: CheckAnswer(ShowHint=true,QNum), v6[HP6]: ShowHint(QNum)
2286         var x = ''; // extra code, if any
2287         if (window.Cheat) {
2288                 // JCross v3 ?
2289         } else if (window.ShowHint) {
2290                 var f = 'ShowHint';
2291                 var a = getFuncArgs(f, true);
2292                 if (a.length==0) {
2293                         if (window.FindCurrent) {
2294                                 // JCloze v3-v6
2295                                 x = 'var q=window.Locked?-1:FindCurrent();if(q>=0&&GetHint(q))hpClick(1,q);';
2296                         } else {
2297                                 // JCross v4
2298                                 // work out which box would have a hint added
2299                                 // work out which question that box is part of using GridMap and WinLetters
2300                         }
2301                 } else if (a[0]=='Across') {
2302                         if (a[1]=='ClueNum') {
2303                                 // JCross v6 [HP6]
2304                                 x = "var args=new Array(ClueNum,Across?'A':'D');hpClick(1,args);";
2305                         } else if (a[1]=='x' && a[2]=='y') {
2306                                 // JCross v5-v6 [HP5]
2307                                 x = "var args=new Array(C[x][y],Across?'A':'D');hpClick(1,args);";
2308                         }
2309                 } else if (a[0]=='QNum') {
2310                         // JQuiz v6[HP6]
2311                         x = 'hpClick(1,QNum);';
2312                 }
2313         } else if (window.Hint) {
2314                 // Rhubarb
2315                 var f = 'Hint';
2316                 var a = getFuncArgs(f, true);
2317                 x = 'hpClick(1,0);'; // question number is always zero
2319         } else if (window.CheckAnswer) {
2320                 var f = 'CheckAnswer';
2321                 var a = getFuncArgs(f, true);
2322                 if (a[0]=='ShowHint') {
2323                         if (a[1]=='QNum') {
2324                                 // JQuiz v3, v5-v6[HP5]
2325                                 x = 'if(ShowHint)hpClick(1,QNum);';
2326                         } else {
2327                                 // JQuiz v4
2328                                 x = 'if(ShowHint)hpClick(1,QNum-1);'; // QNum is a global variable
2329                         }
2330                 } else if (a[0]=='CheckType') {
2331                         // JMix v5-v6
2332                         x = 'if(CheckType==1)hpClick(1,0);'; // question number is always 0;
2333                 }
2334         }
2335         // add the e(x)tra code, if any, to the start of the hint (f)unction
2336         if (x) {
2337                 var s = getFuncCode(f, x, '', true);
2338         hpNewFunction(f, a, s);
2339         }
2341 function hpInterceptClues() {
2342         // modify the function which shows clues (or ShowAnswers in JQuiz)
2343         //      JBC:    none
2344         //      JCloze  v3-v6: ShowClue(ItemNum)
2345         //      JCross  v3-v4: ShowClue(ClueNum), v5-v6: ShowClue(ClueNum,x,y)
2346         //      JMatch  none
2347         //      JMix    none
2348         //      JQuiz   ShowAnswers(QNum)
2349         var x = ''; // extra code, if any
2350         if (window.ShowClue) {
2351                 var f = 'ShowClue';
2352                 var a = getFuncArgs(f, true);
2353                 if (a[0]=='ItemNum') {
2354                         // JCloze (v3-v6)
2355                         x = 'if(!window.Locked)hpClick(2,ItemNum);'; // v6 [HP6] uses window.Locked
2356                 } else if (a[0]=='ClueNum') {
2357                         if (a[1]=='x' && a[2]=='y') {
2358                                 if (window.A && window.D) {
2359                                         // JCross v5-v6 [HP5]
2360                                         x = 'if(A[ClueNum]||D[ClueNum])hpClick(2,ClueNum);';
2361                                 } else if (document.getElementById) {
2362                                         // JCross v6 [HP6]
2363                                         x = "if(document.getElementById('clue_' + ClueNum)||document.getElementById('Clue_D_' + ClueNum))hpClick(2,ClueNum);";
2364                                 }
2365                         } else {
2366                                 if (window.AClues && window.DClues) {
2367                                         // JCross v3-v4
2368                                         x = 'if(AClues[ClueNum]||DClues[ClueNum])hpClick(2,ClueNum);';
2369                                 }
2370                         }
2371                 }
2372         }
2373         // JQuiz: there is no "ShowClue" function but there is a "ShowAnswer" function
2374         if (window.ShowAnswers) {
2375                 var f = 'ShowAnswers';
2376                 var a = getFuncArgs(f, true);
2377                 if (window.State) {
2378                         if (window.ShowMessage) {
2379                                 // JQuiz v6 [HP6]
2380                                 x = 'if(State[QNum][0]<1)hpClick(2,QNum);';
2381                         } else if (window.WriteFeedback) {
2382                                 // JQuiz v3-v4
2383                                 x = 'if(State[QNum-1][0]<1)hpClick(2,QNum-1);';
2384                         }
2385                 } else if (window.Status) {
2386                         // JQuiz v5-v6 [HP5]
2387                         x = 'if(Status[QNum][0]<0)hpClick(5,QNum);';
2388                 }
2389         }
2390         // add the e(x)tra code, if any, to the start of the clue (f)unction
2391         if (x) {
2392                 var s = getFuncCode(f, x, '', true);
2393                 var s = getFuncCode(f, '', '', true);
2394         hpNewFunction(f, a, s);
2395         }
2397 function hpInterceptChecks() {
2398         // modify the function which handles checks
2399         //      JBC:    none
2400         //      JCloze  CheckAnswers()
2401         //              NB: Rottmeier Find-It 3a: CheckText(GapState,GapId)
2402         //      JCross  none
2403         //      JMatch  HP5 v3, v5, v6: CheckAnswer(), HP5 v4: CheckResults(), HP6: CheckAnswers()
2404         //      JMix    CheckAnswer(CheckType)
2405         //      JQuiz
2406         //              HP5: CheckAnswer(ShowHint, QNum)
2407         //              HP6: CheckMCAnswer, CheckMultiSelAnswer, CheckShortAnswer
2408         //      Rhubarb  CheckWord(InputWord)
2409         //      Sequitur CheckAnswer(Chosen, Btn)
2410         // HP6 JQuiz has three "Check Answer" functions
2411         var f = new Array('CheckMCAnswer', 'CheckMultiSelAnswer', 'CheckShortAnswer');
2412         for (var i=0; i<f.length; i++) {
2413                 if (eval('window.' + f[i])) {
2414                         var a = getFuncArgs(f[i], true);
2415                         var x = "";
2416                         if (f[i]=='CheckMCAnswer') {
2417                                 x += "var args=new Array(QNum,I[QNum][3][ANum][0]);";
2418                         } else if (f[i]=='CheckShortAnswer') {
2419                                 x += ""
2420                                 + "var obj=document.getElementById('Q_'+QNum+'_Guess');"
2421                                 + "var args=new Array(QNum,obj.value);"
2422                                 ;
2423                         } else if (f[i]=='CheckMultiSelAnswer') {
2424                                 x += ""
2425                                 + "var g='';"
2426                                 + "for (var ANum=0; ANum<I[QNum][3].length; ANum++){"
2427                                 +       "var obj=document.getElementById('Q_'+QNum+'_'+ANum+'_Chk');"
2428                                 +       "if (obj.checked)g+=(g?'&#43;':'')+I[QNum][3][ANum][0];"
2429                                 + "}"
2430                                 + "var args=new Array(QNum,g);"
2431                                 ;
2432                         }
2433                         if (x) {
2434                                 x = "if(!Finished&&State[QNum].length&&State[QNum][0]<0){" + x + "hpClick(3,args)}";
2435                                 var s = getFuncCode(f[i], x, '', true);
2436                 hpNewFunction(f[i], a, s);
2437                         }
2438                 }
2439         }
2440         var f = ''; // function name
2441         var x = ''; // extra code, if any
2442         if (window.CheckAnswer) {
2443                 f = 'CheckAnswer';
2444                 var a = getFuncArgs(f, true);
2445                 if (a[0]=='ShowHint') {
2446                         if (a[1]=='QNum') {
2447                                 // JQuiz v3, v5-v6[HP5]
2448                                 x = 'if(!ShowHint&&Status[QNum][0]<1)hpClick(3,QNum);';
2449                         } else {
2450                                 // JQuiz v4
2451                                 x = 'if(!ShowHint&&State[QNum-1][0]<1)hpClick(3,QNum-1);'; // QNum is a global variable
2452                         }
2453                 } else if (a[0]=='CheckType') {
2454                         // JMix v5-v6
2455                         x = 'if(CheckType==0)hpClick(3,0);'; // question number is always 0;
2456                 } else if (a[0]=='Chosen') {
2457                         // Sequitur
2458                         x = 'if (!(CurrentNumber==TotalSegments||AllDone||Btn.innerHTML==IncorrectIndicator))hpClick(3,Chosen);';
2459                 }
2460         } else if (window.CheckWord) {
2461                 f = 'CheckWord';
2462                 var a = getFuncArgs(f, true);
2463                 if (a[0]=='InputWord') {
2464                         // Rhubarb
2465                         x = 'if(!window.AllDone)hpClick(3,InputWord);';
2466                 }
2467         } else if (window.CheckText && !window.CheckAnswers) { // Rottmeier Find-It (3a)
2468                 f = 'CheckText';
2469                 var a = getFuncArgs(f, true);
2470                 if ((a[0]=='bool' && a[1]=='item') || (a[0]=='GapState' && a[1]=='GapId')) {
2471                         x = 'if(!window.Finished&&'+a[0]+')hpClick(3,'+a[1]+');';
2472                 }
2473         }
2474         if (f) {
2475                 var s = getFuncCode(f, x, '', true);
2476         hpNewFunction(f, a, s);
2477         }
2478         // JMatch has three possible check functions, depending on the version
2479         // (NB: other quiz types also have these functions)
2480         var f = new Array('CheckAnswers', 'CheckAnswer', 'CheckResults');
2481         for (var i=0; i<f.length; i++) {
2482                 if (eval('window.' + f[i])) {
2483                         var a = getFuncArgs(f[i], true);
2484                         if (a.length==0) {
2485                                 var s = getFuncCode(f[i], "hpClick(3);", '', true);
2486                 hpNewFunction(f[i], a, s);
2487                                 break; // out of the loop
2488                         }
2489                 }
2490         }
2492 // ***************
2493 //  fix IE5 and NS6
2494 // ***************
2495 // add Array.push if required (allows v6 quizzes to run on ie5win)
2496 if (Array.prototype && Array.prototype.push==null) {
2497         Array.prototype.push = new Function("x", "this[this.length]=x");
2499 // add attachEvent function, if required (allows HP5 v6 quizzes to run on ie5mac)
2500 //      NOTE: to allow v6 quizzes on ie5mac, the following code
2501 //      needs to be inserted BEFORE the Hot Potatoes javascript
2502 if (window.attachEvent==null) {
2503         window.attachEvent = new Function('evt', 'fn', 'eval("window."+evt+"="+fn)');
2505 if (document.attachEvent==null) {
2506         document.attachEvent = new Function('evt', 'fn', 'eval("document."+evt+"="+fn)');
2508 // fix the ShowMessage function for NS6
2509 // by removing calls to a button's "focus()" method
2510 if (navigator.userAgent.indexOf("Netscape6")>=0 && window.ShowMessage) {
2511         var s = ShowMessage.toString();
2512         var r = new RegExp('document\\.getElementById\\((\'|")FeedbackOKButton(\'|")\\)\\.focus\\(\\);', 'gi');
2513         s = s.substring(s.indexOf('{')+1, s.lastIndexOf('}')).replace(r, '');
2514         window.ShowMessage = new Function('Feedback', s);
2516 // ns6.0 (in JMix at least) has an error in the FocusAButton function too
2517 // this could be fixed as follows ...
2518 //if (window.FocusAButton) {
2519 //      window.FocusAButton = new Function('return true');
2521 // however, ns6.0 then crashes completely when the mouse moves over a link, so don't bother
2522 // Hot Potatoes quiz sniffing
2523 // === v3 ===
2524 // JBC uses "QuizForm", which contains elements called "Q*_**" (* and ** start at 1)
2525 // JCloze uses "Cloze" form
2526 // JCross uses "Crossword" form
2527 // JMatch uses "QuizForm", which contains elements called "1,2,3..x" and "x+1,x+2...",
2528 //      and "CheckForm" form, which contains an element called "ScoreBox"
2529 //      it is also the only HP quiz type to use an array called "CorrectAnswers"
2530 // JQuiz uses "QForm*" forms (* starts at 1), which each contain an element called "Guess"
2531 // === v4 ===
2532 // JBC uses "QForm" form in "QuestionDiv", which contains elements called "FB* (* starts at 0)"
2533 // JCloze uses "Cloze" form in "QuestionDiv"
2534 // JCross uses "Crossword" form in "CWDiv"
2535 // JMatch uses "ExCheck" form in "TitleDiv"
2536 // (no JMix in hp4)
2537 // JQuiz uses "QForm" form in "QuestionDiv", which contains an element called "Answer"
2538 // === v5 ===
2539 // JBC uses "QForm" form, which contains elements called "FB_*_**" (* and ** start at 0)
2540 // JCloze uses "Cloze" form
2541 // JCross writes out "AnswerForm" from a variable called "GetAnswerOpener"
2542 //      HP5.3: uses "AnswerForm" in "BottomFrame"
2543 //      HP5.5: uses "AnswerForm" in "TopFrame", but it is only there when an answer is being input
2544 // JMatch uses "QForm" form, which contains elements called "sel*" (which disappear by the time the quiz is finished)
2545 // JMix uses "ButtonForm"
2546 // JQuiz uses "QForm*" and "Buttons*" (one for each question)
2547 // === v6 ===
2548 // JBC uses "QForm" form (elements have no name or id)
2549 // JCloze uses "Cloze" form (elements have no name or id)
2550 // JCross does not use any forms,
2551 //      HP5: has "GridDiv" in "MainDiv"
2552 //      HP6: has "Clues" table in "MainDiv"
2553 // JMatch has "MatchDiv" in "MainDiv"
2554 //      HP5: uses "QForm" form, which contains elements called "sel*"
2555 //      HP6: uses "QForm" form, which contains elements called "s*_**"
2556 // JMix does not use any forms, but has "SegmentDiv" in "MainDiv"
2557 // JQuiz
2558 //      HP5: uses "QForm" form, which contains an element called "Guess"
2559 //      HP6: has "Questions" ordered list in "MainDiv"
2560 // === v6+ ===
2561 // JMatch has DIVs called "D*" and  "F*" (* starts at 0)
2562 // JMix has DIVs called "D*" and  "Drop*" (* starts at 0)
2563 // useful sniffing tools (Cut and Paste to browser address box)
2564 //javascript:var s="";var x=new quiz_obj();for(X in x)s+=","+X+"="+x[X];alert(s.substring(1));
2565 //javascript:var s="";var x=document.layers;for(var i=0;i<x.length;i++)s+=","+x[i].name;alert(s.substring(1))
2566 //javascript:var s="";var x=document.forms;for(var i=0;i<x.length;i++)s+=","+x[i].id;alert(s.substring(1))
2567 //javascript:var s="";var x=document.forms;for(var i=0;i<x.length;i++)s+=","+x[i].name;alert(s.substring(1))
2568 //javascript:var s="";var x=document.forms.QForm.elements;for(var i=0;i<x.length;i++)s+=","+x[i].id;alert(s.substring(1))
2569 //javascript:var s="";var x=document.forms.QForm.elements;for(var i=0;i<x.length;i++)s+=","+x[i].name;alert(s.substring(1))
2570 function hpDetectQuiz() {
2571         // "sniff" (=detect) the quiz's type and intended browser version
2572         // and cache the values in a global variable called "quiz"
2573         // Hot Potatoes version
2574         //      5 : HP5.3 (mac) or HP5.5 (win)
2575         //      6 : HP6.0 (mac) or HP6.0 (win)
2576         // intended browser version
2577         //      3   : ns3, ie3 (frames)
2578         //      4   : ns4, ie4 (cross browser dhtml)
2579         //      5   : ie5 (frames, send results via CGI)
2580         //      6   : ie6, op7, gecko (w3 standards)
2581         //      6.1 : "drag and drop" versions of JMatch and JMix v6
2582         // quiz type
2583         //      0 : unknown
2584         //      1 : jbc
2585         //      2 : jcloze
2586         //      3 : jcross
2587         //      4 : jmatch
2588         //      5 : jmix
2589         //      6 : jquiz
2590         //      7 : rhubarb (TexToys)
2591         //      8 : Sequitur (TexToys)
2592         // rottmeier quiz type
2593         //      1 : drop-down (JCloze)
2594         //      2 : find-it (JCloze)
2595         // shortcut to window object
2596         var w = window;
2597         // create the global "quiz" object, if necessary
2598         if (!w.quiz) w.quiz = new Object();
2599         // Hot Potatoes version
2600         //      HP6 v6:    Client()
2601         //      HP5 v4-v5: BrowserCheck()
2602         //          v3:    WinStringToMac() [JCloze, JCross, JQuiz]
2603         //          v3:    winrightchar     [JBC, JMatch]
2604         //          v3:    DownTime()       [JBC, JCloze, JQuiz]
2605         if (!quiz.hp) {
2606                 quiz.hp = (w.Client) ? 6 : (w.BrowserCheck) ? 5 : (w.WinStringToMac || w.winrightchar) ? 5 : -1;
2607         }
2608         // check the version and type are not already set
2609         if (!quiz.v || !quiz.t) {
2610                 // initialize version and type
2611                 var v = 0;
2612                 var t = 0;
2613                 // set shortcuts to DOM objects
2614                 var d = document;
2615                 var f = d.forms;
2616                 if (f.QuizForm && f.CheckForm && w.CorrectAnswers) {
2617                         v = 3;
2618                         t = 4; // jmatch
2619                 } else if (w.FeedbackFrame && w.CodeFrame) {
2620                         v = 3;
2621                         f = CodeFrame.document.forms;
2622                         t = (f.QuizForm) ? 1 : (f.Cloze) ? 2 : (f.Crossword) ? 3 : (f.QForm1) ? 6 : 0;
2623                 } else if (w.DynLayer) {
2624                         v = 4;
2625                         if (d.layers) {
2626                                 // for NS4, adjust "f" to point to a forms object in a layer
2627                                 var lyr = d.QuestionDiv || d.CWDiv || d.TitleDiv || null;
2628                                 if (lyr) f = lyr.document.forms;
2629                         }
2630                         t = (f.QForm && f.QForm.FB0) ? 1 : (f.Cloze) ? 2 : (f.Crossword) ? 3 : (f.ExCheck) ? 4 : (f.QForm && f.QForm.Answer) ? 6 : 0;
2631                 } else if (w.TopFrame && w.BottomFrame) {
2632                         v = 5;
2633                         f = BottomFrame.document.forms;
2634                         t = (f.QForm && f.QForm.elements[0].name.substring(0,3)=='FB_') ? 1 : (f.Cloze) ? 2 : (w.GetAnswerOpener && GetAnswerOpener.indexOf('AnswerForm')>=0) ? 3 : (f.QForm && w.RItems) ? 4 : (f.ButtonForm) ? 5 : (f.QForm0 && f.Buttons0) ? 6 : 0;
2635                 } else if (hpObj(d, 'MainDiv')) {
2636                         v = 6;
2637                         var obj = (f.QForm) ? f.QForm.elements : null;
2638                         t = (obj && obj.length>0 && obj[0].id=='') ? 1 : (f.Cloze) ? 2 : (hpObj(d, 'GridDiv') || hpObj(d, 'Clues')) ? 3 : hpObj(d, 'MatchDiv') ? 4 : hpObj(d, 'SegmentDiv') ? 5 : ((f.QForm && f.QForm.Guess) || hpObj(d, 'Questions')) ? 6 : 0;
2639                 } else if (hpObj(d, 'D0')) {
2640                         v = 6.1; // drag and drop (HP5 and HP6)
2641                         t = (hpObj(d, 'F0')) ? 4 : (hpObj(d, 'Drop0')) ? 5 : 0;
2642                 } else if (w.Words && f.Rhubarb) {
2643                         v = 6;
2644                         t = 7; // rhubarb (TexToys)
2645                 } else if (w.Segments && hpObj(d, 'Story')) {
2646                         v = 6;
2647                         t = 8; // sequitur (TexToys)
2648                 }
2649                 quiz.v = v; // intended browser version
2650                 quiz.t = t; // quiz type
2651         }
2653 function hpRottmeier() {
2654         hpDetectQuiz();
2655         if (typeof(quiz.r)=='undefined') { // first-time only
2656                 quiz.r = 0;
2657                 if (quiz.t==2) { // JCloze
2658                         if (quiz.hp==5) { // HP5
2659                                 // ??
2660                         } else if (quiz.hp==6) { // HP6
2661                                 if (window.Create_StateArray) { // Rottmeier
2662                                         var obj = new Create_StateArray();
2663                                         if (typeof(obj.GapLocked)=='boolean') {
2664                                                 quiz.r = 1; // drop-down (v2.4)
2665                                         } else if (typeof(obj.ErrorFound)=='boolean') {
2666                                                 if (typeof(obj.GapSolved)!='boolean') {
2667                                                         quiz.r = 2.1; // find-it (v3.1a)
2668                                                 } else {
2669                                                         quiz.r = 2.2; // find-it (v3.1b)
2670                                                 }
2671                                         }
2672                                         obj = null; // prevents memory leakage on some versions of IE
2673                                 }
2674                         }
2675                 }
2676         }
2677         return quiz.r;
2679 function hpVersion() {
2680         hpDetectQuiz();
2681         return quiz.hp;
2683 function hpQuizType() {
2684         hpDetectQuiz();
2685         return quiz.t;
2687 function hpQuizVersion() {
2688         hpDetectQuiz();
2689         return quiz.v;
2691 function hpScoreEngine(score_i, a, s, aa, ss, count_c, count_i) {
2692         // calculate the score for the quiz so far
2693         // score_i : amount by which to increment "score"
2694         // a  : outer array
2695         // s  : condition, if any, on outer array (=a)
2696         //      if true, the score will be incremented by "score_i"
2697         // aa : inner array, if any
2698         // ss : condition, if any, on inner array (=aa)
2699         // count_c : condition, if any, on which "count" is to be incremented
2700         // count_i : amount by which to increment "count"
2701         // "a" and "aa" may be passed as arrays or strings containing the name of an array
2702         // "s" and "ss" are strings containing an expression to be eval(uated)
2703         // "score_i", "count_i" and "count_c" strings containing an expression to be eval(uated)
2704         var score = 0;
2705         var count = 0;
2706         // set default condition to increment "count", and amount by which to increment the count
2707         if (count_c==null) count_c = "true";
2708         if (count_i==null) count_i = "1";
2709         // set length of outer array. if any
2710         var l = (typeof(a)=="string") ? eval(a + ".length") : a ? a.length : 0;
2711         // loop through outer array
2712         for (var i=0; i<l; i++) {
2713                 if (s==null && ss==null) {
2714                         score += eval(score_i);
2715                         if (eval(count_c)) count += eval(count_i);
2716                 } else if (s) {
2717                         score += eval(s) ? eval(score_i) : 0;
2718                         if (eval(count_c)) count += eval(count_i);
2719                 } else if (ss) {
2720                         // set length of inner array, if any
2721                         var ll = (typeof(aa)=="string") ? eval(aa + ".length") : aa ? aa.length : 0;
2722                         // loop through inner array. checking inner condition
2723                         for (var ii=0; ii<ll; ii++) {
2724                                 score += eval(ss) ? eval(score_i) : 0;
2725                                 if (eval(count_c)) count += eval(count_i);
2726                         }
2727                 }
2728         }
2729         if (count) {
2730                 // get p(enalties) for JCross and JMatch (and JMix ?)
2731                 if (window.Penalties) {
2732                         score -= (Penalties - (hpFinished() ? 0 : 1));
2733                 }
2734                 // adjust count for Find-It 3a and 3b
2735                 if (window.TotWrongChoices) {
2736                         if (window.CheckText && !window.CheckAnswers) { // Find-It 3a
2737                                 // this seems a little odd, but will replicate behavior of CalculateScore()
2738                                 count = score + TotWrongChoices;
2739                         } else {
2740                                 count += TotWrongChoices;
2741                         }
2742                 }
2743                 score = Math.floor(100*score/count);
2744                 if (score<0) { // just in case
2745                         score = 0;
2746                 }
2747         }
2748         return score;
2750 function hpScore() {
2751         var x = ''; // score
2752         var hp = hpVersion();
2753         var t = hpQuizType();
2754         var v = hpQuizVersion();
2755         if (t==1) { // jbc
2756                 if (v==3) x = hpScoreEngine(1, DoneStatus, "i>0 && a[i]=='0'"); // doesn't work
2757                 else if (v==4) x = hpScoreEngine(1, DoneStatus, "a[i]==0");    // doesn't work
2758                 else if (v==5 || v==6) x = hpScoreEngine("a[i][3]", Status, "a[i][3]");
2759         } else if (t==2) { // jcloze
2760                 if (v==3 || v==4) x = hpScoreEngine("a[i]", Scores);
2761                 else if (hp==5) x = hpScoreEngine("a[i][3]", State); // v==5 && v==6
2762                 else if (hp==6) {
2763                         var r = hpRottmeier();
2764                         if (r==0) x = x = hpScoreEngine("a[i].ItemScore", State);
2765                         else if (r==1) x = hpScoreEngine("a[i][1].Score", GapList, "a[i][1].GapLocked"); // dropdown
2766                         else if (r==2.1) x = hpScoreEngine(1, GapList, "a[i][1].ErrorFound"); // Find-It 3a
2767                         else if (r==2.2) x = hpScoreEngine("a[i][1].Score", GapList, "a[i][1].GapSolved"); // Find-It 3b
2768                 }
2769         } else if (t==3) { // jcross
2770                 if (v==3) x = hpScoreEngine(1, CorrectAnswers, "document.QuizForm.elements[i*2].selectedIndex==a[i]");
2771                 else if (v==4) x = hpScoreEngine(1, WinLetters, "ConvertCase(GetBoxValue(i),1).charAt(0)==a[i].charAt(0)");
2772                 else if (v==5 || v==6) {
2773             if (window.CaseSensitive) { // HP 6.2
2774                 x = hpScoreEngine(1, L, "", "L[i]", "L[i][ii] && L[i][ii]==G[i][ii]", "L[i][ii]");
2775             } else {
2776                 x = hpScoreEngine(1, L, "", "L[i]", "L[i][ii] && L[i][ii].toUpperCase()==G[i][ii].toUpperCase()", "L[i][ii]");
2777             }
2778         }
2779         } else if (t==4) { // jmatch
2780                 if (v==3) x = hpScoreEngine(1, CorrectAnswers, "document.QuizForm.elements[i*2].selectedIndex==a[i]");
2781                 else if (v==4) x = hpScoreEngine(1, Draggables, "a[i].correct=='1'");
2782                 else if (v==5) x = hpScoreEngine(1, I, "I[i][2]<1 && I[i][0].length>0 && Status[i][0]==1");
2783                 else if (v==6) x = hpScoreEngine(1, Status, "Status[i][0]==1");
2784                 else if (v==5.1 || v==6.1) x = hpScoreEngine(1, D, "D[i][2]==D[i][1] && D[i][2]>0", "", "", "i<F.length");
2785         } else if (t==5) { // jmix
2786                 // there was no v3 or v4 of JMix
2787                 if (v==5 || v==6 || v==6.1) x = Math.floor(100*(Segments.length-Penalties)/Segments.length);
2788         } else if (t==6) { // jquiz
2789                 if (hp==5) {
2790                         if (v==3 || v==4) x = hpScoreEngine("a[i][4]/10", State, "a[i][0]==1");
2791                         else if (v==5 || v==6) x = hpScoreEngine("a[i][4]/10", Status, "a[i][0]==1", "", "", "true", "1");
2792                 } else if (hp==6) {
2793                         if (v==6) x = hpScoreEngine("I[i][0]*a[i][0]", State, "a[i]&&a[i][0]>=0", "", "", "a[i]", "I[i][0]");
2794                 }
2795         } else if (t==7) { // rhubarb
2796                 if (v==6) {
2797                         x = Math.floor(100*Correct/TotalWords);
2798                 }
2799         } else if (t==8) { // sequitur
2800                 if (v==6) {
2801                         var myTotalPoints = TotalPoints - (hpFinished() ? 0 : (OptionsThisQ-1));
2802                         x = Math.floor(100*ScoredPoints/myTotalPoints);
2803                 }
2804         }
2805         return x; // result
2807 function hpFinishedEngine(a, s, aa, ss) {
2808         // determine whether or not all quistions in a quiz are finished
2809         // a  : outer array
2810         // s  : condition, if any, on outer array
2811         //      if true for any element in "a", the quiz is NOT finished
2812         // aa : inner array, if any
2813         // ss : condition, if any, on inner array
2814         //      if true for any element in "aa", the quiz is NOT finished
2815         // the arrays "a" and "aa" may be passed as arrays or strings to be eval(uated)
2816         // the conditions "s" and "ss" are specified as strings to be eval(uated)
2817         // assume a positive result
2818         var x = true;
2819         // set length of outer array. if any
2820         var l = (typeof(a)=="string") ? eval(a + ".length") : a ? a.length : 0;
2821         // loop through outer array
2822         for (var i=0; i<l; i++) {
2823                 // do outer condition, if any
2824                 if (s && eval(s)) x = false;
2825                 // set length of inner array, if any
2826                 var ll = (typeof(aa)=="string") ? eval(aa + ".length") : aa ? aa.length : 0;
2827                 // loop through inner array. checking inner condition
2828                 for (var ii=0; ii<ll; ii++) {
2829                         if (ss && eval(ss)) x = false;
2830                 }
2831         }
2832         return x;
2834 function hpTimedOut() {
2835         // v5 uses "min" and "sec"
2836         // v6 uses Seconds
2837         return (typeof(self.Seconds)=='number' && Seconds==0) || (typeof(self.min)=='number' && min==0 && typeof(self.sec)=='number' && sec==0);
2839 function hpFinished() {
2840         // assume false result
2841         var x = false;
2842         var hp = hpVersion();
2843         var t = hpQuizType();
2844         var v = hpQuizVersion();
2845         if (t==1) { // jbc
2846                 if (v==3) x = hpFinishedEngine(DoneStatus, "i>0 && a[i]=='0'");
2847                 else if (v==4) x = hpFinishedEngine(DoneStatus, "a[i]==0");
2848                 else if (v==5 || v==6) x = hpFinishedEngine(Status, "a[i][0]==0");
2849         } else if (t==2) { // jcloze
2850                 var r = hpRottmeier();
2851                 if (r==1) x = hpFinishedEngine(GapList, "a[i][1].GapLocked==false"); // drop-down
2852                 else if (r==2.1) x = hpFinishedEngine(GapList, "a[i][1].ErrorFound==false"); // find-it 3a
2853                 else if (r==2.2) x = hpFinishedEngine(GapList, "a[i][1].GapSolved==false"); // find-it 3b
2854                 else if (v==3 || v==4 || v==5 || v==6) x = hpFinishedEngine(I, "CheckAnswer(i)==-1");
2855                 // also:   else if (v==5 || v==6) x = hpFinishedEngine(State, "a[i][4]!=1")
2856         } else if (t==3) { // jcross
2857                 if (v==3) x = hpFinishedEngine(document.Crossword.elements, "ConvertCase(is.mac?unescape(MacStringToWin(a[i].value)):a[i].value,1)!=Letters[i]");
2858                 else if (v==4) x = hpFinishedEngine(WinLetters, "ConvertCase(GetBoxValue(i),1).charAt(0) != a[i].charAt(0)");
2859                 else if (v==5 || v==6) {
2860             if (window.CaseSensitive) { // 6.2
2861                 x = hpFinishedEngine(L, "", "L[i]", "L[i][ii] && L[i][ii]!=G[i][ii]");
2862             } else {
2863                 x = hpFinishedEngine(L, "", "L[i]", "L[i][ii] && L[i][ii].toUpperCase()!=G[i][ii].toUpperCase()");
2864             }
2865                 }
2866         } else if (t==4) { // jmatch
2867                 if (v==3) x = hpFinishedEngine(CorrectAnswers, "document.QuizForm.elements[i*2].selectedIndex != a[i]");
2868                 else if (v==4) x = hpFinishedEngine(Draggables, "a[i].correct!='1'");
2869                 else if (v==5) x = hpFinishedEngine(I, "I[i][2]<1 && I[i][0].length>0 && Status[i][0]<1 && GetAnswer(i)!=I[i][3]");
2870                 else if (v==6) x = hpFinishedEngine(Status, "Status[i][0]<1");
2871                 else if (v==5.1 || v==6.1) x = hpFinishedEngine(D, "", F, "F[ii][1]==D[i][1]&&D[i][1]!=D[i][2]");
2872         } else if (t==5) { // jmix
2873                 // there was no v3 or v4 of JMix
2874                 if (v==5 || v==6 || v==6.1) x = !hpFinishedEngine(Answers, "a[i].join(',')=='" + GuessSequence.join(',') + "'");
2875         } else if (t==6) { // jquiz
2876                 if (v==3 || v==4) x = hpFinishedEngine(State, "a[i][0]==0");
2877                 else if (v==5 || v==6) {
2878                         if (hp==5) x = hpFinishedEngine(Status, "a[i][0]<1");
2879                         else if (hp==6) x = hpFinishedEngine(State, "a[i] && a[i][0]<0");
2880                 }
2881         } else if (t==7) { // rhubarb
2882                 if (v==6) x = hpFinishedEngine(DoneList, "a[i]==1");
2883         } else if (t==8) { // sequitur
2884                 if (v==6) x = (CurrentNumber==TotalSegments || AllDone);
2885         }
2886         return x;
2888 function hpObj(d, id) {
2889         return d.getElementById ? d.getElementById(id) : d.all ? d.all[id] : d[id];
2891 function GetViewportHeight() {
2892         if (window.innerHeight) {
2893                 return innerHeight;
2894         } else {
2895                 if (hpIsStrict()) {
2896                         return document.documentElement.clientHeight;
2897                 } else {
2898                         return document.body.clientHeight;
2899                 }
2900         }
2902 function hpIsStrict() {
2903         if (!window.hpStrictIsSet) {
2904                 window.hpStrictIsSet = true;
2905                 window.hpStrict = false;
2906                 var s = document.compatMode;
2907                 if (s && s=="CSS1Compat") { // ie6
2908                         window.hpStrict = true;
2909                 } else {
2910                         var obj = document.doctype;
2911                         if (obj) {
2912                                 var s = obj.systemId || obj.name; // n6 || ie5mac
2913                                 if (s && s.indexOf("strict.dtd") >= 0) {
2914                                         window.hpStrict = true;
2915                                 }
2916                         }
2917                 }
2918         }
2919         return window.hpStrict;
2921 // **************
2922 //  initialization
2923 // **************
2924 hpInterceptFeedback();
2925 hpInterceptHints();
2926 hpInterceptClues();
2927 hpInterceptChecks();
2928 function hpFindForm(formname, w) {
2929         if (w==null) w = self;
2930     var f = w.document.getElementById(formname);
2931     if (f) {
2932         return f;
2933     }
2934     var f = w.document.forms[formname];
2935     if (f) {
2936         return f;
2937     }
2938         if (w.frames) {
2939                 for (var i=0; i<w.frames.length; i++) {
2940                                 var f = hpFindForm(formname, w.frames[i]);
2941                                 if (f) {
2942                     return f;
2943                 }
2944                 }
2945         }
2946         return null;
2948 function Finish(quizstatus) {
2949         var mark = hpScore();
2950         window.hpForm = hpFindForm('store');
2951         if (hpForm) { // LMS
2952                 hpForm.starttime.value = getTime(Start_Time);
2953                 hpForm.endtime.value = getTime();
2954                 hpForm.mark.value = mark;
2955                 hpForm.detail.value = '<?xml version="1.0"?><hpjsresult><fields>'+GetQuestionDetails()+'</fields></hpjsresult>';
2956                 if (hpForm.status) {
2957                         if (!quizstatus) {
2958                                 // 4=completed, 3=abandoned, 2=timed-out or 1=in-progress
2959                                 quizstatus = hpFinished() ? 4 : hpTimedOut() ? 2 : 1;
2960                         }
2961                         hpForm.status.value = quizstatus;
2962                 }
2963                 if (!window.hpQuizResultsSent) {
2964                         if (hpForm.status && quizstatus==4) {
2965                                 window.hpQuizResultsSent = true;
2966                         }
2967                         if (quizstatus==4) { // completed
2968                                 // wait 2 seconds for student to see feedback
2969                                 setTimeout("hpForm.submit();", 2000);
2970                         } else {
2971                                 hpForm.submit();
2972                         }
2973                 }
2974         } else if (hpFinished()) {
2975                 SendAllResults(mark);
2976         }
2978 // create form to send results
2979 if (DB[7] && DB[8] && !is_LMS()) {
2980         ResultForm = ''
2981                 + '<html><body>'
2982                 + '<form name="Results" action="" method="post" enctype="x-www-form-encoded">'
2983                 +       hpHiddenField('recipient', '')
2984                 +       hpHiddenField('subject', '')
2985                 +       hpHiddenField('Exercise', '')
2986                 +       hpHiddenField('realname', '')
2987                 +       hpHiddenField('Score', '')
2988                 +       hpHiddenField('Start_Time', '')
2989                 +       hpHiddenField('End_Time', '')
2990                 +       hpHiddenField('title', 'Thanks!')
2991                 + '</form>'
2992                 + '</body></html>'
2993         ;
2995 // reassign the StartUp function
2996 var p = getPrompt(window.GetUserName || window.StartUp);
2997 var c = getStartUpCode(window.StartUp);
2998 if (p && c) {
2999     if (window.C && C.safari) {
3000         eval('window.StartUp=function(){QuizLogin("' + p + '")}');
3001         eval('window.StartQuiz=function(){' + c + '}');
3002     } else {
3003         window.StartUp = new Function('QuizLogin("' + p + '")');
3004         window.StartQuiz = new Function(c);
3005     }
3006         // "QuizLogin" finshes by calling "StartQuiz"
3008 // reassign the SendResults function
3009 window.SendResults = SendAllResults;
3010 // set start time
3011 var Start_Time = new Date();
3012 //-->