Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

changes to database and most scripts to allow enhancements for HotPot…

… v2.1

  - click-by-click reporting
  - manipulate "chains" of hotpot activities
  - standardized reporting across all quiz types
  - several smaller features
  • Loading branch information...
commit 410229b6047f7cd45fd002c85f13ab8709c6a439 1 parent 1c02f70
gbateson authored
View
23 mod/hotpot/README.TXT
@@ -1,5 +1,10 @@
-This is v2.0.8 of the HotPot module
- It has been tested on Moodle 1.1 thru 1.5, MySQL and PostGres7 databases and PHP 4.1 thru 5.0.4
+This is v2.1.0 of the HotPot module
+ This module allows teachers to administer Hot Potatoes and TexToys quizzes via Moodle.
+ It has been tested on:
+ - Hot Potatoes 6
+ - Moodle 1.1 thru 1.5
+ - PHP 4.1 thru 5.0.4
+ - MySQL and PostgreSQL databases
This module may be distributed under the terms of the General Public License
(see http://www.gnu.org/licenses/gpl.txt for details)
@@ -9,7 +14,7 @@ This is v2.0.8 of the HotPot module
================
IMPORTANT NOTICE
================
-* Please be sure to use Hot Potatoes according to the conditions of use which are listed at the end of this file. If you restrict use via a required Moodle login, you most likely can still meet the 'freely available' condition if you make the same material on a separate URL that permits free access. Otherwise, please purchase a license.
+* Please be sure to use Hot Potatoes according to the conditions of use which are listed at the end of this file. If you restrict use via a required Moodle login, you most likely can still meet the 'freely available' condition if you make the same material on a separate URL that permits free access. Otherwise, please purchase a license.
TO INSTALL OR UPDATE THIS MODULE
@@ -121,13 +126,13 @@ HOT POTATOES CONDITIONS OF USE
**Reproduced from the Hot Potatoes site**
Hot Potatoes is offered free to the educational community by the University of Victoria Humanities Computing and Media Centre (formerly the Language Centre), under certain conditions. Hot Potatoes is free for use by state educational institutions which are non-profit making, on the condition that the material produced using the program is freely available to anyone via the WWW. However, you need to purchase a licence under any of the following conditions:
-? You do not work for a public sector educational establishment.
-? You charge money for access to the material you make with Hot Potatoes.
-? You restrict access to the material in some way. (The only exception here is if you have an account on www.hotpot.net, where you ARE allowed to use password restrictions.)
-? You want to use the Masher program included with the Hot Potatoes suite.
+* You do not work for a public sector educational establishment.
+* You charge money for access to the material you make with Hot Potatoes.
+* You restrict access to the material in some way. (The only exception here is if you have an account on www.hotpot.net, where you ARE allowed to use password restrictions.)
+* You want to use the Masher program included with the Hot Potatoes suite.
For more information on licences, and details on how to purchase one, check out our Website at:
- http://www.halfbakedsoftware.com/hotpot/
+ http://www.halfbakedsoftware.com/hotpot/
If you intend using the programs to generate more than a handful of exercises, please make sure you register. This costs you nothing -- see How to register for details.
-Martin Holmes, Half-Baked Software and the University of Victoria HCMC, 1998-2004.
+Martin Holmes, Half-Baked Software and the University of Victoria HCMC, 1998-2004.
View
145 mod/hotpot/attempt.php
@@ -2,8 +2,9 @@
require_once("../../config.php");
require_once("lib.php");
- $next_url = "";
$msg = '';
+ $next_url = "";
+ $quiz_is_finished = true;
$attemptid = required_param("attemptid");
if (is_numeric($attemptid)) {
@@ -33,50 +34,132 @@
// make sure this user is enrolled in this course
require_login($course->id);
- if ($attempt->timefinish && false) {
+ $time = time();
+ $msg = get_string('resultssaved', 'hotpot');
- $msg = 'This attempt has already been submitted';
+ // update attempt record fields using incoming data
+ $attempt->score = optional_param('mark', NULL, PARAM_INT);
+ $attempt->status = optional_param('status', NULL, PARAM_INT);
+ $attempt->details = optional_param('detail', NULL, PARAM_RAW);
+ $attempt->endtime = optional_param('endtime', NULL, PARAM_ALPHA);
+ $attempt->starttime = optional_param('starttime', NULL, PARAM_ALPHA);
+ $attempt->timefinish = $time;
- } else {
- $time = time();
- $msg = get_string('resultssaved', 'hotpot');
-
- $attempt->score = isset($_POST['mark']) ? $_POST['mark'] : NULL;
- $attempt->details = isset($_POST['detail']) ? $_POST['detail'] : NULL;
- $attempt->endtime = isset($_POST['endtime']) ? strtotime($_POST['endtime']) : NULL;
- $attempt->starttime = isset($_POST['starttime']) ? strtotime($_POST['starttime']) : NULL;
- $attempt->timefinish = $time;
-
- // for the rare case where a quiz was "in progress" during an update from hotpot v1 to v2
- if (empty($attempt->timestart) && !empty($attempt->starttime)) {
- $attempt->timestart = $attempt->starttime;
+ if ($attempt->endtime) {
+ $attempt->endtime = strtotime($attempt->endtime);
+ }
+ if ($attempt->starttime) {
+ $attempt->starttime = strtotime($attempt->starttime);
+ }
+
+ // set clickreportid, (for click reporting)
+ $attempt->clickreportid = $attempt->id;
+
+ if (empty($attempt->status)) {
+ if (empty($attempt->endtime)) {
+ $attempt->status = HOTPOT_STATUS_INPROGRESS;
+ } else {
+ $attempt->status = HOTPOT_STATUS_COMPLETED;
}
+ }
- // remove slashes added by lib/setup.php
- $attempt->details = stripslashes($attempt->details);
+ // for the rare case where a quiz was "in progress" during an update from hotpot v1 to v2
+ if (empty($attempt->timestart) && !empty($attempt->starttime)) {
+ $attempt->timestart = $attempt->starttime;
+ }
+
+
+ // check if this is the second (or subsequent) click
+ if (get_field("hotpot_attempts", "timefinish", "id", $attempt->id)) {
+
+ if ($hotpot->clickreporting==HOTPOT_YES) {
+ // add attempt record for each form submission
+ // records are linked via the "clickreportid" field
- // add details of this attempt
- hotpot_add_attempt_details($attempt);
+ // update status in previous records in this group
+ set_field("hotpot_attempts", "status", $attempt->status, "clickreportid", $attempt->clickreportid);
- // add slashes again, so the details can be added to the database
- $attempt->details = addslashes($attempt->details);
+ // add new attempt record
+ unset ($attempt->id);
+ $attempt->id = insert_record("hotpot_attempts", $attempt);
- if (! update_record("hotpot_attempts", $attempt)) {
- error("Could not update attempt record: ".$db->ErrorMsg(), $next_url);
+ if (empty($attempt->id)) {
+ error("Could not insert attempt record: ".$db->ErrorMsg(), $next_url);
+ }
+
+ // add attempt details record, if necessary
+ if (!empty($attempt->details)) {
+ unset($details);
+ $details->attempt = $attempt->id;
+ $details->details = $attempt->details;
+ if (! insert_record("hotpot_details", $details, false)) {
+ error("Could not insert attempt details record: ".$db->ErrorMsg(), $next_url);
+ }
+ }
+
+ } else {
+ // remove previous responses for this attempt
+ // (N.B. this does NOT remove the attempt record, just the responses)
+ $ok = delete_records("hotpot_responses", "attempt", $attempt->id);
}
+ }
- // set previously unfinished attempts of this quiz by this user to "finished"
- hotpot_close_previous_attempts($hotpot->id, $USER->id, $time);
+ // remove slashes added by lib/setup.php
+ $attempt->details = stripslashes($attempt->details);
- add_to_log($course->id, "hotpot", "submit", "review.php?id=$cm->id&attempt=$attempt->id", "$hotpot->id", "$cm->id");
+ // add details of this attempt
+ hotpot_add_attempt_details($attempt);
+
+ // add slashes again, so the details can be added to the database
+ $attempt->details = addslashes($attempt->details);
+
+ // update the attempt record
+ if (! update_record("hotpot_attempts", $attempt)) {
+ error("Could not update attempt record: ".$db->ErrorMsg(), $next_url);
}
- if ($hotpot->shownextquiz==HOTPOT_YES && is_numeric($next_cm = hotpot_get_next_cm($cm))) {
- $next_url = "$CFG->wwwroot/mod/hotpot/view.php?id=$next_cm";
+
+ // get previous attempt details record, if any
+ $details_exist = record_exists("hotpot_details", "attempt", $attempt->id);
+
+ // delete/update/add the attempt details record
+ if (empty($attempt->details)) {
+ if ($details_exist) {
+ delete_records("hotpot_details", "attempt", $attempt->id);
+ }
+ } else {
+ if ($details_exist) {
+ set_field("hotpot_details", "details", $attempt->details, "attempt", $attempt->id);
+ } else {
+ unset($details);
+ $details->attempt = $attempt->id;
+ $details->details = $attempt->details;
+ if (! insert_record("hotpot_details", $details)) {
+ error("Could not insert attempt details record: ".$db->ErrorMsg(), $next_url);
+ }
+ }
+ }
+
+ if ($attempt->status==HOTPOT_STATUS_INPROGRESS) {
+ $quiz_is_finished = false;
+
+ } else { // quiz is finished
+
+ add_to_log($course->id, "hotpot", "submit", "review.php?id=$cm->id&attempt=$attempt->id", "$hotpot->id", "$cm->id");
+
+ if ($hotpot->shownextquiz==HOTPOT_YES && is_numeric($next_cm = hotpot_get_next_cm($cm))) {
+ $next_url = "$CFG->wwwroot/mod/hotpot/view.php?id=$next_cm";
+ }
}
}
- // redirect to the next quiz or the course page
- redirect($next_url, $msg);
+ if ($quiz_is_finished) {
+ // redirect to the next quiz or the course page
+ redirect($next_url, $msg);
+ } else {
+ // continue the quiz
+ header("Status: 204");
+ header("HTTP/1.0 204 No Response");
+ }
// =================
View
229 mod/hotpot/backuplib.php
@@ -6,30 +6,27 @@
// This is the "graphical" structure of the hotpot mod:
//-----------------------------------------------------------
//
- // hotpot
- // (CL, pk->id, files)
+ // hotpot
+ // (CL, pk->id,
+ // fk->course, files)
// |
- // +--------------+--------------+
- // | |
- // | |
- // hotpot_attempts hotpot_questions
- // (UL, pk->id, (UL, pk->id,
- // fk->hotpot) fk->hotpot, text)
- // | | |
- // | | |
- // +--------------+--------------+ |
- // | |
- // | |
- // hotpot_responses |
- // (UL, pk->id, |
- // fk->attempt, question, |
- // correct, wrong, ignored) |
- // | |
- // | |
- // +-----------+-----------+
- // |
- // hotpot_strings
- // (UL, pk->id)
+ // +--------------+---------------+
+ // | |
+ // hotpot_attempts hotpot_questions
+ // (UL, pk->id, (UL, pk->id,
+ // fk->hotpot) fk->hotpot, text)
+ // | | |
+ // +-------------------+----------+ |
+ // | | |
+ // hotpot_details hotpot_responses |
+ // (UL, pk->id, (UL, pk->id, |
+ // fk->attempt) fk->attempt, question, |
+ // correct, wrong, ignored) |
+ // | |
+ // +-------+-------+
+ // |
+ // hotpot_strings
+ // (UL, pk->id)
//
// Meaning: pk->primary key field of the table
// fk->foreign key to link with parent
@@ -39,38 +36,37 @@
// files->table may have files
//
//-----------------------------------------------------------
- // It is not necessary to backup "questions", "responses"
- // and "strings", because they can be restored from the
- // "details" field of the "attempts" records
- //-----------------------------------------------------------
function hotpot_backup_mods($bf, $preferences) {
+ // $bf : resource id for b(ackup) f(ile)
+ // $preferences : object containing switches and settings for this backup
- $level = 3;
+ $level = 3;
$status = true;
$table = 'hotpot';
- $field = 'course';
- $value = $preferences->backup_course;
-
- $modtype = 'hotpot';
+ $select = "course=$preferences->backup_course";
$records_tag = '';
$records_tags = array();
$record_tag = 'MOD';
- $record_tags = array('MODTYPE'=>$modtype);
+ $record_tags = array('MODTYPE'=>'hotpot');
$excluded_tags = array();
$more_backup = '';
- if ($preferences->mods[$modtype]->userinfo) {
- $more_backup .= $modtype.'_backup_attempts($bf, $record, $level, $status);';
+ if ($preferences->mods['hotpot']->userinfo) {
+ $more_backup .= '$GLOBALS["hotpot_backup_string_ids"] = array();';
+ $more_backup .= '$status = hotpot_backup_attempts($bf, $record, $level, $status);';
+ $more_backup .= '$status = hotpot_backup_questions($bf, $record, $level, $status);';
+ $more_backup .= '$status = hotpot_backup_strings($bf, $record, $level, $status);';
+ $more_backup .= 'unset($GLOBALS["hotpot_backup_string_ids"]);'; // tidy up
}
return hotpot_backup_records(
$bf, $status, $level,
- $table, $field, $value,
+ $table, $select,
$records_tag, $records_tags,
$record_tag, $record_tags,
$excluded_tags, $more_backup
@@ -80,8 +76,7 @@ function hotpot_backup_attempts($bf, &$parent, $level, $status) {
// $parent is a reference to a hotpot record
$table = 'hotpot_attempts';
- $field = 'hotpot';
- $value = $parent->id;
+ $select = "hotpot=$parent->id";
$records_tag = 'ATTEMPT_DATA';
$records_tags = array();
@@ -90,25 +85,175 @@ function hotpot_backup_attempts($bf, &$parent, $level, $status) {
$record_tags = array();
$more_backup = '';
- $excluded_tags = array();
+ $more_backup .= 'hotpot_backup_details($bf, $record, $level, $status);';
+ $more_backup .= 'hotpot_backup_responses($bf, $record, $level, $status);';
+
+ $excluded_tags = array('hotpot');
+
+ return hotpot_backup_records(
+ $bf, $status, $level,
+ $table, $select,
+ $records_tag, $records_tags,
+ $record_tag, $record_tags,
+ $excluded_tags, $more_backup
+ );
+ }
+ function hotpot_backup_details($bf, &$parent, $level, $status) {
+ // $parent is a reference to an attempt record
+
+ $table = 'hotpot_details';
+ $select = "attempt=$parent->id";
+
+ $records_tag = '';
+ $records_tags = array();
+
+ $record_tag = '';
+ $record_tags = array();
+
+ $more_backup = '';
+ $excluded_tags = array('id','attempt');
+
+ return hotpot_backup_records(
+ $bf, $status, $level,
+ $table, $select,
+ $records_tag, $records_tags,
+ $record_tag, $record_tags,
+ $excluded_tags, $more_backup
+ );
+ }
+ function hotpot_backup_responses($bf, &$parent, $level, $status) {
+ // $parent is a reference to an attempt record
+
+ $table = 'hotpot_responses';
+ $select = "attempt=$parent->id";
+
+ $records_tag = 'RESPONSE_DATA';
+ $records_tags = array();
+
+ $record_tag = 'RESPONSE';
+ $record_tags = array();
+
+ $more_backup = 'hotpot_backup_string_ids($record, array("correct","wrong","ignored"));';
+ $excluded_tags = array('id','attempt');
+
+ return hotpot_backup_records(
+ $bf, $status, $level,
+ $table, $select,
+ $records_tag, $records_tags,
+ $record_tag, $record_tags,
+ $excluded_tags, $more_backup
+ );
+ }
+ function hotpot_backup_questions($bf, &$parent, $level, $status) {
+ // $parent is a reference to an hotpot record
+
+ $table = 'hotpot_questions';
+ $select = "hotpot=$parent->id";
+
+ $records_tag = 'QUESTION_DATA';
+ $records_tags = array();
+
+ $record_tag = 'QUESTION';
+ $record_tags = array();
+
+ $more_backup = 'hotpot_backup_string_ids($record, array("text"));';
+ $excluded_tags = array('hotpot');
return hotpot_backup_records(
$bf, $status, $level,
- $table, $field, $value,
+ $table, $select,
$records_tag, $records_tags,
$record_tag, $record_tags,
$excluded_tags, $more_backup
);
}
+ function hotpot_backup_string_ids(&$record, $fields) {
+ // as the questions and responses tables are backed up
+ // this function is called to store the ids of strings.
+ // The string ids are used later by "hotpot_backup_strings"
+ // $GLOBALS['hotpot_backup_string_ids'] was initialized in "hotpot_backup_mods"
+
+ // store the ids of strings used in this $record's $fields
+ foreach ($fields as $field) {
+ if (empty($record->$field)) {
+ // do nothing
+ } else {
+ $value = $record->$field;
+ $ids = explode(',', "$value");
+ foreach ($ids as $id) {
+ if (empty($id)) {
+ // do nothing
+ } else {
+ $GLOBALS['hotpot_backup_string_ids'][$id] = true;
+ }
+ }
+ }
+ }
+ }
+ function hotpot_backup_strings($bf, $record, $level, $status) {
+ // This functions backups the strings used
+ // in the question and responses for a single hotpot activity
+ // The ids of the strings were stored by "hotpot_backup_string_ids"
+ // $GLOBALS['hotpot_backup_string_ids'] was initialized in "hotpot_backup_mods"
+
+ // retrieve $ids of strings to be backed up
+ $ids = array_keys($GLOBALS['hotpot_backup_string_ids']);
+
+ if (empty($ids)) {
+ // no strings to backup
+ } else {
+
+ sort($ids);
+ $ids = implode(',', $ids);
+
+ $table = 'hotpot_strings';
+ $select = "id IN ($ids)";
+
+ $records_tag = 'STRING_DATA';
+ $records_tags = array();
+
+ $record_tag = 'STRING';
+ $record_tags = array();
+
+ $more_backup = '';
+ $excluded_tags = array('');
+
+ $status = hotpot_backup_records(
+ $bf, $status, $level,
+ $table, $select,
+ $records_tag, $records_tags,
+ $record_tag, $record_tags,
+ $excluded_tags, $more_backup
+ );
+ }
+ return $status;
+ }
+
+ function hotpot_backup_records(&$bf, $status, $level, $table, $select, $records_tag, $records_tags, $record_tag, $record_tags, $excluded_tags, $more_backup) {
+ // general purpose backup function
+
+ // $bf : resource id of backup file
+ // $status : current status of backup (true or false)
+ // $level : current depth level in the backup XML tree
+
+ // $table : table from which records will be selected and backed up
+ // $select : SQL selection string
+
+ // $records_tag : optional XML tag which starts a group of records (and descends a level)
+ // $records_tags : optional XML tags to be inserted at the start of a group of records
+
+ // $record_tag : optional XML tag which starts a record (and descends a level)
+ // $record_tags : optional XML tags to be inserted at the start of a record
- function hotpot_backup_records(&$bf, $status, $level, $table, $field, $value, $records_tag, $records_tags, $record_tag, $record_tags, $excluded_tags, $more_backup) {
+ // $excluded_tags : fields which will NOT be backed up from the records
+ // $more_backup : optional PHP code to be eval(uated) for each record
// If any of the "fwrite" statements fail,
// no further "fwrite"s will be attempted
// and the function returns "false".
// Otherwise, the function returns "true".
- if ($status && ($records = get_records($table, $field, $value, 'id'))) {
+ if ($status && ($records = get_records_select($table, $select, 'id'))) {
// start a group of records
if ($records_tag) {
View
6 mod/hotpot/db/mysql.php
@@ -15,6 +15,12 @@ function hotpot_upgrade($oldversion) {
$ok = $ok && hotpot_update_to_v2_from_v1();
}
+ // update to HotPot v2.1
+ if ($oldversion < 2005090700) {
+ $ok = $ok && hotpot_get_update_to_v2();
+ $ok = $ok && hotpot_update_to_v2_1();
+ }
+
return $ok;
}
function hotpot_get_update_to_v2() {
View
51 mod/hotpot/db/mysql.sql
@@ -4,7 +4,7 @@
CREATE TABLE prefix_hotpot (
id int(10) unsigned NOT NULL auto_increment,
- course int(10) unsigned NOT NULL,
+ course int(10) unsigned NOT NULL default '0',
name varchar(255) NOT NULL default '',
reference varchar(255) NOT NULL default '',
summary text NOT NULL,
@@ -23,6 +23,9 @@ CREATE TABLE prefix_hotpot (
forceplugins int(4) unsigned NOT NULL default '0',
password varchar(255) NOT NULL default '',
subnet varchar(255) NOT NULL default '',
+ clickreporting tinyint(4) unsigned NOT NULL default '0',
+ studentfeedback tinyint(4) unsigned NOT NULL default '0',
+ studentfeedbackurl varchar(255) default NULL,
PRIMARY KEY (id)
) TYPE=MyISAM COMMENT='details about Hot Potatoes quizzes';
@@ -32,19 +35,33 @@ CREATE TABLE prefix_hotpot (
CREATE TABLE prefix_hotpot_attempts (
id int(10) unsigned NOT NULL auto_increment,
- hotpot int(10) unsigned NOT NULL,
- userid int(10) unsigned NOT NULL,
+ hotpot int(10) unsigned NOT NULL default '0',
+ userid int(10) unsigned NOT NULL default '0',
starttime int(10) unsigned default NULL,
endtime int(10) unsigned default NULL,
score int(6) unsigned default NULL,
penalties int(6) unsigned default NULL,
attempt int(6) unsigned NOT NULL default '0',
- details text,
timestart int(10) unsigned default NULL,
timefinish int(10) unsigned default NULL,
- PRIMARY KEY (id)
+ status tinyint(4) unsigned NOT NULL default '1',
+ clickreportid int(10) unsigned default NULL,
+ PRIMARY KEY (id),
+ KEY prefix_hotpot_attempts_hotpot_idx (hotpot),
+ KEY prefix_hotpot_attempts_userid_idx (userid)
) TYPE=MyISAM COMMENT='details about Hot Potatoes quiz attempts';
+#
+# Table structure for table `hotpot_details`
+#
+
+CREATE TABLE prefix_hotpot_details (
+ id int(10) unsigned NOT NULL auto_increment,
+ attempt int(10) unsigned NOT NULL,
+ details text,
+ PRIMARY KEY (id),
+ KEY prefix_hotpot_details_attempt_idx (attempt)
+) TYPE=MyISAM COMMENT='raw details (as XML) of Hot Potatoes quiz attempts';
#
# Table structure for table `hotpot_questions`
@@ -52,22 +69,23 @@ CREATE TABLE prefix_hotpot_attempts (
CREATE TABLE prefix_hotpot_questions (
id int(10) unsigned NOT NULL auto_increment,
- name varchar(255) NOT NULL default '',
- type int(10) unsigned NOT NULL default '0',
- text text,
+ name text NOT NULL,
+ type tinyint(4) unsigned default NULL,
+ text int(10) unsigned default NULL,
hotpot int(10) unsigned NOT NULL default '0',
- PRIMARY KEY (id)
+ PRIMARY KEY (id),
+ KEY prefix_hotpot_questions_name_idx (name(20)),
+ KEY prefix_hotpot_questions_hotpot_idx (hotpot)
) TYPE=MyISAM COMMENT='details about questions in Hot Potatatoes quiz attempts';
-
#
# Table structure for table `hotpot_responses`
#
CREATE TABLE prefix_hotpot_responses (
id int(10) unsigned NOT NULL auto_increment,
- attempt int(10) unsigned NOT NULL,
- question int(10) unsigned NOT NULL,
+ attempt int(10) unsigned NOT NULL default '0',
+ question int(10) unsigned NOT NULL default '0',
score smallint(8) default NULL,
weighting smallint(8) default NULL,
correct varchar(255) default NULL,
@@ -76,7 +94,9 @@ CREATE TABLE prefix_hotpot_responses (
hints smallint(6) default NULL,
clues smallint(6) default NULL,
checks smallint(6) default NULL,
- PRIMARY KEY (id)
+ PRIMARY KEY (id),
+ KEY prefix_hotpot_responses_attempt_idx (attempt),
+ KEY prefix_hotpot_responses_question_idx (question)
) TYPE=MyISAM COMMENT='details about responses in Hot Potatatoes quiz attempts';
#
@@ -86,6 +106,7 @@ CREATE TABLE prefix_hotpot_responses (
CREATE TABLE prefix_hotpot_strings (
id int(10) unsigned NOT NULL auto_increment,
string text NOT NULL,
- PRIMARY KEY (id)
+ PRIMARY KEY (id),
+ KEY prefix_hotpot_strings_string_idx (string(20))
) TYPE=MyISAM COMMENT='strings used in Hot Potatatoes questions and responses';
-
+
View
8 mod/hotpot/db/postgres7.php
@@ -1,6 +1,8 @@
<?PHP
function hotpot_upgrade($oldversion) {
+ global $CFG;
+
$ok = true;
// update from HotPot v1 to HotPot v2
@@ -9,7 +11,11 @@ function hotpot_upgrade($oldversion) {
$ok = $ok && hotpot_update_to_v2_from_v1();
}
-
+ // update to HotPot v2.1
+ if ($oldversion < 2005090700) {
+ $ok = $ok && hotpot_get_update_to_v2();
+ $ok = $ok && hotpot_update_to_v2_1();
+ }
return $ok;
}
function hotpot_get_update_to_v2() {
View
90 mod/hotpot/db/postgres7.sql
@@ -4,26 +4,32 @@
CREATE TABLE prefix_hotpot (
id SERIAL PRIMARY KEY,
- course INT4 NOT NULL default '0',
- name VARCHAR(255) NOT NULL default '',
- summary TEXT,
- timeopen INT4 NOT NULL default '0',
- timeclose INT4 NOT NULL default '0',
- location INT2 NOT NULL default '0',
- reference VARCHAR(255) NOT NULL default '',
- grade INT4 NOT NULL default '0',
- grademethod INT2 NOT NULL default '1',
- attempts INT2 NOT NULL default '0',
- review INT2 NOT NULL default '0',
- navigation INT2 NOT NULL default '1',
- outputformat INT2 NOT NULL default '1',
- shownextquiz INT2 NOT NULL default '0',
- forceplugins INT2 NOT NULL default '0',
- password VARCHAR(255) NOT NULL default '',
- subnet VARCHAR(255) NOT NULL default '',
- timecreated INT4 NOT NULL default '0',
- timemodified INT4 NOT NULL default '0'
+ course INT4 NOT NULL default '0',
+ name VARCHAR(255) NOT NULL default '',
+ summary TEXT,
+ timeopen INT4 NOT NULL default '0',
+ timeclose INT4 NOT NULL default '0',
+ location INT2 NOT NULL default '0',
+ reference VARCHAR(255) NOT NULL default '',
+ navigation INT2 NOT NULL default '1',
+ outputformat INT2 NOT NULL default '1',
+ forceplugins INT2 NOT NULL default '0',
+ shownextquiz INT2 NOT NULL default '0',
+ microreporting INT2 NOT NULL default '0',
+ studentfeedback VARCHAR(255) NOT NULL default '0',
+
+ review INT2 NOT NULL default '0',
+ grade INT4 NOT NULL default '0',
+ grademethod INT2 NOT NULL default '1',
+ attempts INT2 NOT NULL default '0',
+
+ password VARCHAR(255) NOT NULL default '',
+ subnet VARCHAR(255) NOT NULL default '',
+ timecreated INT4 NOT NULL default '0',
+ timemodified INT4 NOT NULL default '0'
);
+COMMENT ON TABLE prefix_hotpot IS 'details about Hot Potatoes quizzes';
+
#
# Table structure for table `hotpot_attempts`
@@ -31,32 +37,49 @@ CREATE TABLE prefix_hotpot (
CREATE TABLE prefix_hotpot_attempts (
id SERIAL PRIMARY KEY,
- hotpot INT4 NOT NULL default '0',
- userid INT4 NOT NULL default '0',
- attempt INT2 NOT NULL default '0',
- score INT2,
- penalties INT2,
- details TEXT,
- starttime INT4,
- endtime INT4,
- timestart INT4 NOT NULL default '0',
- timefinish INT4 NOT NULL default '0'
+ hotpot INT4 NOT NULL default '0',
+ userid INT4 NOT NULL default '0',
+ groupid INT4 NOT NULL default '0',
+ attempt INT2 NOT NULL default '0',
+ score INT2,
+ penalties INT2,
+ starttime INT4,
+ endtime INT4,
+ timestart INT4 NOT NULL default '0',
+ timefinish INT4 NOT NULL default '0',
+ status INT2 NOT NULL default '1',
+ microreportid INT4
);
+COMMENT ON TABLE prefix_hotpot IS 'details about Hot Potatoes quiz attempts';
CREATE INDEX prefix_hotpot_attempts_hotpot_idx ON prefix_hotpot_attempts (hotpot);
CREATE INDEX prefix_hotpot_attempts_userid_idx ON prefix_hotpot_attempts (userid);
#
+# Table structure for table `prefix_hotpot_details`
+#
+
+CREATE TABLE prefix_hotpot_details (
+ id SERIAL PRIMARY KEY,
+ attempt INT4 NOT NULL default '0',
+ details TEXT
+);
+COMMENT ON TABLE prefix_hotpot_details IS 'raw details (as XML) of Hot Potatoes quiz attempts';
+CREATE INDEX prefix_hotpot_details_attempt_idx ON prefix_hotpot_details (attempt);
+
+
+#
# Table structure for table `hotpot_questions`
#
CREATE TABLE prefix_hotpot_questions (
id SERIAL PRIMARY KEY,
- hotpot INT4 NOT NULL default '0',
- name VARCHAR(255) NOT NULL default '',
+ name TEXT,
type INT2 NOT NULL default '0',
- text TEXT
+ text INT4 NULL,
+ hotpot INT4 NOT NULL default '0'
);
+COMMENT ON TABLE prefix_hotpot_questions IS 'details about questions in Hot Potatatoes quiz attempts';
CREATE INDEX prefix_hotpot_questions_hotpot_idx ON prefix_hotpot_questions (hotpot);
#
@@ -76,6 +99,7 @@ CREATE TABLE prefix_hotpot_responses (
clues INT2,
checks INT2
);
+COMMENT ON TABLE prefix_hotpot_responses IS 'details about responses in Hot Potatatoes quiz attempts';
CREATE INDEX prefix_hotpot_responses_attempt_idx ON prefix_hotpot_responses (attempt);
CREATE INDEX prefix_hotpot_responses_question_idx ON prefix_hotpot_responses (question);
@@ -87,4 +111,4 @@ CREATE TABLE prefix_hotpot_strings (
id SERIAL PRIMARY KEY,
string TEXT NOT NULL
);
-
+COMMENT ON TABLE prefix_hotpot_strings IS 'strings used in Hot Potatatoes questions and responses';
View
255 mod/hotpot/db/update_to_v2.php
@@ -1,5 +1,124 @@
<?PHP
+function hotpot_update_to_v2_1() {
+ global $CFG, $db;
+ $ok = true;
+
+ // hotpot_questions: reduce size of "type" field to "4"
+ $ok = $ok && hotpot_db_update_field_type('hotpot_questions', 'type', 'type', 'INTEGER', 4, 'UNSIGNED', 'NULL');
+
+ // hotpot_questions: change type of "name" field to "text"
+ $ok = $ok && hotpot_db_update_field_type('hotpot_questions', 'name', 'name', 'TEXT', '', '', 'NOT NULL', '');
+
+ // hotpot_questions: nullify empty and non-numeric (shouldn't be any) values in "text" field
+ switch (strtolower($CFG->dbtype)) {
+ case 'mysql' :
+ $NOT_REGEXP = 'NOT REGEXP';
+ break;
+ case 'postgres7' :
+ $NOT_REGEXP = '!~';
+ break;
+ default:
+ $NOT_REGEXP = '';
+ break;
+ }
+ if ($NOT_REGEXP) {
+ $ok = $ok && execute_sql("UPDATE {$CFG->prefix}hotpot_questions SET text=NULL WHERE text $NOT_REGEXP '^[0-9]+$'");
+ }
+
+ // hotpot_questions: change type of "text" field to "INT(10)"
+ $ok = $ok && hotpot_db_update_field_type('hotpot_questions', 'text', 'text', 'INTEGER', 10, 'UNSIGNED', 'NULL');
+
+ // hotpot_attempts
+
+ // hotpot_attempts: create and set status field (1=in-progress, 2=timed-out, 3=abandoned, 4=completed)
+ $ok = $ok && hotpot_db_update_field_type('hotpot_attempts', '', 'status', 'INTEGER', 4, 'UNSIGNED', 'NOT NULL', 1);
+ $ok = $ok && execute_sql("UPDATE {$CFG->prefix}hotpot_attempts SET status=1 WHERE timefinish=0 AND SCORE IS NULL");
+ $ok = $ok && execute_sql("UPDATE {$CFG->prefix}hotpot_attempts SET status=3 WHERE timefinish>0 AND SCORE IS NULL");
+ $ok = $ok && execute_sql("UPDATE {$CFG->prefix}hotpot_attempts SET status=4 WHERE timefinish>0 AND SCORE IS NOT NULL");
+
+ // hotpot_attempts: create and set clickreport fields
+ $ok = $ok && hotpot_db_update_field_type('hotpot', '', 'clickreporting', 'INTEGER', 4, 'UNSIGNED', 'NOT NULL', 0);
+ $ok = $ok && hotpot_db_update_field_type('hotpot_attempts', '', 'clickreportid', 'INTEGER', 10, 'UNSIGNED', 'NULL');
+ $ok = $ok && execute_sql("UPDATE {$CFG->prefix}hotpot_attempts SET clickreportid=id WHERE clickreportid IS NULL");
+
+ // hotpot_attempts: create and set studentfeedback field (0=none, 1=formmail, 2=moodleforum, 3=moodlemessaging)
+ $ok = $ok && hotpot_db_update_field_type('hotpot', '', 'studentfeedback', 'INTEGER', 4, 'UNSIGNED', 'NOT NULL', '0');
+ $ok = $ok && hotpot_db_update_field_type('hotpot', '', 'studentfeedbackurl', 'VARCHAR', 255, '', 'NULL');
+
+ // hotpot_attempts: move "details" to separate table
+ $table = 'hotpot_details';
+ if (hotpot_db_table_exists($table)) {
+ // do nothing
+ } else {
+ $ok = $ok && hotpot_create_table($table);
+ switch (strtolower($CFG->dbtype)) {
+ case 'mysql' :
+ case 'postgres7' :
+ $sql = "
+ INSERT INTO {$CFG->prefix}$table (attempt, details)
+ SELECT a.id AS attempt, a.details AS details
+ FROM {$CFG->prefix}hotpot_attempts AS a
+ WHERE
+ a.details IS NOT NULL AND a.details <> ''
+ AND a.details LIKE '<?xml%' AND a.details LIKE '%</hpjsresult>'
+ ";
+ break;
+ default:
+ $sql = '';
+ break;
+ }
+ if ($sql) {
+ $ok = $ok && execute_sql($sql);
+ }
+ }
+
+ // hotpot_attempts: remove the "details" field
+ $ok = $ok && hotpot_db_remove_field('hotpot_attempts', 'details');
+
+ // add indexes
+ $ok = $ok && hotpot_db_add_index('hotpot_attempts', 'hotpot');
+ $ok = $ok && hotpot_db_add_index('hotpot_attempts', 'userid');
+ $ok = $ok && hotpot_db_add_index('hotpot_details', 'attempt');
+ $ok = $ok && hotpot_db_add_index('hotpot_questions', 'name', 20);
+ $ok = $ok && hotpot_db_add_index('hotpot_questions', 'hotpot');
+ $ok = $ok && hotpot_db_add_index('hotpot_responses', 'attempt');
+ $ok = $ok && hotpot_db_add_index('hotpot_responses', 'question');
+ $ok = $ok && hotpot_db_add_index('hotpot_strings', 'string', 20);
+
+ // hotpot_string: correct double-encoded HTML entities
+ $ok = $ok && execute_sql("
+ UPDATE {$CFG->prefix}hotpot_strings
+ SET string = REPLACE(string, '&amp;','&')
+ WHERE string LIKE '%&amp;#%'
+ AND (string LIKE '<' OR string LIKE '>')
+ ");
+
+ // hotpot_question: remove questions which refer to deleted hotpots
+ if ($ok) {
+ // try and get all hotpot records
+ if ($records = get_records('hotpot')) {
+ $ids = implode(',', array_keys($records));
+ $sql = "DELETE FROM {$CFG->prefix}hotpot_questions WHERE hotpot NOT IN ($ids)";
+ } else {
+ // remove all question records (because there are no valid hotpot ids)
+ $sql = "TRUNCATE {$CFG->prefix}hotpot_questions";
+ }
+ print "Removing unused question records ...";
+ execute_sql($sql);
+ }
+
+ if ($ok) {
+ // remove old 'v6' templates folder (replaced by 'template' folder)
+ $ds = DIRECTORY_SEPARATOR;
+ $dir = "mod{$ds}hotpot{$ds}v6";
+ print "removing old templates ($dir) ... ";
+ $ok = hotpot_rm("$CFG->dirroot{$ds}$dir", false);
+ print $ok ? get_string('success') : 'failed';
+ }
+
+ return $ok;
+}
function hotpot_update_to_v2_from_v1() {
global $CFG;
$ok = true;
@@ -609,6 +728,104 @@ function hotpot_update_print_warning($field, $value, $table, $id) {
// database functions
///////////////////////////
+function hotpot_db_index_exists($table, $index, $feedback=false) {
+ global $CFG, $db;
+ $exists = false;
+
+ // save and switch off SQL message echo
+ $debug = $db->debug;
+ $db->debug = $feedback;
+
+ switch (strtolower($CFG->dbtype)) {
+ case 'mysql' :
+ $rs = $db->Execute("SHOW INDEX FROM `$table`");
+ if ($rs && $rs->RecordCount()>0) {
+ $records = $rs->GetArray();
+ foreach ($records as $record) {
+ if (isset($record['Key_name']) && $record['Key_name']==$index) {
+ $exists = true;
+ break;
+ }
+ }
+ }
+ break;
+
+ case 'postgres7' :
+ $rs = $db->Execute("SELECT relname FROM pg_class WHERE relname = '$index' AND relkind='i'");
+ if ($rs && $rs->RecordCount()>0) {
+ $exists = true;
+ }
+ break;
+ }
+
+ // restore SQL message echo
+ $db->debug = $debug;
+
+ return $exists;
+}
+function hotpot_db_delete_index($table, $index, $feedback=false) {
+ global $CFG, $db;
+ $ok = true;
+
+ // check index exists
+ if (hotpot_db_index_exists($table, $index)) {
+
+ switch (strtolower($CFG->dbtype)) {
+ case 'mysql' :
+ $sql = "ALTER TABLE `$table` DROP INDEX `$index`";
+ break;
+
+ case 'postgres7' :
+ $sql = "DROP INDEX $index";
+ break;
+
+ default: // unknown database type
+ $sql = '';
+ break;
+ }
+ if ($sql) {
+ // save and switch off SQL message echo
+ $debug = $db->debug;
+ $db->debug = $feedback;
+
+ $ok = $db->Execute($sql) ? true : false;
+
+ // restore SQL message echo
+ $db->debug = $debug;
+
+ } else { // unknown database type
+ $ok = false;
+ }
+ }
+ return $ok;
+}
+function hotpot_db_add_index($table, $field, $length='') {
+ global $CFG, $db;
+
+ // expand $table and $index names
+ $table = "{$CFG->prefix}$table";
+ $index = "{$table}_{$field}_idx";
+
+ // delete $index if it already exists
+ $ok = hotpot_db_delete_index($table, $index);
+
+ switch (strtolower($CFG->dbtype)) {
+ case 'mysql' :
+ $length = empty($length) ? '' : " ($length)";
+ $ok = $ok && $db->Execute("ALTER TABLE `$table` ADD INDEX `$index` (`$field`$length)");
+ break;
+
+ case 'postgres7' :
+ $ok = $ok && $db->Execute("CREATE INDEX $index ON $table ($field)");
+ break;
+
+ default: // unknown database type
+ $ok = false;
+ break;
+ }
+
+ return $ok;
+}
function hotpot_db_table_exists($table, $feedback=false) {
return hotpot_db_object_exists($table, '', $feedback);
}
@@ -766,7 +983,7 @@ function hotpot_db_update_field_type($table, $oldfield, $field, $type, $size, $u
}
if (empty($oldfield) && hotpot_db_field_exists($table, $field)) {
$oldfield = $field;
- }
+ }
if (is_string($unsigned)) {
$unsigned = (strtoupper($unsigned)=='UNSIGNED');
}
@@ -901,7 +1118,7 @@ function hotpot_db_update_field_type($table, $oldfield, $field, $type, $size, $u
// transfer $oldfield values, if necessary
if ( $oldfield != '""' ) {
- execute_sql("UPDATE $table SET $tmpfield = $oldfield");
+ execute_sql("UPDATE $table SET $tmpfield = CAST ($oldfield AS $fieldtype)");
execute_sql("ALTER TABLE $table DROP COLUMN $oldfield");
}
@@ -963,5 +1180,39 @@ function hotpot_db_update_record($table, $record, $forcenull=false) {
}
return $ok;
}
+function hotpot_rm($target, $output=true) {
+ $ok = true;
+ if (!empty($target)) {
+ if (is_file($target)) {
+ if ($output) {
+ print "removing file: $target ... ";
+ }
+ $ok = @unlink($target);
+
+ } else if (is_dir($target)) {
+ $dir = dir($target);
+ while(false !== ($entry = $dir->read())) {
+ if ($entry!='.' && $entry!='..') {
+ $ok = $ok && hotpot_rm($target.DIRECTORY_SEPARATOR.$entry, $output);
+ }
+ }
+ $dir->close();
+ if ($output) {
+ print "removing folder: $target ... ";
+ }
+ $ok = $ok && @rmdir($target);
+ } else { // not a file or directory (probably doesn't exist)
+ $output = false;
+ }
+ if ($output) {
+ if ($ok) {
+ print '<font color="green">OK</font><br>';
+ } else {
+ print '<font color="red">Failed</font><br>';
+ }
+ }
+ }
+ return $ok;
+}
?>
View
1,662 mod/hotpot/hotpot-full.js
@@ -171,9 +171,12 @@ if (window.JCloze==null) {
JCloze[1] = true; // show student's correct answer
JCloze[2] = true; // show other correct answer(s), if any
JCloze[3] = true; // show wrong answer(s), if any (NOT available for v5)
- JCloze[4] = true; // show number of hints or penalties
- JCloze[5] = true; // show if clue was asked for or not
+ JCloze[4] = false; // show number of hints + checks (legacy field, replaced by [7]+[9])
+ JCloze[5] = false; // show if clue was asked for or not (legacy field, replaced by [8])
JCloze[6] = true; // show clue
+ JCloze[7] = true; // show number of hints (=next letter requests)
+ JCloze[8] = true; // show number of clues
+ JCloze[9] = true; // show number of checks
}
// JCloze quizzes use the global variables 'I' and 'State'
@@ -214,8 +217,15 @@ if (window.JCross==null) {
JCross[0] = true; // show separator line between answers on email
JCross[1] = true; // show number of penalties (hints or checks before complete)
JCross[2] = true; // show number of letters
- JCross[3] = true; // show answers
+ JCross[3] = true; // show correct answers
JCross[4] = true; // show clues
+
+ JCross[5] = true; // show wrong answers
+ JCross[6] = true; // show if clue was asked for or not
+ JCross[7] = true; // show number of hints (=next letter requests)
+ JCross[8] = true; // show number of checks
+
+ // there are no "ignored" answers for JCross quizzes
}
// JCross quizzes use the following global variables:
@@ -239,9 +249,14 @@ if (window.JCross==null) {
if (window.JMatch==null) {
JMatch = new Array();
JMatch[0] = true; // show separator line between answers on email
- JMatch[1] = true; // show number of attempts for each match
- JMatch[2] = true; // show LHS texts
- JMatch[3] = true; // show RHS texts
+ JMatch[1] = false; // show number of penalties (= total number of checks)
+ JMatch[2] = true; // show LHS texts (the question)
+ JMatch[3] = true; // show correct answers
+ JMatch[4] = true; // show wrong answers
+ JMatch[5] = true; // show checks (per match) [empty or unchanged RHS are not counted]
+
+ // JMatch has no "clue" or "hint" buttons
+ // there cannot be any "ignored" answers
}
// v5 JMatch quizzes use the global variables 'I' and 'Status' (and 'RItems')
@@ -291,10 +306,12 @@ if (window.JMatch==null) {
if (window.JMix==null) {
JMix = new Array();
JMix[0] = true; // show separator line between answers on email
- JMix[1] = true; // show number of wrong guesses
+ JMix[1] = false; // show number of wrong guesses (replaced by JMix[5])
JMix[2] = true; // show right answer
JMix[3] = true; // show wrong answer, if any
JMix[4] = false; // show answer as text (false) or number (true)
+ JMix[5] = true; // show number of checks
+ JMix[6] = true; // show number of hints (=show next word)
}
// JMix quizzes use the global variables
@@ -317,9 +334,9 @@ if (window.JQuiz==null) {
JQuiz[0] = true; // show separator line between answers on email
JQuiz[1] = true; // show question text
JQuiz[2] = true; // show student's correct answer(s)
- JQuiz[3] = true; // show wrong and ignored answer(s)
+ JQuiz[3] = false; // show wrong and ignored answer(s) (legacy field superceded by [8] & [9])
JQuiz[4] = true; // show number of hints requested
- JQuiz[5] = true; // show number of checks of incorrect answers
+ JQuiz[5] = false; // show number of checks of incorrect answers (legacy field superceded by [12])
// HP6 v6 quizzes only
JQuiz[6] = false; // show answer value (false) or A,B,C... index (true)
@@ -328,6 +345,8 @@ if (window.JQuiz==null) {
JQuiz[9] = true; // show ignored answers (not relevant for multi-select questions)
JQuiz[10] = true; // show score weightings
JQuiz[11] = true; // show question type
+ JQuiz[12] = true; // show number of checks (if true, then JQuiz[5] of will be ignored)
+ JQuiz[13] = true; // show number of times ShowAnswer button was pressed (usually 0 or 1)
}
// v5 JQuiz quizzes use the global variables 'I' and 'Status'
@@ -466,6 +485,40 @@ if (window.MSG==null) {
MSG[18] += '\n\n' + 'Tools->Pop-up Blocker->Turn Off Pop-up Blocker';
}
}
+//if (window.FEEDBACK==null) {
+// FEEDBACK = new Array();
+// FEEDBACK[0] = ''; // url of feedback page/script
+
+// FEEDBACK[1] = ''; // array of array('teachername', 'value');
+// FEEDBACK[2] = ''; // 'student name' [formmail only]
+// FEEDBACK[3] = ''; // 'email@somewhere.com>' [formmail only]
+
+// FEEDBACK[4] = ''; // window width
+// FEEDBACK[5] = ''; // window height
+
+// FEEDBACK[6] = ''; // 'Send a message to teacher' [prompt/button text]
+// FEEDBACK[7] = ''; // 'Title'
+// FEEDBACK[8] = ''; // 'Teacher'
+// FEEDBACK[8] = ''; // 'Message'
+// FEEDBACK[10] = ''; // 'Close this window' [formmail only]
+//}
+
+// **********
+// HP array
+// **********
+HP = new Array();
+for (var i=0; i<=7; i++) {
+ HP[i] = new Array();
+}
+// indexes for the HP array (makes the code further down easier to read)
+_score = 0;
+_weight = 1;
+_correct = 2;
+_wrong = 3;
+_unused = 4;
+_hints = 5;
+_clues = 6;
+_checks = 7;
// *************
// Server Fields
@@ -498,7 +551,7 @@ if (window.ServerFields==null) {
// *********************
function QuizLogin(LoginPrompt) {
- if ((Login[0] || Login[1] || Login[2] || Login[3]) && !is_LMS()) {
+ if (!is_LMS() && (Login[0] || Login[1] || Login[2] || Login[3])) {
var html = ''
+ '<HTML>'
+ '<HEAD></HEAD>'
@@ -566,7 +619,7 @@ function QuizLogin(LoginPrompt) {
+ '<OPTION value=""></OPTION>'
;
for(var i=0; i<Login[0].length; i++) {
- // convert name details to array if necesssary
+ // convert name details to array if nececount_cary
if (typeof(Login[0][i])=='string') {
Login[0][i] = Login[0][i].split(comma);
}
@@ -727,6 +780,8 @@ function goBack(w) {
if (w.history.length) w.history.back();
}
function openWindow(url, name, width, height, attributes, html) {
+
+ // set height, width and attributes
if (window.screen && width && height) {
var W = screen.availWidth;
var H = screen.availHeight;
@@ -738,32 +793,56 @@ function openWindow(url, name, width, height, attributes, html) {
;
}
- // workaround for "Access is denied" errors in IE when offline
- // based on an idea seen at http://www.devshed.com/Client_Side/JavaScript/Mini_FAQ
- var ie_offline = (document.all && location.protocol=='file:');
+ // create global hpWindows object, if necessary
+ if (!window.hpWindows) window.hpWindows = new Array();
+
+ // initialize window object
+ var w = null;
- // open the window
- var w = window.open((ie_offline ? '' : url), name, attributes);
+ // has a window with this name been opened before?
+ if (name && hpWindows[name]) {
- // check window opened OK (user may have prevented popups)
- if (w) {
- // center the window
- if (window.screen && width && height) {
- w.moveTo((W-width)/2, (H-height)/2);
+ // http://www.webreference.com/js/tutorial1/exist.html
+ if (hpWindows[name].open && !hpWindows[name].closed) {
+ w = hpWindows[name];
+ w.focus();
+ } else {
+ hpWindows[name] = null;
}
+ }
+
+ // check window is not already open
+ if (w==null) {
- // add content, if required
- if (html) {
- with (w.document) {
- clear();
- open();
- write(html);
- close();
+ // workaround for "Access is denied" errors in IE when offline
+ // based on an idea seen at http://www.devshed.com/Client_Side/JavaScript/Mini_FAQ
+ var ie_offline = (document.all && location.protocol=='file:');
+
+ // try and open the new window
+ w = window.open((ie_offline ? '' : url), name, attributes);
+
+ // check window opened OK (user may have prevented popups)
+ if (w) {
+ // center the window
+ if (window.screen && width && height) {
+ w.moveTo((W-width)/2, (H-height)/2);
}
- } else {
- if (ie_offline && url) w.location = url;
+
+ // add content, if required
+ if (html) {
+ with (w.document) {
+ clear();
+ open();
+ write(html);
+ close();
+ }
+ } else if (url && ie_offline) {
+ w.location = url;
+ }
+ if (name) hpWindows[name] = w;
}
}
+
return w;
}
@@ -792,7 +871,7 @@ function SendAllResults(Score) {
ResultForm = replaceLast('method="get"', 'method="post"', ResultForm);
// create results window and form
- var w = openWindow('', '', 500, 400, 'RESIZABLE,SCROLLBARS,HOTPOT_LOCATION', ResultForm);
+ var w = openWindow('', '', 500, 400, 'RESIZABLE,SCROLLBARS,LOCATION', ResultForm);
// check window opened OK (user may have prevented popups)
if (w) {
@@ -887,13 +966,13 @@ function AddStudentDetailsToResultForm() {
var sDetails = '';
if (Login[0]) { // user name
// use 'realname' instead of a separate 'Name' field
- // sDetails += makeHiddenField('Name', window.UserName);
+ // sDetails += hpHiddenField('Name', window.UserName);
}
if (Login[1]) { // user ID
- sDetails += makeHiddenField('ID', window.UserID);
+ sDetails += hpHiddenField('ID', window.UserID);
}
if (Login[2]) { // user email
- sDetails += makeHiddenField('email', window.UserEmail);
+ sDetails += hpHiddenField('email', window.UserEmail);
}
if (sDetails && window.RegExp) {
// insert sDetails before '<input...Score...></input>'
@@ -905,7 +984,7 @@ function AddStudentDetailsToResultForm() {
}
}
if (Login[3]) { // quiz password
- sDetails += makeHiddenField('Password', window.Password);
+ sDetails += hpHiddenField('Password', window.Password);
ResultForm = replaceLast('</form>', sDetails + '</form>', ResultForm);
}
}
@@ -936,10 +1015,10 @@ function AddDatabaseDetailsToResultForm() {
var ext = (DB[3] ? DB[3] : 'txt');
if (ext.charAt(0)!='.') ext = '.' + ext;
- dbDetails += makeHiddenField('append_db', folder + file + ext);
- dbDetails += makeHiddenField('db_fields', DB[4]);
- dbDetails += makeHiddenField('db_delimiter', ''); // NS6+ requires this be set later
- if (DB[6]) dbDetails += makeHiddenField('env_report', DB[6]);
+ dbDetails += hpHiddenField('append_db', folder + file + ext);
+ dbDetails += hpHiddenField('db_fields', DB[4]);
+ dbDetails += hpHiddenField('db_delimiter', ''); // NS6+ requires this be set later
+ if (DB[6]) dbDetails += hpHiddenField('env_report', DB[6]);
// insert dbDetails before the final </form> tag in the ResultForm
ResultForm = replaceLast('</form>', dbDetails + '</form>', ResultForm);
@@ -957,7 +1036,7 @@ function AddServerFieldsToResultForm() {
}
}
if (ServerFields[i][1]) {
- s += makeHiddenField(ServerFields[i][0], ServerFields[i][1]);
+ s += hpHiddenField(ServerFields[i][0], ServerFields[i][1]);
}
} // end for
if (s) ResultForm = replaceLast('</form>', s + '</form>', ResultForm);
@@ -975,23 +1054,24 @@ function replaceLast(a, b, c) {
// *************************
function GetQuestionDetails() {
- var t = get_quiz_type();
- var v = get_quiz_version();
-
- return (t==1) ? GetJbcQuestionDetails(v) :
- (t==2) ? GetJClozeQuestionDetails(v) :
- (t==3) ? GetJCrossQuestionDetails(v) :
- (t==4) ? GetJMatchQuestionDetails(v) :
- (t==5) ? GetJMixQuestionDetails(v) :
- (t==6) ? GetJQuizQuestionDetails(v) :
- (t==7) ? GetRhubarbDetails(v) :
- (t==8) ? GetSequiturDetails(v) : '';
-}
-function GetJbcQuestionDetails(v) {
+ var hp = hpVersion();
+ var t = hpQuizType();
+ var v = hpQuizVersion();
+
+ return (t==1) ? GetJbcQuestionDetails(hp, v) :
+ (t==2) ? GetJClozeQuestionDetails(hp, v) :
+ (t==3) ? GetJCrossQuestionDetails(hp, v) :
+ (t==4) ? GetJMatchQuestionDetails(hp, v) :
+ (t==5) ? GetJMixQuestionDetails(hp, v) :
+ (t==6) ? GetJQuizQuestionDetails(hp, v) :
+ (t==7) ? GetRhubarbDetails(hp, v) :
+ (t==8) ? GetSequiturDetails(hp, v) : '';
+}
+function GetJbcQuestionDetails(hp, v) {
qDetails = '';
// check the quiz version
- if (v==5 || v==6) {
+ if (hp==5 || hp==6) {
// get question details
for(var q=0; q<I.length; q++) {
@@ -1015,19 +1095,19 @@ function GetJbcQuestionDetails(v) {
if (JBC[0]) qDetails += makeSeparator(Q);
if (JBC[1]) { // number of attempts to answer question
- qDetails += makeHiddenField(Q+'attempts', Status[q][2] + (Status[q][0]==1 ? 1 : 0));
+ qDetails += hpHiddenField(Q+'attempts', Status[q][2] + (Status[q][0]==1 ? 1 : 0));
}
if (JBC[2]) { // question text
- qDetails += makeHiddenField(Q+'text', I[q][0]);
+ qDetails += hpHiddenField(Q+'text', I[q][0]);
}
if (JBC[3] && (DB[0] || aDetails[0].length>0)) { // right
- qDetails += makeHiddenField(Q+'right', aDetails[0]);
+ qDetails += hpHiddenField(Q+'right', aDetails[0]);
}
if (JBC[4] && (DB[0] || aDetails[1].length>0)) { // wrong
- qDetails += makeHiddenField(Q+'wrong', aDetails[1]);
+ qDetails += hpHiddenField(Q+'wrong', aDetails[1]);
}
if (JBC[5] && (DB[0] || aDetails[2].length>0)) { // ignored
- qDetails += makeHiddenField(Q+'ignored', aDetails[2]);
+ qDetails += hpHiddenField(Q+'ignored', aDetails[2]);
}
// calculate score for this question, if required (for HP version < 5.5)
if (isNaN(Status[q][3])) {
@@ -1036,21 +1116,22 @@ function GetJbcQuestionDetails(v) {
Status[q][3] = (a1<1 || a1<(a2-1)) ? 0 : ((a1 - (a2-1)) / a1);
}
// add 'score' for this question
- qDetails += makeHiddenField(Q+'score', Math.floor(Status[q][3]*100)+'%');
+ qDetails += hpHiddenField(Q+'score', Math.floor(Status[q][3]*100)+'%');
} // end for
}
return qDetails;
}
-function GetJClozeQuestionDetails(v) {
+function GetJClozeQuestionDetails(hp, v) {
var qDetails = '';
// check the quiz version
- if (v==5 || v==6) {
+ if (hp==5 || hp==6) {
- var hp5 = (State[0].Guesses==null);
+ var r = hpRottmeier();
// get details for each question
- for (var q=0; q<State.length; q++) {
+ var q_max = (r==0) ? State.length : GapList.length;
+ for (var q=0; q<q_max; q++) {
// format 'Q' (a padded, two-digit version of 'q')
var Q = getQ('JCloze', q);
@@ -1059,51 +1140,92 @@ function GetJClozeQuestionDetails(v) {
if (JCloze[0]) qDetails += makeSeparator(Q);
// score (as %)
- var x = (hp5 ? State[q][3] : State[q].ItemScore);
- qDetails += makeHiddenField(Q+'score', Math.floor(x*100)+'%');
+ var x = (hp==5) ? State[q][3] : (r==0) ? State[q].ItemScore : GapList[q][1].Score;
+ qDetails += hpHiddenField(Q+'score', Math.floor(x*100)+'%');
+
+ // create Guesses array for Rottmeier Find-it v3.1a
+ if (r==2 && GapList[q][1].Guesses==null) {
+ GapList[q][1].Guesses = new Array();
+ GapList[q][1].Guesses[0] = I[q][1][0][0];
+ }
+
+ // shortcut to guesses to this answer
+ var guesses = (hp==5) ? null : (r==0) ? State[q].Guesses : GapList[q][1].Guesses;
- // shortcut to students correct answer
- var correct = (hp5 ? State[q][5] : State[q].Guesses[State[q].Guesses.length-1]);
+ // is this question correctly answered yet?
+ var is_correct = (hp==5) ? State[q][4] : (r==0) ? State[q].AnsweredCorrectly : (r==1) ? GapList[q][1].GapLocked : (r==2) ? GapList[q][1].ErrorFound : true;
+
+ // shortcut ot correct answer
+ var correct = '';
+ if (is_correct) {
+ correct = (hp==5) ? State[q][5] : guesses[guesses.length-1];
+ }
if (JCloze[1]) { // student's correct answer
- qDetails += makeHiddenField(Q+'correct', correct);
+ qDetails += hpHiddenField(Q+'correct', correct);
}
if (JCloze[2]) { // other correct answers
var ignored = new Array();
- for (var i=0, ii=0; i<I[q][1].length; i++) {
- if (I[q][1][i][0] && (I[q][1][i][0].toUpperCase() != correct.toUpperCase())) {
- ignored[ii++] = I[q][1][i][0];
+ if (r==0) {
+ for (var i=0, ii=0; i<I[q][1].length; i++) {
+ if (I[q][1][i][0] && (I[q][1][i][0].toUpperCase() != correct.toUpperCase())) {
+ ignored[ii++] = I[q][1][i][0];
+ }
}
+ } else if (r==1 || r==2) { // rottmeier quizzes
+ // do nothing
}
- if (DB[0] || ignored.length>0) qDetails += makeHiddenField(Q+'ignored', ignored);
+ if (DB[0] || ignored.length>0) qDetails += hpHiddenField(Q+'ignored', ignored);
}
- if (JCloze[3] && State[q].Guesses) {
- var wrong = new Array();
- for (var i=0, ii=0; i<State[q].Guesses.length-1; i++) {
- wrong[ii++] = State[q].Guesses[i];
+ if (JCloze[3]) {
+ if (guesses) {
+ var wrong = new Array();
+ var i_max = guesses.length - (is_correct ? 1 : 0);
+ for (var i=0, ii=0; i<i_max; i++) {
+ if (guesses[i]) {
+ for (var iii=0; iii<ii; iii++) {
+ if (wrong[iii]==guesses[i]) break;
+ }
+ // add guess if it has not already been added
+ if (iii==ii) wrong[ii++] = guesses[i];
+ }
+ }
+ if (DB[0] || ii>0) qDetails += hpHiddenField(Q+'wrong', wrong);
}
- if (DB[0] || ii>0) qDetails += makeHiddenField(Q+'wrong', wrong);
}
+
+ var HintsAndChecks = (hp==5) ? State[q][1] : (r==0) ? State[q].HintsAndChecks : (r==1) ? GapList[q][1].NumOfTrials : (r==2) ? GapList[q][1].HintsAndChecks : 0;
+ var Hints = (HP[_hints][q] ? HP[_hints][q] : 0);
+
if (JCloze[4]) { // number of penalties
- var x = (hp5 ? State[q][1] : State[q].HintsAndChecks);
- qDetails += makeHiddenField(Q+'penalties', x);
+ qDetails += hpHiddenField(Q+'penalties', HintsAndChecks);
}
if (JCloze[5]) { // clue shown?
- var x = (hp5 ? State[q][0] : State[q].ClueGiven);
- qDetails += makeHiddenField(Q+'clue_shown', (x ? 'HOTPOT_YES' : 'HOTPOT_NO'));
+ var x = (hp==5) ? State[q][0] : (r==0) ? State[q].ClueGiven: (r==1) ? GapList[q][1].ClueAskedFor : false;
+ qDetails += hpHiddenField(Q+'clue_shown', (x ? 'YES' : 'NO'));
}
if (JCloze[6]) { // clue text
- qDetails += makeHiddenField(Q+'clue_text', I[q][2]);
+ qDetails += hpHiddenField(Q+'clue_text', I[q][2]);
+ }
+ if (JCloze[7]) { // number of hints
+ qDetails += hpHiddenField(Q+'hints', Hints);
+ }
+ if (JCloze[8]) { // number of clues
+ x = HP[_clues][q] ? HP[_clues][q] : 0;
+ qDetails += hpHiddenField(Q+'clues', x);
+ }
+ if (JCloze[9]) { // number of checks (including the final one for the correct answer)
+ qDetails += hpHiddenField(Q+'checks', HintsAndChecks - Hints + (is_correct ? 1 : 0));
}
} // end for
}
return qDetails;
}
-function GetJCrossQuestionDetails(v) {
+function GetJCrossQuestionDetails(hp, v) {
var qDetails = '';
// check the quiz version
- if (v==5 || v==6) {
+ if (hp==5 || hp==6) {
// inialize letter count
var letters = 0;
@@ -1116,13 +1238,13 @@ function GetJCrossQuestionDetails(v) {
if (L[row][col]) letters++;
// show answers and clues, if required
- var q = (v==5) ? C[row][col] : CL[row][col];
+ var q = (hp==5) ? C[row][col] : CL[row][col];
if (q) {
// format 'Q' (a padded, two-digit version of 'q')
var Q = getQ('JCross', q);
- var clue_A = (v==5) ? A[q] : GetJCrossClue('Clue_A_' + q);
- var clue_D = (v==5) ? D[q] : GetJCrossClue('Clue_D_' + q);
+ var clue_A = (hp==5) ? A[q] : GetJCrossClue('Clue_A_' + q);
+ var clue_D = (hp==5) ? D[q] : GetJCrossClue('Clue_D_' + q);
// add separator, if required
if (JCross[0] && (clue_A || clue_D)) {
@@ -1130,22 +1252,48 @@ function GetJCrossQuestionDetails(v) {
}
if (clue_A) { // across question
- if (JCross[3]) qDetails += makeHiddenField(Q+'across', GetJCrossWord(G, row, col));
- if (JCross[4]) qDetails += makeHiddenField(Q+'across_clue', clue_A);
+ if (JCross[3]) qDetails += hpHiddenField(Q+'across', GetJCrossWord(G, row, col));
+ if (JCross[4]) qDetails += hpHiddenField(Q+'across_clue', clue_A);
+ if (JCross[5]) {
+ var x = (HP[_wrong]['A'] && HP[_wrong]['A'][q]) ? HP[_wrong]['A'][q] : '';
+ qDetails += hpHiddenField(Q+'across_wrong', x);
+ }
+ if (JCross[6]) {
+ var x = HP[_clues][q] ? HP[_clues][q] : 0;
+ qDetails += hpHiddenField(Q+'across_clues', x);
+ }
+ if (JCross[7]) {
+ var x = (HP[_hints]['A'] && HP[_hints]['A'][q]) ? HP[_hints]['A'][q] : 0;
+ qDetails += hpHiddenField(Q+'across_hints', x);
+ }
+ if (JCross[8]) {
+ var x = (HP[_checks]['A'] && HP[_checks]['A'][q]) ? HP[_checks]['A'][q] : '';
+ qDetails += hpHiddenField(Q+'across_checks', x);
+ }
}
if (clue_D) { // down question
- if (JCross[3]) qDetails += makeHiddenField(Q+'down', GetJCrossWord(G, row, col, true));
- if (JCross[4]) qDetails += makeHiddenField(Q+'down_clue', clue_D);
+ if (JCross[3]) qDetails += hpHiddenField(Q+'down', GetJCrossWord(G, row, col, true));
+ if (JCross[4]) qDetails += hpHiddenField(Q+'down_clue', clue_D);
+ if (JCross[5]) qDetails += hpHiddenField(Q+'down_wrong', '');
+ if (JCross[6]) {
+ var x = HP[_clues][q] ? HP[_clues][q] : 0;
+ qDetails += hpHiddenField(Q+'across_clues', x);
+ }
+ if (JCross[7]) {
+ qDetails += hpHiddenField(Q+'down_hints', '');
+ var hints = (HP[_hints]['D'] && HP[_hints]['D'][q]) ? HP[_hints]['D'][q] : 0;
+ }
+ if (JCross[8]) qDetails += hpHiddenField(Q+'down_checks', '');
}
} // end if q
} // end for col
} // end for row
if (JCross[2]) { // show number of letters
- qDetails = makeHiddenField('JCross_letters', letters) + qDetails;
+ qDetails = hpHiddenField('JCross_letters', letters) + qDetails;
}
if (JCross[1]) { // show penalties
- qDetails = makeHiddenField('JCross_penalties', window.Penalties) + qDetails;
+ qDetails = hpHiddenField('JCross_penalties', window.Penalties) + qDetails;
}
}
@@ -1168,92 +1316,80 @@ function GetJCrossWord(a, r, c, goDown) {
}
return s;
}
-function GetJMatchQuestionDetails(v) {
- var qDetails = '';
- // HP5.5 uses "I" for v5 and v6 JMatch quizzes
- var hp5 = (window.I) ? true : false;
+function GetJMatchText(q, className) {
+ var obj = (document.getElementById) ? document.getElementById('Questions') : null;
+ return (obj) ? GetChildNodesText(obj.childNodes[q], className) : '';
+}
+function GetJMatchRHS(v, q, getCorrect) {
+ var rhs = '';
- // check the quiz version
- if (hp5 || v==6 || v==6.1) {
+ if (v==5.1 || v==6.1) { // Drag-and-drop
- if (JMatch[1] && v==6.1) { // attempts
- qDetails += makeHiddenField('JMatch_attempts', Penalties+1);
+ var max_i = (window.F && window.D) ? F.length : 0;
+ for (var i=0; i<max_i; i++) {
+ if (D[i][getCorrect ? 1 : 2]==F[q][1]) break;
}
+ if (i<max_i) rhs = D[i][0];
- // get number of questions
- var max_q = (hp5 || v==6) ? Status.length : F.length;
+ } else if (v==5 || v==6) { // drop-down list of options
- // get details for each question
- for (var q=0; q<max_q; q++) {
-
- // format 'Q' (a padded, two-digit version of 'q')
- var Q = getQ('JMatch', q);
-
- // add separator, if required
- if (JMatch[0] && (JMatch[1] || JMatch[2] || JMatch[3])) {
- qDetails += makeSeparator(Q);
- }
- if (JMatch[1] && (hp5 || v==6)) { // attempts
- qDetails += makeHiddenField(Q+'attempts', Status[q][1]);
- }
- if (JMatch[2]) { // LHS text
- var x = (hp5) ? I[q][0] : (v==6) ? GetJMatchText(q, 'LeftItem') : F[q][0];
- qDetails += makeHiddenField(Q+'lhs', x);
- }
- if (JMatch[3]) { // RHS text
- var x = (hp5) ? I[q][1] : (v==6) ? GetJMatchText(q, 'RightItem') : GetJMatchRHS(q);
- qDetails += makeHiddenField(Q+'rhs', x);
+ var obj=document.getElementById(Status[q][2]);
+ if (obj) { // not correct yet
+ if (getCorrect) {
+ var k = GetKeyFromSelect(obj);
+ var i_max = obj.options.length;
+ for (var i=0; i<i_max; i++) {
+ if (obj.options[i].value==k) break;
+ }
+ if (i>=i_max) i = 0; // shouldn't happen
+ } else {
+ // get current guess, if any
+ var i = obj.selectedIndex;
}
- } // end for
- }
- return qDetails;
-}
-function GetJMatchText(q, className) {
- var obj = (document.getElementById) ? document.getElementById('Questions') : null;
- return (obj) ? GetChildNodesText(obj.childNodes[q], className) : '';
-}
-function GetJMatchRHS(q) { // Drag-and-drop only (v==6.1)
- var max_i = (window.F && window.D) ? F.length : 0;
- for (var i=0; i<max_i; i++) {
- if (D[i][2]==F[q][1]) break;
+ if (i) rhs = obj.options[i].innerHTML;
+ } else { // correct
+ rhs = GetJMatchText(q, 'RightItem');
+ }
}
- return (i<max_i) ? D[i][0] : '';
+ return rhs;
}
-function GetJMixQuestionDetails(v) {
+function GetJMixQuestionDetails(hp, v) {
qDetails = '';
// check the quiz version
- if (v==5 || v==6 || v==6.1) {
+ if (hp==5 || hp==6) {
- var A = Answers.length;
- for (var a=0; a<A; a++) {
- var G = Answers[a].length;
- for (var g=0; g<G; g++) {
- if (Answers[a][g] != GuessSequence[g]) break;
- }
- if (g>=G) break;
- }
- var isWrong = (a>=A);
+ var q = 0; // question number
// format 'Q' (a padded, two-digit version of 'q')
- var Q = getQ('JMix', 0);
+ var Q = getQ('JMix', q);
// add separator, if required
if (JMix[0]) qDetails += makeSeparator(Q);
// add 'score' for this question
- var score = isWrong ? 0 : ((Segments.length-Penalties)/Segments.length);
- qDetails += makeHiddenField(Q+'score', Math.floor(score*100)+'%');
+ var score = HP[_correct]==null ? 0 : ((Segments.length-Penalties)/Segments.length);
+ qDetails += hpHiddenField(Q+'score', Math.floor(score*100)+'%');
if (JMix[1]) { // number of wrong guesses
- qDetails += makeHiddenField(Q+'wrongGuesses', Penalties);
+ qDetails += hpHiddenField(Q+'wrongGuesses', Penalties);
}
if (JMix[2]) { // right answer
- qDetails += makeHiddenField(Q+'right', GetJMixSequence(Answers[isWrong ? 0 : a]));
+ var x = (HP[_correct][q]) ? HP[_correct][q] : '';
+ qDetails += hpHiddenField(Q+'correct', x);
}
- if (JMix[3] && isWrong) { // wrong answer
- qDetails += makeHiddenField(Q+'wrong', GetJMixSequence(GuessSequence));
+ if (JMix[3]) { // wrong answer(s)
+ var x = (HP[_wrong][q]) ? HP[_wrong][q] : '';
+ qDetails += hpHiddenField(Q+'wrong', x);
+ }
+ if (JMix[5]) { // checks
+ var x = (HP[_checks][q]) ? HP[_checks][q] : 0;
+ qDetails += hpHiddenField(Q+'checks', x);
+ }
+ if (JMix[6]) { // hints
+ var x = (HP[_hints][q]) ? HP[_hints][q] : 0;
+ qDetails += hpHiddenField(Q+'hints', x);
}
}
return qDetails;
@@ -1272,14 +1408,14 @@ function GetJMixSegmentText(index){
}
return (i<i_max) ? Segments[i][0] : '';
}
-function GetJQuizQuestionDetails(v) {
+function GetJQuizQuestionDetails(hp, v) {
var qDetails = '';
// HP5.5 uses "Status" for v5 and v6 JMatch quizzes (HP6 uses "State")
- var hp = (window.Status) ? 5 : (window.State) ? 6 : 0;
+ // var hp = (window.Status) ? 5 : (window.State) ? 6 : 0;
// check the quiz version
- if (hp) {
+ if (hp==5 || hp==6) {
// get details for each question
var max_q = (hp==5) ? Status.length : State.length;
@@ -1297,60 +1433,97 @@ function GetJQuizQuestionDetails(v) {
if (hp==6 && JQuiz[11]) { // question type
var x = parseInt(I[q][2]);
x = (x==0) ? 'multiple-choice' : (x==1) ? 'short-answer' : (x==2) ? 'hybrid' : (x==3) ? 'multi-select' : 'n/a';
- qDetails += makeHiddenField(Q+'type', x);
+ qDetails += hpHiddenField(Q+'type', x);
}
// score (as %)
var x = (hp==5) ? Status[q][4]*10 : I[q][0]*State[q][0];
- qDetails += makeHiddenField(Q+'score', Math.floor(x)+'%');
+ if (x<0) x = 0;
+ qDetails += hpHiddenField(Q+'score', Math.floor(x)+'%');
if (hp==6 && JQuiz[10]) { // weighting
- qDetails += makeHiddenField(Q+'weighting', I[q][0]);
+ qDetails += hpHiddenField(Q+'weighting', I[q][0]);
}
if (JQuiz[1]) { // question text
var x = (hp==5) ? I[q][0] : (document.getElementById) ? GetChildNodesText(document.getElementById('Q_'+q), 'QuestionText') : '';
- qDetails += makeHiddenField(Q+'question', x);
+ qDetails += hpHiddenField(Q+'question', x);
}
if (JQuiz[2]) { // student's correct answers
- var x = (hp==5) ? Status[q][3] : GetJQuizAnswerDetails(q, 2);
- qDetails += makeHiddenField(Q+'correct', x);
+ var x = HP[_correct][q];
+ qDetails += hpHiddenField(Q+'correct', x);
}
if (JQuiz[3]) { // ignored and wrong answers
- var x = (hp==5) ? '' : GetJQuizAnswerDetails(q, 1);
+ var x = (hp==5) ? new Array() : GetJQuizAnswerDetails(q, 1);
if (hp==5) {
for (var i=0; i<I[q][1].length; i++) {
- if (I[q][1][i][0] && (I[q][1][i][0].toUpperCase() != Status[q][3].toUpperCase())) {
- x += ((x ? ',' : '') + I[q][1][i][0]);
+ var correct = HP[_correct][q] ? HP[_correct][q] : '';
+ if (I[q][1][i][0] && I[q][1][i][0].toUpperCase()!=correct.toUpperCase()) {
+ x[x.length] = I[q][1][i][0];
}
}
}
- if (DB[0] || x) qDetails += makeHiddenField(Q+'other', x);
+ if (DB[0] || x) qDetails += hpHiddenField(Q+'other', x);
}
if (hp==6 && JQuiz[7]) { // all selected answers
var x = GetJQuizAnswerDetails(q, 0);
- qDetails += makeHiddenField(Q+'selected', x);
+ qDetails += hpHiddenField(Q+'selected', x);
}
- if (hp==6 && JQuiz[8]) { // wrong answers
- var x = GetJQuizAnswerDetails(q, 3);
- qDetails += makeHiddenField(Q+'wrong', x);
+ if (JQuiz[8]) { // wrong answers
+ if (hp==6) {
+ var x = GetJQuizAnswerDetails(q, 3);
+ var i_max = (State[q][6]==null) ? 0 : (State[q][6].length - (State[q][0]<0||x.length ? 0 : 1));
+ for (var i=0; i<i_max; i++) {
+ x[x.length] = State[q][6][i];
+ }
+ } else if (hp==5) {
+ var x = HP[_wrong][q];
+ }
+ qDetails += hpHiddenField(Q+'wrong', x);
}
if (hp==6 && JQuiz[9]) { // ignored answers
var x = GetJQuizAnswerDetails(q, 4);
- qDetails += makeHiddenField(Q+'ignored', x);
+ qDetails += hpHiddenField(Q+'ignored', x);
}
if (JQuiz[4]) { // number of hints
- var x = (hp==5) ? Status[q][2] : State[q][4];
- qDetails += makeHiddenField(Q+'hints', x);
+ var x = (HP[_hints][q]) ? HP[_hints][q] : 0;
+ qDetails += hpHiddenField(Q+'hints', x);
+ }
+ if (JQuiz[5] || JQuiz[12]) { // number of checks
+ if (JQuiz[12]) { // strictly checks only
+ var x = (HP[_checks][q]) ? HP[_checks][q] : 0;
+ } else { // checks (+ hints in HP6)
+ var x = (hp==5) ? Status[q][1] : (State[q][2]-1);
+ }
+ qDetails += hpHiddenField(Q+'checks', x);
}
- if (JQuiz[5]) { // number of checks of incorrect answers
- var x = (hp==5) ? Status[q][1] : (State[q][2]-1);
- qDetails += makeHiddenField(Q+'checks', x);
+ if (JQuiz[13]) { // ShowAnswer
+ var x = (HP[_clues][q]) ? HP[_clues][q] : 0;
+ qDetails += hpHiddenField(Q+'clues', x);
}
} // end for
} // end if
return qDetails;
}
+function GetChildNodesText_NEW(obj, className) {
+ // search this node (obj) and its child nodes and
+ // return all text under node with required classname
+ var txt = '';
+ if (obj) {
+ if (className && obj.className==className) {
+ className = '';
+ }
+ if (className=='') {
+ txt = obj.innerHTML;
+ }
+ if (obj.childNodes) {
+ for (var i=0; i<obj.childNodes.length; i++) {
+ txt += GetChildNodesText(obj.childNodes[i], className);
+ }
+ }
+ }
+ return txt;
+}
function GetChildNodesText(obj, className) {
// search this node (obj) and its child nodes and
// return all text under node with required classname
@@ -1379,7 +1552,7 @@ function GetJQuizAnswerDetails(q, flag) {
// 3 : student's wrong answers
// 4 : ignored answers
- var x = State[q][5];
+ var x = State[q][5]; //Sequence of answers chosen by number
if (I[q][2]=='3') { // multi-select
@@ -1440,6 +1613,8 @@ function GetJQuizAnswerDetails(q, flag) {
x[i] = I[q][3][ii][0];
}
}
+ } else {
+ x = new Array();
}
return x;
}
@@ -1448,15 +1623,15 @@ function GetRhubarbDetails(v) {
if (v==6) {
var Q = getQ('Rhubarb', 0);
if (document.title) { // use quiz title as question name
- qDetails += makeHiddenField(Q+'name', document.title);
+ qDetails += hpHiddenField(Q+'name', document.title);
}
if (Rhubarb[0]) { // correct words
- qDetails += makeHiddenField(Q+'correct', Words.length+' words');
+ qDetails += hpHiddenField(Q+'correct', Words.length+' words');
}
if (Rhubarb[1]) { // incorrect words
// remove leading 'Wrong guesses: ' from Detail
var x = Detail.substring(15).split(' ');
- qDetails += makeHiddenField(Q+'wrong', x);
+ qDetails += hpHiddenField(Q+'wrong', x);
}
}
return qDetails;
@@ -1468,6 +1643,366 @@ function GetSequiturDetails(v) {
}
// *********************
+// click event handlers
+// *********************
+
+function hpClick(x, args) {
+ // x is the button type
+ // args is either empty, a single argument, or an array of arguments
+ var btn = (x==1) ? 'Hint' : (x==2) ? 'Clue' : (x==3) ? 'Check' : (x==4) ? 'Enter' : '';
+ if (btn) {
+
+ // convert args to array, if necessary
+ var t = typeof(args);
+ if (t=='object') {
+ // do nothing (args is already an array)
+ } else if (t=='undefined') {
+ args = new Array();
+ } else {
+ args = new Array(''+args);
+ }
+
+ // call handler for this kind of button
+ var x = eval('hpClick'+btn+'('+hpVersion()+','+hpQuizType()+','+hpQuizVersion()+',args);');
+ }
+}
+
+function hpClickHint(hp, t, v, args) {
+ if (t==2 || t==5 || t==6) { // JCloze, JMix or JQuiz
+ var q = args[0]; // clue/question number
+ if (!HP[_hints][q]) HP[_hints][q] = 0;
+ HP[_hints][q]++;
+ }
+ if (t==3) { // JCross
+ if (v==6 || v==5) {
+ var q = args[0]; // clue/question number
+ var AD = args[1]; // direction ('A' or 'D')
+ if (!HP[_hints][AD]) HP[_hints][AD] = new Array();
+ if (!HP[_hints][AD][q]) HP[_hints][AD][q] = 0;
+ HP[_hints][AD][q]++;
+ }
+ }
+ return true;
+}
+function hpClickClue(hp, t, v, args) {
+ if (t==2 || t==3 || t==6) { // JCloze or JCross, or JQuiz (ShowAnswer button)
+ var q = args[0]; // clue/question number
+ if (!HP[_clues][q]) HP[_clues][q] = 0;
+ HP[_clues][q]++;
+ }
+ return true;
+}
+function hpClickCheck(hp, t, v, args) {
+ if (t==3) { // JCross
+ if (v==5 || v==6) {
+ var q = args[0]; // clue/question number
+ for (var row=0; row<L.length; row++) {
+ for (var col=0; col<L[row].length; col++) {
+ var q = (v==5) ? C[row][col] : CL[row][col];
+ if (q) {
+ hpClickCheckJCrossV5V6(hp, v, 'A', q, row, col);
+ hpClickCheckJCrossV5V6(hp, v, 'D', q, row, col);
+ }
+ }
+ }
+ }
+ }
+ if (t==4) { // JMatch
+ var a = new Array();
+ var extra = ''; // extra js code to eval(uate)
+ var guess = ''; // js code to eval(uate) guess
+ var correct = ''; // js code to eval(uate) correct answer
+
+ if (window.D && window.F) {
+ // drag-and-drop, i.e. v5+ and v6+ (HP5 and HP6)
+ a = F;
+ guess = 'GetJMatchRHS(v,i)';
+ correct = 'GetJMatchRHS(v,i,true)';
+
+ } else if (window.GetKeyFromSelect) {
+ // HP6 v6
+ a = Status;
+ guess = 'GetJMatchRHS(v,i)';
+ correct = 'GetJMatchRHS(v,i,true)';
+
+ } else if (window.GetAnswer) {
+ // HP5 v6,v5
+ a = I;
+ guess = "(I[i][2]==0||I[i][0]=='')?'':GetAnswer(i)";
+ correct = 'I[i][3])';
+
+ } else if (window.Draggables) {
+ // HP5 v4
+ a = Draggables;
+ s = "Draggables[i].correct=='1'";
+
+ } else if (window.CorrectAnswers) {
+ // HP5 v3
+ a = CorrectAnswers;
+ guess = 'document.QuizForm.elements[i*2].selectedIndex';
+ correct = 'CorrectAnswers[i]';
+ }
+
+ for (var i=0; i<a.length; i++) {
+
+ // check this match has not already been finished
+ if (!HP[_correct][i]) {
+
+ // do extra setup, if necessary
+ if (extra) eval(extra);
+
+ // get the guess, if any
+ var g = ''+eval(guess);
+ if (g) {
+
+ // is the guess correct?
+ if (g==eval(correct)) {
+ HP[_correct][i] = g;
+
+ } else { // wrong answer
+
+ // initialize wrong guess array if necessary
+ if (!HP[_wrong][i]) HP[_wrong][i] = new Array();
+
+ // check to see if the guess is already in the guess array
+ var i_max = HP[_wrong][i].length;
+ for (var ii=0; ii<i_max; ii++) {
+ if (HP[_wrong][i][ii]==g) break;
+ }
+
+ // add the guess if it was not found
+ if (ii==i_max) {
+ HP[_wrong][i][ii]=g;
+ } else {
+ g = null; // this is not a new answer
+ }
+ }
+
+ // increment checks for this question, if necessary
+ if (g) {
+ if (!HP[_checks][i]) HP[_checks][i] = 0;
+ HP[_checks][i]++;
+ }
+ }
+ }
+ }
+ } // end if JMatch
+
+ if (t==5) { // JMix
+
+ // get question number (always 0)
+ var q = args[0];
+
+ // check question has not already been answered correctly
+ if (!HP[_correct][q]) {
+
+ // match current guess against possible correct answers
+ var a_max = Answers.length;
+ for (var a=0; a<a_max; a++) {
+ var i_max = Answers[a].length;
+ for (var i=0; i<i_max; i++) {
+ if (Answers[a][i] != GuessSequence[i]) break;
+ }
+ if (i==i_max) break; // correct answer was found
+ }
+ // at this point, (a==a_max) means guess is wrong
+
+ // get array of segment texts in this g(uess)
+ var g = GetJMixSequence(GuessSequence);
+
+ // convert g(uess) array and to a s(tring)
+ var s = '';
+ var i_max = g.length;
+ for (var i=0; i<i_max; i++) {
+ g[i] = trim(g[i]);
+ if (g[i]!='') {
+ s += (s=='' ? '' : '+') + g[i];
+ }
+ }
+ if (s) {