diff --git a/mod/hotpot/README.TXT b/mod/hotpot/README.TXT
index 872b041ac71..4906193a1a6 100644
--- a/mod/hotpot/README.TXT
+++ b/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.
\ No newline at end of file
+Martin Holmes, Half-Baked Software and the University of Victoria HCMC, 1998-2004.
diff --git a/mod/hotpot/attempt.php b/mod/hotpot/attempt.php
index 56ce07c615c..dbc5af6a9ff 100644
--- a/mod/hotpot/attempt.php
+++ b/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;
+ 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;
+ }
+ }
+
+ // 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
+
+ // update status in previous records in this group
+ set_field("hotpot_attempts", "status", $attempt->status, "clickreportid", $attempt->clickreportid);
+
+ // add new attempt record
+ unset ($attempt->id);
+ $attempt->id = insert_record("hotpot_attempts", $attempt);
+
+ 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);
+ }
+ }
+
+ // remove slashes added by lib/setup.php
+ $attempt->details = stripslashes($attempt->details);
+
+ // 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);
+ }
+
+ // 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 {
- $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 ($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);
+ }
}
+ }
- // remove slashes added by lib/setup.php
- $attempt->details = stripslashes($attempt->details);
+ if ($attempt->status==HOTPOT_STATUS_INPROGRESS) {
+ $quiz_is_finished = false;
- // 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);
-
- if (! update_record("hotpot_attempts", $attempt)) {
- error("Could not update attempt record: ".$db->ErrorMsg(), $next_url);
- }
-
- // set previously unfinished attempts of this quiz by this user to "finished"
- hotpot_close_previous_attempts($hotpot->id, $USER->id, $time);
+ } 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";
+
+ 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");
+ }
// =================
diff --git a/mod/hotpot/backuplib.php b/mod/hotpot/backuplib.php
index ceab3997ff2..b0f2865fabb 100644
--- a/mod/hotpot/backuplib.php
+++ b/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 @@
// $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 @@
$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, $field, $value,
+ $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
- function hotpot_backup_records(&$bf, $status, $level, $table, $field, $value, $records_tag, $records_tags, $record_tag, $record_tags, $excluded_tags, $more_backup) {
+ $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, $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
+
+ // $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) {
diff --git a/mod/hotpot/db/mysql.php b/mod/hotpot/db/mysql.php
index 43eca1350c3..002d989b12c 100644
--- a/mod/hotpot/db/mysql.php
+++ b/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() {
diff --git a/mod/hotpot/db/mysql.sql b/mod/hotpot/db/mysql.sql
index beab33ca162..49887bcd1f1 100644
--- a/mod/hotpot/db/mysql.sql
+++ b/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';
-
+
\ No newline at end of file
diff --git a/mod/hotpot/db/postgres7.php b/mod/hotpot/db/postgres7.php
index d9e639391b5..14e8437bd67 100644
--- a/mod/hotpot/db/postgres7.php
+++ b/mod/hotpot/db/postgres7.php
@@ -1,6 +1,8 @@
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 ''
+ ";
+ 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, '&','&')
+ WHERE string LIKE '%&#%'
+ 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 'OK ';
+ } else {
+ print 'Failed ';
+ }
+ }
+ }
+ return $ok;
+}
?>
diff --git a/mod/hotpot/hotpot-full.js b/mod/hotpot/hotpot-full.js
index 9f7ed8a0783..031aa966a61 100644
--- a/mod/hotpot/hotpot-full.js
+++ b/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 = ''
+ ''
+ '
'
@@ -566,7 +619,7 @@ function QuizLogin(LoginPrompt) {
+ ''
;
for(var i=0; i'
@@ -905,7 +984,7 @@ function AddStudentDetailsToResultForm() {
}
}
if (Login[3]) { // quiz password
- sDetails += makeHiddenField('Password', window.Password);
+ sDetails += hpHiddenField('Password', window.Password);
ResultForm = replaceLast('', sDetails + '', 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 tag in the ResultForm
ResultForm = replaceLast('', dbDetails + '', 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('', s + '', ResultForm);
@@ -975,23 +1054,24 @@ function replaceLast(a, b, c) {
// *************************
function GetQuestionDetails() {
- var t = get_quiz_type();
- var v = get_quiz_version();
+ var hp = hpVersion();
+ var t = hpQuizType();
+ var v = hpQuizVersion();
- 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) : '';
+ 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(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; q0)) { // 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; q0) 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; i0) 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;
-
- // check the quiz version
- if (hp5 || v==6 || v==6.1) {
-
- if (JMatch[1] && v==6.1) { // attempts
- qDetails += makeHiddenField('JMatch_attempts', Penalties+1);
- }
-
- // get number of questions
- var max_q = (hp5 || v==6) ? Status.length : F.length;
-
- // get details for each question
- for (var q=0; q=i_max) i = 0; // shouldn't happen
+ } else {
+ // get current guess, if any
+ var i = obj.selectedIndex;
+ }
+ if (i) rhs = obj.options[i].innerHTML;
+ } else { // correct
+ rhs = GetJMatchText(q, 'RightItem');
+ }
}
- return (i=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) {
+ HP[_wrong][AD][q][i] = guess;
+ check = true;
+ }
+ }
+
+ // update HP[_checks], if necessary
+ if (check) {
+ if (!HP[_checks][AD]) HP[_checks][AD] = new Array();
+ if (!HP[_checks][AD][q]) HP[_checks][AD][q] = 0;
+ HP[_checks][AD][q]++;
+ }
+ }
+}
+function hpClickEnter(hp, t, v, args) {
+ if (t==3) { // JCross
+ var q = args[0]; // clue/question number
+ if (!HP[_enter][q]) HP[_enter][q] = 0;
+ HP[_enter][q]++;
+ }
+ return true;
+}
+function GetJMatchQuestionDetails(hp, v) {
+ var qDetails = '';
+
+ // HP5.5 uses "I" for v5 and v6 JMatch quizzes
+ // var hp5 = (window.I) ? true : false;
+
+ // check the quiz version
+ if (hp==5 || hp==6) {
+
+ if (JMatch[1] && v==6.1) { // attempts
+ qDetails += hpHiddenField('JMatch_attempts', Penalties+1);
+ }
+
+ // get number of questions
+ var max_q = (hp==5 || v==6) ? Status.length : F.length;
+
+ // get details for each question
+ for (var q=0; q=0 && value.indexOf('>')>=0) {
value = '';
}
@@ -1527,11 +2063,12 @@ function encode_entities(s_in) {
var c = s_in.charCodeAt(i);
// 34 : double quote .......["] &
// 38 : single quote .......['] '
+ // 43 : plus sign ..........[+]
// 44 : comma ..............[,]
// 60 : left angle bracket .[<] <
// 62 : right angle bracket [>] >
// >=128 multibyte character
- s_out += (c<128) ? s_in.charAt(i) : ('' + pad(c.toString(16), 4) + ';');
+ s_out += (c==43 || c==44 || c>=128) ? ('' + pad(c.toString(16), 4) + ';') : s_in.charAt(i);
}
return s_out;
}
@@ -1568,15 +2105,15 @@ function getTime(obj) {
return s;
}
-function getFunction(fn) {
+function getFunc(fn) {
if (typeof(fn)=='string') {
fn = eval('window.' + fn);
}
return (typeof(fn)=='function') ? fn : null;
}
-function getFunctionCode(fn, extra) {
+function getFuncCode(fn, extraCode, anchorCode, beforeAnchor) {
var s = '';
- var obj = getFunction(fn);
+ var obj = getFunc(fn);
if (obj) {
s = obj.toString();
var i1 = s.indexOf('{')+1;
@@ -1585,27 +2122,63 @@ function getFunctionCode(fn, extra) {
s = s.substring(i1, i2);
}
}
- return s + (extra ? extra : '');
-}
-function getFunctionArgs(fn) {
- var a = new Array();
- var obj = getFunction(fn);
- if (obj) {
- var s = obj.toString();
- var i1 = s.indexOf('(')+1;
- var i2 = s.indexOf(')');
- if (i1>0 && i10 && i1i2) i3 = i2;
+ a[i++] = trim(s.substring(i1, i3));
+ i1 = i3+1;
+ }
+ }
+
+ return flag ? a : getArgsStr(a);
}
function getPrompt(fn) {
// the LoginPrompt is the text string in the first prompt(...) statement
// v5 : in the StartUp function
// v6 : in the GetUserName function
// Note: netscape uses double-quote as delimiter, others use single quote
- var s = getFunctionCode(fn);
+ var s = getFuncCode(fn);
var i1 = s.indexOf('prompt') + 8;
var i2 = s.indexOf(s.charAt(i1-1), i1);
@@ -1629,7 +2202,7 @@ function getStartUpCode(fn) {
// and the code after the 2nd subsequent '}'
// v6 : the code before and after 'GetUserName();'
// i.e. all the code except the call to 'GetUserName();'
- var s = getFunctionCode(fn);
+ var s = getFuncCode(fn);
var i1 = s.indexOf('GetUserName();');
if (i1>=0) { // v6
var i2 = i1 + 14;
@@ -1640,7 +2213,308 @@ function getStartUpCode(fn) {
return (0'
+ + ''
+ ;
+ } else if (FEEDBACK[1]) { // url only
+
+ if (typeof(FEEDBACK[1])=='object') {
+ var i_max = FEEDBACK[1].length;
+ if (i_max>1) { // several teachers
+ html += ''
+ + ''
+ ;
+ } else if (i_max==1) { // one teacher
+ url = FEEDBACK[0] + FEEDBACK[1][0][1];
+ }
+
+ } else if (typeof(FEEDBACK[1])=='string') {
+ url = FEEDBACK[0] + FEEDBACK[1];
+ }
+ } else {
+ url = FEEDBACK[0];
+ }
+ if (url || html) {
+ var w = openWindow(url, 'feedback', FEEDBACK[4], FEEDBACK[5], 'RESIZABLE,SCROLLBARS', html);
+ if (!w) {
+ // unable to open popup window
+ alert(MSG[18]);
+ }
+ }
+ }
+}
+
+// ********************
+// intercept clicks
+// ********************
+function hpInterceptFeedback() {
+ // modify the function which writes feedback
+ // v6: ShowMessage(Feedback)
+ // v5: WriteFeedback(Feedback)
+ // v4: WriteFeedback(Stuff)
+ // v3: WriteFeedback(Feedback) [except JMatch]
+ // v3: CheckAnswer() [JMatch only]
+ var f = window.ShowMessage ? 'ShowMessage' : window.WriteFeedback ? 'WriteFeedback' : 'CheckAnswer';
+ var s = getFuncCode(f) + 'Finish();';
+ var a = getFuncArgs(f, true);
+ if (a[0] && window.FEEDBACK && FEEDBACK[0]) {
+ s = a[0] + "+='
" + '' + FEEDBACK[6] + "';" + s;
+ }
+ eval('window.' + f + '=new Function(' + getArgsStr(a) + 's);');
+}
+function hpInterceptHints() {
+ // modify the function which shows hints
+ // JBC: none
+ // JCloze v3-v6: ShowHint()
+ // JCross v3: Cheat(), v4: ShowHint(), v5-v6[HP5]: ShowHint(Across,x,y,BoxName), v6[HP6]: ShowHint(Across,ClueNum,x,y,BoxId)
+ // JMatch: none
+ // JMix v5-v6: CheckAnswer(CheckType=1)
+ // JQuiz v3: CheckAnswer(ShowHint=true, QNum), v4: CheckAnswer(ShowHint=true), v5-v6[HP5]: CheckAnswer(ShowHint=true,QNum), v6[HP6]: ShowHint(QNum)
+
+ var x = ''; // extra code, if any
+
+ if (window.Cheat) {
+ // JCross v3 ?
+
+ } else if (window.ShowHint) {
+ var f = 'ShowHint';
+ var a = getFuncArgs(f, true);
+
+ if (a.length==0) {
+ if (window.FindCurrent) {
+ // JCloze v3-v6
+ x = 'var q=window.Locked?-1:FindCurrent();if(q>=0&&GetHint(q))hpClick(1,q);';
+ } else {
+ // JCross v4
+ // work out which box would have a hint added
+ // work out which question that box is part of using GridMap and WinLetters
+ }
+
+ } else if (a[0]=='Across') {
+ if (a[1]=='ClueNum') {
+ // JCross v6 [HP6]
+ x = "var args=new Array(ClueNum,Across?'A':'D');hpClick(1,args);";
+ } else if (a[1]=='x' && a[2]=='y') {
+ // JCross v5-v6 [HP5]
+ x = "var args=new Array(C[x][y],Across?'A':'D');hpClick(1,args);";
+ }
+
+ } else if (a[0]=='QNum') {
+ // JQuiz v6[HP6]
+ x = 'hpClick(1,QNum);';
+ }
+
+ } else if (window.CheckAnswer) {
+ var f = 'CheckAnswer';
+ var a = getFuncArgs(f, true);
+
+ if (a[0]=='ShowHint') {
+ if (a[1]=='QNum') {
+ // JQuiz v3, v5-v6[HP5]
+ x = 'if(ShowHint)hpClick(1,QNum);';
+ } else {
+ // JQuiz v4
+ x = 'if(ShowHint)hpClick(1,QNum-1);'; // QNum is a global variable
+ }
+ } else if (a[0]=='CheckType') {
+ // JMix v5-v6
+ x = 'if(CheckType==1)hpClick(1,0);'; // question number is always 0;
+ }
+ }
+ // add the e(x)tra code, if any, to the start of the hint (f)unction
+ if (x) {
+ var s = getFuncCode(f, x, '', true);
+ eval('window.' + f + '=new Function(' + getArgsStr(a) + 's);');
+ }
+}
+function hpInterceptClues() {
+ // modify the function which shows clues (or ShowAnswers in JQuiz)
+ // JBC: none
+ // JCloze v3-v6: ShowClue(ItemNum)
+ // JCross v3-v4: ShowClue(ClueNum), v5-v6: ShowClue(ClueNum,x,y)
+ // JMatch none
+ // JMix none
+ // JQuiz ShowAnswers(QNum)
+
+ var x = ''; // extra code, if any
+
+ if (window.ShowClue) {
+ var f = 'ShowClue';
+ var a = getFuncArgs(f, true);
+
+ if (a[0]=='ItemNum') {
+ // JCloze (v3-v6)
+ x = 'if(!window.Locked)hpClick(2,ItemNum);'; // v6 [HP6] uses window.Locked
+
+ } else if (a[0]=='ClueNum') {
+ if (a[1]=='x' && a[2]=='y') {
+ if (window.A && window.D) {
+ // JCross v5-v6 [HP5]
+ x = 'if(A[ClueNum]||D[ClueNum])hpClick(2,ClueNum);';
+ } else if (document.getElementById) {
+ // JCross v6 [HP6]
+ x = "if(document.getElementById('Clue_A_' + ClueNum)||document.getElementById('Clue_D_' + ClueNum))hpClick(2,ClueNum);";
+ }
+ } else {
+ if (window.AClues && window.DClues) {
+ // JCross v3-v4
+ x = 'if(AClues[ClueNum]||DClues[ClueNum])hpClick(2,ClueNum);';
+ }
+ }
+ }
+ }
+ // JQuiz: there is no "ShowClue" function but there is a "ShowAnswer" function
+ if (window.ShowAnswers) {
+ var f = 'ShowAnswers';
+ var a = getFuncArgs(f, true);
+ if (window.State) {
+ if (window.ShowMessage) {
+ // JQuiz v6 [HP6]
+ x = 'if(State[QNum][0]<1)hpClick(2,QNum);';
+ } else if (window.WriteFeedback) {
+ // JQuiz v3-v4
+ x = 'if(State[QNum-1][0]<1)hpClick(2,QNum-1);';
+ }
+ } else if (window.Status) {
+ // JQuiz v5-v6 [HP5]
+ x = 'if(Status[QNum][0]<0)hpClick(5,QNum);';
+ }
+
+ }
+ // add the e(x)tra code, if any, to the start of the clue (f)unction
+ if (x) {
+ var s = getFuncCode(f, x, '', true);
+ eval('window.' + f + '=new Function(' + getArgsStr(a) + 's);');
+ }
+}
+function hpInterceptChecks() {
+ // modify the function which handles checks
+ // JBC: none
+ // JCloze none
+ // JCross none
+ // JMatch HP5 v3, v5, v6: CheckAnswer(), HP5 v4: CheckResults(), HP6: CheckAnswers()
+ // JMix CheckAnswer(CheckType)
+ // JQuiz
+ // HP5: CheckAnswer(ShowHint, QNum)
+ // HP6: CheckMCAnswer, CheckMultiSelAnswer, CheckShortAnswer
+ // Sequitur CheckAnswer(Chosen, Btn)
+
+ // HP6 JQuiz has three "Check Answer" functions
+ var f = new Array('CheckMCAnswer', 'CheckMultiSelAnswer', 'CheckShortAnswer');
+ for (var i=0; i=0 && window.ShowMessage) {
//javascript:var s="";var x=document.forms.QForm.elements;for(var i=0;i=0) ? 3 : (f.QForm && self.RItems) ? 4 : (f.ButtonForm) ? 5 : (f.QForm0 && f.Buttons0) ? 6 : 0;
+ 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;
- } else if (GetObj(d, 'MainDiv')) {
+ } else if (hpObj(d, 'MainDiv')) {
v = 6;
var obj = (f.QForm) ? f.QForm.elements : null;
- t = (obj && obj.length>0 && obj[0].id=='') ? 1 : (f.Cloze) ? 2 : (GetObj(d, 'GridDiv') || GetObj(d, 'Clues')) ? 3 : GetObj(d, 'MatchDiv') ? 4 : GetObj(d, 'SegmentDiv') ? 5 : ((f.QForm && f.QForm.Guess) || GetObj(d, 'Questions')) ? 6 : 0;
+ 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;
+
+ // sniff Rottmeier quizzes
+ if (window.Create_StateArray) {
+ if (t==2) { // JCloze
+ var obj = new Create_StateArray();
+ if (typeof(obj.GapLocked)=='boolean') r = 1; // drop-down (v2.4)
+ else if (typeof(obj.ErrorFound)=='boolean') r = 2; // find-it (v3.1a + v3.1b)
+ obj = null; // prevents memory leakage on some versions of IE
+ }
+ }
- } else if (GetObj(d, 'D0')) {
+ } else if (hpObj(d, 'D0')) {
v = 6.1; // drag and drop (HP5 and HP6)
- t = (GetObj(d, 'F0')) ? 4 : (GetObj(d, 'Drop0')) ? 5 : 0;
+ t = (hpObj(d, 'F0')) ? 4 : (hpObj(d, 'Drop0')) ? 5 : 0;
- } else if (window.Words && f.Rhubarb) {
+ } else if (w.Words && f.Rhubarb) {
v = 6;
t = 7; // rhubarb (TexToys)
- } else if (window.Segments && GetObj(d, 'Story')) {
+ } else if (w.Segments && hpObj(d, 'Story')) {
v = 6;
t = 8; // sequitur (TexToys)
}
-
- if (v) quiz.v = v; // intended browser version
- if (t) quiz.t = t; // quiz type
+ quiz.v = v; // intended browser version
+ quiz.t = t; // quiz type
+ quiz.r = r; // rottmeier quiz type
}
}
-function get_quiz_type() {
- sniff_quiz();
+function hpRottmeier() {
+ hpDetectQuiz();
+ return quiz.r;
+}
+function hpVersion() {
+ hpDetectQuiz();
+ return quiz.hp;
+}
+function hpQuizType() {
+ hpDetectQuiz();
return quiz.t;
}
-function get_quiz_version() {
- sniff_quiz();
+function hpQuizVersion() {
+ hpDetectQuiz();
return quiz.v;
}
+function hpScoreEngine(score_i, a, s, aa, ss, count_c, count_i) {
+ // calculate the score for the quiz so far
-function all_finished(a, s, aa, ss) {
+ // score_i : amount by which to increment "score"
+ // a : outer array
+ // s : condition, if any, on outer array (=a)
+ // if true, the score will be incremented by "score_i"
+ // aa : inner array, if any
+ // ss : condition, if any, on inner array (=aa)
+ // count_c : condition, if any, on which "count" is to be incremented
+ // count_i : amount by which to increment "count"
+
+ // "a" and "aa" may be passed as arrays or strings containing the name of an array
+ // "s" and "ss" are strings containing an expression to be eval(uated)
+ // "score_i", "count_i" and "count_c" strings containing an expression to be eval(uated)
+
+ var score = 0;
+ var count = 0;
+
+ // set default condition to increment "count", and amount by which to increment the count
+ if (count_c==null) count_c = "true";
+ if (count_i==null) count_i = "1";
+
+ // set length of outer array. if any
+ var l = (typeof(a)=="string") ? eval(a + ".length") : a ? a.length : 0;
+
+ // loop through outer array
+ for (var i=0; i0 && a[i]=='0'"); // doesn't work
+ else if (v==4) x = hpScoreEngine(1, DoneStatus, "a[i]==0"); // doesn't work
+ else if (v==5 || v==6) x = hpScoreEngine("a[i][3]", Status, "a[i][3]");
+
+
+ } else if (t==2) { // jcloze
+
+ if (v==3 || v==4) x = hpScoreEngine("a[i]", Scores);
+ else if (hp==5) x = hpScoreEngine("a[i][3]", State); // v==5 && v==6
+ else if (hp==6) {
+ var r = hpRottmeier();
+ if (r) x = hpScoreEngine("a[i][1].Score", GapList);
+ else x = hpScoreEngine("a[i].ItemScore", State);
+ }
+
+ } else if (t==3) { // jcross
+
+ if (v==3) x = hpScoreEngine(1, CorrectAnswers, "document.QuizForm.elements[i*2].selectedIndex==a[i]");
+ else if (v==4) x = hpScoreEngine(1, WinLetters, "ConvertCase(GetBoxValue(i),1).charAt(0)==a[i].charAt(0)");
+ else if (v==5 || v==6) x = hpScoreEngine(1, L, "", "L[i]", "L[i][ii] && L[i][ii]==G[i][ii]", "L[i][ii]");
+
+ } else if (t==4) { // jmatch
+
+ if (v==3) x = hpScoreEngine(1, CorrectAnswers, "document.QuizForm.elements[i*2].selectedIndex==a[i]");
+ else if (v==4) x = hpScoreEngine(1, Draggables, "a[i].correct=='1'");
+ else if (v==5) x = hpScoreEngine(1, I, "I[i][2]<1 && I[i][0].length>0 && Status[i][0]==1");
+ else if (v==6) x = hpScoreEngine("Math.min(score,(Status.length-Status[i][1])/Status.length)-score", Status, "true");
+ else if (v==5.1 || v==6.1) x = hpScoreEngine(1, D, "D[i][2]==D[i][1] && D[i][2]>0");
+
+ } else if (t==5) { // jmix
+
+ // there was no v3 or v4 of JMix
+ if (v==5 || v==6 || v==6.1) x = Math.floor(100*(Segments.length-Penalties)/Segments.length);
+
+ } else if (t==6) { // jquiz
+
+ if (hp==5) {
+ if (v==3 || v==4) x = hpScoreEngine("a[i][4]/10", State, "a[i][0]==1");
+ else if (v==5 || v==6) x = hpScoreEngine("a[i][4]/10", Status, "a[i][0]==1", "", "", "true", "1");
+
+ } else if (hp==6) {
+ if (v==6) x = hpScoreEngine("I[i][0]*a[i][0]", State, "a[i]&&a[i][0]>=0", "", "", "a[i]", "I[i][0]");
+ }
+
+ } else if (t==7) { // rhubarb
+ if (v==6) {
+ x = hpScoreEngine(1, DoneList, "a[i]==1");
+ }
+
+ } else if (t==8) { // sequitur
+ if (v==6) x = Math.floor(100*ScoredPoints/TotalPoints);
+ }
+
+ return x; // result
+}
+
+function hpFinishedEngine(a, s, aa, ss) {
// determine whether or not all quistions in a quiz are finished
// a : outer array
// s : condition, if any, on outer array
+ // if true for any element in "a", the quiz is NOT finished
// aa : inner array, if any
// ss : condition, if any, on inner array
+ // if true for any element in "aa", the quiz is NOT finished
// the arrays "a" and "aa" may be passed as arrays or strings to be eval(uated)
// the conditions "s" and "ss" are specified as strings to be eval(uated)
// assume a positive result
- var r = true;
+ var x = true;
// set length of outer array. if any
var l = (typeof(a)=="string") ? eval(a + ".length") : a ? a.length : 0;
@@ -1846,73 +2890,87 @@ function all_finished(a, s, aa, ss) {
for (var i=0; i0 && a[i]=='0'");
- else if (v==4) r = all_finished(DoneStatus, "a[i]==0");
- else if (v==5 || v==6) r = all_finished(Status, "a[i][0]==0");
+ if (v==3) x = hpFinishedEngine(DoneStatus, "i>0 && a[i]=='0'");
+ else if (v==4) x = hpFinishedEngine(DoneStatus, "a[i]==0");
+ else if (v==5 || v==6) x = hpFinishedEngine(Status, "a[i][0]==0");
} else if (t==2) { // jcloze
- if (v==3 || v==4 || v==5 || v==6) r = all_finished(I, "CheckAnswer(i)==-1");
- // also: else if (v==5 || v==6) r = all_finished(State, "a[i][4]!=1")
+ var r = hpRottmeier();
+ if (r==1) x = hpFinishedEngine(GapList, "a[i][1].GapLocked==false"); // drop-down
+ else if (r==2) x = hpFinishedEngine(GapList, "a[i][1].ErrorFound==false"); // find-it
+ else if (v==3 || v==4 || v==5 || v==6) x = hpFinishedEngine(I, "CheckAnswer(i)==-1");
+ // also: else if (v==5 || v==6) x = hpFinishedEngine(State, "a[i][4]!=1")
} else if (t==3) { // jcross
- if (v==3) r = all_finished(document.Crossword.elements, "ConvertCase(is.mac?unescape(MacStringToWin(a[i].value)):a[i].value,1)!=Letters[i]");
- else if (v==4) r = all_finished(WinLetters, "ConvertCase(GetBoxValue(i),1).charAt(0) != a[i].charAt(0)");
- else if (v==5) r = all_finished(L, "", "L[i]", "L[i][ii] && L[i][ii]!=G[i][ii]");
+ if (v==3) x = hpFinishedEngine(document.Crossword.elements, "ConvertCase(is.mac?unescape(MacStringToWin(a[i].value)):a[i].value,1)!=Letters[i]");
+ else if (v==4) x = hpFinishedEngine(WinLetters, "ConvertCase(GetBoxValue(i),1).charAt(0) != a[i].charAt(0)");
+ else if (v==5 || v==6) x = hpFinishedEngine(L, "", "L[i]", "L[i][ii] && L[i][ii]!=G[i][ii]");
} else if (t==4) { // jmatch
- if (v==3) r = all_finished(CorrectAnswers, "document.QuizForm.elements[i*2].selectedIndex != a[i]");
- else if (v==4) r = all_finished(Draggables, "a[i].correct!='1'");
- else if (v==5) r = all_finished(I, "I[i][2]<1 && I[i][0].length>0 && Status[i][0]<1 && GetAnswer(i)!=I[i][3]");
- else if (v==6) r = all_finished(D, "D[i][2]==0 || D[i][2]!=D[i][1]");
+ if (v==3) x = hpFinishedEngine(CorrectAnswers, "document.QuizForm.elements[i*2].selectedIndex != a[i]");
+ else if (v==4) x = hpFinishedEngine(Draggables, "a[i].correct!='1'");
+ 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]");
+ else if (v==6) x = hpFinishedEngine(Status, "Status[i][0]<1");
+ else if (v==5.1 || v==6.1) x = hpFinishedEngine(D, "D[i][2]==0 || D[i][2]!=D[i][1]");
} else if (t==5) { // jmix
// there was no v3 or v4 of JMix
- if (v==5 || v==6) r = !all_finished(Answers, "a[i].join(',')=='" + GuessSequence.join(',') + "'");
+ if (v==5 || v==6 || v==6.1) x = !hpFinishedEngine(Answers, "a[i].join(',')=='" + GuessSequence.join(',') + "'");
} else if (t==6) { // jquiz
- if (v==3 || v==4) r = all_finished(State, "a[i][0]==0");
- else if (v==5 || v==6) r = all_finished(State, "a[i] && a[i][0]<0");
+ if (v==3 || v==4) x = hpFinishedEngine(State, "a[i][0]==0");
+ else if (v==5 || v==6) {
+ if (hp==5) x = hpFinishedEngine(Status, "a[i][0]<1");
+ else if (hp==6) x = hpFinishedEngine(State, "a[i] && a[i][0]<0");
+ }
} else if (t==7) { // rhubarb
- if (v==6) r = all_finished(DoneList, "a[i]==1");
+ if (v==6) x = hpFinishedEngine(DoneList, "a[i]==1");
} else if (t==8) { // sequitur
- if (v==6) r = (CurrentNumber==TotalSegments || AllDone);
+ if (v==6) x = (CurrentNumber==TotalSegments || AllDone);
}
- return r; // result
+ return x;
}
-function GetObj(d, id) {
+function hpObj(d, id) {
return d.getElementById ? d.getElementById(id) : d.all ? d.all[id] : d[id];
}
@@ -1920,28 +2978,51 @@ function GetObj(d, id) {
// initialization
// **************
-if (window.Finish==null) { // v3, v4 and v5
- // modify the function which writes feedback to call Finish() if the quiz is finished
- // usually this is the WriteFeedback()
- // but v3 of JMatch uses CheckAnswer()
- var f = window.WriteFeedback ? 'WriteFeedback' : 'CheckAnswer';
- var s = getFunctionCode(f, 'if(is_finished())Finish();');
- var a = getFunctionArgs(f);
- eval('window.' + f + '=new Function(' + a + 's)');
-}
+hpInterceptFeedback();
+hpInterceptHints();
+hpInterceptClues();
+hpInterceptChecks();
-// the standard Finish() function
-// for v6, this overwrites the original function
-function Finish(){
- var f = document.store;
- if (f) {
- // hotpot use "Score", TexToys use "FinalScore"
- var mark = (window.Score ? Score : window.FinalScore ? FinalScore : 0);
- f.starttime.value = getTime(Start_Time);
- f.endtime.value = getTime();
- f.mark.value = mark;
- f.detail.value = ''+GetQuestionDetails()+'';
- f.submit();
+function hpFindForm(name, w) {
+ if (w==null) w = self;
+ var f = w.document.forms[name];
+ if (f==null && w.frames) {
+ for (var i=0; i';
+ if (hpForm.status) {
+ if (!quizstatus) {
+ // 4=completed, 3=abandoned, 2=timed-out or 1=in-progress
+ quizstatus = hpFinished() ? 4 : hpTimedOut() ? 2 : 1;
+ }
+ hpForm.status.value = quizstatus;
+ }
+ if (!window.sentquizresults) {
+ if (hpForm.status && quizstatus==4) {
+ window.sentquizresults = true;
+ }
+ if (quizstatus==4) { // completed
+ // wait 2 seconds for student to see feedback
+ setTimeout("hpForm.submit();", 2000);
+ } else {
+ hpForm.submit();
+ }
+ }
+ } else if (hpFinished()) {
+ SendAllResults(mark);
}
}
@@ -1950,14 +3031,14 @@ if (DB[7] && DB[8] && !is_LMS()) {
ResultForm = ''
+ ''
+ ''
+ ''
;
@@ -1967,7 +3048,8 @@ var p = getPrompt(window.GetUserName || window.StartUp);
var c = getStartUpCode(window.StartUp);
if (p && c) {
window.StartUp = new Function('QuizLogin("' + p + '")');
- window.StartQuiz = new Function('if(!is_LMS()){' + c + '}');
+ window.StartQuiz = new Function(c);
+ // "QuizLogin" finshes by calling "StartQuiz"
}
// reassign the SendResults function
diff --git a/mod/hotpot/index.php b/mod/hotpot/index.php
index 9025e98350a..8b7eb7806c9 100644
--- a/mod/hotpot/index.php
+++ b/mod/hotpot/index.php
@@ -3,6 +3,7 @@
// This page lists all the instances of hotpot in a particular course
require_once("../../config.php");
+ require_once("../../course/lib.php");
require_once("lib.php");
$id = required_param("id"); // course
@@ -15,11 +16,22 @@
add_to_log($course->id, "hotpot", "view all", "index.php?id=$course->id", "");
- // Print the header
+ // Moodle 1.4+ requires sesskey to be passed in forms
+ if (isset($USER->sesskey)) {
+ $sesskey = '';
+ } else {
+ $sesskey = '';
+ }
+ // get message strings for titles
$strmodulenameplural = get_string("modulenameplural", "hotpot");
$strmodulename = get_string("modulename", "hotpot");
+ // string translation array for single and double quotes
+ $quotes = array("'"=>"\'", '"'=>'"');
+
+ // Print the header
+
$title = "$course->shortname: $strmodulenameplural";
$heading = "$course->fullname";
$navigation = "$strmodulenameplural";
@@ -30,89 +42,235 @@
$next_url = "$CFG->wwwroot/course/view.php?id=$course->id";
- // Get all instances of this module
- if (! $hotpots = get_all_instances_in_course("hotpot", $course)) {
- notice("There are no $strmodulenameplural", $next_url);
- die;
+ // get display section, if any
+ $section = optional_param('section', 0);
+ if ($section) {
+ $displaysection = course_set_display($course->id, $section);
+ } else {
+ if (isset($USER->display[$course->id])) {
+ $displaysection = $USER->display[$course->id];
+ } else {
+ $displaysection = 0;
+ }
}
+
+ // Get all instances of this module
+ if (!$hotpots = hotpot_get_all_instances_in_course("hotpot", $course)) {
+ $hotpots = array();
+ }
+
- if (isadmin()) {
- if (isset($_POST['regrade'])) {
- $hotpotids = array();
- foreach ($hotpots as $hotpot) {
- $hotpotids[] = $hotpot->id;
- }
- $hotpotids = implode(',', $hotpotids);
-
- $select = "hotpot IN ($hotpotids)";
-
- $questionids = array();
- if ($questions = get_records_select("hotpot_questions", $select)) {
- $questionids = array_keys($questions);
- }
- $questionids = implode(',', $questionids);
-
- if ($questionids) {
- hotpot_delete_and_notify(
- 'hotpot_questions',
- "id IN ($questionids)",
- get_string('question', 'quiz')
- );
- hotpot_delete_and_notify(
- 'hotpot_responses',
- "question IN ($questionids)",
- get_string('answer', 'quiz')
- );
- }
-
- if ($attempts = get_records_select('hotpot_attempts', $select)) {
- $count = 0;
- foreach ($attempts as $attempt) {
- if (isset($attempt->score)) {
- hotpot_add_attempt_details($attempt);
- $attempt->details = addslashes($attempt->details);
- if (! update_record('hotpot_attempts', $attempt)) {
- error("Could not update attempt record: ".$db->ErrorMsg(), $next_url);
- }
- $count++;
- if ($count%10 == 0) {
- print ".";
- if ($count%200 == 0) {
- print " \n";
- }
- hotpot_flush(300);
- }
- }
- }
- if ($count) {
- notify(get_string('added', 'moodle', "$count x ".get_string('attempts', 'quiz')));
- }
- notify(get_string('regradecomplete', 'quiz'));
+ // if necessary, remove hotpots that are not in section0 or this $USER's display section
+ if ($displaysection) {
+ foreach ($hotpots as $cmid=>$hotpot) {
+ if ($hotpot->section!=0 && $hotpot->section!=$displaysection) {
+ unset($hotpots[$cmid]);
}
}
- print '
'."\n";
}
- // Print the list of instances of this module
+ if (empty($hotpots)) {
+ notice("There are no $strmodulenameplural", $next_url);
+ exit;
+ }
- $timenow = time();
- $strupdate = get_string("update");
- $strusers = get_string("users");
+ // get list of hotpot ids
+ $hotpotids = array();
+ foreach ($hotpots as $cmid=>$hotpot) {
+ $hotpotids[] = $hotpot->id;
+ }
+ $hotpotids = implode(',', $hotpotids);
- // Moodle 1.4+ requires sesskey to be passed in forms
- if (isset($USER->sesskey)) {
- $sesskey = '';
+ if (isadmin()) {
+
+ // get regrade settings, if any
+ $regrade = optional_param("regrade");
+ $confirm = optional_param("confirm");
+
+ // check regrade is valid
+ unset($regrade_cmid);
+ if (isset($regrade)) {
+ foreach ($hotpots as $cmid=>$hotpot) {
+ $found = false;
+ if ($hotpot->id==$regrade) {
+ $regrade_cmid = $cmid;
+ }
+ }
+ }
+
+ // regrade, if necessary
+ if (isset($regrade_cmid)) {
+
+ if (empty($confirm)) {
+
+ $strregradecheck = get_string('regradecheck', 'hotpot', $hotpots[$regrade_cmid]->name);
+
+ print_simple_box_start("center", "60%", "#FFAAAA", 20, "noticebox");
+ print_heading($strregradecheck);
+ print ''
+ . '