You can subscribe to this list here.
2004 |
Jan
|
Feb
|
Mar
|
Apr
|
May
|
Jun
|
Jul
|
Aug
|
Sep
|
Oct
|
Nov
|
Dec
(58) |
---|---|---|---|---|---|---|---|---|---|---|---|---|
2005 |
Jan
(53) |
Feb
(56) |
Mar
|
Apr
|
May
(30) |
Jun
(78) |
Jul
(121) |
Aug
(155) |
Sep
(77) |
Oct
(61) |
Nov
(45) |
Dec
(94) |
2006 |
Jan
(116) |
Feb
(33) |
Mar
(11) |
Apr
(23) |
May
(60) |
Jun
(89) |
Jul
(130) |
Aug
(109) |
Sep
(124) |
Oct
(63) |
Nov
(82) |
Dec
(45) |
2007 |
Jan
(31) |
Feb
(35) |
Mar
(123) |
Apr
(36) |
May
(18) |
Jun
(134) |
Jul
(133) |
Aug
(241) |
Sep
(126) |
Oct
(31) |
Nov
(15) |
Dec
(5) |
2008 |
Jan
(11) |
Feb
(6) |
Mar
(16) |
Apr
(29) |
May
(43) |
Jun
(149) |
Jul
(27) |
Aug
(29) |
Sep
(37) |
Oct
(20) |
Nov
(4) |
Dec
(6) |
2009 |
Jan
(34) |
Feb
(30) |
Mar
(16) |
Apr
(6) |
May
(1) |
Jun
(32) |
Jul
(22) |
Aug
(7) |
Sep
(18) |
Oct
(50) |
Nov
(22) |
Dec
(8) |
2010 |
Jan
(17) |
Feb
(15) |
Mar
(10) |
Apr
(9) |
May
(67) |
Jun
(30) |
Jul
|
Aug
|
Sep
(2) |
Oct
|
Nov
(1) |
Dec
|
From: Mike G. v. a. <we...@ma...> - 2009-07-06 12:08:42
|
Log Message: ----------- restore MIME::Parser to check_modules list back off wwdb_upgrade from the MAIN version (which uses DBupgrade.pm) to the previous 2.4.5 version (released Aug 13, 2007) which contains all of the update code internally. It's clear that Sam intended to move the subroutines of wwdb_upgrade to DBUpgrade.pm, but apparently he didn't finish debugging the new version. We'll back off on the update until the new version of wwdb_upgrade is debugged. Tags: ---- rel-2-4-patches Modified Files: -------------- webwork2/bin: check_modules.pl wwdb_upgrade Revision Data ------------- Index: check_modules.pl =================================================================== RCS file: /webwork/cvs/system/webwork2/bin/check_modules.pl,v retrieving revision 1.12.2.4.2.2 retrieving revision 1.12.2.4.2.3 diff -Lbin/check_modules.pl -Lbin/check_modules.pl -u -r1.12.2.4.2.2 -r1.12.2.4.2.3 --- bin/check_modules.pl +++ bin/check_modules.pl @@ -65,6 +65,7 @@ Iterator Iterator::Util Mail::Sender + MIME::Parser MIME::Base64 Net::IP Net::LDAPS Index: wwdb_upgrade =================================================================== RCS file: /webwork/cvs/system/webwork2/bin/wwdb_upgrade,v retrieving revision 1.13.2.1.2.1 retrieving revision 1.13.2.1.2.2 diff -Lbin/wwdb_upgrade -Lbin/wwdb_upgrade -u -r1.13.2.1.2.1 -r1.13.2.1.2.2 --- bin/wwdb_upgrade +++ bin/wwdb_upgrade @@ -18,6 +18,7 @@ use strict; use warnings; use Getopt::Std; +use DBI; use Data::Dumper; BEGIN { @@ -27,23 +28,447 @@ use lib "$ENV{WEBWORK_ROOT}/lib"; use WeBWorK::CourseEnvironment; -use WeBWorK::Utils::DBUpgrade; +use WeBWorK::Utils qw/runtime_use/; +use WeBWorK::Utils::CourseManagement qw/listCourses/; our ($opt_v); getopts("v"); if ($opt_v) { - $WeBWorK::Debug::Enabled = 1; + $| = 1; + *verbose = sub { print STDERR @_ }; } else { - $WeBWorK::Debug::Enabled = 0; + *verbose = sub {}; } -my $ce = new WeBWorK::CourseEnvironment({webwork_dir=>$ENV{WEBWORK_ROOT}}); +# global variables, hah hah. +my ($dbh, %sql_tables); -my $upgrader = new WeBWorK::Utils::DBUpgrade( - ce => $ce, - verbose_sub => sub { print STDERR @_ }, +################################################################################ + +my $i = -1; +our @DB_VERSIONS; + +$DB_VERSIONS[++$i]{desc} = "is the initial version of database, identical to database structure in WeBWorK 2.2.x."; + +$DB_VERSIONS[++$i]{desc} = "adds dbupgrade table to facilitate automatic database upgrades."; +$DB_VERSIONS[ $i]{global_code} = sub { + $dbh->do("CREATE TABLE `dbupgrade` (`name` VARCHAR(255) NOT NULL PRIMARY KEY, `value` TEXT)"); + $dbh->do("INSERT INTO `dbupgrade` (`name`, `value`) VALUES (?, ?)", {}, "db_version", 1); + $sql_tables{dbupgrade} = (); +}; + +$DB_VERSIONS[++$i]{desc} = "adds problems_per_page field to set and set_user tables of each course."; +$DB_VERSIONS[ $i]{course_code} = sub { + my $course = shift; + $dbh->do("ALTER TABLE `${course}_set` ADD COLUMN `problems_per_page` INT") + if exists $sql_tables{"${course}_set"}; + $dbh->do("ALTER TABLE `${course}_set_user` ADD COLUMN `problems_per_page` INT") + if exists $sql_tables{"${course}_set_user"}; +}; + +$DB_VERSIONS[++$i]{desc} = "adds depths table to keep track of dvipng depth information."; +$DB_VERSIONS[ $i]{global_code} = sub { + $dbh->do("CREATE TABLE depths (md5 CHAR(33) NOT NULL, depth SMALLINT, PRIMARY KEY (md5))"); + $sql_tables{depths} = (); +}; + +$DB_VERSIONS[++$i]{desc} = "changes type of key timestamp field to BIGINT"; +$DB_VERSIONS[ $i]{course_code} = sub { + my $course = shift; + return unless exists $sql_tables{"${course}_key"}; + $dbh->do("ALTER TABLE `${course}_key` CHANGE COLUMN `timestamp` `timestamp` BIGINT"); +}; + +$DB_VERSIONS[++$i]{desc} = "changes type of problem_user status field to FLOAT"; +$DB_VERSIONS[ $i]{course_code} = sub { + my $course = shift; + return unless exists $sql_tables{"${course}_problem_user"}; + $dbh->do("UPDATE `${course}_problem_user` SET `status`=NULL WHERE `status`=''"); + $dbh->do("ALTER TABLE `${course}_problem_user` CHANGE COLUMN `status` `status` FLOAT"); +}; + +$DB_VERSIONS[++$i]{desc} = "changes types of alphanumeric keyfields to TINYBLOB NOT NULL"; +$DB_VERSIONS[ $i]{course_code} = sub { + my $course = shift; + $dbh->do("ALTER TABLE `${course}_user` CHANGE COLUMN `user_id` `user_id` TINYBLOB NOT NULL") + if exists $sql_tables{"${course}_user"}; + $dbh->do("ALTER TABLE `${course}_password` CHANGE COLUMN `user_id` `user_id` TINYBLOB NOT NULL") + if exists $sql_tables{"${course}_password"}; + $dbh->do("ALTER TABLE `${course}_permission` CHANGE COLUMN `user_id` `user_id` TINYBLOB NOT NULL") + if exists $sql_tables{"${course}_permission"}; + $dbh->do("ALTER TABLE `${course}_key` CHANGE COLUMN `user_id` `user_id` TINYBLOB NOT NULL") + if exists $sql_tables{"${course}_key"}; + $dbh->do("ALTER TABLE `${course}_set` CHANGE COLUMN `set_id` `set_id` TINYBLOB NOT NULL") + if exists $sql_tables{"${course}_set"}; + $dbh->do("ALTER TABLE `${course}_problem` CHANGE COLUMN `set_id` `set_id` TINYBLOB NOT NULL") + if exists $sql_tables{"${course}_problem"}; + $dbh->do("ALTER TABLE `${course}_set_user` CHANGE COLUMN `user_id` `user_id` TINYBLOB NOT NULL") + if exists $sql_tables{"${course}_set_user"}; + $dbh->do("ALTER TABLE `${course}_set_user` CHANGE COLUMN `set_id` `set_id` TINYBLOB NOT NULL") + if exists $sql_tables{"${course}_set_user"}; + $dbh->do("ALTER TABLE `${course}_problem_user` CHANGE COLUMN `user_id` `user_id` TINYBLOB NOT NULL") + if exists $sql_tables{"${course}_problem_user"}; + $dbh->do("ALTER TABLE `${course}_problem_user` CHANGE COLUMN `set_id` `set_id` TINYBLOB NOT NULL") + if exists $sql_tables{"${course}_problem_user"}; +}; + +$DB_VERSIONS[++$i]{desc} = "fixes KEY length, adds UNIQUE KEY for user table"; +$DB_VERSIONS[ $i]{course_code} = sub { + my $course = shift; + return unless exists $sql_tables{"${course}_user"}; + $dbh->do("ALTER TABLE `${course}_user` DROP KEY `user_id`"); + $dbh->do("ALTER TABLE `${course}_user` ADD UNIQUE KEY (`user_id`(255))"); +}; + +$DB_VERSIONS[++$i]{desc} = "fixes KEY length, adds UNIQUE KEY for password table"; +$DB_VERSIONS[ $i]{course_code} = sub { + my $course = shift; + return unless exists $sql_tables{"${course}_password"}; + $dbh->do("ALTER TABLE `${course}_password` DROP KEY `user_id`"); + $dbh->do("ALTER TABLE `${course}_password` ADD UNIQUE KEY (`user_id`(255))"); +}; + +$DB_VERSIONS[++$i]{desc} = "fixes KEY length, adds UNIQUE KEY for permission table"; +$DB_VERSIONS[ $i]{course_code} = sub { + my $course = shift; + return unless exists $sql_tables{"${course}_permission"}; + $dbh->do("ALTER TABLE `${course}_permission` DROP KEY `user_id`"); + $dbh->do("ALTER TABLE `${course}_permission` ADD UNIQUE KEY (`user_id`(255))"); +}; + +$DB_VERSIONS[++$i]{desc} = "fixes KEY length, adds UNIQUE KEY for key table"; +$DB_VERSIONS[ $i]{course_code} = sub { + my $course = shift; + return unless exists $sql_tables{"${course}_key"}; + $dbh->do("ALTER TABLE `${course}_key` DROP KEY `user_id`"); + $dbh->do("ALTER TABLE `${course}_key` ADD UNIQUE KEY (`user_id`(255))"); +}; + +$DB_VERSIONS[++$i]{desc} = "fixes KEY length, adds UNIQUE KEY for set table"; +$DB_VERSIONS[ $i]{course_code} = sub { + my $course = shift; + return unless exists $sql_tables{"${course}_set"}; + $dbh->do("ALTER TABLE `${course}_set` DROP KEY `set_id`"); + $dbh->do("ALTER TABLE `${course}_set` ADD UNIQUE KEY (`set_id`(255))"); +}; + +$DB_VERSIONS[++$i]{desc} = "fixes KEY length, adds UNIQUE KEY for problem table"; +$DB_VERSIONS[ $i]{course_code} = sub { + my $course = shift; + return unless exists $sql_tables{"${course}_problem"}; + $dbh->do("ALTER TABLE `${course}_problem` DROP KEY `set_id`"); + $dbh->do("ALTER TABLE `${course}_problem` ADD UNIQUE KEY (`set_id`(255), `problem_id`)"); + $dbh->do("ALTER TABLE `${course}_problem` DROP KEY `problem_id`"); + $dbh->do("ALTER TABLE `${course}_problem` ADD KEY (`problem_id`)"); +}; + +$DB_VERSIONS[++$i]{desc} = "fixes KEY length, adds UNIQUE KEY for set_user table"; +$DB_VERSIONS[ $i]{course_code} = sub { + my $course = shift; + return unless exists $sql_tables{"${course}_set_user"}; + $dbh->do("ALTER TABLE `${course}_set_user` DROP KEY `user_id`"); + $dbh->do("ALTER TABLE `${course}_set_user` ADD UNIQUE KEY (`user_id`(255), `set_id`(255))"); + $dbh->do("ALTER TABLE `${course}_set_user` DROP KEY `set_id`"); + $dbh->do("ALTER TABLE `${course}_set_user` ADD KEY (`set_id`(255))"); +}; + +$DB_VERSIONS[++$i]{desc} = "fixes KEY length, adds UNIQUE KEY for problem_user table"; +$DB_VERSIONS[ $i]{course_code} = sub { + my $course = shift; + return unless exists $sql_tables{"${course}_problem_user"}; + $dbh->do("ALTER TABLE `${course}_problem_user` DROP KEY `user_id`"); + $dbh->do("ALTER TABLE `${course}_problem_user` ADD UNIQUE KEY (`user_id`(255), `set_id`(255), `problem_id`)"); + $dbh->do("ALTER TABLE `${course}_problem_user` DROP KEY `set_id`"); + $dbh->do("ALTER TABLE `${course}_problem_user` ADD KEY (`set_id`(255), `problem_id`)"); + $dbh->do("ALTER TABLE `${course}_problem_user` DROP KEY `problem_id`"); + $dbh->do("ALTER TABLE `${course}_problem_user` ADD KEY (`problem_id`)"); +}; + +$DB_VERSIONS[++$i]{desc} = "changes psvn index from PRIMARY KEY to UNIQUE KEY"; +$DB_VERSIONS[ $i]{course_code} = sub { + my $course = shift; + return unless exists $sql_tables{"${course}_set_user"}; + $dbh->do("ALTER TABLE `${course}_set_user` ADD UNIQUE KEY (`psvn`)"); + $dbh->do("ALTER TABLE `${course}_set_user` DROP PRIMARY KEY"); +}; + +$DB_VERSIONS[++$i]{desc} = "adds hide_score and hide_work fields to set and set_user"; +$DB_VERSIONS[ $i]{course_code} = sub { + my $course = shift; + if ( exists $sql_tables{"${course}_set"} ) { + $dbh->do("ALTER TABLE `${course}_set` ADD COLUMN `hide_score` ENUM('0','1')"); + $dbh->do("ALTER TABLE `${course}_set` ADD COLUMN `hide_work` ENUM('0','1')"); + } + if ( exists $sql_tables{"${course}_set_user"} ) { + $dbh->do("ALTER TABLE `${course}_set_user` ADD COLUMN `hide_score` ENUM('0','1')"); + $dbh->do("ALTER TABLE `${course}_set_user` ADD COLUMN `hide_work` ENUM('0','1')"); + } +}; + +$DB_VERSIONS[++$i]{desc} = "updates hide_score and hide_work in set and set_user tables to allow more (and more descriptive) possible values"; +$DB_VERSIONS[ $i]{course_code} = sub { + my $course = shift; + if ( exists $sql_tables{"${course}_set"} ) { + $dbh->do("ALTER TABLE `${course}_set` MODIFY COLUMN `hide_score` ENUM('0','1','2')"); + $dbh->do("ALTER TABLE `${course}_set` MODIFY COLUMN `hide_work` ENUM('0','1','2')"); + } + if ( exists $sql_tables{"${course}_set_user"} ) { + $dbh->do("ALTER TABLE `${course}_set_user` MODIFY COLUMN `hide_score` ENUM('0','1','2')"); + $dbh->do("ALTER TABLE `${course}_set_user` MODIFY COLUMN `hide_work` ENUM('0','1','2')"); + } +}; + +$DB_VERSIONS[++$i]{desc} = "adds time_limit_cap field to set and set_user tables"; +$DB_VERSIONS[ $i]{course_code} = sub { + my $course = shift; + if ( exists $sql_tables{"${course}_set"} ) { + $dbh->do("ALTER TABLE `${course}_set` ADD COLUMN `time_limit_cap` ENUM('0','1')"); + } + if ( exists $sql_tables{"${course}_set_user"} ) { + $dbh->do("ALTER TABLE `${course}_set_user` ADD COLUMN `time_limit_cap` ENUM('0','1')"); + } +}; + +$DB_VERSIONS[++$i]{desc} = "updates hide_score and hide_work in set and set_user tables to have more descriptive values, set default values"; +$DB_VERSIONS[ $i]{course_code} = sub { + my $course = shift; + if ( exists $sql_tables{"${course}_set"} ) { + $dbh->do("ALTER TABLE `${course}_set` MODIFY COLUMN `hide_score` ENUM('N','Y','BeforeAnswerDate') DEFAULT 'N'"); + $dbh->do("ALTER TABLE `${course}_set` MODIFY COLUMN `hide_work` ENUM('N','Y','BeforeAnswerDate') DEFAULT 'N'"); + } + if ( exists $sql_tables{"${course}_set_user"} ) { + $dbh->do("ALTER TABLE `${course}_set_user` MODIFY COLUMN `hide_score` ENUM('N','Y','BeforeAnswerDate') DEFAULT 'N'"); + $dbh->do("ALTER TABLE `${course}_set_user` MODIFY COLUMN `hide_work` ENUM('N','Y','BeforeAnswerDate') DEFAULT 'N'"); + } +}; + +$DB_VERSIONS[++$i]{desc} = "adds locations, location_addresses, set_locations and set_locations_user tables to database, and add restrict_ip to set and set_user."; +$DB_VERSIONS[ $i]{global_code} = sub { + $dbh->do("CREATE TABLE locations (location_id TINYBLOB NOT NULL, description TEXT, PRIMARY KEY (location_id(1000)))"); + $dbh->do("CREATE TABLE location_addresses (location_id TINYBLOB NOT NULL, ip_mask TINYBLOB NOT NULL, PRIMARY KEY (location_id(500),ip_mask(500)))"); +}; +$DB_VERSIONS[ $i]{course_code} = sub { + my $course = shift; + + $dbh->do("CREATE TABLE `${course}_set_locations` (set_id TINYBLOB NOT NULL, location_id TINYBLOB NOT NULL, PRIMARY KEY (set_id(500),location_id(500)))"); + $dbh->do("CREATE TABLE `${course}_set_locations_user` (set_id TINYBLOB NOT NULL, user_id TINYBLOB NOT NULL, location_id TINYBLOB NOT NULL, PRIMARY KEY (set_id(300),user_id(300),location_id(300)))"); + + if ( exists $sql_tables{"${course}_set"} ) { + $dbh->do("ALTER TABLE `${course}_set` ADD COLUMN `restrict_ip` enum('No','RestrictTo','DenyFrom') DEFAULT 'No'"); + } + if ( exists $sql_tables{"${course}_set_user"} ) { + $dbh->do("ALTER TABLE `${course}_set_user` ADD COLUMN `restrict_ip` enum('No','RestrictTo','DenyFrom')"); + } +}; + +$DB_VERSIONS[++$i]{desc} = "updates defaults for hide_work and hide_score in set_user tables."; +$DB_VERSIONS[ $i]{course_code} = sub { + my $course = shift; + + if ( exists $sql_tables{"${course}_set_user"} ) { + $dbh->do("ALTER TABLE `${course}_set_user` MODIFY COLUMN `hide_score` ENUM('N','Y','BeforeAnswerDate')"); + $dbh->do("ALTER TABLE `${course}_set_user` MODIFY COLUMN `hide_work` ENUM('N','Y','BeforeAnswerDate')"); + } +}; + +$DB_VERSIONS[++$i]{desc} = "adds relax_restrict_ip, hide_problem_score columns to set and set_user tables."; +$DB_VERSIONS[ $i]{course_code} = sub { + my $course = shift; + + if ( exists $sql_tables{"${course}_set"} ) { + $dbh->do("ALTER TABLE `${course}_set` ADD COLUMN `relax_restrict_ip` ENUM('No','AfterAnswerDate','AfterVersionAnswerDate') DEFAULT 'No'"); + $dbh->do("ALTER TABLE `${course}_set` ADD COLUMN `hide_score_by_problem` ENUM('N','Y') DEFAULT 'N'"); + } + if ( exists $sql_tables{"${course}_set_user"} ) { + $dbh->do("ALTER TABLE `${course}_set_user` ADD COLUMN `relax_restrict_ip` ENUM('No','AfterAnswerDate','AfterVersionAnswerDate')"); + $dbh->do("ALTER TABLE `${course}_set_user` ADD COLUMN `hide_score_by_problem` ENUM('N','Y')"); + } +}; + +$DB_VERSIONS[++$i]{desc} = "adds set and set_user fields to allow set-level proctor, updates permissions to allow finer-grained regulation of proctoring."; +$DB_VERSIONS[ $i]{course_code} = sub { + my $course = shift; + if ( exists $sql_tables{"${course}_permission"} ) { + $dbh->do("UPDATE `${course}_permission` SET `permission`=3 where `permission`=2"); + } + if ( exists $sql_tables{"${course}_set"} ) { + $dbh->do("ALTER TABLE `${course}_set` ADD COLUMN `restricted_login_proctor` ENUM('No','Yes') DEFAULT 'No'"); + } + if ( exists $sql_tables{"${course}_set_user"} ) { + $dbh->do("ALTER TABLE `${course}_set_user` ADD COLUMN `restricted_login_proctor` ENUM('No','Yes')"); + } +}; + +our $THIS_DB_VERSION = $i; + +################################################################################ + +my $ce = WeBWorK::CourseEnvironment->new({ + webwork_dir => $ENV{WEBWORK_ROOT}, +}); + +my @ww_courses = listCourses($ce); + +$dbh = DBI->connect( + $ce->{database_dsn}, + $ce->{database_username}, + $ce->{database_password}, + { + PrintError => 0, + RaiseError => 1, + }, ); -$upgrader->do_upgrade; +{ + verbose("Obtaining dbupgrade lock...\n"); + my ($lock_status) = $dbh->selectrow_array("SELECT GET_LOCK('dbupgrade', 10)"); + if (not defined $lock_status) { + print "Couldn't obtain lock because an error occurred.\n"; + exit 2; + } + if ($lock_status) { + verbose("Got lock.\n"); + } else { + print "Timed out while waiting for lock.\n"; + exit 2; + } +} + +%sql_tables = get_sql_tables(); + +my $db_version = exists $sql_tables{dbupgrade} ? get_db_version() : 0; + +if (not defined $db_version) { + print "Failed to get db_version -- can't continue.\n"; + exit 1; +} + +verbose("Initial db_version is $db_version\n"); + +if ($db_version > $THIS_DB_VERSION) { + print "db_version is $db_version, but the current database version is only $THIS_DB_VERSION. This database was probably used with a newer version of WeBWorK.\n"; + exit; +} + +while ($db_version < $THIS_DB_VERSION) { + $db_version++; + unless (upgrade_to_version($db_version)) { + print "\nUpgrading from version ".($db_version-1)." to $db_version failed.\n\n"; + unless (ask_permission("Ignore this error and go on to the next version?", 0)) { + exit 3; + } + } + set_db_version($db_version); +} + +print "\nDatabase is up-to-date at version $db_version.\n"; + +END { + verbose("Releasing dbupgrade lock...\n"); + my ($lock_status) = $dbh->selectrow_array("SELECT RELEASE_LOCK('dbupgrade')"); + if (not defined $lock_status) { + print "Couldn't release lock because the lock does not exist.\n"; + exit 2; + } + if ($lock_status) { + verbose("Released lock.\n"); + } else { + print "Couldn't release lock because the lock is not held by this thread.\n"; + exit 2; + } +} + +################################################################################ + +sub get_sql_tables { + my $sql_tables_ref = $dbh->selectcol_arrayref("SHOW TABLES"); + my %sql_tables; @sql_tables{@$sql_tables_ref} = (); + + return %sql_tables; +} + +sub get_db_version { + my $vers_value_should_be = "This value should always be a positive integer."; + my $vers_stop_now = "You should stop now and take a closer look."; + + my @record = $dbh->selectrow_array("SELECT `value` FROM `dbupgrade` WHERE `name`='db_version'"); + if (@record) { + my $db_version = $record[0]; + if (not defined $db_version) { + print "'db_version' exists, but it has a NULL value. $vers_value_should_be $vers_stop_now\n"; + return; + } elsif ($db_version !~ /^-?\d+$/) { + print "'db_version' is set to the non-numeric value '$db_version'. $vers_value_should_be $vers_stop_now\n"; + return; + } elsif ($db_version < 0) { + print "'db_version' is set to the negative value '$db_version'. $vers_value_should_be $vers_stop_now\n"; + return; + } elsif ($db_version == 0) { + print "'db_version' is set 0, which is reserved to indicate a pre-automatic-upgrade version. $vers_value_should_be $vers_stop_now\n"; + return; + } else { + # db_version is positive! yay! + return $db_version; + } + } else { + print "The 'dbupgrade' table exists, but doesn't contain a 'db_version' setting. $vers_stop_now\n"; + return; + } +} + +sub set_db_version { + my $vers = shift; + $dbh->do("UPDATE `dbupgrade` SET `value`=? WHERE `name`='db_version'", {}, $vers); +} +sub upgrade_to_version { + my $vers = shift; + my %info = %{$DB_VERSIONS[$vers]}; + + print "\nUpgrading database from version " . ($vers-1) . " to $vers...\n"; + my $desc = $info{desc} || "has no description."; + print "(Version $vers $desc)\n"; + + if (exists $info{global_code}) { + eval { $info{global_code}->() }; + if ($@) { + print "\nAn error occured while running the system upgrade code for version $vers:\n"; + print "$@"; + return 0 unless ask_permission("Ignore this error and keep going?", 0); + } + } + + if (@ww_courses and exists $info{course_code}) { + foreach my $curr_course (@ww_courses) { + eval { $info{course_code}->($curr_course) }; + if ($@) { + print "\nAn error occured while running the course upgrade code for version $vers on course $curr_course:\n"; + print "$@"; + next if ask_permission("Ignore this error and go on to the next course?", 0); + } + } + } + + print "Done.\n"; + return 1; +} + +################################################################################ + +sub ask_permission { + my ($prompt, $default) = @_; + + $default = 1 if not defined $default; + my $options = $default ? "[Y/n]" : "[y/N]"; + + while (1) { + print "$prompt $options "; + my $resp = <STDIN>; + chomp $resp; + return $default if $resp eq ""; + return 1 if lc $resp eq "y"; + return 0 if lc $resp eq "n"; + $prompt = 'Please enter "y" or "n".'; + } +} |
From: Mike G. v. a. <we...@ma...> - 2009-06-28 14:46:31
|
Log Message: ----------- Fixed spelling errors Modified Files: -------------- pg/macros: contextLimitedPowers.pl contextScientificNotation.pl Revision Data ------------- Index: contextLimitedPowers.pl =================================================================== RCS file: /webwork/cvs/system/pg/macros/contextLimitedPowers.pl,v retrieving revision 1.14 retrieving revision 1.15 diff -Lmacros/contextLimitedPowers.pl -Lmacros/contextLimitedPowers.pl -u -r1.14 -r1.15 --- macros/contextLimitedPowers.pl +++ macros/contextLimitedPowers.pl @@ -26,8 +26,8 @@ No raising e to a power Only allowing integer powers (positive or negative) - Only allowing positive interger powers - Only allowing positive interger powers (and 0) + Only allowing positive integer powers + Only allowing positive integer powers (and 0) You install these via one of the commands: @@ -45,7 +45,7 @@ $context = Context("Numeric")->copy; LimitedPowers::OnlyIntegers($context); -For the Interger power functions, you can pass additional +For the integer power functions, you can pass additional parameters that control the range of values that are allowed for the powers. The oprtions include: Index: contextScientificNotation.pl =================================================================== RCS file: /webwork/cvs/system/pg/macros/contextScientificNotation.pl,v retrieving revision 1.11 retrieving revision 1.12 diff -Lmacros/contextScientificNotation.pl -Lmacros/contextScientificNotation.pl -u -r1.11 -r1.12 --- macros/contextScientificNotation.pl +++ macros/contextScientificNotation.pl @@ -41,7 +41,7 @@ Context("ScientificNotation"); -to select the contxt and make it active. You can create +to select the context and make it active. You can create values in scientific notation in two ways: $n1 = Compute("1.23 x 10^3"); |
From: Mike G. v. a. <we...@ma...> - 2009-06-26 04:18:34
|
Log Message: ----------- updating check_modules Modified Files: -------------- webwork2/bin: check_modules.pl Revision Data ------------- Index: check_modules.pl =================================================================== RCS file: /webwork/cvs/system/webwork2/bin/check_modules.pl,v retrieving revision 1.17 retrieving revision 1.18 diff -Lbin/check_modules.pl -Lbin/check_modules.pl -u -r1.17 -r1.18 --- bin/check_modules.pl +++ bin/check_modules.pl @@ -73,6 +73,7 @@ PHP::Serialization Pod::Usage Safe + Scalar::Util SOAP::Lite Socket SQL::Abstract |
From: Mike G. v. a. <we...@ma...> - 2009-06-26 01:13:02
|
Log Message: ----------- syncing HEAD with webwork2.2.4.7 on 6/25/2009 Modified Files: -------------- webwork2: LICENSE README webwork2/courses.dist: adminClasslist.lst Added Files: ----------- webwork2/bin: integrity_check.pl webwork2/htdocs: show-source.cgi Revision Data ------------- Index: README =================================================================== RCS file: /webwork/cvs/system/webwork2/README,v retrieving revision 1.19 retrieving revision 1.20 diff -LREADME -LREADME -u -r1.19 -r1.20 --- README +++ README @@ -2,6 +2,7 @@ Online Homework Delivery System Version 2.4.x - Copyright 2000-2007, The WeBWorK Project + Copyright 2000-2009, The WeBWorK Project + http://webwork.maa.org All rights reserved. Index: LICENSE =================================================================== RCS file: /webwork/cvs/system/webwork2/LICENSE,v retrieving revision 1.7 retrieving revision 1.8 diff -LLICENSE -LLICENSE -u -r1.7 -r1.8 --- LICENSE +++ LICENSE @@ -1,9 +1,9 @@ WeBWorK Online Homework Delivery System - Version 2.x + Version 2.4.x - Copyright 2000-2007, The WeBWorK Project + Copyright 2000-2009, The WeBWorK Project All rights reserved. This program is free software; you can redistribute it and/or modify |
From: Mike G. v. a. <we...@ma...> - 2009-06-26 01:11:00
|
Log Message: ----------- fixed hardcoded port number Modified Files: -------------- webwork2/lib/WebworkWebservice: RenderProblem.pm Revision Data ------------- Index: RenderProblem.pm =================================================================== RCS file: /webwork/cvs/system/webwork2/lib/WebworkWebservice/RenderProblem.pm,v retrieving revision 1.7 retrieving revision 1.8 diff -Llib/WebworkWebservice/RenderProblem.pm -Llib/WebworkWebservice/RenderProblem.pm -u -r1.7 -r1.8 --- lib/WebworkWebservice/RenderProblem.pm +++ lib/WebworkWebservice/RenderProblem.pm @@ -42,7 +42,7 @@ our $PG_DIRECTORY = $WebworkWebservice::PG_DIRECTORY; our $COURSENAME = $WebworkWebservice::COURSENAME; our $HOST_NAME = $WebworkWebservice::HOST_NAME; -our $HOSTURL ="http://$HOST_NAME:8002"; #FIXME +our $HOSTURL ="http://$HOST_NAME:80"; our $ce =$WebworkWebservice::SeedCE; # create a local course environment for some course $ce = WeBWorK::CourseEnvironment->new($WW_DIRECTORY, "", "", $COURSENAME); |
From: Mike G. v. a. <we...@ma...> - 2009-06-26 01:08:00
|
Log Message: ----------- adding show-source.cgi syncing with HEAD on 6/25/2009 Tags: ---- rel-2-4-patches Modified Files: -------------- webwork2/htdocs/helpFiles: InstructorPGProblemEditor.html Units.html webwork2/htdocs/js: ww_applet_support.js webwork2/htdocs/jsMath: jsMath-easy-load.js jsMath-fallback-pc.js jsMath.js webwork2/htdocs/jsMath/easy: load.js webwork2/htdocs/jsMath/extensions: AMSmath.js bbox.js eqn-number.js webwork2/htdocs/jsMath/plugins: autoload.js webwork2/htdocs/jsMath/uncompressed: jsMath-fallback-pc.js jsMath.js Added Files: ----------- webwork2/htdocs: show-source.cgi webwork2/htdocs/css: union.css webwork2/htdocs/images: navProbListGrey.gif webwork2/htdocs/js: Base64.js Revision Data ------------- --- /dev/null +++ htdocs/show-source.cgi @@ -0,0 +1,72 @@ +#!/usr/bin/env perl + +print "Content-type: text/HTML\n\n"; + +$file = $ENV{PATH_INFO}; + +Error("Can't load specified file") if ($file =~ m!(\.\./|//|:|~)!); +Error("Can't load specified file") if ($file !~ m!\.p[lg]$!); + + +#$root = '/usr/local/WeBWorK'; +$root = '/opt/webwork/'; +$filedir = $file; $filedir =~ s!/[^/]+$!!; +$file =~ s!.*/!!; + +@PGdirs = ( + "../templates$filedir", + "../templates/macros", +# "$root/local/macros", + "$root/pg/macros", +); + +foreach $dir (@PGdirs) {ShowSource("$dir/$file") if (-e "$dir/$file")} + +Error("Can't find specified file",join(",",@PGdirs).": ".$file); + +sub Error { + print "<HTML>\n<HEAD>\n<TITLE>Show-Source Error</TITLE>\n</HEAD>\n"; + print "<BODY>\n\n<H1>Show-Source Error:</H1>\n\n"; + print join("\n",@_),"\n"; + print "</BODY>\n</HTML>"; + exit; +} + +sub ShowSource { + my $file = shift; + my $name = $file; $name =~ s!.*/!!; + + open(PGFILE,$file); + $program = join('',<PGFILE>); + close(PGFILE); + + $program =~ s/&/\&/g; + $program =~ s/</\</g; + $program =~ s/>/\>/g; + $program =~ s/\t/ /g; + print "<HTML>\n<HEAD>\n<TITLE>Problem Source Code</TITLE>\n</HEAD>\n"; + print "<BODY>\n\n<H1>Source Code for <CODE>$name</CODE>:</H1>\n\n"; + print "<HR>\n<BLOCKQUOTE>\n"; + print "<PRE>"; + print MarkSource($program); + print "</PRE>\n"; + print "</BLOCKQUOTE>\n<HR>\n"; + print "</BODY>\n</HTML>\n"; + exit; +} + +sub MarkSource { + my $program = shift; + local $cgi = $ENV{SCRIPT_NAME}; + $program =~ s/loadMacros *\(([^\)]*)\)/MakeLinks($1)/ge; + return $program; +} + +sub MakeLinks { + my $macros = shift; + $macros =~ s!"([^\"<]*)"!"<A HREF="$cgi$filedir/\1">\1</A>"!g; + $macros =~ s!'([^\'<]*)'!'<A HREF="$cgi$filedir/\1">\1</A>'!g; + return 'loadMacros('.$macros.')'; +} + +1; --- /dev/null +++ htdocs/css/union.css @@ -0,0 +1,190 @@ +/******************************************************************************/ +/* union.css classes: These classes appear in ur.template and are NOT + * emitted by WeBWorK code. They need only appear in this template. */ + +body { margin: 0px; } + +/* left table cell, contains logo and menus */ +td.LeftPanel { background-color: #660033; color: white; white-space: nowrap; width: 8em; zoom: 1; } +td.LeftPanel ul { zoom: 1; margin-left: 0em} +td.LeftPanel ul ul ul li { margin-left: .5em} +td.LeftPanel a:link, +td.LeftPanel a:visited { color: #EEBBBB; } + +div.Logo { } +div.Links { font-size: small; } +div.Links ul { list-style: none; margin-left: 0; padding-left: 0; } +div.Links ul ul { list-style: none; margin-left: 0.5em; padding-left: 0; } +div.Siblings { font-size: small; } +div.Siblings ul { list-style: none; margin-left: 0; padding-left: 0; } +div.Siblings ul ul { list-style: none; margin-left: 0.5em; padding-left: 0; } +div.Options { font-size: small; } + +/* top table cell, contains login message and path */ +td.TopPanel { background-color: #660033; color: white; height: 1; } +td.TopPanel a:link, +td.TopPanel a:visited { color: #EEBBBB; } + +.Path { } + +.LoginStatus { text-align: right; font-size: small;} + +.TimeLeft { font-size: small; } +.LittleTimeLeft { font-size: small; color: #FF7777; font-weight: bold; margin: 3px} +.TimeLeftReload { font-size: small; color: #EEBBBB; font-style: italic; } +td.Timestamp { text-align: left; font-size: small; font-style: italic; } + +/* main content panel, contains body */ +td.ContentPanel { background-color: white; color: black; } +td.ContentPanel a:link, +td.ContentPanel a:visited { color: blue; } +td.ContentPanel a:active { color: red; } + +td.SetMakerPanel { background-color: #DDDDDD; color: black; width: 50%; padding: 3px; } + +div.Nav { } +div.Title { font-size: 16pt; padding-bottom: 5px; } +div.SubmitError { color: red; font-style: italic; } +div.Message { font-style: italic; } +div.Body { } +div.Warnings { } + +/* contains info */ +td.InfoPanel { background-color: #DDDDDD; color: black; width: 40%; } +td.InfoPanel a:link, +td.InfoPanel a:visited { color: blue; } +td.InfoPanel a:active { color: red; } +div.Info { } + +/******************************************************************************/ +/* WeBWorK classes: These classes are emitted by WeBWorK code and should + * appear in ANY WeBWorK template. */ + +/* tables used for laying out form fields shouldn't have a border */ +table.FormLayout { border: 0; } +table.FormLayout tr { vertical-align: top; } +table.FormLayout th.LeftHeader { text-align: right; white-space: nowrap; } +table.FormLayout tr.ButtonRow { text-align: left; } +table.FormLayout tr.ButtonRowCenter { text-align: center; } + + +/* for problems which are rendered by themselves, e.g., by Set Maker */ +div.RenderSolo { background-color: #E0E0E0; color: black; padding: 5px; } +div.AuthorComment { background-color: #00E0E0; color: black; } + +/* minimal style for lists of links (generated by the links escape) */ +/* ul.LinksMenu { list-style: none; margin: 0 0 0 0.5em; padding: 0; }*/ +/* ul.LinksMenu ul { list-style: none; margin: 0 0 0 0.5em; padding: 0; }*/ + +a.HelpLink { border: 0px; } +a.HelpLink img { vertical-align: -5px; margin: 1px 2px; border: 0px; } + +/* background colors for success and failure messages */ +div.WarningMessage { background-color: #ffcccc; padding: 3px 3px 3px 3px; } +div.ResultsWithoutError { + margin-right: 6px; margin-left: 6px; + border-color: #009900; border-style: solid; border-width: 1px; + text-align: center; font-weight: bold; font-style: italic; + margin-right: 6px; margin-left: 6px; + background-color: #99ffAA; + padding: 3px 3px 3px 10px; +} /* light green */ +div.ResultsWithError { + margin-right: 6px; margin-left: 6px; + border-color: #CC0000; border-style: solid; border-width: 1px; + text-align: center; font-weight: bold; font-style: italic; + background-color: #ffcccc; + padding: 3px 3px 3px 10px; +} /* light red */ +div.ResultsAlert { + margin-right: 6px; margin-left: 6px; + border-color: #CCCC00; border-style: solid; border-width: 1px; + text-align: center; font-weight: bold; font-style: italic; + background-color: #ffffcc; + padding: 3px 3px 3px 10px; +} /* light yellow */ + +/* styles used by WeBWorK::HTML::ScrollingRecordList */ +div.ScrollingRecordList { padding: 1em; white-space: nowrap; border: thin solid gray; } +div.ScrollingRecordList select.ScrollingRecordList { width: 99%; } + +/* wraps the View Options form (generated by &optionsMacro) */ +/* FIXME: can't this style information just go in div.Options above? */ +div.viewOptions { border: thin groove; background-color: #882848; + line-height: 75%; margin: .5ex; margin-top: 2ex; + padding: 1ex; align: left; } + +.viewChoices { margin: 5px 0px 5px 5px; } +.viewChoices input {margin-top: 0px} + + +/* messages, attempt results, answer previews, etc. go in this DIV */ +/* this used to be "float:left", but that was suspected of causing MSIE peekaboo bug */ + +div.problemHeader {} + +/* styles for the attemptResults table */ +table.attemptResults { + border-style: outset; + border-width: 1px; +# margin: 0px auto 10pt auto; + margin: 0px 10pt; + border-spacing: 1px; +} +table.attemptResults td, +table.attemptResults th { + border-style: inset; + border-width: 1px; + text-align: center; + padding: 2px 5px; + background-color: #DDDDDD; +} +/* override above settings in tables used to display ans_array results */ +table.attemptResults td td, +table.attemptResults td th, +table.ArrayLayout td { + border-style: none; + border-width: 0px; + padding: 0px; +} +table.attemptResults td.Message { text-align: left; width: auto; } +.attemptResultsSummary { font-style:italic; } + +.parsehilight { background-color:yellow; } + +/* the problem TEXT itself does in this box */ +div.problem { + clear: both; padding: 0 5px; color: black; + border-top-style: solid; border-bottom-style: solid; + border-width: 2px; border-color: #DDBBBB; +} + +/* jsMath emits this class when appropriate math fonts aren't available */ +div.NoFontMessage { + padding: 10; + border-style: solid; + border-width:3px; + border-color: #DD0000; + background-color: #FFF8F8; + width: 75%; + text-align: left; + margin: 10px auto 10px 12%; +} + +/* text colors for published and unpublished sets */ +font.Published { font-style: normal; font-weight: normal; color: #000000; } /* black */ +font.Unpublished { font-style: italic; font-weight: normal; color: #aaaaaa; } /* light grey */ + +/* styles used when editing a temporary file */ +.temporaryFile { + margin-right: 6px; margin-left: 6px; + border-color: #AA6600; border-style: solid; border-width: 1px; + text-align: center; font-style: italic; + background-color: #FFBB88; + padding: 3px 3px 3px 10px; +} + +/* text colors for Auditing, Current, and Dropped students */ +.Audit { font-style: normal; color: purple; } +.Enrolled { font-weight: normal; color: black; } +.Drop { font-style: italic; color: grey; } Index: InstructorPGProblemEditor.html =================================================================== RCS file: /webwork/cvs/system/webwork2/htdocs/helpFiles/InstructorPGProblemEditor.html,v retrieving revision 1.3.4.1 retrieving revision 1.3.4.1.2.1 diff -Lhtdocs/helpFiles/InstructorPGProblemEditor.html -Lhtdocs/helpFiles/InstructorPGProblemEditor.html -u -r1.3.4.1 -r1.3.4.1.2.1 --- htdocs/helpFiles/InstructorPGProblemEditor.html +++ htdocs/helpFiles/InstructorPGProblemEditor.html @@ -29,7 +29,7 @@ <h3>PG Problem Editor Help Page</h3> <blockquote> <p> -This page allows to edit the contents of PG problem files (templates) +This page allows one to edit the contents of PG problem files (templates) as well as set headers and other files. </p> <p> @@ -41,8 +41,40 @@ <dl> - <dt>| Manpages | macro list | pod docs | report problem bugs |</dt> - <dd>The first three links are to pages that give some information about writing problems: "Manpages" is an (incomplete) list of the most commonly used macros with a description of their parameters and how to use them; a complete list of macros is provided by the "macro list"; and details for many macros can be obtained using the "pod docs" link which links to documentation embedded in the macro files themselves. If you find an error in a library problem please use the "Report problem bugs" link to notify us so that we can fix it. You will need to register with an email address the first time you use bugzilla, but after that you will not. </dd> + <dt>| <a href="http://webwork.math.rochester.edu/docs/docs/pglanguage/manpages/">Manpages</a> + | <a href="http://devel.webwork.rochester.edu/twiki/bin/view/Webwork/PGmacrosByFile"> + macro list</a> + | <a href="http://webwork.maa.org/wiki/Category:Authors"> + authoring info and help</a> + | <a href="http://devel.webwork.rochester.edu/doc/cvs/pg_HEAD/"> + testing lab</a> + | <a href="http://devel.webwork.rochester.edu/doc/cvs/pg_HEAD/"> + pod docs</a> + | <a href="http://bugs.webwork.rochester.edu">Report problem bugs</a> + |</dt> + <dd>The first five links are to pages that give some information about writing problems: + + <dl> + <dt><a href="http://webwork.math.rochester.edu/docs/docs/pglanguage/manpages/">Manpages</a></dt> + <dd>This links to an (incomplete) list of the most commonly used macros with a description of their parameters and how to use them; </dd> + <dt><a href="http://devel.webwork.rochester.edu/twiki/bin/view/Webwork/PGmacrosByFile"> + macro list</a></dt> + <dd>This link provides a complete list of macros;</dd> + + <dt><a href="http://webwork.maa.org/wiki/Category:Authors"> + authoring info and help</a> </dt> + <dd>This link takes you to the authoring section of the WeBWorK wiki;</dd> + <dt><a href="http://devel.webwork.rochester.edu/doc/cvs/pg_HEAD/"> + testing lab</a></dt> + <dd>This links to a WeBWorK "problem" which allows you to try out fragments of PG code.</dd> + <dt><a href="http://devel.webwork.rochester.edu/doc/cvs/pg_HEAD/"> + pod docs</a></dt> + <dd>This link gives details for many macros. It links to documentation embedded in the macro files themselves;</dd> + <dt><a href="http://bugs.webwork.rochester.edu">Report problem bugs</a></dt> + <dd>If you find an error in a library problem please use the link to notify us so that we can fix it. You will need to register with an email address the first time you use bugzilla, but after that you will not. </dd> + </dl> + + </dd> <dt> The large text window</dt> <dd> This is where you edit the text of the problem template. Nothing happens until you pick an action (below) and click the "Take action!" button.</dd> @@ -50,7 +82,7 @@ <h4>Action items:</h4> <i>(If an action cannot be executed it will not appear as an action item.) </i> -</dl> +<dl> <dt> View using seed "12345" and display mode "image"</dt> <dd> Will bring up a window to show the results of your editing. It does not change the permanent file on the disk. You can view different versions of the same problem by changing the seed (any number will do). You can also change mannerin which the @@ -59,22 +91,23 @@ <dd> Add this problem as the last problem of an existing set, either as a problem or as the set header (the text that appears on the home page of a homework set). You can rearrange the order of the problems later using the "Hmwk Sets Editor"</dd> - <dt> Create a copy at [TMPL]/"path"...</dt> + <dt> Save as [TMPL]/"path"...</dt> <dd> Makes a new copy of the file you are editing at the location relative to the course's templates ([TMPL]) directory given by "path". - The checkbox allows you to replace the current problem with the new problem in the homework set. + The checkbox allows you to replace the current problem with the new problem in the current homework set. <p> Check the checkbox if you want to edit the current problem without changing the original problem file.</p> <p> Leave the checkbox unchecked if you are using the current problem as a model for a brand new problem. You can add the new file to the homework set from the Library Browser.</p> - <p>If the original problem could not be edited than you will have to change the path name - before you will be allowed to save the problem. (Adding "local/" to the beginning of the suggested path will work. All of your - edited files will then appear in a subdirectory named "local". A new problem whose path ends in blankProblem.pg should be given - a new name, for example, "myNewProblem.pg")</P.</dd> + <p>If the original problem can not be edited than the path name + must be changed in order to be allowed to save the problem. (Adding "local/" to the beginning of the original path is the default solution. All locally created and + edited files will then appear in a subdirectory named "local". </p> + <p>A new problem whose path ends in blankProblem.pg should be given a new name, for example, "myNewProblem.pg")</P></dd> <dt> Revert</dt> <dd> Reverts to the copy of the file saved on the disk -- all unsaved editing changes will be lost. This option only appears when you have making provisional edits using the first option "View..." above</dd> <dt> Take action</dt> - <dd> Executes the</dd> action item selected above. You can elect to have this action appear in a new window or not. + <dd> Executes the action item selected above. You can elect to have the results of this action appear in a new window. + </dd> </dl> </blockquote> Index: Units.html =================================================================== RCS file: /webwork/cvs/system/webwork2/htdocs/helpFiles/Units.html,v retrieving revision 1.3 retrieving revision 1.3.10.1 diff -Lhtdocs/helpFiles/Units.html -Lhtdocs/helpFiles/Units.html -u -r1.3 -r1.3.10.1 --- htdocs/helpFiles/Units.html +++ htdocs/helpFiles/Units.html @@ -66,6 +66,15 @@ <tr><td> electron volts <td align="center"> eV <tr><td> kilo Watt hours <td align="center"> kWh +<tr><td colspan="2" align="center"><b>Misc</b> +<tr><td> Amperes <td align="center"> amp +<tr><td> Moles <td align="center"> mol +<tr><td> Degrees Centrigrade <td align="center"> degC +<tr><td> Degrees Fahrenheit <td align="center"> degF +<tr><td> Degrees Kelvin <td align="center"> degK +<tr><td> Angle degrees <td align="center"> deg +<tr><td> Angle radians <td align="center"> rad + </table> --- /dev/null +++ htdocs/js/Base64.js @@ -0,0 +1,143 @@ + +/** +* +* Base64 encode / decode +* http://www.webtoolkit.info/ +* +**/ + +var Base64 = { + + // private property + _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", + + // public method for encoding + encode : function (input) { + var output = ""; + var chr1, chr2, chr3, enc1, enc2, enc3, enc4; + var i = 0; + + input = Base64._utf8_encode(input); + + while (i < input.length) { + + chr1 = input.charCodeAt(i++); + chr2 = input.charCodeAt(i++); + chr3 = input.charCodeAt(i++); + + enc1 = chr1 >> 2; + enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); + enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); + enc4 = chr3 & 63; + + if (isNaN(chr2)) { + enc3 = enc4 = 64; + } else if (isNaN(chr3)) { + enc4 = 64; + } + + output = output + + this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) + + this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4); + + } + + return output; + }, + + // public method for decoding + decode : function (input) { + var output = ""; + var chr1, chr2, chr3; + var enc1, enc2, enc3, enc4; + var i = 0; + + input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); + + while (i < input.length) { + + enc1 = this._keyStr.indexOf(input.charAt(i++)); + enc2 = this._keyStr.indexOf(input.charAt(i++)); + enc3 = this._keyStr.indexOf(input.charAt(i++)); + enc4 = this._keyStr.indexOf(input.charAt(i++)); + + chr1 = (enc1 << 2) | (enc2 >> 4); + chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); + chr3 = ((enc3 & 3) << 6) | enc4; + + output = output + String.fromCharCode(chr1); + + if (enc3 != 64) { + output = output + String.fromCharCode(chr2); + } + if (enc4 != 64) { + output = output + String.fromCharCode(chr3); + } + + } + + output = Base64._utf8_decode(output); + + return output; + + }, + + // private method for UTF-8 encoding + _utf8_encode : function (string) { + string = string.replace(/\r\n/g,"\n"); + var utftext = ""; + + for (var n = 0; n < string.length; n++) { + + var c = string.charCodeAt(n); + + if (c < 128) { + utftext += String.fromCharCode(c); + } + else if((c > 127) && (c < 2048)) { + utftext += String.fromCharCode((c >> 6) | 192); + utftext += String.fromCharCode((c & 63) | 128); + } + else { + utftext += String.fromCharCode((c >> 12) | 224); + utftext += String.fromCharCode(((c >> 6) & 63) | 128); + utftext += String.fromCharCode((c & 63) | 128); + } + + } + + return utftext; + }, + + // private method for UTF-8 decoding + _utf8_decode : function (utftext) { + var string = ""; + var i = 0; + var c = c1 = c2 = 0; + + while ( i < utftext.length ) { + + c = utftext.charCodeAt(i); + + if (c < 128) { + string += String.fromCharCode(c); + i++; + } + else if((c > 191) && (c < 224)) { + c2 = utftext.charCodeAt(i+1); + string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); + i += 2; + } + else { + c2 = utftext.charCodeAt(i+1); + c3 = utftext.charCodeAt(i+2); + string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); + i += 3; + } + + } + + return string; + } + +} Index: ww_applet_support.js =================================================================== RCS file: /webwork/cvs/system/webwork2/htdocs/js/ww_applet_support.js,v retrieving revision 1.9.2.1 retrieving revision 1.9.2.2 diff -Lhtdocs/js/ww_applet_support.js -Lhtdocs/js/ww_applet_support.js -u -r1.9.2.1 -r1.9.2.2 --- htdocs/js/ww_applet_support.js +++ htdocs/js/ww_applet_support.js @@ -53,7 +53,7 @@ } function initializeAction() { // deprecated call -- removed - alert("You might be using an old template (stored at webwork2/conf/templates). The <body> tag in the system.template calls a function 'initializeAction()' -- this function name should be replaced by 'initializeWWquestion()'. Please update to a recent version of system.template"); + alert("You might be using an old template (stored at webwork2/conf/templates). The <body> tag in the system.template calls a function 'initializeAction()' instead of 'intializeWWquestion()'-- this function name should be replaced by 'initializeWWquestion()'. Please update to a recent version of system.template"); initializeWWquestion(); } @@ -67,9 +67,9 @@ } // applet can set isReady flag by calling applet_loaded(appletName, loaded); -function applet_loaded(appletName,loaded) { - debug_add("applet reporting that it has been loaded = " + loaded ); - ww_applet_list[appletName].reportsLoaded = loaded; // 0 means not loaded +function applet_loaded(appletName,ready) { + debug_add("applet reporting that it has been loaded = " + ready ); + ww_applet_list[appletName].reportsLoaded = ready; // 0 means not loaded } @@ -209,17 +209,21 @@ var applet = getApplet(appletName); var setConfigAlias = this.setConfigAlias; - try { - if ( this.methodDefined(setConfigAlias) ) { + + try { + if ( this.methodDefined(this.setConfigAlias) ) { applet[setConfigAlias](this.configuration); - } + this.debug_add(" Calling " + appletName +"."+ setConfigAlias +"( " + this.configuration + " ) " ); + } else { + this.debug_add(" unable to execute " + appletName +"."+ setConfigAlias +"( " + this.configuration + " ) " ); + } } catch(e) { var msg = "Error in configuring " + appletName + " using command " + setConfigAlias + " : " + e ; alert(msg); } - this.debug_add(" Calling " + appletName +"."+ setConfigAlias +"( " + this.configuration + " ) " ); + }; @@ -273,11 +277,15 @@ if ( base64Q(state) ) { state=Base64.decode(state); } - - if (state.match(/^<xml>restart_applet<\/xml>/) ) { - alert("The applet " +appletName + "has been reset to its virgin state." + this.initialState); + if (state.match(/^<xml>restart_applet<\/xml>/) || + state.match(/^\s*$/) || + state.match(/^<xml>\s*<\/xml>/ ) ) { + //blank state also restarts applet ww_preserve_applet_state.value =this.initialState; //Fixme? should we set the last answer to blank as well? state = ww_preserve_applet_state.value; + if (state.match(/^<xml>restart_applet<\/xml>/) ) { + alert("The applet " +appletName + "has been reset to its virgin state." + this.initialState); + } } if (state.match(/<xml/i) || state.match(/<?xml/i) ) { // if state starts with <?xml @@ -398,7 +406,7 @@ eval(this.submitActionScript); //getQE(this.answerBox).value = applet.[getAnswer](); //FIXME -- not needed in general? this.debug_add("Completed submitAction(" + this.submitActionScript + ") \nfor applet " + appletName+ "\n"); - if (this.debugMode){alert(debugText); debugText="";} + if (this.debugMode>=2){alert(debugText); debugText="";} }; @@ -418,13 +426,23 @@ this.debug_add("Test 4 methods to see if the applet " + appletName + " has been loaded: \n"); + try { + if ( this.methodDefined(this.setConfigAlias) ) { + ready = 1; + } + } catch(e) { + var msg = "Unable to find set configuration command in applet " + appletName; + alert(msg); + } - if ( this.methodDefined(this.setConfigAlias) ) { - ready = 1; + try { + if ( this.methodDefined(this.setStateAlias) ) { + ready =1; + } + } catch(e) { + var msg = "Unable to set State command in applet " + appletName; + alert(msg); } - if ( this.methodDefined(this.setStateAlias) ) { - ready =1; - } if (typeof(this.reportsLoaded) !="undefined" && this.reportsLoaded != 0 ) { @@ -449,7 +467,7 @@ return(ready); } ww_applet.prototype.debug_add = function(str) { - if (this.debugMode) { + if (this.debugMode>=2) { debugText = debugText + "\n" +str; // a global text string } } @@ -475,7 +493,7 @@ if ( 0 < i && !applet_loaded ) { // wait until applet is loaded this.debug_add("Applet " + appletName + " is not yet ready try again\n"); - if (this.debugMode) { + if (this.debugMode>=2) { alert(debugText ); debugText=""; } @@ -492,11 +510,11 @@ //alert("setDebug") try{ - this.setDebug(this.debugMode); + this.setDebug((this.debugMode) ? 1:0); } catch(e) { var msg = "Unable set debug in " + appletName + " \n " +e; - if (this.debugMode) {this.debug_add(msg);} else {alert(msg)}; + if (this.debugMode>=2) {this.debug_add(msg);} else {alert(msg)}; } //alert("config applet"); @@ -506,7 +524,7 @@ } catch(e) { var msg = "Unable to configure " + appletName + " \n " +e; - if (this.debugMode) {this.debug_add(msg);} else {alert(msg)}; + if (this.debugMode>=2) {this.debug_add(msg);} else {alert(msg)}; } @@ -517,20 +535,20 @@ } catch(e) { var msg = "unable to initialize " + appletName + " \n " +e; - if (this.debugMode) { + if (this.debugMode>=2) { this.debug_add(msg); } else { alert(msg); } } - if (this.debugMode) { + if (this.debugMode>=2) { alert("\nBegin debugmode\n " + debugText ); debugText=""; }; } else { this.debug_add("Error: timed out waiting for applet " +appletName + " to load"); //alert("4 jsDebugMode " + jsDebugMode + " applet debugMode " +ww_applet.debugMode + " local debugMode " +debugMode); - if (this.debugMode) { + if (this.debugMode>=2) { alert(" in safe applet " + debugText ); debugText=""; } Index: jsMath-easy-load.js =================================================================== RCS file: /webwork/cvs/system/webwork2/htdocs/jsMath/jsMath-easy-load.js,v retrieving revision 1.1.4.2 retrieving revision 1.1.4.3 diff -Lhtdocs/jsMath/jsMath-easy-load.js -Lhtdocs/jsMath/jsMath-easy-load.js -u -r1.1.4.2 -r1.1.4.3 --- htdocs/jsMath/jsMath-easy-load.js +++ htdocs/jsMath/jsMath-easy-load.js @@ -93,6 +93,7 @@ jsMath.Autoload.findCustomSettings = jsMath.Easy.findCustomSettings; jsMath.Autoload.loadFiles = jsMath.Easy.loadFiles; jsMath.Autoload.loadFonts = jsMath.Easy.loadFonts; + jsMath.Autoload.macros = jsMath.Easy.macros; jsMath.Autoload.delayCheck = 1; jsMath.Easy.autoloadCheck = 1; document.write('<script src="'+jsMath.Autoload.root+'plugins/autoload.js"></script>'); @@ -110,12 +111,26 @@ if (!jsMath.Setup) {jsMath.Setup = {}} if (!jsMath.Setup.UserEvent) {jsMath.Setup.UserEvent = {}} jsMath.Setup.UserEvent.onload = function () { - if (jsMath.Easy.tex2math) jsMath.Setup.Script("plugins/tex2math.js"); + var easy = jsMath.Easy; + if (easy.tex2math) jsMath.Setup.Script("plugins/tex2math.js"); var i; - for (i = 0; i < jsMath.Easy.loadFiles.length; i++) - jsMath.Setup.Script(jsMath.Easy.loadFiles[i]); - for (i = 0; i < jsMath.Easy.loadFonts.length; i++) - jsMath.Font.Load(jsMath.Easy.loadFonts[i]); + if (easy.loadFiles) { + for (i = 0; i < easy.loadFiles.length; i++) + jsMath.Setup.Script(easy.loadFiles[i]); + } + if (easy.loadFonts) { + for (i = 0; i < easy.loadFonts.length; i++) + jsMath.Font.Load(easy.loadFonts[i]); + } + if (easy.macros) { + for (i in easy.macros) { + if (typeof(easy.macros[i]) == 'string') { + jsMath.Macro(i,easy.macros[i]); + } else { + jsMath.Macro(i,easy.macros[i][0],easy.macros[i][1]); + } + } + } } document.write('<script src="'+jsMath.Easy.root+'/jsMath.js"></script>'+"\n"); } Index: jsMath.js =================================================================== RCS file: /webwork/cvs/system/webwork2/htdocs/jsMath/jsMath.js,v retrieving revision 1.36.2.1.2.3 retrieving revision 1.36.2.1.2.4 diff -Lhtdocs/jsMath/jsMath.js -Lhtdocs/jsMath/jsMath.js -u -r1.36.2.1.2.3 -r1.36.2.1.2.4 --- htdocs/jsMath/jsMath.js +++ htdocs/jsMath/jsMath.js @@ -40,11 +40,11 @@ //start = new Date().getTime(); jsMath.Script.Uncompress([ - ['if(!','window','.jsMath','||!',1,'.','jsMath.','loaded','){var ','jsMath_old','=',1,2,';',0,'document.','getElementById','||!',15,'childNodes||!',15,'createElement','){','alert("','The',' mathematics ','on this page requires W3C DOM support in its JavaScript. Unfortunately, your ','browser',' doesn\'t seem to have this.")}else{',1,2,'={version:"3.6a",document:document,',1,':',1,',','platform',':(','navigator.',36,'.match(/','Mac/)?"mac":',38,36,40,'Win/)?"pc":"unix"),','sizes',':[50,60,70,85,100,120,144,173,207,249],styles:{".math','":{"font-family":"serif","font-style":"normal","font-weight":"normal','"},".typeset',48,'","line-height":"normal','","text-indent":"0px','"},".typeset .','normal',48,'"},"div','.typeset','":{"text-align":"','center",margin:"1em 0px"},"span',57,58,'left',49,' span',58,'left",border',':"0px",margin:"0px','",padding',':"0px"},"a .typeset img, .typeset a img','":{border:"0px','","border-bottom":"','1px solid',' blue;"},".typeset .size0','":{"fon! t-size":"','50','%"},".typeset .','size1',74,'60',76,'size2',74,'70',76,'size3',74,'85',76,'size4',74,'100',76,'size5',74,'120',76,'size6',74,'144',76,'size7',74,'173',76,'size8',74,'207',76,'size9',74,'249',76,'cmr10','":{"font-family":"jsMath-',113,', serif',53,'cmbx10',114,118,', ','jsMath-cmr10',53,'cmti10',114,124,', ',122,53,'cmmi10',114,130,53,'cmsy10',114,134,53,'cmex10',114,138,53,'textit','":{"font-family":"','serif","','font-style":"italic',53,'textbf',143,'serif","font-weight":"bold',53,'link":{"','text-decoration":"none',53,'error',74,'90%","',145,'","background-color','":"#FFFFCC',68,':"1px','",border:"',72,' #CC0000',53,'blank','":{display:"','inline-block','",overflow:"','hidden',162,'0px none",width:"0px",height:"0px',53,'spacer',167,168,'"},"#','jsMath_hiddenSpan":{','visibility:"hidden",position:"absolute",','top:"0px",left:"0px',51,52,177,'jsMath_message','":{position:"fixed",bottom:"','1px",left:"2px',158,'":"#E6E6E6','",border:"solid 1px #959595",margi! n:"0px",padding:"','1px 8px','","z-index":"','102','",color:"b! lack","f ont-size":"','small",width:"auto','"},"#jsMath_panel',185,'1.75em",right:"1.5em',68,':".8em 1.6em',158,'":"#DDDDDD',162,'outset 2px',191,'103",','width:"auto',193,'10pt","font-style":"',54,195,' .disabled":{color:"#888888',195,' .infoLink',74,'85%"},"#jsMath_panel *":{"','font-size":"inherit","font-style":"inherit","font-family":"inherit',51,195,' div":{"','background-color":"inherit",color:"inherit"},"#jsMath_panel ','span":{"',220,'td',70,68,67,'","',220,'tr',70,68,67,'","',220,'table',70,68,67,158,'":"inherit",color:"inherit",height:"auto",',206,177,'jsMath_button',185,'1px",right:"2px',158,'":"white',189,'0px 3px 1px 3px',191,'102",color:"black","',152,'","font-size":"x-',194,'",cursor:"hand"},"#',243,' *":{padding:"0px",border',67,51,'","',216,177,'jsMath_global":{"',145,177,'jsMath_noFont',' .message":{"text-align":"center",padding:".8em 1.6em",border:"3px solid #DD0000","background-color":"#FFF8F8",color:"#AA0000","font-size":"',194,177,'jsMath_noFont .link":{padding:! "0px 5px 2px 5px',162,'2px outset',158,'":"#E8E8E8',193,'80%",',206,255,'jsMath_PrintWarning',267,'x-',194,'"},"@media print":{"#',243,167,'none',177,'jsMath_Warning',167,'none"}},"@media screen":{"#',279,167,'none"}}},Element',':function(','A','){return ',6,15,16,'("jsMath_"+A)},','BBoxFor',294,'A','){this.','hidden.innerHTML','=\'<nobr><','span class="','typeset"><',307,'scale">\'+A+"</span></span></nobr>";var B={w:this.',170,'.offsetWidth',',h:this.',170,'.offsetHeight','};this.',305,'="";return B},EmBoxFor',294,'B){var A=',6,'Global.cache.R;if(!A[this.em]){A[this.em]={}}if(!A[this.em][B]){var C=this.BBoxFor(B);','A[this.em][B]={w:C.w/this.em,h:C.h/this.em}}return A[this.em][B]},','EmBoxForItalics',294,320,6,322,'if(B.match(/<i>|class=\\"(icm|italic|igreek|iaccent)/i)){C.w=C.Mw=','this.BBoxFor','(B+',6,'Browser.','italicString',').w-',6,333,'italicCorrection','}',323,'Init',':function(){','if(',6,'Setup.inited','!=1){',0,6,345,'){',6,'Setup.','Body()}if(',6,345,'!=1){if(! ',6,345,'==-100','){return }',23,'It looks like jsMath failed ! to set u p properly (error code "+',6,345,'+"). I will try to keep going, but it could get ugly.");',6,345,'=1}}this.em=this.CurrentEm();','var A=',6,'Global.cache','.B;',0,'A[this.em]){A[this.em]={};','A[this.em].','bb=',330,'("x");var C=',375,'bb.h;',375,'d=',330,'("x"+',6,'HTML.Rule(1,','C/',6,'em)).h-C;if(',6,333,334,'){',375,'ic=',6,301,'(',6,333,334,').w}}',6,333,338,'=',375,'ic;var F=',375,'bb;var D=F.h;var E=',375,'d;this.h=(D-E)/this.em;this.d=E/this.em;this.hd=this.h+','this.d;this.',352,'TeXfonts','();var B=this.EmBoxFor(\'<',307,113,'">M</span>\').w/2;this.TeX.M_height=B*(26/14);this.TeX.h=this.h;this.TeX.d=',413,'TeX.hd=this.hd;this.Img.Scale();',0,'this.initialized',304,352,'Sizes','();this.','Img.UpdateFonts()}this.p_height=(','this.TeX.cmex10[0].','h+',429,'d)/0.85;',423,'=1},ReInit',342,'if(this.','em!=this.CurrentEm()){this.Init()}},CurrentEm',342,369,330,'(\'<span style="\'+',6,333,'block+\';','width:13em;height:1em','"></span>\').','w/13;if(A>0',296,'A}return ',3! 30,'(\'<img src="\'+',6,166,'+\'" style="',445,'"/>\').w/13},Loaded',342,'if(',9,8,'B=["Process","ProcessBeforeShowing","ConvertTeX","ConvertTeX2","ConvertLaTeX","ConvertCustom","CustomSearch","Synchronize","Macro","document"];','for(var A=0;A<','B','.length;A++){','if(',9,'[B[A]]){','delete ',9,'[B[A]]}}}if(',9,304,'Insert(jsMath,',9,')}',9,'=null;',6,7,'=1},Add',294,'C,A){for(var B in A){','C[B]=A[B]}},Insert',294,482,'if(C[B]&&typeof (A[B])=="object"&&(','typeof (C[B])=="','object"||',487,'function")){this.Insert(C[B],A[B])}else{C[B]=A[B]}}},Package',294,'B,A',304,'Insert(B.prototype,A)}};',6,'Global={isLocal:1,cache:{','T:{},D:{},R:{},B',':{}},ClearCache',342,6,371,'={',497,':{}}},GoGlobal',294,320,'String(',6,1,'.','location);var C','=(',6,'isCHMmode','?"#":"?");if(B){A=A.replace(/\\?.*/,"")+"?"+B}',6,'Controls.','Reload(',6,'root+"jsMath-','global.html"+C+escape(A))},Init',342,'if(',6,'Controls.cookie.','global=="always"&&!',6,'noGoGlobal','){if(',38,'accentColorName'! ,360,0,6,1,'){',6,1,'=',1,'}',6,517,7,'=1;',6,517,'defaults.hi! ddenGlob al=null;this.GoGlobal(',6,517,'SetCookie(2))}},Register',342,369,6,1,'.parent;',0,6,'isCHMode){',6,514,'=(',6,1,'.','location.','protocol=="mk:")}try{',0,6,514,304,'Domain()}if(A',2,'&&A.',6,'isGlobal){A.',6,'Register(',6,1,')}}catch(B){',6,528,'=1}},Domain',342,'if(',38,'appName=="Microsoft Internet Explorer"&&',6,36,'=="mac"&&',38,'userProfile','!=null',360,'if(',1,'==parent',360,'var B=',6,15,'domain',';try{while(true){try{if(parent.',15,'title',594,'){return }}','catch(A){}',0,15,603,'.match(/\\..*\\./)){break}',6,15,603,'=',6,15,603,'.replace(/^[^.]*\\./,"")}}',609,6,15,603,'=B}};',6,'Script={request:null,Init',342,'if(!(',6,525,'asynch&&',6,525,'progress',')){if(',1,'.XMLHttpRequest','){try{','this.request','=new XMLHttpRequest}catch(C){}if(',641,'&&',6,'root.match','(/^file:\\/\\//)){try{',641,'.open("GET",',6,'root+"',6,'js",false);',641,'.send(null)}catch(','C){',641,'=null;if(',1,'.postMessage&&',1,'.addEventListener',304,'mustPost','=1;',6,1,662,'("message",',6,'P! ost.','Listener,false)}}}}',0,641,'&&',1,'.ActiveXObject&&!this.',664,8,'A=["MSXML2.XMLHTTP.5','.0","MSXML2.XMLHTTP','.4',681,'.3',681,'","Microsoft.XMLHTTP"];','for(var B=0;B<','A.length&&!',641,';B++){try{',641,'=new ActiveXObject(A[B])}catch(C){}}}}',0,641,'||',6,352,'domainChanged',304,'Load=this.delayedLoad;this.needsBody=1}},Load',294,'B,A){','if(A){',6,'Message.Set("Loading "+','B);',6,'Script.','Delay(1);',6,'Script.Push(','this,"xmlRequest",B',');',6,711,6,'Message',',"Clear")}else{',6,711,712,')}},xmlRequest',294,'url){','this.blocking','=1;try{',641,649,'url,false);',641,655,'err){',725,'=0;if(',6,'Translate.','restart&&',6,'Translate.asynchronous){return""}throw"jsMath can\'t load the file \'"+url+"\'\\','nMessage: "+err.message}if(',641,'.status',594,'&&(',641,742,'>=400||',641,742,'<0)){',725,'=0;if(',6,736,'restart&&',6,739,'nError status: "+',641,742,'}',0,'url','.match(/\\.js$/)){','return(',641,'.responseText',')}var tmpQueue','=this.queue;this.queue','=[]! ;',6,1,'.eval(',641,767,');',725,'=0;','this.queue=this.queue.! concat(' ,'tmpQueue);this.Process();return""},cancelTimeout:30*1000,blocking:0,cancelTimer:null,needsBody:0,queue:[],Synchronize',294,'A,B){','if(typeof (','A)!="string"){',6,711,'null,A,B',')}else{',6,711,6,1,',"eval",A)}},Push',294,'A,C,B',304,'queue[','this.queue.length',']=[A,C,B];if(!(',725,'||(this.needsBody&&!',6,15,'body))){this.Process()}},Process',342,'while(',798,'&&!',725,8,'C=this.queue[0];this.queue=this.queue.slice(1);',369,'this.SaveQueue();var B=C[0];var E=C[1];var D=C[2];if(B){B[E](D',788,'if(E){E(D)}}this.','RestoreQueue','(A)}},SaveQueue',342,'var A',769,'=[];return A},',816,294,'A){',779,'A)},delayedLoad',294,'A',304,'Push(','this,"','startLoad','",A)},',832,294,'A',8,'B=',6,15,21,'("iframe");','B.style.','visibility="','hidden";',843,'position="absolute";',843,'width="0px";B','.style.height="','0px";if(',6,15,'body.firstChild','){',6,15,'body.insertBefore(B,',6,15,854,788,6,15,'body','.appendChild(','B)}',725,'=1;this.','url=A;if(A','.substr(0,',6,'root.length',! ')==',6,'root){A=A.substr(',6,873,')}',6,705,'A);this.cancelTimer=setTimeout("',6,708,'cancelLoad','()",this.cancelTimeout);',436,664,'){B.src=',6,671,832,'(A,B',788,'if(A',764,'B.src=',6,520,'loader.html"}else{B.src=this.url}}},','endLoad',294,'A){if(this.cancelTimer){clearTimeout(this.cancelTimer);this.cancelTimer=null}',6,671,901,'();',6,717,'.Clear();if(A!="cancel"){',725,'=0;this.Process','()}},Start',342,'this.tmpQueue',769,'=[]},End',342,779,915,');',468,915,'},',885,294,'B,',903,'if(B==null){B','="Can\'t load file"}if(A==null){A=2000}',6,717,'.Set(B);setTimeout(\'',6,708,901,'("cancel")\',A)},Delay',294,'A){',725,'=1;setTimeout("',6,708,'endDelay','()",A)},',944,342,725,912,'()},','imageCount',':0,WaitForImage',294,'B){',725,869,951,'++;',436,'img==null',304,'img=[]}',369,'new Image',427,'img[this.img.length]=A;A.onload=function(){if(--',6,708,951,'==0){',6,708,944,'()}};A.onerror=A.onload;A.onabort=A.onload;A.src=B},Uncompress',294,'data){for(var k=0;k<data.length;! k++){var d=data[k];var n=d.length;for(var i=0;i<n;i++){',783,'! d[i])==" number"){d[i]=d[d[i]]}}data[k]=d.join("")}',1,773,'data.join(""))}};',6,'Post={',1,':null,Listener',294,'D){if(D.source!=',6,671,1,360,'var E=D.origin','.replace(/^file:\\/\\//,"");',369,15,603,993,'if(E==null||E==""){E','="localhost"}if(','A==null||A==""){A',999,'E!=A||!','D.data.substr(','0,6).match(/jsM(CP|LD):/)){return }var B=',1003,'6,3).replace(/ /g,"");var C=',1003,'10);if(',6,'Post.Commands[B',']){(',6,1010,'])(C)}},Commands:{SCR',294,'message){',6,1,773,'message)},ERR',294,'A){',6,708,885,'(A,3000)},BGN',294,'A){',6,708,'Start()},END',294,'A){',703,6,708,'End()}',6,708,901,'()}},',832,294,'A,B',304,1,'=B.contentWindow;',0,'A',764,'return ',6,'root+',449,6,520,'loader-post.html?"+A},',901,342,'this.',1,'=null}};',6,717,'={',166,':null,message:null,text:null,clear:null,Init',342,0,6,15,'body||!',6,525,636,360,'if(',6,352,'stylesReady){','this.message','=',6,'Setup.DIV("','message",{visibility:"',170,'"},',6,'fixedDiv',788,1081,'=',6,1084,'message",{',179,'bottom:"',1! 86,'",','backgroundColor',':"#E6E6E6',189,190,'",zIndex:102,color:"black",fontSize:"',194,'"},',6,1089,')}','this.text','=',6,15,'createTextNode','("");',1081,866,1110,');',1081,'.onmousedown=',6,736,'Cancel},Set',294,702,'if(this.clear){clearTimeout(this.clear',');this.clear=null}if(',6,525,636,'){',0,1110,304,'Init();',0,1110,608,'if(',6,333,'textNodeBug','){',1081,'.innerHTML','=B','}else{',1110,'.nodeValue','=B}','this.message.style.',844,'visible";',703,1152,'cursor="pointer','";',0,1152,'cursor){',1152,'cursor="hand"}',1081,'.title=" Cancel Processing of Math "}else{',1152,'cursor="";',1081,'.title=""}}else{if(B',871,'8)!="Loading "){',6,1,742,'=B}}},Clear',342,1127,')}this.clear=setTimeout("',6,717,'.doClear()",1000)},doClear',342,436,'clear',304,'clear=null;',6,1,742,'="";',436,'text){',1110,1150,'=""}if(',1081,'){',1152,844,170,'"}}},Blank',342,436,166,'||!',6,15,'body',360,'this.blank','=',6,1084,166,'",{position',':(',6,333,'msiePositionFixedBug','?"absolute":"fi! xed"),',180,'",bottom:"0px",right:"0px",zIndex:101,',1100,':"w! hite"},' ,6,1089,');if(',6,333,'msieBlankBug){',1210,1146,'=" ";',1210,'.style.width="110%";',1210,850,'110%"}},UnBlank',342,436,166,'){',6,15,'body.removeChild(',1210,')}',1210,'=null}};',6,'Setup={',7,':[],DIV',294,'E,C,B){',929,'=',6,15,'body}var D=',6,15,21,'("div");D.id="jsMath_"+E;','for(var A in C){','D.style[A]=C[A]}',0,'B.hasChildNodes){B',866,'D',788,'B.insertBefore(D,','B.firstChild',')}return D},Script',294,702,436,7,'[B]){return }else{this.',7,'[B]=1}',0,'B.match("^([a-zA-Z]+:/?)?/")){B=',6,'root+B}',6,708,'Load(B,A)},Hidden',342,6,170,'=this.DIV("Hidden",{',179,'top:0,left:0,border:0,padding:0,margin:0});',6,'hiddenTop=',6,170,';return },Source',342,'if(',6,'Autoload','&&',6,1303,'.root){',6,'root=',6,1303,'.','root}else{',6,'root="";',369,6,15,'getElementsByTagName("','script");',703,687,'A.','length;B++){','var D=A[B].src;if(D&&D.match("(^|/|\\\\\\\\)',6,'js$")){',6,1309,'D.replace','(/',6,'js$/,"");break}}}}if(',6,'root.charAt(','0)=="\\\\"){',6,1309,6,'root.rep! lace','(/\\\\/g,"/")}if(',6,1335,'0)=="/"){if(',6,1335,'1)!="/"){if(',6,15,566,'port){',6,'root=":"+',6,15,566,'port+',6,'root}',6,'root="//"+',6,15,566,'host+',6,1359,6,1309,6,15,566,'protocol+',6,1313,0,6,646,'(/^[a-z]+:/i)){var D=','new String(',6,15,511,'=new RegExp("/[^/]*/\\\\.\\\\./");',6,1309,1330,'(new RegExp("[^/]*$"),"")+',6,'root;while(',6,646,'(C)){',6,1309,6,1340,'(C,"/")}}}',6,'Img.',1309,6,651,'fonts/";',6,166,'=',6,651,166,'.gif";this.Domain()},Domain',342,'try{',6,15,603,'}catch(D',360,'var C="";',369,6,15,603,';if(',6,646,'("://([^/]*)/")){C=RegExp.$1}C=C.replace(/:\\d+$/,"");if(C==""||C==A',360,'if(',38,'appName=="Microsoft Internet Explorer"&&',6,36,'=="mac"&&',38,'onLine&&',38,593,'&&',6,15,'all',360,'C=C.split(/\\./);A=A.split(/\\./);if(C.length<2||A.length<2||','C[C.length-','1',']!=A[A.length-','1]||',1445,'2',1447,'2]){this.','DomainWarning','();return }var E=',1445,'2]+"."+',1445,'1];for(var B=3;B<=C.length&&B<=A.',1324,'if(',1445,'B',1447,'B]){br! eak}E=',1445,'B]+"."+E}',6,15,603,'=E;this.',698,'=1},',1453,3! 42,23,'I n order for jsMath to be able to load the additional components that it may need, the ',6,'js file must be ',7,' from a server in the same ',603,' as the page that contains it. Because that is not the case for this page, the',25,'displayed here may not appear correctly.")},','EncodeFont',294,'C',8,'B=',6,'TeX[C];if(B','[0].c',594,360,462,'128;A++){var D=B[A];B[A]=D[3];if(B[A]==null){B[A]={}}B[A].w=D[0];B[A].h=D[1];if(D[2]!=null){B[A].d=D[2]}B[A].c=',6,'TeX.encoding[A]}},Fonts',342,687,6,'TeX.fam','.',1324,369,6,1502,'[B];if(A',304,1485,'(A)}}},TeXfont',294,'C',8,'B=',6,1491,'==null',360,369,6,'EmBoxFor(\'<span class="\'+C+\'">\'+B[65].c','+"</span>");B.hd=A.h;B.dh=0.05;B.d=',6,1522,'+',6,386,'B.hd)+"</span>").h-B.hd;B.h=B.hd-B.d;if(C=="',130,'"){B.skewchar=','127','}else{if(','C=="',134,1531,'48}}},',415,342,462,6,1502,464,'if(',6,1502,'[A]){this.TeXfont(',6,1502,'[A])}}},Sizes',342,6,'TeXparams','=[];var B;var A;for(A=0;A<',6,46,464,6,1553,'[A]={}}for(B in ',6,'TeX){',783,! 6,'TeX[B])!="object"){for(A=0;A<',6,46,464,6,1553,'[A][B]=',6,46,'[A]*',6,'TeX[B]/100}}}},Styles',294,'A){',0,'A){A=',6,'styles;A[".typeset .scale"]={"font-size":',6,525,'scale+"%"};this.stylesReady=1}',6,711,831,'AddStyleSheet','",A);if(',6,333,'styleChangeDelay','){',6,711,6,'Script,"Delay",1)}},StyleString',294,'E',8,'A={},F;for(F in E){if(typeof E[F]==="string"){A[F]=E[F]}else{if(F',871,'1)==="@"){A[F]=','this.StyleString(','E[F])}else{if(E[F]!=null',8,'D=[];for(var C in E[F]){if(E[F][C]!=null){D[D.length]=C+": "+E[F][C]}}A[F]=D.join("; ")}}}}var B="";for(F in A){B+=F+" {"+A[F]+"}\\n"}return B},',1589,294,'D',8,'B=',6,15,1319,'head")[0];',369,1605,'D);if(',6,15,'createStyleSheet){B.insertAdjacentHTML("beforeEnd",\'<','span style="display:','none">x</span><style type="text/css">\'+A+"</style>")}else{var C=',6,15,21,'("style");C.type="text/css";C',866,6,15,1114,'(A));B',866,'C)}},Body',342,436,'inited',360,'this.inited=-','1;',6,352,'Hidden();',1641,'2;',6,333,1136,1641,'! 3;if(',6,525,166,'){',6,717,'.Blank()}',1641,'4;',6,352,'Style! s();',16 41,'5;',6,517,1136,1641,'6;',6,711,6,'Setup,"User","pre-font");',1641,'7;',6,711,6,'Font,"Check");if(',6,'Font.register.length){',6,711,6,'Font,"LoadRegistered")}this.inited=1},User',294,'A){if(',6,'Setup.UserEvent[A',']){(',6,1691,'])()}},UserEvent:{"pre-font":null,onload:null}};',6,'Update={',415,294,'D){for(var A in D){for(var B in D[A]){for(var C in D[A][B]){',6,'TeX[A][B][C]=D[A][B][C]}}}},TeXfontCodes',294,'C){',1265,687,'C[A].',1324,6,'TeX[A][B].c=C[A][B]}}}};',6,'Browser={allowAbsolute:1,allowAbsoluteDelim:0,','separateSkips',':0,valignBug:0,operaHiddenFix:"",','msieCenterBugFix',':"",','msieInlineBlockFix',':"",msieSpaceFix:"",imgScale:1,renameOK:1,',1593,':0,delay:1,version:0,','TestSpanHeight',342,6,305,'=\'<span><','span style="\'+this.block','+\';height:2em;width:','1px"></span></span>\';var B=',6,'hidden.firstChild;',369,1273,';this.','spanHeightVaries','=(B',315,'>=A',315,'&&B',315,'>0);','this.spanHeightTooBig','=(B',315,'>A',315,');',6,305,'=""},','TestInlin! eBlock',342,'this.block="display',':-','moz-inline-box";','this.hasInlineBlock','=',6,'BBoxFor(\'<span style="\'+this.block','+\';width:10px;height:5px"></span>\').w>0;if','(',1756,'){',6,'styles[".typeset',' .',166,'"].display="-',1755,468,6,1765,' .spacer"].display',1148,1753,':',168,'";',1756,'=',6,1759,1760,'(!',1756,608,'this.block+=";overflow:',845,369,6,301,'("x").h;this.mozInlineBlockBug=',6,1759,'+";height:"+A+\'px;width:1px','"></span>x','<',1726,'+";height:"+A+"px;width:1px;vertical-align:-"+A+\'px',446,'h>2*A;this.widthAddsBorder=',6,1759,'+\';overflow:',170,';height:1px;width:10px',';border-left:','10px solid',446,'w>10;','this.msieBorderBug','=',6,1759,1795,1796,'\').h!=',6,1759,1795,1807,72,1796,'\').h;','this.blankWidthBug=',1811,'||',6,1759,1727,'0px',446,'h==0},','TestRenameOK',342,6,305,'="<span></span>";',369,6,1730,'A.setAttribute("name","','jsMath_test','");this.renameOK=(',6,15,'getElementsByName("',1843,'").length>0);',6,305,1750,'TestStyleChange',34! 2,6,305,'=\'<span ID="',1843,'">x</span>\';var B=',6,1730,369,! 'B',312, ';',6,352,1589,'({"#',1843,'":"font-size:200%"});this.',1593,'=(B',312,'==A);',6,305,1750,'VersionAtLeast',294,320,1380,'this.version).split(".");B=',1380,'B).split(".");if(B[1]==null){B[1]="0"}return A[0]>B[0]||(A[0]==B[0]&&A[1]>=B[1])},Init',342,6,27,'="unknown";this.',1751,427,1721,427,1834,427,1853,427,'MSIE',427,'Mozilla',427,'Opera',427,'OmniWeb',427,'Safari',427,'Konqueror();if(','this.allowAbsoluteDelim','){',6,'Box.DelimExtend=',6,'Box.DelimExtendAbsolute;',6,'Box.Layout=',6,'Box.LayoutAbsolute',1148,6,'Box.DelimExtend=',6,'Box.DelimExtendRelative;',6,'Box.Layout=',6,'Box.LayoutRelative}',436,1713,'){',6,'HTML.Place=',6,'HTML.','PlaceSeparateSkips',';',6,'Typeset.prototype.','Place=',6,1938,1935,'}},MSIE',342,436,1734,'&&!',1742,'){',6,27,'="MSIE";if(',6,36,'=="pc"){this.','IE7=(',1,639,594,');','this.quirks','=(',6,15,'compatMode=="BackCompat");this.msieStandard6=!',1961,'&&!this.IE7;',1909,869,1713,'=1',';this.buttonCheck=1;this.','msieBlankBug=1;this.','msieAccen! tBug',869,'msieRelativeClipBug','=1;this.msieDivWidthBug=1;this.',1219,869,'msieIntegralBug',869,'waitForImages',869,'msieAlphaBug','=!this.IE7;this.','alphaPrintBug',1985,1715,'="position:relative; ";this.',1717,'=" display:',168,';";this.msieTeXfontBaselineBug=!',1961,';',1811,'=',1825,'1;this.msieSpaceFix=\'<',1624,168,'"></span>\';',6,'Macro("joinrel","\\\\mathrel{\\\\kern-5mu}"),',6,'Parser.prototype.mathchardef.','mapstocharOrig','=',6,2006,'mapstochar;',468,6,2006,2011,6,'Macro("mapstochar","\\\\rlap{\\\\',2007,'\\\\,}\\\\kern1mu"),',6,1765,' .arial"]={"font-family":"\'Arial unicode MS\'"};',0,'this.IE7||',1961,'){',6,'styles["#jsMath_message"].',847,468,6,2028,'width;',6,'styles["#jsMath_panel"].',847,468,6,2035,'width;',6,'styles["#jsMath_button"].','width="1px";',6,2042,847,468,6,2042,'width;',6,1089,'=',6,1084,1089,1215,':"absolute",zIndex:101});',6,1,'.attachEvent("','onscroll",',6,'Controls.MoveButton',');',6,1,2061,'onresize",',6,2064,');',6,2064,'()}',6,'styl! es["#jsMath_noFont .link"].','display="',168,'";',468,6,1765,1! 773,';', 6,'styles[".tex2math_div','"]={};',6,'Add(',6,2087,'"],',6,'styles["div',57,'"]);',6,2087,'"].width="100%";',6,2087,'"].',2078,168,'";',6,1765,'"]["letter-spacing"]="0";if(','screen.deviceXDPI','&&','screen.logicalXDPI','&&',2110,'!=',2112,304,'imgScale*=',2112,'/',2110,';',6,525,'alpha=0}this.',334,'="<i>x</i>";',6,'EmBoxFor=',6,324,1533,6,36,'=="mac"){this.msieAbsoluteBug',869,'msieButtonBug',1977,1973,'quirks=1;',6,'Setup.Script("jsMath-','msie-mac.js");',6,'Parser.prototype.macros.angle=["Replace","ord",\'<font face="Symbol">‹</font>\',"',54,'"];',6,2035,'width="42em";',6,525,'printwarn=0}}',6,'Macro("not","\\\\mathrel{\\\\rlap{\\\\kern3mu','/}}")}},',1900,342,'if(',6,170,'.ATTRIBUTE_NODE){',6,27,'="',1900,'";if(',6,36,1955,1986,'=1}',1909,'=1;',6,2042,'cursor=',6,2077,1157,'",',6,2155,'/}}");if(',38,'vendor=="Firefox"){','this.version=',38,'vendorSub',1533,38,'userAgent.match','(" Firefox/([0-9.]+)([a-z ]|$)")){',2187,'RegExp.$1}}',436,1879,'("3.0")){this.mozImageS! izeBug=1}}},',1904,342,'if(',38,'accentColorName){',6,27,'="',1904,'";','this.allowAbsolute','=',1756,';',1909,'=',2209,';this.valignBug=!',2209,1972,1143,'=1;',6,'noChangeGlobal=1;',0,1756,'){',6,2142,'old-browsers.js','")}}},Opera',342,'if(',1742,'){',6,27,'="Opera";var B=',38,2192,'("Opera 7");',2209,'=0;this.delay=10;this.operaHiddenFix="[Processing]";if(B){',6,2142,2228,'")}',369,38,'appVersion.match(/^(\\d+\\.\\d+)/);',703,2187,'A[1]}else{this.vesion=0}this.operaAbsoluteWidthBug=this.operaL'], - ['ineHeightBug=(A[1]>=9.5)}},Safari',':function(){','if(navigator.','appVersion.match(/Safari\\//)){','jsMath.','browser','="Safari";','var A=','navigator.userAgent.match','("Safari/([0-9]+)");A=(A)?A[1]:400;this.version=A;','for(var B=0;B<',4,'TeX.fam','.length',';B++){','if(',4,12,'[B]&&',4,'TeX[',4,12,'[B]]){',4,20,4,12,'[B]].dh=0.1}}',4,'TeX.axis_height+=0.05;',4,'TeX.','default_rule_thickness','+=0.025',';this.allowAbsoluteDelim=','A>=125;this.safariIFRAMEbug=A>=312&&A<412;this.safariButtonBug=A<412;this.safariImgBug=1;this.textNodeBug=1;this.buttonCheck=A<500;this.styleChangeDelay=1}},','Konqueror',1,2,'product&&navigator.product.match("',37,'")){',4,5,'="',37,'";this.allowAbsolute=0',35,'0;if(',8,'(/',37,'\\/(\\d+)\\.(\\d+)/)){if(RegExp.$1<3||(RegExp.$1==3&&RegExp.$2<3)){this.separateSkips=1;this.valignBug=1;',4,'Setup.Script("jsMath-','old-browsers.js")}}',4,'Add(',4,'styles,{".typeset .cmr10','":"font-family: jsMath-','cmr10',', jsMath ','cmr10, serif','",".types! et .','cmbx10',61,66,63,66,', jsMath-cmr10, jsMath cmr10",".typeset .','cmti10',61,72,63,72,71,'cmmi10',61,78,63,78,65,'cmsy10',61,84,63,84,65,'cmex10',61,'cmex10, jsMath cmex10','"});',4,'Font.testFont','="jsMath','-',92,'"}}};',4,'Font={testFont:"jsMath-cmex10",','fallback',':"symbol",register:[],message:"<b>No jsMath TeX fonts found</b> -- using',' image fonts instead','.<br/>\\nThese',' may be slow and might not print well.<br/>\\nUse the jsMath control panel to get additional information','.",','extra_message',':\'Extra TeX fonts not found: <b><span id="jsMath_ExtraFonts"></span></b><br/>Using',104,'. This',106,'.\',','print_message',':"To print higher-resolution math symbols, click the<br/>\\n<b>Hi-Res Fonts for Printing</b> button on',' the jsMath control panel.<br/>\\n",','alpha_message',':"If the math symbols print as black boxes, turn off <b>image alpha channels</b><br/>\\nusing the <B>Options</B> pane of',116,'Test1',':function(','C',',F,D,E){if(F==null){F=124}i! f(D==null){D=2}if(E==null){E=""}var B=jsMath.BBoxFor(\'<span s! tyle="fo nt-family: \'+E+C+\', serif">\'+jsMath.TeX[C][F].c+"</span>");var A=jsMath.BBoxFor(\'<span style="font-family: serif">\'+jsMath.TeX[C][F].c+"</span>");return','(B.w>D*A.w&&B.h!=0)},Test2',121,'C',123,'(A.w>D*B.w&&B.h!=0)},CheckTeX',1,7,4,'BBoxFor(\'<span style="font-family',': \'+',4,95,'+\', serif">\'+',4,'TeX.cmex10[1','].c+"</span>");',4,'nofonts=((A.w*3>A.h||A.h==0)&&!this.Test1("cmr10','",null,null,"jsMath-"));if(!',4,'nofonts){','return }if(',4,5,'!="Mozilla"||(',4,'platform','=="mac"&&(!',4,'Browser.VersionAtLeast(','1.5)||',4,153,'3)))||(',4,150,'!="mac"&&!',4,153,'3))){A=',4,132,': CMEX10, serif">\'+',4,'TeX.cmex10[1',139,4,141,'"));if(!',4,144,4,55,'BaKoMa-fonts.js")}}},Check',1,7,4,'Controls.','cookie;this.CheckTeX();if(',4,144,'if(A.autofont','||A','.font=="tex"){','A.font=this.',102,';if(A.warn){',4,'nofontMessage=1;A.warn=0;',4,181,'SetCookie(0);if','(',4,'window.NoFontMessage','){',4,198,'()}else{','this.Message(this.','message)}}}}else{',185,'){A.font="tex"}i! f(A',187,'return }}if(',4,'noImgFonts){','A.font="unicode"}if(A','.font=="unicode','"){',4,55,102,'-"+',4,150,'+".js");',4,'Box.TeXnonfallback=',4,'Box.TeX',';',4,224,'=',4,'Box.TeXfallback;return }','if(!A.print&&A.printwarn){this.','PrintMessage','((',4,'Browser.','alphaPrintBug&&',4,181,'cookie.alpha)?this.',114,'+this.',117,':this.',114,')}if(',4,235,'waitForImages){',4,'Script.','Push(',4,'Script,"WaitForImage",',4,'blank)}if(A.font=="symbol"){',4,55,102,'-symbols.js");',4,'Box.TeXnonfallback=',4,224,';',4,224,'=',4,230,4,'Img.SetFont','({cmr10',':["all"],',78,273,84,273,'cmex10',273,66,273,72,':["all"]});',4,'Img.LoadFont','("cm-fonts")},Message',121,'A){if(',4,'Element("Warning',42,'return }var ','B=',4,'Setup.DIV("','Warning','",{});B.innerHTML=\'<center><table><tr><td><div ','id="jsMath_noFont"><div ','class="message">\'+A','+\'<div style="text-align:left"><span style="float:left; margin: 8px 0px 0px 20px"><span onclick="',4,181,'Panel()" ','title=" Open ','the ','! jsMath Control Panel',' " class="link">',306,'</span></span','! ><span s tyle="margin: 8px 20px 0px 0px; float:right"><span onclick="',4,'Font.','HideMessage','()" title=" Remove this font warning message',307,'Hide this Message',309,'></div><div style="height:6px"></div><br clear="all"/></div></','div><div style="width:22em; height:1px"></div></td></tr></table></center><hr/>\'},',313,1,7,4,290,'");if(A','){A.style.display="none"}},',232,121,288,4,'Element("','PrintWarning',42,292,'B=',4,295,332,297,299,'+\'</',319,'Register',121,'H,B){if(typeof (H)=="string"){H={name:H}}if(!',4,'Setup.inited','&&!B){','this.register','[',349,13,']=H;',292,'I=H.name;',7,'I.replace(/10$/,"");var F=',4,12,13,';if(H.prefix==null){H.prefix=""}if(!H.style){H.style="font-family: "+H.prefix+I+", serif"}if(!H.styles){H.styles={}}if(!H.macros){H.macros={}}',4,12,'[F]=I;',4,'TeX.famName[I]=F;H.macros[A]=["HandleFont",F];',4,58,4,'Parser.prototype.macros,H.macros);H.styles[".typeset ."+I]=H.style;',4,'Setup.Styles(H.styles);if(',4,'initialized){',4,250,'Push(',4,'Setup,"TeX! font",I)}var C=',4,181,'cookie;var E=!',4,'nofonts&&H.test(I,H.testChar,H.testFactor,H.prefix);if(E&&C',187,'if(H.tex){H.tex(I,F,H)}return }if(!E&&C.warn&&C.font=="tex"&&!',4,144,'if(!C.fonts.match("/"+I+"/")){C.fonts+=I+"/";',4,181,195,'(!',4,290,42,203,108,')}var G=',4,331,'ExtraFonts");if(G){if(G','.innerHTML','!=""){G',403,'+=","}G',403,'+=" "+H.prefix+I}}}if(C',212,'"||',4,210,'if(H.',102,'){H.',102,'(I,F,H)}',292,'D={};if(C.font=="symbol"&&H.symbol!=null){D[I]=H.symbol(I,F,H)}else{D[I]=["all"]}',4,271,'(D);',4,285,'(I);if(',4,'initialized){',4,250,'Push(',4,'Img,"Scale");',4,250,'Push(',4,'Img,"UpdateFonts")}},LoadRegistered',1,7,'0;while(A<',349,13,'){this.Register(',349,'[A++],1)}',349,'=[]},Load',121,'A){',4,'Setup.Script(this.URL(A))},URL',121,'A){','return ',4,'Img.root+A+"/def.js"}};',4,'Controls={cookie:{scale:100,font:"tex",autofont:1,scaleImg:0,alpha:1,warn:1,fonts:"/",printwarn:1,stayhires:0,button:1,progress:1,asynch:0,blank:0,print:0,keep:"0D",global:"auto! ",hiddenGlobal:1},cookiePath:"/",','noCookiePattern',':/^(file! |mk):$/, Init',1,'this.panel=',4,295,'panel",{display:"none"},',4,'fixedDiv);','if(!',4,235,'msieButtonBug){this.Button',202,'setTimeout("',4,181,'Button()",500)}},Panel',1,4,'Translate.Cancel();if(this.loaded){this.Main',202,4,250,'delayedLoad(',4,'root+"jsMath-controls.html")}},Button',1,7,4,295,'button",{},',4,467,'A.',304,306,' ";A',403,'=\'<span onclick="',4,181,'Panel()">jsMath</span>\';if(!',4,'Global.','isLocal&&!',4,'noShowGlobal){A',403,'+=\'<span id="jsMath_global" ',304,'jsMath Global Panel " onclick="',4,503,'Show(1)">Global </span>\'}if(A.offsetWidth<30){A.style.width="auto"}if(!','this.cookie','.button',326,'MoveButton',1,4,'fixedDiv.style.','left','=document.body.','scrollLeft+"px";',4,520,'top',522,'scrollTop+"px";',4,520,'width',522,'clientWidth+"px";',4,520,'height',522,'clientHeight+"px"},GetCookie',1,'if(','this.defaults','==null){',541,'={}}',4,58,541,',',514,');this.userSet={};var C=',4,'document.cookie',';if(',4,'window.location','.protocol.match(this.',4! 59,')){C=this.','localGetCookie','();','this.isLocalCookie','=1}if(','C.match(/jsMath','=([^;]+)/)){var D=unescape(RegExp.$1).split(/,/);',10,'D',13,14,7,'D[B].match(/(.*):(.*)/);if(A[2].match(/^\\d+$/)){A[2]=1*A[2]}',514,'[A[1]]=A[2];this.userSet[A[1]]=1}}},',559,1,454,4,555,'.search.substr(1)},SetCookie',121,'F){var B=[];for(var E in ',514,'){if(',541,'[E]==null||',514,'[E]!=',541,'[E]){B[B',13,']=E+":"+',514,'[E]}}B=B.join(",");if(',561,'){if(F==2){return"','jsMath="+escape(B)}','this.','localSetCookie','(B,F)}else{B=escape(B);if(B==""){F=0}if(','this.cookiePath','){B+="; path="+',599,'}if(','this.cookieDomain','){B+="; domain="+',603,'}if(',514,'.keep!="0D"){var A={D',':1000*60*60*24',',W',609,'*7,M',609,'*30,Y',609,'*365};var D=new Date;D.setTime(D.getTime()+',514,'.keep.substr(','0,1)*A[',514,618,'1,1)]);B+="; expires="+D.toGMTString()}if(B!=""){',4,552,96,'="+B;var C=',4,552,';if(F&&!',563,'=/)){alert("Cookies must be enabled in order to save jsMath options")}}}',454! ,'null},',597,121,'B,C){if(!C){return }',7,'String(',4,555,').! replace( /\\?.*/,"");if(B!=""){A+="?',595,'if(A!=',4,555,'.href){this.Reload(A)}},Reload',121,'A){if(!this.loaded){return }this.loaded=0;',4,347,'=-100;',4,503,'ClearCache();if(A){',4,555,'.replace(A)}else{',4,555,'.reload()}}};',4,'Click={CheckClick',121,'A){if(!A){A=',4,'window.event}if','(A.altKey){',4,181,'Panel()}},CheckDblClick',121,'B){if(!B){B=',4,666,'(!',4,'Click.DblClick){',4,'Extension.Require("double-click",1);',7,'B;B={};for(var C in A){B[C]=A[C]}}',4,250,'Push(',4,'Click,"DblClick",[B,this.alt])}};',4,'TeX={thinmuskip:3/18,medmuskip:4/18,thickmuskip:5/18,x_height:0.430554,quad:1,num1:0.676508,num2:0.393732,num3:0.44373,denom1:0.685951,denom2:0.344841,sup1:0.412892,sup2:0.362892,sup3:0.288888,sub1:0.15,sub2:0.247217,sup_drop:0.386108,sub_drop:0.05,delim1:2.39,delim2:1,axis_height:0.25,',33,':0.06,big_op_spacing1:0.111111,big_op_spacing2:0.166666,big_op_spacing3:0.2,big_op_spacing4:0.6,big_op_spacing5:0.1,integer:6553.6,scriptspace:0.05,nulldel... [truncated message content] |
From: Mike G. v. a. <we...@ma...> - 2009-06-26 01:07:39
|
Log Message: ----------- syncing with HEAD Tags: ---- rel-2-4-patches Modified Files: -------------- webwork2: README webwork2/bin: remove_stale_images wwdb_upgrade webwork2/conf: global.conf.dist webwork2/conf/templates/math: system.template webwork2/conf/templates/ur: system.template webwork2/doc: Copying webwork2/lib: WebworkSOAP.pm webwork2/lib/WeBWorK: Constants.pm ContentGenerator.pm DB.pm URLPath.pm webwork2/lib/WeBWorK/ContentGenerator: CourseAdmin.pm GatewayQuiz.pm Login.pm Problem.pm ProblemSets.pm webwork2/lib/WeBWorK/ContentGenerator/Instructor: Index.pm PGProblemEditor.pm ProblemSetDetail.pm SetMaker.pm Stats.pm StudentProgress.pm UserDetail.pm webwork2/lib/WeBWorK/DB: Schema.pm webwork2/lib/WeBWorK/DB/Record: UserSetLocations.pm webwork2/lib/WeBWorK/DB/Schema/NewSQL: Std.pm webwork2/lib/WeBWorK/Utils: CourseManagement.pm DBUpgrade.pm Tasks.pm webwork2/lib/WebworkSOAP/Classes: GlobalProblem.pm GlobalSet.pm Key.pm Password.pm Permission.pm User.pm UserProblem.pm UserSet.pm Added Files: ----------- webwork2/conf/templates/math2: math2.css webwork2/lib/WeBWorK/Utils: CourseIntegrityCheck.pm Removed Files: ------------- webwork2/doc/docbook/install: Installing-WeBWorK.xml Revision Data ------------- Index: README =================================================================== RCS file: /webwork/cvs/system/webwork2/README,v retrieving revision 1.17.4.3.2.3 retrieving revision 1.17.4.3.2.4 diff -LREADME -LREADME -u -r1.17.4.3.2.3 -r1.17.4.3.2.4 --- README +++ README @@ -1,6 +1,6 @@ WeBWorK Online Homework Delivery System - Version 2.4.5 + Version 2.4.7 Branch: rel-2-4-patches http://webwork.maa.org/wiki/Release_notes_for_WeBWorK_2.4.5 Index: remove_stale_images =================================================================== RCS file: /webwork/cvs/system/webwork2/bin/remove_stale_images,v retrieving revision 1.4.4.1.2.1 retrieving revision 1.4.4.1.2.2 diff -Lbin/remove_stale_images -Lbin/remove_stale_images -u -r1.4.4.1.2.1 -r1.4.4.1.2.2 --- bin/remove_stale_images +++ bin/remove_stale_images @@ -197,12 +197,12 @@ sub count_report { my $j; print "Days\n"; - for $j (sort keys(%days)) { + for $j (sort {$a <=> $b} keys(%days)) { print "$j $days{$j}\n"; } print "\nWeeks\n"; - for $j (sort keys(%week)) { + for $j (sort {$a <=> $b} keys(%week)) { print " $j $week{$j}\n"; } @@ -281,7 +281,4 @@ chmod $perms, $cachePath; chown $uid, $groupID, $cachePath; - - - -1; \ No newline at end of file +1; Index: wwdb_upgrade =================================================================== RCS file: /webwork/cvs/system/webwork2/bin/wwdb_upgrade,v retrieving revision 1.13.2.1 retrieving revision 1.13.2.1.2.1 diff -Lbin/wwdb_upgrade -Lbin/wwdb_upgrade -u -r1.13.2.1 -r1.13.2.1.2.1 --- bin/wwdb_upgrade +++ bin/wwdb_upgrade @@ -18,7 +18,6 @@ use strict; use warnings; use Getopt::Std; -use DBI; use Data::Dumper; BEGIN { @@ -28,447 +27,23 @@ use lib "$ENV{WEBWORK_ROOT}/lib"; use WeBWorK::CourseEnvironment; -use WeBWorK::Utils qw/runtime_use/; -use WeBWorK::Utils::CourseManagement qw/listCourses/; +use WeBWorK::Utils::DBUpgrade; our ($opt_v); getopts("v"); if ($opt_v) { - $| = 1; - *verbose = sub { print STDERR @_ }; + $WeBWorK::Debug::Enabled = 1; } else { - *verbose = sub {}; + $WeBWorK::Debug::Enabled = 0; } -# global variables, hah hah. -my ($dbh, %sql_tables); +my $ce = new WeBWorK::CourseEnvironment({webwork_dir=>$ENV{WEBWORK_ROOT}}); -################################################################################ - -my $i = -1; -our @DB_VERSIONS; - -$DB_VERSIONS[++$i]{desc} = "is the initial version of database, identical to database structure in WeBWorK 2.2.x."; - -$DB_VERSIONS[++$i]{desc} = "adds dbupgrade table to facilitate automatic database upgrades."; -$DB_VERSIONS[ $i]{global_code} = sub { - $dbh->do("CREATE TABLE `dbupgrade` (`name` VARCHAR(255) NOT NULL PRIMARY KEY, `value` TEXT)"); - $dbh->do("INSERT INTO `dbupgrade` (`name`, `value`) VALUES (?, ?)", {}, "db_version", 1); - $sql_tables{dbupgrade} = (); -}; - -$DB_VERSIONS[++$i]{desc} = "adds problems_per_page field to set and set_user tables of each course."; -$DB_VERSIONS[ $i]{course_code} = sub { - my $course = shift; - $dbh->do("ALTER TABLE `${course}_set` ADD COLUMN `problems_per_page` INT") - if exists $sql_tables{"${course}_set"}; - $dbh->do("ALTER TABLE `${course}_set_user` ADD COLUMN `problems_per_page` INT") - if exists $sql_tables{"${course}_set_user"}; -}; - -$DB_VERSIONS[++$i]{desc} = "adds depths table to keep track of dvipng depth information."; -$DB_VERSIONS[ $i]{global_code} = sub { - $dbh->do("CREATE TABLE depths (md5 CHAR(33) NOT NULL, depth SMALLINT, PRIMARY KEY (md5))"); - $sql_tables{depths} = (); -}; - -$DB_VERSIONS[++$i]{desc} = "changes type of key timestamp field to BIGINT"; -$DB_VERSIONS[ $i]{course_code} = sub { - my $course = shift; - return unless exists $sql_tables{"${course}_key"}; - $dbh->do("ALTER TABLE `${course}_key` CHANGE COLUMN `timestamp` `timestamp` BIGINT"); -}; - -$DB_VERSIONS[++$i]{desc} = "changes type of problem_user status field to FLOAT"; -$DB_VERSIONS[ $i]{course_code} = sub { - my $course = shift; - return unless exists $sql_tables{"${course}_problem_user"}; - $dbh->do("UPDATE `${course}_problem_user` SET `status`=NULL WHERE `status`=''"); - $dbh->do("ALTER TABLE `${course}_problem_user` CHANGE COLUMN `status` `status` FLOAT"); -}; - -$DB_VERSIONS[++$i]{desc} = "changes types of alphanumeric keyfields to TINYBLOB NOT NULL"; -$DB_VERSIONS[ $i]{course_code} = sub { - my $course = shift; - $dbh->do("ALTER TABLE `${course}_user` CHANGE COLUMN `user_id` `user_id` TINYBLOB NOT NULL") - if exists $sql_tables{"${course}_user"}; - $dbh->do("ALTER TABLE `${course}_password` CHANGE COLUMN `user_id` `user_id` TINYBLOB NOT NULL") - if exists $sql_tables{"${course}_password"}; - $dbh->do("ALTER TABLE `${course}_permission` CHANGE COLUMN `user_id` `user_id` TINYBLOB NOT NULL") - if exists $sql_tables{"${course}_permission"}; - $dbh->do("ALTER TABLE `${course}_key` CHANGE COLUMN `user_id` `user_id` TINYBLOB NOT NULL") - if exists $sql_tables{"${course}_key"}; - $dbh->do("ALTER TABLE `${course}_set` CHANGE COLUMN `set_id` `set_id` TINYBLOB NOT NULL") - if exists $sql_tables{"${course}_set"}; - $dbh->do("ALTER TABLE `${course}_problem` CHANGE COLUMN `set_id` `set_id` TINYBLOB NOT NULL") - if exists $sql_tables{"${course}_problem"}; - $dbh->do("ALTER TABLE `${course}_set_user` CHANGE COLUMN `user_id` `user_id` TINYBLOB NOT NULL") - if exists $sql_tables{"${course}_set_user"}; - $dbh->do("ALTER TABLE `${course}_set_user` CHANGE COLUMN `set_id` `set_id` TINYBLOB NOT NULL") - if exists $sql_tables{"${course}_set_user"}; - $dbh->do("ALTER TABLE `${course}_problem_user` CHANGE COLUMN `user_id` `user_id` TINYBLOB NOT NULL") - if exists $sql_tables{"${course}_problem_user"}; - $dbh->do("ALTER TABLE `${course}_problem_user` CHANGE COLUMN `set_id` `set_id` TINYBLOB NOT NULL") - if exists $sql_tables{"${course}_problem_user"}; -}; - -$DB_VERSIONS[++$i]{desc} = "fixes KEY length, adds UNIQUE KEY for user table"; -$DB_VERSIONS[ $i]{course_code} = sub { - my $course = shift; - return unless exists $sql_tables{"${course}_user"}; - $dbh->do("ALTER TABLE `${course}_user` DROP KEY `user_id`"); - $dbh->do("ALTER TABLE `${course}_user` ADD UNIQUE KEY (`user_id`(255))"); -}; - -$DB_VERSIONS[++$i]{desc} = "fixes KEY length, adds UNIQUE KEY for password table"; -$DB_VERSIONS[ $i]{course_code} = sub { - my $course = shift; - return unless exists $sql_tables{"${course}_password"}; - $dbh->do("ALTER TABLE `${course}_password` DROP KEY `user_id`"); - $dbh->do("ALTER TABLE `${course}_password` ADD UNIQUE KEY (`user_id`(255))"); -}; - -$DB_VERSIONS[++$i]{desc} = "fixes KEY length, adds UNIQUE KEY for permission table"; -$DB_VERSIONS[ $i]{course_code} = sub { - my $course = shift; - return unless exists $sql_tables{"${course}_permission"}; - $dbh->do("ALTER TABLE `${course}_permission` DROP KEY `user_id`"); - $dbh->do("ALTER TABLE `${course}_permission` ADD UNIQUE KEY (`user_id`(255))"); -}; - -$DB_VERSIONS[++$i]{desc} = "fixes KEY length, adds UNIQUE KEY for key table"; -$DB_VERSIONS[ $i]{course_code} = sub { - my $course = shift; - return unless exists $sql_tables{"${course}_key"}; - $dbh->do("ALTER TABLE `${course}_key` DROP KEY `user_id`"); - $dbh->do("ALTER TABLE `${course}_key` ADD UNIQUE KEY (`user_id`(255))"); -}; - -$DB_VERSIONS[++$i]{desc} = "fixes KEY length, adds UNIQUE KEY for set table"; -$DB_VERSIONS[ $i]{course_code} = sub { - my $course = shift; - return unless exists $sql_tables{"${course}_set"}; - $dbh->do("ALTER TABLE `${course}_set` DROP KEY `set_id`"); - $dbh->do("ALTER TABLE `${course}_set` ADD UNIQUE KEY (`set_id`(255))"); -}; - -$DB_VERSIONS[++$i]{desc} = "fixes KEY length, adds UNIQUE KEY for problem table"; -$DB_VERSIONS[ $i]{course_code} = sub { - my $course = shift; - return unless exists $sql_tables{"${course}_problem"}; - $dbh->do("ALTER TABLE `${course}_problem` DROP KEY `set_id`"); - $dbh->do("ALTER TABLE `${course}_problem` ADD UNIQUE KEY (`set_id`(255), `problem_id`)"); - $dbh->do("ALTER TABLE `${course}_problem` DROP KEY `problem_id`"); - $dbh->do("ALTER TABLE `${course}_problem` ADD KEY (`problem_id`)"); -}; - -$DB_VERSIONS[++$i]{desc} = "fixes KEY length, adds UNIQUE KEY for set_user table"; -$DB_VERSIONS[ $i]{course_code} = sub { - my $course = shift; - return unless exists $sql_tables{"${course}_set_user"}; - $dbh->do("ALTER TABLE `${course}_set_user` DROP KEY `user_id`"); - $dbh->do("ALTER TABLE `${course}_set_user` ADD UNIQUE KEY (`user_id`(255), `set_id`(255))"); - $dbh->do("ALTER TABLE `${course}_set_user` DROP KEY `set_id`"); - $dbh->do("ALTER TABLE `${course}_set_user` ADD KEY (`set_id`(255))"); -}; - -$DB_VERSIONS[++$i]{desc} = "fixes KEY length, adds UNIQUE KEY for problem_user table"; -$DB_VERSIONS[ $i]{course_code} = sub { - my $course = shift; - return unless exists $sql_tables{"${course}_problem_user"}; - $dbh->do("ALTER TABLE `${course}_problem_user` DROP KEY `user_id`"); - $dbh->do("ALTER TABLE `${course}_problem_user` ADD UNIQUE KEY (`user_id`(255), `set_id`(255), `problem_id`)"); - $dbh->do("ALTER TABLE `${course}_problem_user` DROP KEY `set_id`"); - $dbh->do("ALTER TABLE `${course}_problem_user` ADD KEY (`set_id`(255), `problem_id`)"); - $dbh->do("ALTER TABLE `${course}_problem_user` DROP KEY `problem_id`"); - $dbh->do("ALTER TABLE `${course}_problem_user` ADD KEY (`problem_id`)"); -}; - -$DB_VERSIONS[++$i]{desc} = "changes psvn index from PRIMARY KEY to UNIQUE KEY"; -$DB_VERSIONS[ $i]{course_code} = sub { - my $course = shift; - return unless exists $sql_tables{"${course}_set_user"}; - $dbh->do("ALTER TABLE `${course}_set_user` ADD UNIQUE KEY (`psvn`)"); - $dbh->do("ALTER TABLE `${course}_set_user` DROP PRIMARY KEY"); -}; - -$DB_VERSIONS[++$i]{desc} = "adds hide_score and hide_work fields to set and set_user"; -$DB_VERSIONS[ $i]{course_code} = sub { - my $course = shift; - if ( exists $sql_tables{"${course}_set"} ) { - $dbh->do("ALTER TABLE `${course}_set` ADD COLUMN `hide_score` ENUM('0','1')"); - $dbh->do("ALTER TABLE `${course}_set` ADD COLUMN `hide_work` ENUM('0','1')"); - } - if ( exists $sql_tables{"${course}_set_user"} ) { - $dbh->do("ALTER TABLE `${course}_set_user` ADD COLUMN `hide_score` ENUM('0','1')"); - $dbh->do("ALTER TABLE `${course}_set_user` ADD COLUMN `hide_work` ENUM('0','1')"); - } -}; - -$DB_VERSIONS[++$i]{desc} = "updates hide_score and hide_work in set and set_user tables to allow more (and more descriptive) possible values"; -$DB_VERSIONS[ $i]{course_code} = sub { - my $course = shift; - if ( exists $sql_tables{"${course}_set"} ) { - $dbh->do("ALTER TABLE `${course}_set` MODIFY COLUMN `hide_score` ENUM('0','1','2')"); - $dbh->do("ALTER TABLE `${course}_set` MODIFY COLUMN `hide_work` ENUM('0','1','2')"); - } - if ( exists $sql_tables{"${course}_set_user"} ) { - $dbh->do("ALTER TABLE `${course}_set_user` MODIFY COLUMN `hide_score` ENUM('0','1','2')"); - $dbh->do("ALTER TABLE `${course}_set_user` MODIFY COLUMN `hide_work` ENUM('0','1','2')"); - } -}; - -$DB_VERSIONS[++$i]{desc} = "adds time_limit_cap field to set and set_user tables"; -$DB_VERSIONS[ $i]{course_code} = sub { - my $course = shift; - if ( exists $sql_tables{"${course}_set"} ) { - $dbh->do("ALTER TABLE `${course}_set` ADD COLUMN `time_limit_cap` ENUM('0','1')"); - } - if ( exists $sql_tables{"${course}_set_user"} ) { - $dbh->do("ALTER TABLE `${course}_set_user` ADD COLUMN `time_limit_cap` ENUM('0','1')"); - } -}; - -$DB_VERSIONS[++$i]{desc} = "updates hide_score and hide_work in set and set_user tables to have more descriptive values, set default values"; -$DB_VERSIONS[ $i]{course_code} = sub { - my $course = shift; - if ( exists $sql_tables{"${course}_set"} ) { - $dbh->do("ALTER TABLE `${course}_set` MODIFY COLUMN `hide_score` ENUM('N','Y','BeforeAnswerDate') DEFAULT 'N'"); - $dbh->do("ALTER TABLE `${course}_set` MODIFY COLUMN `hide_work` ENUM('N','Y','BeforeAnswerDate') DEFAULT 'N'"); - } - if ( exists $sql_tables{"${course}_set_user"} ) { - $dbh->do("ALTER TABLE `${course}_set_user` MODIFY COLUMN `hide_score` ENUM('N','Y','BeforeAnswerDate') DEFAULT 'N'"); - $dbh->do("ALTER TABLE `${course}_set_user` MODIFY COLUMN `hide_work` ENUM('N','Y','BeforeAnswerDate') DEFAULT 'N'"); - } -}; - -$DB_VERSIONS[++$i]{desc} = "adds locations, location_addresses, set_locations and set_locations_user tables to database, and add restrict_ip to set and set_user."; -$DB_VERSIONS[ $i]{global_code} = sub { - $dbh->do("CREATE TABLE locations (location_id TINYBLOB NOT NULL, description TEXT, PRIMARY KEY (location_id(1000)))"); - $dbh->do("CREATE TABLE location_addresses (location_id TINYBLOB NOT NULL, ip_mask TINYBLOB NOT NULL, PRIMARY KEY (location_id(500),ip_mask(500)))"); -}; -$DB_VERSIONS[ $i]{course_code} = sub { - my $course = shift; - - $dbh->do("CREATE TABLE `${course}_set_locations` (set_id TINYBLOB NOT NULL, location_id TINYBLOB NOT NULL, PRIMARY KEY (set_id(500),location_id(500)))"); - $dbh->do("CREATE TABLE `${course}_set_locations_user` (set_id TINYBLOB NOT NULL, user_id TINYBLOB NOT NULL, location_id TINYBLOB NOT NULL, PRIMARY KEY (set_id(300),user_id(300),location_id(300)))"); - - if ( exists $sql_tables{"${course}_set"} ) { - $dbh->do("ALTER TABLE `${course}_set` ADD COLUMN `restrict_ip` enum('No','RestrictTo','DenyFrom') DEFAULT 'No'"); - } - if ( exists $sql_tables{"${course}_set_user"} ) { - $dbh->do("ALTER TABLE `${course}_set_user` ADD COLUMN `restrict_ip` enum('No','RestrictTo','DenyFrom')"); - } -}; - -$DB_VERSIONS[++$i]{desc} = "updates defaults for hide_work and hide_score in set_user tables."; -$DB_VERSIONS[ $i]{course_code} = sub { - my $course = shift; - - if ( exists $sql_tables{"${course}_set_user"} ) { - $dbh->do("ALTER TABLE `${course}_set_user` MODIFY COLUMN `hide_score` ENUM('N','Y','BeforeAnswerDate')"); - $dbh->do("ALTER TABLE `${course}_set_user` MODIFY COLUMN `hide_work` ENUM('N','Y','BeforeAnswerDate')"); - } -}; - -$DB_VERSIONS[++$i]{desc} = "adds relax_restrict_ip, hide_problem_score columns to set and set_user tables."; -$DB_VERSIONS[ $i]{course_code} = sub { - my $course = shift; - - if ( exists $sql_tables{"${course}_set"} ) { - $dbh->do("ALTER TABLE `${course}_set` ADD COLUMN `relax_restrict_ip` ENUM('No','AfterAnswerDate','AfterVersionAnswerDate') DEFAULT 'No'"); - $dbh->do("ALTER TABLE `${course}_set` ADD COLUMN `hide_score_by_problem` ENUM('N','Y') DEFAULT 'N'"); - } - if ( exists $sql_tables{"${course}_set_user"} ) { - $dbh->do("ALTER TABLE `${course}_set_user` ADD COLUMN `relax_restrict_ip` ENUM('No','AfterAnswerDate','AfterVersionAnswerDate')"); - $dbh->do("ALTER TABLE `${course}_set_user` ADD COLUMN `hide_score_by_problem` ENUM('N','Y')"); - } -}; - -$DB_VERSIONS[++$i]{desc} = "adds set and set_user fields to allow set-level proctor, updates permissions to allow finer-grained regulation of proctoring."; -$DB_VERSIONS[ $i]{course_code} = sub { - my $course = shift; - if ( exists $sql_tables{"${course}_permission"} ) { - $dbh->do("UPDATE `${course}_permission` SET `permission`=3 where `permission`=2"); - } - if ( exists $sql_tables{"${course}_set"} ) { - $dbh->do("ALTER TABLE `${course}_set` ADD COLUMN `restricted_login_proctor` ENUM('No','Yes') DEFAULT 'No'"); - } - if ( exists $sql_tables{"${course}_set_user"} ) { - $dbh->do("ALTER TABLE `${course}_set_user` ADD COLUMN `restricted_login_proctor` ENUM('No','Yes')"); - } -}; - -our $THIS_DB_VERSION = $i; - -################################################################################ - -my $ce = WeBWorK::CourseEnvironment->new({ - webwork_dir => $ENV{WEBWORK_ROOT}, -}); - -my @ww_courses = listCourses($ce); - -$dbh = DBI->connect( - $ce->{database_dsn}, - $ce->{database_username}, - $ce->{database_password}, - { - PrintError => 0, - RaiseError => 1, - }, +my $upgrader = new WeBWorK::Utils::DBUpgrade( + ce => $ce, + verbose_sub => sub { print STDERR @_ }, ); -{ - verbose("Obtaining dbupgrade lock...\n"); - my ($lock_status) = $dbh->selectrow_array("SELECT GET_LOCK('dbupgrade', 10)"); - if (not defined $lock_status) { - print "Couldn't obtain lock because an error occurred.\n"; - exit 2; - } - if ($lock_status) { - verbose("Got lock.\n"); - } else { - print "Timed out while waiting for lock.\n"; - exit 2; - } -} - -%sql_tables = get_sql_tables(); - -my $db_version = exists $sql_tables{dbupgrade} ? get_db_version() : 0; - -if (not defined $db_version) { - print "Failed to get db_version -- can't continue.\n"; - exit 1; -} - -verbose("Initial db_version is $db_version\n"); - -if ($db_version > $THIS_DB_VERSION) { - print "db_version is $db_version, but the current database version is only $THIS_DB_VERSION. This database was probably used with a newer version of WeBWorK.\n"; - exit; -} - -while ($db_version < $THIS_DB_VERSION) { - $db_version++; - unless (upgrade_to_version($db_version)) { - print "\nUpgrading from version ".($db_version-1)." to $db_version failed.\n\n"; - unless (ask_permission("Ignore this error and go on to the next version?", 0)) { - exit 3; - } - } - set_db_version($db_version); -} - -print "\nDatabase is up-to-date at version $db_version.\n"; - -END { - verbose("Releasing dbupgrade lock...\n"); - my ($lock_status) = $dbh->selectrow_array("SELECT RELEASE_LOCK('dbupgrade')"); - if (not defined $lock_status) { - print "Couldn't release lock because the lock does not exist.\n"; - exit 2; - } - if ($lock_status) { - verbose("Released lock.\n"); - } else { - print "Couldn't release lock because the lock is not held by this thread.\n"; - exit 2; - } -} - -################################################################################ - -sub get_sql_tables { - my $sql_tables_ref = $dbh->selectcol_arrayref("SHOW TABLES"); - my %sql_tables; @sql_tables{@$sql_tables_ref} = (); - - return %sql_tables; -} - -sub get_db_version { - my $vers_value_should_be = "This value should always be a positive integer."; - my $vers_stop_now = "You should stop now and take a closer look."; - - my @record = $dbh->selectrow_array("SELECT `value` FROM `dbupgrade` WHERE `name`='db_version'"); - if (@record) { - my $db_version = $record[0]; - if (not defined $db_version) { - print "'db_version' exists, but it has a NULL value. $vers_value_should_be $vers_stop_now\n"; - return; - } elsif ($db_version !~ /^-?\d+$/) { - print "'db_version' is set to the non-numeric value '$db_version'. $vers_value_should_be $vers_stop_now\n"; - return; - } elsif ($db_version < 0) { - print "'db_version' is set to the negative value '$db_version'. $vers_value_should_be $vers_stop_now\n"; - return; - } elsif ($db_version == 0) { - print "'db_version' is set 0, which is reserved to indicate a pre-automatic-upgrade version. $vers_value_should_be $vers_stop_now\n"; - return; - } else { - # db_version is positive! yay! - return $db_version; - } - } else { - print "The 'dbupgrade' table exists, but doesn't contain a 'db_version' setting. $vers_stop_now\n"; - return; - } -} - -sub set_db_version { - my $vers = shift; - $dbh->do("UPDATE `dbupgrade` SET `value`=? WHERE `name`='db_version'", {}, $vers); -} +$upgrader->do_upgrade; -sub upgrade_to_version { - my $vers = shift; - my %info = %{$DB_VERSIONS[$vers]}; - - print "\nUpgrading database from version " . ($vers-1) . " to $vers...\n"; - my $desc = $info{desc} || "has no description."; - print "(Version $vers $desc)\n"; - - if (exists $info{global_code}) { - eval { $info{global_code}->() }; - if ($@) { - print "\nAn error occured while running the system upgrade code for version $vers:\n"; - print "$@"; - return 0 unless ask_permission("Ignore this error and keep going?", 0); - } - } - - if (@ww_courses and exists $info{course_code}) { - foreach my $curr_course (@ww_courses) { - eval { $info{course_code}->($curr_course) }; - if ($@) { - print "\nAn error occured while running the course upgrade code for version $vers on course $curr_course:\n"; - print "$@"; - next if ask_permission("Ignore this error and go on to the next course?", 0); - } - } - } - - print "Done.\n"; - return 1; -} - -################################################################################ - -sub ask_permission { - my ($prompt, $default) = @_; - - $default = 1 if not defined $default; - my $options = $default ? "[Y/n]" : "[y/N]"; - - while (1) { - print "$prompt $options "; - my $resp = <STDIN>; - chomp $resp; - return $default if $resp eq ""; - return 1 if lc $resp eq "y"; - return 0 if lc $resp eq "n"; - $prompt = 'Please enter "y" or "n".'; - } -} Index: global.conf.dist =================================================================== RCS file: /webwork/cvs/system/webwork2/conf/global.conf.dist,v retrieving revision 1.189.2.7.2.10 retrieving revision 1.189.2.7.2.11 diff -Lconf/global.conf.dist -Lconf/global.conf.dist -u -r1.189.2.7.2.10 -r1.189.2.7.2.11 --- conf/global.conf.dist +++ conf/global.conf.dist @@ -375,6 +375,32 @@ # the templates directory. $courseFiles{login_info} = "login_info.txt"; +# Additional library buttons can be added to the Library Browser (SetMaker.pm) +# by adding the libraries you want to the following line. For each key=>value +# in the list, if a directory (or link to a directory) with name 'key' appears +# in the templates directory, then a button with name 'value' will be placed at +# the top of the problem browser. (No button will appear if there is no +# directory or link with the given name in the templates directory.) For +# example, +# +# $courseFiles{problibs} = {rochester => "Rochester", asu => "ASU"}; +# +# would add two buttons, one for the Rochester library and one for the ASU +# library, provided templates/rochester and templates/asu exists either as +# subdirectories or links to other directories. The "NPL Directory" button +# activated below gives access to all the directories in the National +# Problem Library. +# +$courseFiles{problibs} = { + Library => "NPL Directory", +# rochesterLibrary => "Rochester", +# asuLibrary => "Arizona State", +# dcdsLibrary => "Detroit CDS", +# dartmouthLibrary => "Dartmouth", +# indianaLibrary => "Indiana", +# osuLibrary => "Ohio State", +# capaLibrary => "CAPA", +}; ################################################################################ # Status system @@ -472,35 +498,6 @@ passwd => $database_password, }; - -# Additional library buttons can be added to the Library Browser (SetMaker.pm) -# by adding the libraries you want to the following line. For each key=>value -# in the list, if a directory (or link to a directory) with name 'key' appears -# in the templates directory, then a button with name 'value' will be placed at -# the top of the problem browser. (No button will appear if there is no -# directory or link with the given name in the templates directory.) For -# example, -# -# $courseFiles{problibs} = {rochester => "Rochester", asu => "ASU"}; -# -# would add two buttons, one for the Rochester library and one for the ASU -# library, provided templates/rochester and templates/asu exists either as -# subdirectories or links to other directories. The "NPL Directory" button -# activated below gives access to all the directories in the National -# Problem Library. -# -$courseFiles{problibs} = { - Library => "NPL Directory", -# rochesterLibrary => "Rochester", -# asuLibrary => "Arizona State", -# dcdsLibrary => "Detroit CDS", -# dartmouthLibrary => "Dartmouth", -# indianaLibrary => "Indiana", -# osuLibrary => "Ohio State", - capaLibrary => "CAPA", - ucsbLibrary => "UCSB" -}; - ################################################################################ # Logs ################################################################################ @@ -551,7 +548,7 @@ # your school. If just a few courses are in a different timezone, set this in # course.conf for the affected courses instead. # -$siteDefaults{timezone} ="US/Eastern"; +$siteDefaults{timezone} = ""; # The default_templates_course is used by default to create a new course. # The contents of the templates directory are copied from this course Index: system.template =================================================================== RCS file: /webwork/cvs/system/webwork2/conf/templates/math/system.template,v retrieving revision 1.6.2.1.2.2 retrieving revision 1.6.2.1.2.3 diff -Lconf/templates/math/system.template -Lconf/templates/math/system.template -u -r1.6.2.1.2.2 -r1.6.2.1.2.3 --- conf/templates/math/system.template +++ conf/templates/math/system.template @@ -28,7 +28,8 @@ <title><!--#path style="text" text=" : " textonly="1"--></title> <!--#head--> </head> -<body bgcolor="white" onload="if (typeof(initializeAction) == 'function') {initializeAction()}"> + +<body bgcolor="white" onload="if (typeof(initializeWWquestion) == 'function') {initializeWWquestion()}"> <div id="masthead"> <div id="loginstatus"> Index: system.template =================================================================== RCS file: /webwork/cvs/system/webwork2/conf/templates/ur/system.template,v retrieving revision 1.3.4.1.2.2 retrieving revision 1.3.4.1.2.3 diff -Lconf/templates/ur/system.template -Lconf/templates/ur/system.template -u -r1.3.4.1.2.2 -r1.3.4.1.2.3 --- conf/templates/ur/system.template +++ conf/templates/ur/system.template @@ -5,7 +5,7 @@ <!-- ################################################################################ # WeBWorK Online Homework Delivery System -# Copyright © 2000-2007 The WeBWorK Project, http://openwebwork.sf.net/ +# Copyright � 2000-2007 The WeBWorK Project, http://openwebwork.sf.net/ # $CVSHeader$ # # This program is free software; you can redistribute it and/or modify it under @@ -27,6 +27,9 @@ <link rel="stylesheet" type="text/css" href="<!--#url type="webwork" name="htdocs"-->/css/ur.css"/> <title><!--#path style="text" text=" : " textonly="1"--></title> <!--#head--> + +<link rel="stylesheet" type="text/css" href="<!--#url type="webwork" name="htdocs"-->/css/ur.css"/> + </head> <body bgcolor="white" onload="if (typeof(initializeAction) == 'function') {initializeAction()}"> Index: Copying =================================================================== RCS file: /webwork/cvs/system/webwork2/doc/Copying,v retrieving revision 1.1.26.1 retrieving revision 1.1.26.2 diff -Ldoc/Copying -Ldoc/Copying -u -r1.1.26.1 -r1.1.26.2 --- doc/Copying +++ doc/Copying @@ -136,12 +136,12 @@ under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: - a) accompany it with the complete corresponding machine-readable + a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, - b) accompany it with a written offer, valid for at least three + b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be --- doc/docbook/install/Installing-WeBWorK.xml +++ /dev/null @@ -1,155 +0,0 @@ -<?xml version="1.0" standalone="no"?> -<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.1.2//EN" -"http://www.oasis-open.org/docbook/xml/4.1.2/docbookx.dtd" [ -<!ENTITY WW "WeBWorK"> -]> -<article> - <articleinfo> - <title>Installing WeBWorK</title> - <author><firstname>Sam</firstname><surname>Hathaway</surname></author> - <copyright><year>2005</year><holder>The WeBWorK Project</holder></copyright> - </articleinfo> - <section id="intro"> - <title>Introduction</title> - <para>FiXME: purpose of this article, assumptions, etc.</para> - </section> - <section id="sysreq"> - <title>System Requirements</title> - <para>&WW; requires several software packages to run. The installation procedure varies depending on your operating system.</para> - <section id="os"> - <title>Operating System</title> - <para>&WW; runs on most POSIX-compliant operating systems. It has been tested on FreeBSD, Linux, Solaris, Mac OS X.</para> - </section> - <section id="perl"> - <title>Perl</title> - <para>&WW; requires Perl 5.6 or higher and several of the core modules included in the Perl distribution.</para> - <itemizedlist> - <listitem><simpara><ulink type="http" url="http://perl.org/">Perl web site</ulink></simpara></listitem> - <listitem><simpara><ulink type="http" url="http://packages.debian.org/perl">Debian package perl</ulink></simpara></listitem> - </itemizedlist> - <para>The following non-core Perl modules are also required. All are available from <ulink type="http" url="http://cpan.org/">CPAN</ulink>, and many OS vendors provide packages of these modules. To see if <replaceable>Module</replaceable> is installed on your system, run <command>perl -M<replaceable>Module</replaceable> -e 'print "installed!\n"'</command>. To install <replaceable>Module</replaceable> from CPAN, run <command>perl -MCPAN -e "install <replaceable>Module</replaceable>"</command>.</para> - <segmentedlist> - <title>Required Perl Modules</title> - <?dbhtml list-presentation="table"?> - <segtitle>Module</segtitle> - <segtitle>Debian package</segtitle> - <seglistitem> - <seg>Apache::Request</seg> - <seg><ulink type="http" url="http://packages.debian.org/libapache-request-perl">libapache-request-perl</ulink></seg> - </seglistitem> - <seglistitem> - <seg>Data::UUID</seg> - <seg>not available<footnote><para>The package <ulink type="http" url="http://packages.debian.org/libossp-uuid-perl">libossp-uuid-perl</ulink> provides a clone of Data::UUID that alledgedly has the same interface. It has not been tested with &WW;</para></footnote></seg> - </seglistitem> - <seglistitem> - <seg>String::ShellQuote</seg> - <seg><ulink type="http" url="http://packages.debian.org/libstring-shellquote-perl">libstring-shellquote-perl</ulink></seg> - </seglistitem> - <seglistitem> - <seg>DateTime</seg> - <seg><ulink type="http" url="http://packages.debian.org/libdatetime-perl">libdatetime-perl</ulink></seg> - </seglistitem> - <seglistitem> - <seg>Date::Format</seg> - <seg><ulink type="http" url="http://packages.debian.org/libtimedate-perl">libtimedate-perl</ulink></seg> - </seglistitem> - <seglistitem> - <seg>Date::Parse</seg> - <seg><ulink type="http" url="http://packages.debian.org/libtimedate-perl">libtimedate-perl</ulink></seg> - </seglistitem> - <seglistitem> - <seg>GD</seg> - <seg><ulink type="http" url="http://packages.debian.org/libgd-gd2-perl">libgd-gd2-perl</ulink></seg> - </seglistitem> - <seglistitem> - <seg>Mail::Sender</seg> - <seg><ulink type="http" url="http://packages.debian.org/libmail-sender-perl">libmail-sender-perl</ulink></seg> - </seglistitem> - <seglistitem> - <seg>SOAP::Lite</seg> - <seg><ulink type="http" url="http://packages.debian.org/libsoap-lite-perl">libsoap-lite-perl</ulink></seg> - </seglistitem> - <seglistitem> - <seg>Time::HiRes</seg> - <seg><ulink type="http" url="http://packages.debian.org/libtime-hires-perl">libtime-hires-perl</ulink></seg> - </seglistitem> - <seglistitem> - <seg>XML::Parser</seg> - <seg><ulink type="http" url="http://packages.debian.org/libxml-parser-perl">libxml-parser-perl</ulink></seg> - </seglistitem> - <seglistitem> - <seg>XML::Parser::EasyTree</seg> - <seg>not available</seg> - </seglistitem> - <seglistitem> - <seg>XML::Writer</seg> - <seg><ulink type="http" url="http://packages.debian.org/libxml-writer-perl">libxml-writer-perl</ulink></seg> - </seglistitem> - <seglistitem> - <seg>DBI</seg> - <seg><ulink type="http" url="http://packages.debian.org/libdbi-perl">libdbi-perl</ulink></seg> - </seglistitem> - <seglistitem> - <seg>DBD::mysql</seg> - <seg><ulink type="http" url="http://packages.debian.org/libdbd-mysql-perl">libdbd-mysql-perl</ulink></seg> - </seglistitem> - </segmentedlist> - </section> - <section id="apache"> - <title>Apache 1.3</title> - <para>&WW; requires Apache 1.3.x and the <literal>mod_alias</literal> module.</para> - <itemizedlist> - <listitem><simpara><ulink type="http" url="http://httpd.apache.org/">Apache web site</ulink></simpara></listitem> - <listitem><simpara><ulink type="http" url="http://packages.debian.org/apache">Debian package apache</ulink></simpara></listitem> - </itemizedlist> - </section> - <section id="modperl"> - <title>mod_perl</title> - <para>&WW; is a mod_perl application and requires mod_perl 1.x. If compiling mod_perl from source, use the <envar>EVERYTHING=1</envar> flag to enable all mod_perl features. Most vendors compile their mod_perl packages with this setting enabled.</para> - <itemizedlist> - <listitem><simpara><ulink type="http" url="http://perl.apache.org/">mod_perl web site</ulink></simpara></listitem> - <listitem><simpara><ulink type="http" url="http://packages.debian.org/libapache-mod-perl">Debian package libapache-mod-perl</ulink></simpara></listitem> - </itemizedlist> - </section> - <section id="mysql"> - <title>MySQL</title> - <para>&WW; stores its data in a MySQL database. &WW; has been tested with MySQL 3 and 4. It should work with MySQL 5 as well. The MySQL server can be on the same machine as &WW; or on a remote machine. You will need the <command>mysql</command> client to create the &WW; database.</para> - <itemizedlist> - <listitem><simpara><ulink type="http" url="http://mysql.com/">MySQL web site</ulink></simpara></listitem> - <listitem><simpara><ulink type="http" url="http://packages.debian.org/mysql-server">Debian package mysql-server</ulink></simpara></listitem> - </itemizedlist> - </section> - <section id="latex"> - <title>LaTeX</title> - <para>&WW; uses LaTeX for generating hardcopy output and displaying mathematics graphically. Any standard LaTeX distribution that provides the commands <command>latex</command> and <command>pdflatex</command> should work. &WW; has been tested with teTeX.</para> - <itemizedlist> - <listitem><simpara><ulink type="http" url="http://tug.org/tetex/">teTeX web site</ulink></simpara></listitem> - <listitem><simpara>Debian packages <ulink type="http" url="http://packages.debian.org/tetex-bin">tetex-bin</ulink> and <ulink type="http" url="http://packages.debian.org/tetex-extra">tetex-extra</ulink></simpara></listitem> - </itemizedlist> - </section> - <section id="netpbm"> - <title>Netpbm</title> - <para>&WW; requires Netpbm, an image manipulation library, to convert images among the GIF, PNG, and EPS formats.</para> - <itemizedlist> - <listitem><simpara><ulink type="http" url="http://netpbm.sf.net/">Netpbm web site</ulink></simpara></listitem> - <listitem><simpara>Debian package <ulink type="http" url="http://packages.debian.org/netpbm">netpbm</ulink></simpara></listitem> - </itemizedlist> - </section> - <section id="dvipng"> - <title>dvipng</title> - <para>&WW; uses dvipng to display mathematics graphically. It is only required if you wish to use the <literal>images</literal> display mode. &WW; is initially configured to work with dvipng 1.0 or greater, but can be reconfigured to work with dvipng 0.8 or 0.9. dvipng requires the <filename>preview.sty</filename> file from the preview-latex package.</para> - <itemizedlist> - <listitem><simpara><ulink type="http" url="http://dvipng.sf.net/">dvipng web site</ulink></simpara></listitem> - <listitem><simpara>Debian packages <ulink type="http" url="http://packages.debian.org/dvipng">dvipng</ulink> and <ulink type="http" url="http://packages.debian.org/preview-latex-style">preview-latex-style</ulink></simpara></listitem> - </itemizedlist> - </section> - <section id="tth"> - <title>tth</title> - <para>&WW; uses TtH to display mathematics as formatted HTML. It is only required if you wish to use the <literal>formatted-text</literal> display mode.</para> - <itemizedlist> - <listitem><simpara><ulink type="http" url="http://hutchinson.belmont.ma.us/tth/">TtH web site</ulink></simpara></listitem> - <listitem><simpara>Debian package <ulink type="http" url="http://packages.debian.org/tth">tth</ulink></simpara></listitem> - </itemizedlist> - </section> - </section> -</article> \ No newline at end of file Index: WebworkSOAP.pm =================================================================== RCS file: /webwork/cvs/system/webwork2/lib/WebworkSOAP.pm,v retrieving revision 1.12.2.1 retrieving revision 1.12.2.2 diff -Llib/WebworkSOAP.pm -Llib/WebworkSOAP.pm -u -r1.12.2.1 -r1.12.2.2 --- lib/WebworkSOAP.pm +++ lib/WebworkSOAP.pm @@ -57,6 +57,12 @@ return $self; } +sub array_to_soap_string { + my @array = @_; + @array = map { SOAP::Data->type( 'string', $_ ) } @array; + return \@array; +} + sub soap_fault_authen { die SOAP::Fault->faultcode(SOAPERROR_AUTHEN_FAILED) ->faultstring("SOAP Webservice Authentication Failed!"); @@ -103,7 +109,7 @@ } $@ and soap_fault_major("Course Environment cannot be constructed."); my @test = listCourses($ce); - return \@test; + return array_to_soap_string( @test ); } =pod @@ -127,7 +133,7 @@ eval { $soapEnv->{db}->deleteKey($userID) }; eval { $soapEnv->{db}->addKey($Key) }; $@ and soap_fault(SOAPERROR_USER_NOT_FOUND,"User not found."); - return $newKey; + return SOAP::Data->type( 'string', $newKey ); } =pod @@ -171,7 +177,7 @@ initializeUserProblem($UserProblem, $seed); eval { $db->addUserProblem($UserProblem) }; } - return @results; + return array_to_soap_string( @results ); #FIXME WSDL says $string, not @string? } =pod @@ -196,7 +202,7 @@ } push(@grades,$grade); } - return \@grades; + return array_to_soap_string( @grades ); } =pod @@ -204,7 +210,7 @@ _IN authenKey $string _IN courseName $string _IN setID $string -_RETURN @string +_RETURN $WebworkSOAP::Classes::GlobalSet =end WSDL =cut sub get_set_data { @@ -216,8 +222,6 @@ } my $set = new WebworkSOAP::Classes::GlobalSet($setData); return $set; - - } #################################################################### @@ -240,7 +244,7 @@ my $soapEnv = new WebworkSOAP($authenKey,$courseName); my $newPassword = $soapEnv->{db}->newPassword; %$newPassword = %$record; - return $soapEnv->{db}->addPassword($newPassword); + return SOAP::Data->type( 'string', $soapEnv->{db}->addPassword($newPassword) ); } =pod @@ -254,7 +258,7 @@ sub put_password { my ($self,$authenKey,$courseName,$record) = @_; my $soapEnv = new WebworkSOAP($authenKey,$courseName); - return $soapEnv->{db}->putPassword($record); + return SOAP::Data->type( 'string', $soapEnv->{db}->putPassword($record) ); } =pod @@ -268,7 +272,7 @@ my ($self,$authenKey,$courseName) = @_; my $soapEnv = new WebworkSOAP($authenKey,$courseName); my @tempArray = $soapEnv->{db}->listPasswords; - return \@tempArray; + return array_to_soap_string( @tempArray ); } =pod @@ -326,7 +330,7 @@ my $soapEnv = new WebworkSOAP($authenKey,$courseName); my $newPermissionLevel = $soapEnv->{db}->newPermissionLevel; %$newPermissionLevel = %$record; - return $soapEnv->{db}->addPermissionLevel($newPermissionLevel); + return SOAP::Data->type( 'string', $soapEnv->{db}->addPermissionLevel($newPermissionLevel) ); } =pod @@ -340,7 +344,7 @@ sub put_permission { my ($self,$authenKey,$courseName,$record) = @_; my $soapEnv = new WebworkSOAP($authenKey,$courseName); - return $soapEnv->{db}->putPermissionLevel($record); + return SOAP::Data->type( 'string', $soapEnv->{db}->putPermissionLevel($record) ); } =pod @@ -354,7 +358,7 @@ my ($self,$authenKey,$courseName) = @_; my $soapEnv = new WebworkSOAP($authenKey,$courseName); my @tempArray = $soapEnv->{db}->listPermissionLevels; - return \@tempArray; + return array_to_soap_string( @tempArray ); } =pod @@ -412,7 +416,7 @@ my $soapEnv = new WebworkSOAP($authenKey,$courseName); my $newKey = $soapEnv->{db}->newKey; %$newKey = %$record; - return $soapEnv->{db}->addKey($newKey); + return SOAP::Data->type( 'string', $soapEnv->{db}->addKey($newKey) ); } =pod @@ -426,7 +430,7 @@ sub put_key { my ($self,$authenKey,$courseName,$record) = @_; my $soapEnv = new WebworkSOAP($authenKey,$courseName); - return $soapEnv->{db}->putKey($record); + return SOAP::Data->type( 'string', $soapEnv->{db}->putKey($record) ); } =pod @@ -440,7 +444,7 @@ my ($self,$authenKey,$courseName) = @_; my $soapEnv = new WebworkSOAP($authenKey,$courseName); my @tempArray = $soapEnv->{db}->listKeys; - return \@tempArray; + return array_to_soap_string( @tempArray ); } =pod @@ -498,7 +502,7 @@ my $soapEnv = new WebworkSOAP($authenKey,$courseName); my $newUser = $soapEnv->{db}->newUser; %$newUser = %$record; - return $soapEnv->{db}->addUser($newUser); + return SOAP::Data->type( 'string', $soapEnv->{db}->addUser($newUser) ); } =pod @@ -512,7 +516,7 @@ sub put_user { my ($self,$authenKey,$courseName,$record) = @_; my $soapEnv = new WebworkSOAP($authenKey,$courseName); - return $soapEnv->{db}->putUser($record); + return SOAP::Data->type( 'string', $soapEnv->{db}->putUser($record) ); } =pod @@ -526,7 +530,7 @@ my ($self,$authenKey,$courseName) = @_; my $soapEnv = new WebworkSOAP($authenKey,$courseName); my @tempArray = $soapEnv->{db}->listUsers; - return \@tempArray; + return array_to_soap_string( @tempArray ); } =pod @@ -578,7 +582,7 @@ sub delete_user { my ($self,$authenKey,$courseName,$userID) = @_; my $soapEnv = new WebworkSOAP($authenKey,$courseName); - return $soapEnv->{db}->deleteUser($userID); + return SOAP::Data->type( 'string', $soapEnv->{db}->deleteUser($userID) ); } ################################################## @@ -598,7 +602,7 @@ my $soapEnv = new WebworkSOAP($authenKey,$courseName); my $newGlobalSet = $soapEnv->{db}->newGlobalSet; %$newGlobalSet = %$record; - return $soapEnv->{db}->addGlobalSet($newGlobalSet); + return SOAP::Data->type( 'string', $soapEnv->{db}->addGlobalSet($newGlobalSet) ); } =pod @@ -612,7 +616,7 @@ sub put_global_set { my ($self,$authenKey,$courseName,$record) = @_; my $soapEnv = new WebworkSOAP($authenKey,$courseName); - return $soapEnv->{db}->putGlobalSet($record); + return SOAP::Data->type( 'string', $soapEnv->{db}->putGlobalSet($record) ); } =pod @@ -626,10 +630,9 @@ my ($self,$authenKey,$courseName) = @_; my $soapEnv = new WebworkSOAP($authenKey,$courseName); my @tempArray = $soapEnv->{db}->listGlobalSets; - return \@tempArray; + return array_to_soap_string( @tempArray ); } - =pod =begin WSDL _IN authenKey $string @@ -698,7 +701,7 @@ sub delete_global_set { my ($self,$authenKey,$courseName,$setID) = @_; my $soapEnv = new WebworkSOAP($authenKey,$courseName); - return $soapEnv->{db}->deleteGlobalSet($setID); + return SOAP::Data->type( 'string', $soapEnv->{db}->deleteGlobalSet($setID) ); } ################################################## @@ -718,7 +721,7 @@ my $soapEnv = new WebworkSOAP($authenKey,$courseName); my $newGlobalProblem = $soapEnv->{db}->newGlobalProblem; %$newGlobalProblem = %$record; - return $soapEnv->{db}->addGlobalProblem($newGlobalProblem); + return SOAP::Data->type( 'string', $soapEnv->{db}->addGlobalProblem($newGlobalProblem) ); } =pod @@ -732,7 +735,7 @@ sub put_global_problem { my ($self,$authenKey,$courseName,$record) = @_; my $soapEnv = new WebworkSOAP($authenKey,$courseName); - return $soapEnv->{db}->putGlobalProblem($record); + return SOAP::Data->type( 'string', $soapEnv->{db}->putGlobalProblem($record) ); } =pod @@ -747,7 +750,7 @@ my ($self,$authenKey,$courseName,$setID) = @_; my $soapEnv = new WebworkSOAP($authenKey,$courseName); my @tempArray = $soapEnv->{db}->listGlobalProblems($setID); - return \@tempArray; + return array_to_soap_string( @tempArray ); } =pod @@ -773,7 +776,7 @@ =begin _IN authenKey $string _IN courseName $string -_IN problemIDs @string +_IN problemIDs @string An array reference: [userID setID problemID] _RETURN @WebworkSOAP::Classes::GlobalProblem Array of user objects =end WSDL =cut @@ -783,7 +786,7 @@ my @problemData = $soapEnv->{db}->getGlobalProblems(@$problemIDs); my @problems; for(my $i=0;$i<@problemData;$i++) { - push(@problems,new WebworkSOAP::Classes::GlobalProblem(@problemData[$i])); + push(@problems,new WebworkSOAP::Classes::GlobalProblem(@problemData[$i])); #FIXME $problemData[$i]? } return \@problems; } @@ -820,7 +823,7 @@ sub delete_global_problem { my ($self,$authenKey,$courseName,$setID,$problemID) = @_; my $soapEnv = new WebworkSOAP($authenKey,$courseName); - return $soapEnv->{db}->deleteGlobalProblem($setID,$problemID); + return SOAP::Data->type( 'string', $soapEnv->{db}->deleteGlobalProblem($setID,$problemID) ); } ################################################## @@ -840,7 +843,7 @@ my $soapEnv = new WebworkSOAP($authenKey,$courseName); my $newUserProblem = $soapEnv->{db}->newUserProblem; %$newUserProblem = %$record; - return $soapEnv->{db}->addUserProblem($newUserProblem); + return SOAP::Data->type( 'string', $soapEnv->{db}->addUserProblem($newUserProblem) ); } =pod @@ -854,7 +857,7 @@ sub put_user_problem { my ($self,$authenKey,$courseName,$record) = @_; my $soapEnv = new WebworkSOAP($authenKey,$courseName); - return $soapEnv->{db}->putUserProblem($record); + return SOAP::Data->type( 'string', $soapEnv->{db}->putUserProblem($record) ); } =pod @@ -862,14 +865,15 @@ _IN authenKey $string _IN courseName $string _IN userID $string +_IN setID $string _RETURN @string of names objects. =end WSDL =cut sub list_user_problems { - my ($self,$authenKey,$courseName,$userID) = @_; + my ($self,$authenKey,$courseName,$userID,$setID) = @_; my $soapEnv = new WebworkSOAP($authenKey,$courseName); - my @tempArray = $soapEnv->{db}->listUserProblems($userID); - return \@tempArray; + my @tempArray = $soapEnv->{db}->listUserProblems($userID,$setID); + return array_to_soap_string( @tempArray ); } =pod @@ -896,7 +900,7 @@ =begin WSDL _IN authenKey $string _IN courseName $string -_IN userProblemIDs @string +_IN userProblemIDs @string A 3 element array: { user_ID, setID, problemID } _RETURN @WebworkSOAP::Classes::UserProblem of names objects. =end WSDL =cut @@ -945,7 +949,7 @@ sub delete_user_problem { my ($self,$authenKey,$courseName,$userID,$setID,$problemID) = @_; my $soapEnv = new WebworkSOAP($authenKey,$courseName); - return $soapEnv->{db}->deleteUserProblem($userID,$setID,$problemID); + return SOAP::Data->type( 'string', $soapEnv->{db}->deleteUserProblem($userID,$setID,$problemID) ); } ################################################## @@ -965,7 +969,7 @@ my $soapEnv = new WebworkSOAP($authenKey,$courseName); my $newUserSet = $soapEnv->{db}->newUserSet; %$newUserSet = %$record; - return $soapEnv->{db}->addUserSet($newUserSet); + return SOAP::Data->type( 'string', $soapEnv->{db}->addUserSet($newUserSet) ); } =pod @@ -979,7 +983,7 @@ sub put_user_set { my ($self,$authenKey,$courseName,$record) = @_; my $soapEnv = new WebworkSOAP($authenKey,$courseName); - return $soapEnv->{db}->addUserSet($record); + return SOAP::Data->type( 'string', $soapEnv->{db}->addUserSet($record) ); } =pod @@ -994,7 +998,7 @@ my ($self,$authenKey,$courseName,$userID) = @_; my $soapEnv = new WebworkSOAP($authenKey,$courseName); my @tempArray = $soapEnv->{db}->listUserSets($userID); - return \@tempArray; + return array_to_soap_string( @tempArray ); } =pod @@ -1066,7 +1070,7 @@ sub delete_user_set { my ($self,$authenKey,$courseName,$userID,$setID) = @_; my $soapEnv = new WebworkSOAP($authenKey,$courseName); - return $soapEnv->{db}->deleteUserSet($userID,$setID); + return SOAP::Data->type( 'string', $soapEnv->{db}->deleteUserSet($userID,$setID) ); } Index: DB.pm =================================================================== RCS file: /webwork/cvs/system/webwork2/lib/WeBWorK/DB.pm,v retrieving revision 1.104.2.2.2.1 retrieving revision 1.104.2.2.2.2 diff -Llib/WeBWorK/DB.pm -Llib/WeBWorK/DB.pm -u -r1.104.2.2.2.1 -r1.104.2.2.2.2 --- lib/WeBWorK/DB.pm +++ lib/WeBWorK/DB.pm @@ -151,6 +151,10 @@ 'WeBWorK::DB::Ex::DependencyNotFound' => { isa => 'WeBWorK::DB::Ex::RecordNotFound', }, + 'WeBWorK::DB::Ex::TableMissing' => { + isa => 'WeBWorK::DB::Ex', + description =>"missing table", + }, ); ################################################################################ @@ -496,7 +500,7 @@ eval { return $self->{user}->add($User); }; - if (my $ex = caught WeBWorK::DB::Schema::Ex::RecordExists) { + if (my $ex = caught WeBWorK::DB::Ex::RecordExists) { croak "addUser: user exists (perhaps you meant to use putUser?)"; } elsif ($@) { die $@; @@ -601,7 +605,7 @@ eval { return $self->{password}->add($Password); }; - if (my $ex = caught WeBWorK::DB::Schema::Ex::RecordExists) { + if (my $ex = caught WeBWorK::DB::Ex::RecordExists) { croak "addPassword: password exists (perhaps you meant to use putPassword?)"; } elsif ($@) { die $@; @@ -694,7 +698,7 @@ eval { return $self->{permission}->add($PermissionLevel); }; - if (my $ex = caught WeBWorK::DB::Schema::Ex::RecordExists) { + if (my $ex = caught WeBWorK::DB::Ex::RecordExists) { croak "addPermissionLevel: permission level exists (perhaps you meant to use putPermissionLevel?)"; } elsif ($@) { die $@; @@ -780,7 +784,7 @@ eval { return $self->{key}->add($Key); }; - if (my $ex = caught WeBWorK::DB::Schema::Ex::RecordExists) { + if (my $ex = caught WeBWorK::DB::Ex::RecordExists) { croak "addKey: key exists (perhaps you meant to use putKey?)"; } elsif ($@) { die $@; @@ -913,7 +917,7 @@ eval { return $self->{locations}->add($Location); }; - if ( my $ex = caught WeBWorK::DB::Schema::Ex::RecordExists ) { + if ( my $ex = caught WeBWorK::DB::Ex::RecordExists ) { croak "addLocation: location exists (perhaps you meant to use putLocation?)"; } elsif ($@) { die $@; @@ -1014,7 +1018,7 @@ eval { return $self->{location_addresses}->add($LocationAddress); }; - if (my $ex = caught WeBWorK::DB::Schema::Ex::RecordExists) { + if (my $ex = caught WeBWorK::DB::Ex::RecordExists) { croak "addLocationAddress: location address exists (perhaps you meant to use putLocationAddress?)"; } elsif ($@) { die $@; @@ -1085,7 +1089,7 @@ return $self->{set}->add($GlobalSet); }; - if (my $ex = caught WeBWorK::DB::Schema::Ex::RecordExists) { + if (my $ex = caught WeBWorK::DB::Ex::RecordExists) { croak "addGlobalSet: global set exists (perhaps you meant to use putGlobalSet?)"; } elsif ($@) { die $@; @@ -1177,7 +1181,7 @@ eval { return $self->{set_user}->add($UserSet); }; - if (my $ex = caught WeBWorK::DB::Schema::Ex::RecordExists) { + if (my $ex = caught WeBWorK::DB::Ex::RecordExists) { croak "addUserSet: user set exists (perhaps you meant to use putUserSet?)"; } elsif ($@) { die $@; @@ -1289,7 +1293,7 @@ eval { return $self->{set_version}->add($SetVersion); }; - if (my $ex = caught WeBWorK::DB::Schema::Ex::RecordExists) { + if (my $ex = caught WeBWorK::DB::Ex::RecordExists) { croak "addSetVersion: set version exists (perhaps you meant to use putSetVersion?)"; } elsif ($@) { die $@; @@ -1403,7 +1407,7 @@ eval { return $self->{set_locations}->add($GlobalSetLocation); }; - if (my $ex = caught WeBWorK::DB::Schema::Ex::RecordExists) { + if (my $ex = caught WeBWorK::DB::Ex::RecordExists) { croak "addGlobalSetLocation: global set_location exists (perhaps you meant to use putGlobalSetLocation?)"; } elsif ($@) { die $@; @@ -1502,7 +1506,7 @@ eval { return $self->{set_locations_user}->add($UserSetLocation); }; - if (my $ex = caught WeBWorK::DB::Schema::Ex::RecordExists) { + if (my $ex = caught WeBWorK::DB::Ex::RecordExists) { croak "addUserSetLocation: user set_location exists (perhaps you meant to use putUserSetLocation?)"; } elsif ($@) { die $@; @@ -1604,7 +1608,7 @@ eval { return $self->{problem}->add($GlobalProblem); }; - if (my $ex = caught WeBWorK::DB::Schema::Ex::RecordExists) { + if (my $ex = caught WeBWorK::DB::Ex::RecordExists) { croak "addGlobalProblem: global problem exists (perhaps you meant to use putGlobalProblem?)"; } elsif ($@) { die $@; @@ -1702,7 +1706,7 @@ eval { return $self->{problem_user}->add($UserProblem); }; - if (my $ex = caught WeBWorK::DB::Schema::Ex::RecordExists) { + if (my $ex = caught WeBWorK::DB::Ex::RecordExists) { croak "addUserProblem: user problem exists (perhaps you meant to use putUserProblem?)"; } elsif ($@) { die $@; @@ -1846,7 +1850,7 @@ eval { return $self->{problem_version}->add($ProblemVersion); }; - if (my $ex = caught WeBWorK::DB::Schema::Ex::RecordExists) { + if (my $ex = caught WeBWorK::DB::Ex::RecordExists) { croak "addProblemVersion: problem version exists (perhaps you meant to use putProblemVersion?)"; } elsif ($@) { die $@; Index: ContentGenerator.pm =================================================================== RCS file: /webwork/cvs/system/webwork2/lib/WeBWorK/ContentGenerator.pm,v retrieving revision 1.191.2.1.2.2 retrieving revision 1.191.2.1.2.3 diff -Llib/WeBWorK/ContentGenerator.pm -Llib/WeBWorK/ContentGenerator.pm -u -r1.191.2.1.2.2 -r1.191.2.1.2.3 --- lib/WeBWorK/ContentGenerator.pm +++ lib/WeBWorK/ContentGenerator.pm @@ -51,6 +51,7 @@ use URI::Escape; use WeBWorK::Debug; use WeBWorK::PG; +use MIME::Base64; use WeBWorK::Template qw(template); use mod_perl; @@ -661,8 +662,17 @@ if (defined $setID) { print CGI::start_ul(); print CGI::start_li(); # $setID - print &$makelink("${pfx}ProblemSet", text=>"$prettySetID", urlpath_args=>{%args,setID=>$setID}, systemlink_args=>\%systemlink_args); - # FIXME i think we only want this if the problem set is not a gateway quiz + # show a link if we're displaying a homework set, or a version + # of a gateway assignment; to know if it's a gateway + # assignment, we have to get the set record. + my ($globalSetID) = ( $setID =~ /(.+?)(,v\d+)?$/ ); + my $setRecord = $db->getGlobalSet( $globalSetID ); + if ( $setRecord->assignment_type !~ /gateway/ ) { + print &$makelink("${pfx}ProblemSet", text=>"$prettySetID", urlpath_args=>{%args,setID=>$setID}, systemlink_args=>\%systemlink_args); + } elsif ($setID =~ /,v(\d)+$/) { + print &$makelink("${pfx}GatewayQuiz", text=>"$prettySetID", urlpath_args=>{%args,setID=>$setID}, systemlink_args=>\%systemlink_args); + } + if (defined $problemID) { print CGI::start_ul(); print CGI::start_li(); # $problemID @@ -693,7 +703,8 @@ print CGI::start_li(); # Homework Set Editor print &$makelink("${pfx}ProblemSetList", urlpath_args=>{%args}, systemlink_args=>\%systemlink_args); - if (defined $setID) { + ## only show editor link for non-versioned sets + if (defined $setID && $setID !~ /,v\d+$/ ) { print CGI::start_ul(); print CGI::start_li(); # $setID print &$makelink("${pfx}ProblemSetDetail", text=>"$prettySetID", urlpath_args=>{%args,setID=>$setID}, systemlink_args=>\%systemlink_args); @@ -719,7 +730,11 @@ print CGI::li(&$makelink("${pfx}Stats", text=>"$eUserID", urlpath_args=>{%args,statType=>"student",userID=>$eUserID}, systemlink_args=>\%systemlink_args)); } if (defined $setID) { - print CGI::li(&$makelink("${pfx}Stats", text=>"$prettySetID", urlpath_args=>{%args,statType=>"set",setID=>$setID}, systemlink_args=>\%systemlink_args)); + # make sure we don't try to send a versioned + # set id in to the stats link + my ( $nvSetID ) = ( $setID =~ /(.+?)(,v\d+)?$/ ); + my ( $nvPretty ) = ( $prettySetID =~ /(.+?)(,v\d+)?$/ ); + print CGI::li(&$makelink("${pfx}Stats", text=>"$nvPretty", urlpath_args=>{%args,statType=>"set",setID=>$nvSetID}, systemlink_args=>\%systemlink_args)); } print CGI::end_ul(); } @@ -733,7 +748,11 @@ print CGI::li(&$makelink("${pfx}StudentProgress", text=>"$eUserID", urlpath_args=>{%args,statType=>"student",userID=>$eUserID}, systemlink_args=>\%systemlink_args)); } if (defined $setID) { - print CGI::li(&$makelink("${pfx}StudentProgress", text=>"$prettySetID", urlpath_args=>{%args,statType=>"set",setID=>$setID}, systemlink_args=>\%systemlink_args)); + # make sure we don't try to send a versioned + # set id in to the stats link + my ( $nvSetID ) = ( $setID =~ /(.+?)(,v\d+)?$/ ); + my ( $nvPretty ) = ( $prettySetID =~ /(.+?)(,v\d+)?$/ ); + print CGI::li(&$makelink("${pfx}StudentProgress", text=>"$nvPretty", urlpath_args=>{%args,statType=>"set",setID=>$nvSetID}, systemlink_args=>\%systemlink_args)); } print CGI::end_ul(); } @@ -1439,8 +1458,11 @@ return "" unless $authz->hasPermissions($userID, "submit_feedback"); my $feedbackURL = $r->ce->{courseURLs}{feedbackURL}; + my $feedbackFormURL = $r->ce->{courseURLs}{feedbackFormURL}; if (defined $feedbackURL and $feedbackURL ne "") { return $self->feedbackMacro_url($feedbackURL); + } elsif (defined $feedbackFormURL and $feedbackFormURL ne "") { + return $self->feedbackMacro_form($feedbackFormURL,%params); } else { return $self->feedbackMacro_email(%params); } @@ -1462,6 +1484,7 @@ $result .= $self->hidden_authen_fields . "\n"; while (my ($key, $value) = each %params) { + next if $key eq 'pg_object'; # not used in internal feedback mechanism $result .= CGI::hidden($key, $value) . "\n"; } $result .= CGI::p({-align=>"left"}, CGI::submit(-name=>"feedbackForm", -label=>$feedbackName)); @@ -1470,6 +1493,34 @@ return $result; } +sub feedbackMacro_form { + my ($self, $feedbackFormURL, %params) = @_; + my $r = $self->r; + my $ce = $r->ce; + my $urlpath = $r->urlpath; + my $courseID = $urlpath->arg("courseID"); + + # feedback form url + my $feedbackName = $ce->{feedback_button_name} || "Email instructor"; + + my $result = CGI::start_form(-method=>"POST", -action=>$feedbackFormURL,-target=>"WW_info") . "\n"; + $result .= $self->hidden_authen_fields . "\n"; + + while (my ($key,... [truncated message content] |
From: Mike G. v. a. <we...@ma...> - 2009-06-26 01:07:10
|
Log Message: ----------- updating the link to the list of TimeZones Modified Files: -------------- webwork2/lib/WeBWorK: Constants.pm webwork2/lib/WeBWorK/ContentGenerator: CourseAdmin.pm Revision Data ------------- Index: Constants.pm =================================================================== RCS file: /webwork/cvs/system/webwork2/lib/WeBWorK/Constants.pm,v retrieving revision 1.58 retrieving revision 1.59 diff -Llib/WeBWorK/Constants.pm -Llib/WeBWorK/Constants.pm -u -r1.58 -r1.59 --- lib/WeBWorK/Constants.pm +++ lib/WeBWorK/Constants.pm @@ -144,7 +144,7 @@ type => 'number'}, { var => 'siteDefaults{timezone}', doc => 'Timezone for the course', - doc2 => 'Some servers handle courses taking place in different timezones. If this course is not showing the correct timezone, enter the correct value here. The format consists of unix times, such as "America/New_York","America/Chicago", "America/Denver", "America/Phoenix" or "America/Los_Angeles". Complete list: <a href="http://cpan.uwinnipeg.ca/dist/DateTime-TimeZone">TimeZoneFiles</a>', + doc2 => 'Some servers handle courses taking place in different timezones. If this course is not showing the correct timezone, enter the correct value here. The format consists of unix times, such as "America/New_York","America/Chicago", "America/Denver", "America/Phoenix" or "America/Los_Angeles". Complete list: <a href="http://en.wikipedia.org/wiki/List_of_zoneinfo_time_zones">TimeZoneFiles</a>', type => 'text'},], ['Permissions', { var => 'permissionLevels{login}', Index: CourseAdmin.pm =================================================================== RCS file: /webwork/cvs/system/webwork2/lib/WeBWorK/ContentGenerator/CourseAdmin.pm,v retrieving revision 1.84 retrieving revision 1.85 diff -Llib/WeBWorK/ContentGenerator/CourseAdmin.pm -Llib/WeBWorK/ContentGenerator/CourseAdmin.pm -u -r1.84 -r1.85 --- lib/WeBWorK/ContentGenerator/CourseAdmin.pm +++ lib/WeBWorK/ContentGenerator/CourseAdmin.pm @@ -599,9 +599,7 @@ my $add_initial_firstName = trim_spaces( $r->param("add_initial_firstName") ) || ""; my $add_initial_lastName = trim_spaces( $r->param("add_initial_lastName") ) || ""; my $add_initial_email = trim_spaces( $r->param("add_initial_email") ) || ""; - my $add_templates_course = trim_spaces( $r->param("add_templates_course") ) || ""; - my $add_dbLayout = trim_spaces( $r->param("add_dbLayout") ) || ""; |
From: Mike G. v. a. <we...@ma...> - 2009-06-26 01:06:52
|
Log Message: ----------- adding integrity_check.pl Tags: ---- rel-2-4-patches Added Files: ----------- webwork2/bin: integrity_check.pl Revision Data ------------- --- /dev/null +++ bin/integrity_check.pl @@ -0,0 +1,58 @@ +#!/usr/bin/env perl +################################################################################ +# WeBWorK Online Homework Delivery System +# Copyright © 2000-2007 The WeBWorK Project, http://openwebwork.sf.net/ +# $CVSHeader: webwork2/bin/integrity_check.pl,v 1.1.2.1 2009/06/26 00:37:51 gage Exp $ +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of either: (a) the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version, or (b) the "Artistic License" which comes with this package. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the +# Artistic License for more details. +################################################################################ + +use strict; +use warnings; +use Getopt::Std; +use Data::Dumper; + +BEGIN { + die "WEBWORK_ROOT not found in environment.\n" + unless exists $ENV{WEBWORK_ROOT}; +} + +use lib "$ENV{WEBWORK_ROOT}/lib"; +use WeBWorK::CourseEnvironment; +use WeBWorK::Utils::CourseIntegrityCheck; +use WeBWorK; + +our ($opt_v); +getopts("v"); + +if ($opt_v) { + $WeBWorK::Debug::Enabled = 1; +} else { + $WeBWorK::Debug::Enabled = 0; +} + + +my $courseName = "tmp_course"; + +my $ce = new WeBWorK::CourseEnvironment( + {webwork_dir=>$ENV{WEBWORK_ROOT}, + courseName=> $courseName + }); + + +print "ce ready $ce"; + +my $CIchecker = new WeBWorK::Utils::CourseIntegrityCheck($ce); + +my $return = $CIchecker->checkCourseDirectories(); + +print "result $return"; +1; \ No newline at end of file |
From: Mike G. v. a. <we...@ma...> - 2009-06-26 01:06:26
|
Log Message: ----------- adding source.pl to standard macros collection Tags: ---- rel-2-4-patches Added Files: ----------- pg/macros: source.pl Revision Data ------------- --- /dev/null +++ macros/source.pl @@ -0,0 +1,18 @@ +if ($displayMode =~ m/HTML/ && !defined($_slides_loaded)) { + TEXT( + '<DIV ID="source_button" STYLE="float:right; margin-right:2em">' + . '<SCRIPT>function showSource () {' + . ' window.open("'.$htmlURL."show-source.cgi/$probFileName".'","ww_source");' + . '}</SCRIPT>' + . '<INPUT TYPE="button" VALUE="Show Problem Source" ONCLICK="showSource()">' + .'</DIV>' + ); +} + +sub NoSourceButton { +# if ($displayMode =~ m/HTML/) { +# TEXT('<SCRIPT>document.getElementById("source_button").style.display = "none"</SCRIPT>'); +# } +} + +1; |
From: Mike G. v. a. <we...@ma...> - 2009-06-26 01:05:58
|
Log Message: ----------- changing version number Modified Files: -------------- webwork2/lib: WeBWorK.pm Revision Data ------------- Index: WeBWorK.pm =================================================================== RCS file: /webwork/cvs/system/webwork2/lib/WeBWorK.pm,v retrieving revision 1.101 retrieving revision 1.102 diff -Llib/WeBWorK.pm -Llib/WeBWorK.pm -u -r1.101 -r1.102 --- lib/WeBWorK.pm +++ lib/WeBWorK.pm @@ -34,7 +34,7 @@ =cut -BEGIN { $main::VERSION = "2.4.5"; } +BEGIN { $main::VERSION = "2.4.7"; } use strict; use warnings; |
From: Mike G. v. a. <we...@ma...> - 2009-06-25 23:49:25
|
Log Message: ----------- update copyright. Tags: ---- rel-2-4-patches Modified Files: -------------- pg/macros: PGbasicmacros.pl Revision Data ------------- Index: PGbasicmacros.pl =================================================================== RCS file: /webwork/cvs/system/pg/macros/PGbasicmacros.pl,v retrieving revision 1.47.2.2.2.7 retrieving revision 1.47.2.2.2.8 diff -Lmacros/PGbasicmacros.pl -Lmacros/PGbasicmacros.pl -u -r1.47.2.2.2.7 -r1.47.2.2.2.8 --- macros/PGbasicmacros.pl +++ macros/PGbasicmacros.pl @@ -1,9 +1,18 @@ - - -#################################################################### -# Copyright @ 1995-1998 University of Rochester -# All Rights Reserved -#################################################################### +################################################################################ +# WeBWorK Program Generation Language +# Copyright 2000-2007 The WeBWorK Project, http://openwebwork.sf.net/ +# $CVSHeader$ +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of either: (a) the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version, or (b) the "Artistic License" which comes with this package. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the +# Artistic License for more details. +################################################################################ =head1 NAME |
From: Mike G. v. a. <we...@ma...> - 2009-06-25 23:48:39
|
Log Message: ----------- add copyright to PGbasicmacros.pl Modified Files: -------------- pg/macros: PGbasicmacros.pl Revision Data ------------- Index: PGbasicmacros.pl =================================================================== RCS file: /webwork/cvs/system/pg/macros/PGbasicmacros.pl,v retrieving revision 1.59 retrieving revision 1.60 diff -Lmacros/PGbasicmacros.pl -Lmacros/PGbasicmacros.pl -u -r1.59 -r1.60 --- macros/PGbasicmacros.pl +++ macros/PGbasicmacros.pl @@ -1,10 +1,20 @@ +################################################################################ +# WeBWorK Program Generation Language +# Copyright 2000-2007 The WeBWorK Project, http://openwebwork.sf.net/ +# $CVSHeader$ +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of either: (a) the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version, or (b) the "Artistic License" which comes with this package. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the +# Artistic License for more details. +################################################################################ -#################################################################### -# Copyright @ 1995-1998 University of Rochester -# All Rights Reserved -#################################################################### - =head1 NAME PGbasicmacros.pl --- located in the courseScripts directory |
From: Mike G. v. a. <we...@ma...> - 2009-06-25 23:42:41
|
Log Message: ----------- syncing pg HEAD with pg2.4.7 on 6/25/2009 Modified Files: -------------- pg: LICENSE README pg/lib: AnswerHash.pm Applet.pm Complex1.pm pg/lib/Parser/Legacy: PGcomplexmacros.pl pg/lib/Value: Formula.pm pg/lib/WeBWorK: EquationCache.pm pg/lib/WeBWorK/PG: IO.pm ImageGenerator.pm Translator.pm pg/lib/WeBWorK/PG/IO: Daemon2.pm WW1.pm WW2.pm pg/macros: AppletObjects.pl IO.pl LinearProgramming.pl MathObjects.pl PG.pl PGanswermacros.pl PGchoicemacros.pl PGcomplexmacros.pl PGfunctionevaluators.pl PGmiscevaluators.pl PGnumericevaluators.pl PGstringevaluators.pl PGtextevaluators.pl Parser.pl answerComposition.pl answerCustom.pl answerHints.pl answerVariableList.pl contextABCD.pl contextCurrency.pl contextInequalities.pl contextIntegerFunctions.pl contextLimitedComplex.pl contextLimitedNumeric.pl contextLimitedPoint.pl contextLimitedPolynomial.pl contextLimitedPowers.pl contextLimitedVector.pl contextPeriodic.pl contextPiecewiseFunction.pl contextScientificNotation.pl contextString.pl contextTF.pl dangerousMacros.pl displayMacros.pl extraAnswerEvaluators.pl parserAssignment.pl parserAutoStrings.pl parserCustomization.pl parserDifferenceQuotient.pl parserFormulaUpToConstant.pl parserFormulaWithUnits.pl parserFunction.pl parserImplicitEquation.pl parserImplicitPlane.pl parserMultiAnswer.pl parserMultiPart.pl parserNumberWithUnits.pl parserParametricLine.pl parserPopUp.pl parserRadioButtons.pl parserSolutionFor.pl parserVectorUtils.pl problemPanic.pl problemPreserveAnswers.pl problemRandomize.pl Revision Data ------------- Index: Formula.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/Value/Formula.pm,v retrieving revision 1.64 retrieving revision 1.65 diff -Llib/Value/Formula.pm -Llib/Value/Formula.pm -u -r1.64 -r1.65 --- lib/Value/Formula.pm +++ lib/Value/Formula.pm @@ -476,27 +476,27 @@ $M = $M->decompose_LR; if (abs($M->det_LR) > 1E-6) { if (($D,$B,$M) = $M->solve_LR($B)) { - if ($D == 0) { - # - # Get parameter values and recompute the points using them - # - my @a; my $i = 0; my $max = $l->getFlag('max_adapt',1E8); - foreach my $row (@{$B->[0]}) { - if (abs($row->[0]) > $max) { - $max = Value::makeValue($max); $row->[0] = Value::makeValue($row->[0]); - $l->Error(["Constant of integration is too large: %s\n(maximum allowed is %s)", - $row->[0]->string,$max->string]) if $params[$i] eq 'C0' or $params[$i] eq 'n00'; - $l->Error(["Adaptive constant is too large: %s = %s\n(maximum allowed is %s)", - $params[$i],$row->[0]->string,$max->string]); - } - push @a, $row->[0]; $i++; - } - my $context = $l->context; - foreach my $i (0..$#a) {$context->{variables}{$params[$i]}{value} = $a[$i]} - $l->{parameters} = [@a]; - $l->createAdaptedValues; - return 1; - } + if ($D == 0) { + # + # Get parameter values and recompute the points using them + # + my @a; my $i = 0; my $max = $l->getFlag('max_adapt',1E8); + foreach my $row (@{$B->[0]}) { + if (abs($row->[0]) > $max) { + $max = Value::makeValue($max); $row->[0] = Value::makeValue($row->[0]); + $l->Error(["Constant of integration is too large: %s\n(maximum allowed is %s)", + $row->[0]->string,$max->string]) if $params[$i] eq 'C0' or $params[$i] eq 'n00'; + $l->Error(["Adaptive constant is too large: %s = %s\n(maximum allowed is %s)", + $params[$i],$row->[0]->string,$max->string]); + } + push @a, $row->[0]; $i++; + } + my $context = $l->context; + foreach my $i (0..$#a) {$context->{variables}{$params[$i]}{value} = $a[$i]} + $l->{parameters} = [@a]; + $l->createAdaptedValues; + return 1; + } } } } Index: Translator.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/WeBWorK/PG/Translator.pm,v retrieving revision 1.23 retrieving revision 1.24 diff -Llib/WeBWorK/PG/Translator.pm -Llib/WeBWorK/PG/Translator.pm -u -r1.23 -r1.24 --- lib/WeBWorK/PG/Translator.pm +++ lib/WeBWorK/PG/Translator.pm @@ -1620,17 +1620,8 @@ } -#sub original_preprocess_code { -# my $evalString = shift; -# # BEGIN_TEXT and END_TEXT must occur on a line by themselves. -# $evalString =~ s/\n\s*END_TEXT[\s;]*\n/\nEND_TEXT\n/g; -# $evalString =~ s/\n\s*BEGIN_TEXT[\s;]*\n/\nTEXT\(EV3\(<<'END_TEXT'\)\);\n/g; -# $evalString =~ s/ENDDOCUMENT.*/ENDDOCUMENT();/s; # remove text after ENDDOCUMENT - -# $evalString =~ s/\\/\\\\/g; # \ can't be used for escapes because of TeX conflict -# $evalString =~ s/~~/\\/g; # use ~~ as escape instead, use # for comments -# $evalString; -#} + + sub default_preprocess_code { my $evalString = shift; # BEGIN_TEXT and END_TEXT must occur on a line by themselves. Index: README =================================================================== RCS file: /webwork/cvs/system/pg/README,v retrieving revision 1.3 retrieving revision 1.4 diff -LREADME -LREADME -u -r1.3 -r1.4 --- README +++ README @@ -4,7 +4,7 @@ Branch: rel-2-4-patche - http://webwork.maa.org/wiki/Release_notes_for_WeBWorK_2.4.5 + http://webwork.maa.org/wiki/Release_notes_for_WeBWorK_2.4.7 - Copyright 2000-2006, The WeBWorK Project + Copyright 2000-2009, The WeBWorK Project All rights reserved. Index: LICENSE =================================================================== RCS file: /webwork/cvs/system/pg/LICENSE,v retrieving revision 1.2 retrieving revision 1.3 diff -LLICENSE -LLICENSE -u -r1.2 -r1.3 --- LICENSE +++ LICENSE @@ -1,8 +1,8 @@ WeBWorK - Program Generation Language - Version 2.x + Online Homework Delivery System + Version 2.4.x - Copyright 2000-2006, The WeBWorK Project + Copyright 2000-2009, The WeBWorK Project All rights reserved. This program is free software; you can redistribute it and/or modify Index: PGcomplexmacros.pl =================================================================== RCS file: /webwork/cvs/system/pg/macros/PGcomplexmacros.pl,v retrieving revision 1.13 retrieving revision 1.14 diff -Lmacros/PGcomplexmacros.pl -Lmacros/PGcomplexmacros.pl -u -r1.13 -r1.14 --- macros/PGcomplexmacros.pl +++ macros/PGcomplexmacros.pl @@ -8,6 +8,7 @@ #$Id$ + =head1 NAME Macros for complex numbers for the PG language |
From: Mike G. v. a. <we...@ma...> - 2009-06-25 23:41:20
|
Log Message: ----------- Syncing pg2.4.7 with pg HEAD on 6/25/2009 Tags: ---- rel-2-4-patches Modified Files: -------------- pg: LICENSE README pg/lib: Applet.pm pg/lib/Parser: Function.pm pg/lib/Parser/Legacy: LimitedNumeric.pm pg/lib/Value: AnswerChecker.pm Complex.pm Context.pm Formula.pm Union.pm pg/lib/WeBWorK/PG: Translator.pm pg/macros: AppletObjects.pl PGbasicmacros.pl contextInequalities.pl contextLimitedPolynomial.pl contextPiecewiseFunction.pl contextString.pl dangerousMacros.pl parserFormulaUpToConstant.pl parserImplicitEquation.pl parserPopUp.pl parserRadioButtons.pl Revision Data ------------- Index: README =================================================================== RCS file: /webwork/cvs/system/pg/README,v retrieving revision 1.2.4.2.2.1 retrieving revision 1.2.4.2.2.2 diff -LREADME -LREADME -u -r1.2.4.2.2.1 -r1.2.4.2.2.2 --- README +++ README @@ -1,6 +1,10 @@ WeBWorK Program Generation Language - Version 2.4.5 - - Copyright 2000-2007, The WeBWorK Project + Version 2.4.x + Branch: rel-2-4-patche + + + http://webwork.maa.org/wiki/Release_notes_for_WeBWorK_2.4.7 + + Copyright 2000-2009, The WeBWorK Project All rights reserved. Index: LICENSE =================================================================== RCS file: /webwork/cvs/system/pg/LICENSE,v retrieving revision 1.2.4.2.2.1 retrieving revision 1.2.4.2.2.2 diff -LLICENSE -LLICENSE -u -r1.2.4.2.2.1 -r1.2.4.2.2.2 --- LICENSE +++ LICENSE @@ -1,8 +1,8 @@ WeBWorK - Program Generation Language - Version 2.4.5 + Online Homework Delivery System + Version 2.4.7 - Copyright 2000-2008, The WeBWorK Project + Copyright 2000-2009, The WeBWorK Project All rights reserved. This program is free software; you can redistribute it and/or modify Index: Function.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/Parser/Function.pm,v retrieving revision 1.18.6.2.2.1 retrieving revision 1.18.6.2.2.2 diff -Llib/Parser/Function.pm -Llib/Parser/Function.pm -u -r1.18.6.2.2.1 -r1.18.6.2.2.2 --- lib/Parser/Function.pm +++ lib/Parser/Function.pm @@ -256,7 +256,9 @@ foreach my $x (@{$self->{params}}) {push(@pstr,$x->string)} $string = ($self->{def}{string} || $self->{name})."$power".'('.join(',',@pstr).')'; $string = $self->addParens($string) - if (defined($precedence) and $precedence > $fn_precedence) || $showparens; + if $showparens eq 'all' or $showparens eq 'extra' or + (defined($precedence) and $precedence > $fn_precedence) or + (defined($precedence) and $precedence == $fn_precedence and $showparens eq 'same'); return $string; } @@ -275,7 +277,9 @@ if ($fn->{braceTeX}) {$TeX = $name.'{'.join(',',@pstr).'}'} else {$TeX = $name."$power".'\!\left('.join(',',@pstr).'\right)'} $TeX = '\left('.$TeX.'\right)' - if (defined($precedence) and $precedence > $fn_precedence) or $showparens; + if $showparens eq 'all' or $showparens eq 'extra' or + (defined($precedence) and $precedence > $fn_precedence) or + (defined($precedence) and $precedence == $fn_precedence and $showparens eq 'same'); return $TeX; } Index: Complex.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/Value/Complex.pm,v retrieving revision 1.18.6.2.2.1 retrieving revision 1.18.6.2.2.2 diff -Llib/Value/Complex.pm -Llib/Value/Complex.pm -u -r1.18.6.2.2.1 -r1.18.6.2.2.2 --- lib/Value/Complex.pm +++ lib/Value/Complex.pm @@ -336,8 +336,7 @@ $a->{format} = $b->{format} = $format if defined $format; my $bi = 'i'; return $a->$method($equation) if $b == 0; - $bi = CORE::abs($b)->with(format=>$format)->$method($equation,1) . 'i' - if CORE::abs($b) !~ m/^1(\.0*)?$/; + $bi = CORE::abs($b)->with(format=>$format)->$method($equation,1) . 'i' if CORE::abs($b) !~ m/^1(\.0*)?$/; $bi = '-' . $bi if $b < 0; return $bi if $a == 0; $bi = '+' . $bi if $b > 0; Index: AnswerChecker.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/Value/AnswerChecker.pm,v retrieving revision 1.91.2.2.2.4 retrieving revision 1.91.2.2.2.5 diff -Llib/Value/AnswerChecker.pm -Llib/Value/AnswerChecker.pm -u -r1.91.2.2.2.4 -r1.91.2.2.2.5 --- lib/Value/AnswerChecker.pm +++ lib/Value/AnswerChecker.pm @@ -30,9 +30,9 @@ # Internal use. # Set default flags for the answer checker in this object -# showTypeWarnings => 1 -# showEqualErrors => 1 -# ignoreStrings => 1 +# showTypeWarnings => 1 +# showEqualErrors => 1 +# ignoreStrings => 1 # studentsMustReduceUnions => 1 # showUnionReduceWarnings => 1 # @@ -241,6 +241,7 @@ return eval {$self == $other} unless ref($ans->{checker}) eq 'CODE'; my @equal = eval {&{$ans->{checker}}($self,$other,$ans,$nth,@_)}; if (!defined($equal) && $@ ne '' && (!$context->{error}{flag} || $ans->{showAllErrors})) { + $nth = "" if ref($nth) eq 'AnswerHash'; $context->setError(["<I>An error occurred while checking your$nth answer:</I>\n". '<DIV STYLE="margin-left:1em">%s</DIV>',$@],'',undef,undef,$CMP_ERROR); warn "Please inform your instructor that an error occurred while checking your answer"; @@ -415,12 +416,13 @@ $self->{format_options} = [%options] unless $self->{format_options}; my ($open,$close,$sep) = ($options{open},$options{close},$options{sep}); my ($rows,$cols) = (scalar(@{$array}),scalar(@{$array->[0]})); - my $tex = ""; + my $tex = ""; my @rows = (); $open = '\\'.$open if $open =~ m/[{}]/; $close = '\\'.$close if $close =~ m/[{}]/; $tex .= '\(\left'.$open; $tex .= '\setlength{\arraycolsep}{2pt}', $sep = '\,'.$sep if $sep; $tex .= '\begin{array}{'.('c'x$cols).'}'; - foreach my $i (0..$rows-1) {$tex .= join($sep.'&',@{$array->[$i]}).'\cr'."\n"} + foreach my $i (0..$rows-1) {push(@rows,join($sep.'&',@{$array->[$i]}))} + $tex .= join('\cr'."\n",@rows); $tex .= '\end{array}\right'.$close.'\)'; return $tex; } @@ -1646,6 +1648,7 @@ my $self = shift; my $ans = shift; $ans->{_filter_name} = "produce_equivalence_message"; return $ans if $ans->{ans_message}; # don't overwrite other messages + return $ans unless defined($ans->{prev_ans}); # if prefilters are erased, don't do this check my $context = $self->context; $ans->{prev_formula} = Parser::Formula($context,$ans->{prev_ans}); if (defined($ans->{prev_formula}) && defined($ans->{student_formula})) { @@ -1784,7 +1787,7 @@ my @P = (map {(scalar(@{$_}) == 1)? $_->[0]: $self->Package("Point")->make(@{$_})} @{$self->{test_points}}); my @i = sort {$P[$a] <=> $P[$b]} (0..$#P); foreach $p (@P) {if (Value::isValue($p) && $p->length > 2) {$p = $p->string; $p =~ s|,|,<br />|g}} - my $zeroLevelTol = $self->getFlag('zeroLevelTol'); + my $zeroLevelTol = $self->{context}{flags}{zeroLevelTol}; $self->{context}{flags}{zeroLevelTol} = 0; # always show full resolution in the tables below my $names = join(',',@names); $names = '('.$names.')' if scalar(@names) > 1; Index: Formula.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/Value/Formula.pm,v retrieving revision 1.43.2.2.2.2 retrieving revision 1.43.2.2.2.3 diff -Llib/Value/Formula.pm -Llib/Value/Formula.pm -u -r1.43.2.2.2.2 -r1.43.2.2.2.3 --- lib/Value/Formula.pm +++ lib/Value/Formula.pm @@ -188,7 +188,7 @@ # situations where this is a problem. # if ($l->AdaptParameters($r,$self->{context}->variables->parameters)) { - my $avalues = $l->{test_adapt}; + my $avalues = $l->{test_adapt}; my $tolerance = $self->getFlag('tolerance',1E-4); my $isRelative = $self->getFlag('tolType','relative') eq 'relative'; my $zeroLevel = $self->getFlag('zeroLevel',1E-14); @@ -220,6 +220,17 @@ } # +# Inherit should make sure the tree is copied +# (so it's nodes point to the correct equation, for one thing) +# +sub inherit { + my $self = shift; + $self = $self->SUPER::inherit(@_); + $self->{tree} = $self->{tree}->copy($self); + return $self; +} + +# # Don't inherit test values or adapted values, or other temporary items # sub noinherit { @@ -446,10 +457,15 @@ foreach my $i (0..$d-1) { my @a = (); my @p = @{$p->[$i]}; foreach my $j (0..$d-1) { - $P[$j] = 1; push(@a,(&$f(@p,@P)-$v->[$i])->value); - $P[$j] = 0; + $P[$j] = 1; + my $y = eval {&$f(@p,@P)}; + $l->Error(["Can't evaluate correct answer at adapted point (%s)",join(",",@$p,@P)]) + unless defined $y; + push(@a,($y-$v->[$i])->value); + $P[$j] = 0; } - push @A, [@a]; push @b, [(&$F(@p,@P)-$v->[$i])->value]; + my $y = eval {&$F(@p,@P)}; return unless defined $y; + push @A, [@a]; push @b, [($y-$v->[$i])->value]; } # # Use MatrixReal1.pm to solve system of linear equations @@ -458,27 +474,29 @@ my $B = MatrixReal1->new($d,1); $B->[0] = \@b; ($M,$B) = $M->normalize($B); $M = $M->decompose_LR; - if (($D,$B,$M) = $M->solve_LR($B)) { - if ($D == 0) { - # - # Get parameter values and recompute the points using them - # - my @a; my $i = 0; my $max = $l->getFlag('max_adapt',1E8); - foreach my $row (@{$B->[0]}) { - if (abs($row->[0]) > $max) { - $max = Value::makeValue($max); $row->[0] = Value::makeValue($row->[0]); - $l->Error(["Constant of integration is too large: %s\n(maximum allowed is %s)", - $row->[0]->string,$max->string]) if $params[$i] eq 'C0' or $params[$i] eq 'n00'; - $l->Error(["Adaptive constant is too large: %s = %s\n(maximum allowed is %s)", - $params[$i],$row->[0]->string,$max->string]); - } - push @a, $row->[0]; $i++; - } - my $context = $l->context; - foreach my $i (0..$#a) {$context->{variables}{$params[$i]}{value} = $a[$i]} - $l->{parameters} = [@a]; - $l->createAdaptedValues; - return 1; + if (abs($M->det_LR) > 1E-6) { + if (($D,$B,$M) = $M->solve_LR($B)) { + if ($D == 0) { + # + # Get parameter values and recompute the points using them + # + my @a; my $i = 0; my $max = $l->getFlag('max_adapt',1E8); + foreach my $row (@{$B->[0]}) { + if (abs($row->[0]) > $max) { + $max = Value::makeValue($max); $row->[0] = Value::makeValue($row->[0]); + $l->Error(["Constant of integration is too large: %s\n(maximum allowed is %s)", + $row->[0]->string,$max->string]) if $params[$i] eq 'C0' or $params[$i] eq 'n00'; + $l->Error(["Adaptive constant is too large: %s = %s\n(maximum allowed is %s)", + $params[$i],$row->[0]->string,$max->string]); + } + push @a, $row->[0]; $i++; + } + my $context = $l->context; + foreach my $i (0..$#a) {$context->{variables}{$params[$i]}{value} = $a[$i]} + $l->{parameters} = [@a]; + $l->createAdaptedValues; + return 1; + } } } } Index: Context.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/Value/Context.pm,v retrieving revision 1.10.6.2.2.2 retrieving revision 1.10.6.2.2.3 diff -Llib/Value/Context.pm -Llib/Value/Context.pm -u -r1.10.6.2.2.2 -r1.10.6.2.2.3 --- lib/Value/Context.pm +++ lib/Value/Context.pm @@ -142,8 +142,8 @@ while ($message && $error->{msg}{$message}) {$message = $error->{msg}{$message}} while ($more && $error->{msg}{$more}) {$more = $error->{msg}{$more}} $message = sprintf($message,@args) if scalar(@args) > 0; - $message .= sprintf($more,$pos->[0]+1) if $more; while ($message && $error->{msg}{$message}) {$message = $error->{msg}{$message}} + $message .= sprintf($more,$pos->[0]+1) if $more; $message = &{$error->{convert}}($message) if defined $error->{convert}; $error->{message} = $message; $error->{string} = $string; Index: Union.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/Value/Union.pm,v retrieving revision 1.24.6.2.2.1 retrieving revision 1.24.6.2.2.2 diff -Llib/Value/Union.pm -Llib/Value/Union.pm -u -r1.24.6.2.2.1 -r1.24.6.2.2.2 --- lib/Value/Union.pm +++ lib/Value/Union.pm @@ -3,7 +3,6 @@ package Value::Union; my $pkg = 'Value::Union'; - qw(Value); use strict; no strict "refs"; our @ISA = qw(Value); Index: LimitedNumeric.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/Parser/Legacy/LimitedNumeric.pm,v retrieving revision 1.3.6.1 retrieving revision 1.3.6.1.2.1 diff -Llib/Parser/Legacy/LimitedNumeric.pm -Llib/Parser/Legacy/LimitedNumeric.pm -u -r1.3.6.1 -r1.3.6.1.2.1 --- lib/Parser/Legacy/LimitedNumeric.pm +++ lib/Parser/Legacy/LimitedNumeric.pm @@ -120,7 +120,7 @@ $context = $context->copy; $Parser::Context::Default::context{'LimitedNumeric-StrictFraction'} = $context; Parser::Number::NoDecimals($context); -$context->{name} = "LimitedNumeric-StrictFractions"; +$context->{name} = "LimitedNumeric-StrictFraction"; ###################################################################### Index: Applet.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/Applet.pm,v retrieving revision 1.12.2.1.2.2 retrieving revision 1.12.2.1.2.3 diff -Llib/Applet.pm -Llib/Applet.pm -u -r1.12.2.1.2.2 -r1.12.2.1.2.3 --- lib/Applet.pm +++ lib/Applet.pm @@ -127,8 +127,7 @@ ---------------------------------------------------------------------------- - List of accessor methods made available by the (perl) FlashApplet class. - They are also the names of the instance variables in + List of accessor methods made available by the FlashApplet class: Usage: $current_value = $applet->method(new_value or empty) These can also be set when creating the class -- for exampe: $applet = new FlashApplet( @@ -200,7 +199,13 @@ returnFieldName -- (deprecated) synonmym for answerBoxAlias - debugMode (default: 0) in debug mode several alerts mark progress through the procedure of calling the applet + debugMode (default: 0) for debugMode==1 the answerBox and the box preserving the applet state + between questions are made visible along with some buttons for manually getting the state of + the applet and setting the state of the applet. + + for debugMode==2, in addition to the answerBox and stateBox there are several alerts + which mark progress through the procedures of calling the applet. Useful for troubleshooting + where in the chain of command a communication failure occurs Methods: Index: Translator.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/WeBWorK/PG/Translator.pm,v retrieving revision 1.18.2.2.2.3 retrieving revision 1.18.2.2.2.4 diff -Llib/WeBWorK/PG/Translator.pm -Llib/WeBWorK/PG/Translator.pm -u -r1.18.2.2.2.3 -r1.18.2.2.2.4 --- lib/WeBWorK/PG/Translator.pm +++ lib/WeBWorK/PG/Translator.pm @@ -908,7 +908,6 @@ =cut - my ($PG_PROBLEM_TEXT_REF, $PG_HEADER_TEXT_REF, $PG_ANSWER_HASH_REF, $PG_FLAGS_REF) =$safe_cmpt->reval(" $evalString"); @@ -1621,26 +1620,17 @@ } -sub original_preprocess_code { - my $evalString = shift; - # BEGIN_TEXT and END_TEXT must occur on a line by themselves. - $evalString =~ s/\n\s*END_TEXT[\s;]*\n/\nEND_TEXT\n/g; - $evalString =~ s/\n\s*BEGIN_TEXT[\s;]*\n/\nTEXT\(EV3\(<<'END_TEXT'\)\);\n/g; - $evalString =~ s/ENDDOCUMENT.*/ENDDOCUMENT();/s; # remove text after ENDDOCUMENT - $evalString =~ s/\\/\\\\/g; # \ can't be used for escapes because of TeX conflict - $evalString =~ s/~~/\\/g; # use ~~ as escape instead, use # for comments - $evalString; -} + sub default_preprocess_code { my $evalString = shift; # BEGIN_TEXT and END_TEXT must occur on a line by themselves. - $evalString =~ s/\n\s*END_TEXT[\s;]*\n/\nEND_TEXT\n/g; - $evalString =~ s/\n\s*END_SOLUTION[\s;]*\n/\nEND_SOLUTION\n/g; - $evalString =~ s/\n\s*END_HINT[\s;]*\n/\nEND_HINT\n/g; - $evalString =~ s/\n\s*BEGIN_TEXT[\s;]*\n/\nTEXT\(EV3P\(<<'END_TEXT'\)\);\n/g; - $evalString =~ s/\n\s*BEGIN_SOLUTION[\s;]*\n/\nSOLUTION\(EV3P\(<<'END_SOLUTION'\)\);\n/g; - $evalString =~ s/\n\s*BEGIN_HINT[\s;]*\n/\nHINT\(EV3P\(<<'END_HINT'\)\);\n/g; + $evalString =~ s/^[ \t]*END_TEXT[ \t;]*$/END_TEXT/gm; + $evalString =~ s/^[ \t]*END_SOLUTION[ \t;]*$/END_SOLUTION/mg; + $evalString =~ s/^[ \t]*END_HINT[ \t;]*$/END_HINT/mg; + $evalString =~ s/^[ \t]*BEGIN_TEXT[ \t;]*$/TEXT\(EV3P\(<<'END_TEXT'\)\);/mg; + $evalString =~ s/^[ \t]*BEGIN_SOLUTION[ \t;]*$/SOLUTION\(EV3P\(<<'END_SOLUTION'\)\);/mg; + $evalString =~ s/^[ \t]*BEGIN_HINT[ \t;]*$/HINT\(EV3P\(<<'END_HINT'\)\);/mg; $evalString =~ s/ENDDOCUMENT.*/ENDDOCUMENT();/s; # remove text after ENDDOCUMENT $evalString =~ s/\\/\\\\/g; # \ can't be used for escapes because of TeX conflict Index: parserRadioButtons.pl =================================================================== RCS file: /webwork/cvs/system/pg/macros/parserRadioButtons.pl,v retrieving revision 1.2.2.2.2.1 retrieving revision 1.2.2.2.2.2 diff -Lmacros/parserRadioButtons.pl -Lmacros/parserRadioButtons.pl -u -r1.2.2.2.2.1 -r1.2.2.2.2.2 --- macros/parserRadioButtons.pl +++ macros/parserRadioButtons.pl @@ -2,12 +2,12 @@ # WeBWorK Online Homework Delivery System # Copyright © 2000-2007 The WeBWorK Project, http://openwebwork.sf.net/ # $CVSHeader$ -# +# # This program is free software; you can redistribute it and/or modify it under # the terms of either: (a) the GNU General Public License as published by the # Free Software Foundation; either version 2, or (at your option) any later # version, or (b) the "Artistic License" which comes with this package. -# +# # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the @@ -158,7 +158,7 @@ Value::Error("A RadioButton's second argument should be the correct button choice") unless defined($value) && $value ne ""; my $context = Parser::Context->getCopy("String"); - my %choiceHash = $self->choiceHash(1); + my %choiceHash = $self->choiceHash; $context->strings->add(map {$_=>{}} (keys %choiceHash)); $value = $self->correctChoice($value); $self = bless $context->Package("String")->new($context,$value)->with(choices => $choices, %options), $class; @@ -166,18 +166,19 @@ return $self; } -# +# # Given a choice, a label, or an index into the choices array, # return the label. -# +# sub findChoice { my $self = shift; my $value = shift; my $index = $self->Index($value); foreach my $i (0..scalar(@{$self->{choices}})-1) { my $label = $self->{labels}[$i]; my $choice = $self->{choices}[$i]; - $label = $self->makeLabel($choice) unless defined $label; + $label = $choice unless defined $label; return $label if $label eq $value || $index == $i || $choice eq $value; } + return undef; } # @@ -197,15 +198,10 @@ # ans_radio_buttons() routine # sub choiceHash { - my $self = shift; my $noChecked = shift; - my @radio = (); - my $index = $self->Index($self->{checked}); - my $checked = $self->{checked}; $checked = "" unless defined $checked; - if ($noChecked) {$checked = ""; $index = -1} + my $self = shift; my @radio = (); my %labels; foreach my $i (0..scalar(@{$self->{choices}})-1) { my $label = $self->{labels}[$i]; my $choice = $self->{choices}[$i]; - $label = $self->makeLabel($choice) unless defined $label; - $label = "%$label" if $label eq $checked || $index == $i || $choice eq $checked; + $label = $choice unless defined $label; push(@radio, $label,$choice); } return @radio; @@ -213,15 +209,14 @@ # # Create a label for the answer, either using the labels -# provided by the user, or by creating one from the answer +# provided by the author, or by creating one from the answer # string (restrict its length so that the results table # will not be overflowed). # -sub makeLabel { +sub labelText { my $self = shift; my $choice = shift; return $choice if length($choice) < $self->{maxLabelSize}; - my @words = split(/\b/,$choice); - my ($s,$e) = ('',''); + my @words = split(/\b/,$choice); my ($s,$e) = ('',''); do {$s .= shift(@words); $e = pop(@words) . $e} while length($s) + length($e) + 15 < $self->{maxLabelSize} && scalar(@words); return $s . " ... " . $e; @@ -240,8 +235,7 @@ # Print the JavaScript needed for uncheckable radio buttons # sub JavaScript { - return if $main::displayMode eq 'TeX'; - return if $jsPrinted; + return if $jsPrinted || $main::displayMode eq 'TeX'; main::TEXT( "\n<script>\n" . "if (window.ww == null) {var ww = {}}\n" . @@ -263,7 +257,7 @@ "}\n". "</script>\n" ); - $jsSPrinted = 1; + $jsPrinted = 1; } sub makeUncheckable { @@ -275,20 +269,20 @@ return @radio; } -# +# # Determine the order the choices should be in. -# +# sub orderedChoices { my $self = shift; my %choiceHash = $self->choiceHash; my @labels = keys %choiceHash; - - my @order = @{$self->{order}}; - my @first = @{$self->{first}}; - my @last = @{$self->{last}}; - + + my @order = @{$self->{order} || []}; + my @first = @{$self->{first} || []}; + my @last = @{$self->{last} || []}; + my @orderLabels; - + if (@order) { my %remainingChoices = %choiceHash; Value::Error("When using the 'order' option, you must list all possible choices.") @@ -306,7 +300,7 @@ my @firstLabels; my @lastLabels; my %remainingChoices = %choiceHash; - + foreach my $i (0..$#first) { my $label = $self->findChoice($first[$i]); Value::Error("Item $i of the 'first' option is not a choice.") @@ -316,7 +310,7 @@ push @firstLabels, $label; delete $remainingChoices{$label}; } - + foreach my $i (0..$#last) { my $label = $self->findChoice($last[$i]); Value::Error("Item $i of the 'last' option is not a choice.") @@ -334,8 +328,9 @@ # might we want to explicitly randomize these? @orderLabels = @labels; } - - return map { $_ => $choiceHash{$_} } @orderLabels; + + my $label = $self->findChoice($self->{checked}); + return map { ($_ eq $label ? "%$_" : $_) => $choiceHash{$_} } @orderLabels; } # @@ -366,4 +361,11 @@ sub ans_rule {shift->buttons(@_)} sub named_ans_rule {shift->named_buttons(@_)} +sub cmp_postprocess { + my $self = shift; my $ans = shift; + my $text = $self->labelText($ans->{student_value}->value); + $ans->{preview_text_string} = $ans->{student_ans} = $text; + $ans->{preview_latex_string} = "\\hbox{$text}"; +} + 1; Index: parserImplicitEquation.pl =================================================================== RCS file: /webwork/cvs/system/pg/macros/parserImplicitEquation.pl,v retrieving revision 1.2.2.2.2.1 retrieving revision 1.2.2.2.2.2 diff -Lmacros/parserImplicitEquation.pl -Lmacros/parserImplicitEquation.pl -u -r1.2.2.2.2.1 -r1.2.2.2.2.2 --- macros/parserImplicitEquation.pl +++ macros/parserImplicitEquation.pl @@ -214,12 +214,12 @@ my $self = shift; my $class = ref($self) || $self; my $context = (Value::isContext($_[0]) ? shift : $self->context); my $f = shift; return $f if ref($f) eq $class; - $f = main::Formula($f); + $f = $context->Package("Formula")->new($context,$f); Value::Error("Your formula doesn't look like an implicit equation") unless $f->type eq 'Equality'; my $F = ($context->Package("Formula")->new($context,$f->{tree}{lop}) - $context->Package("Formula")->new($context,$f->{tree}{rop}))->reduce; - $F = $context->Package("Formula")->new($F) unless Value::isFormula($F); + $F = $context->Package("Formula")->new($context,$F) unless Value::isFormula($F); Value::Error("Your equation must be real-valued") unless $F->isRealNumber; Value::Error("Your equation should not be constant") if $F->isConstant; Value::Error("Your equation can not contain adaptive parameters") Index: contextString.pl =================================================================== RCS file: /webwork/cvs/system/pg/macros/contextString.pl,v retrieving revision 1.4.2.2.2.1 retrieving revision 1.4.2.2.2.2 diff -Lmacros/contextString.pl -Lmacros/contextString.pl -u -r1.4.2.2.2.1 -r1.4.2.2.2.2 --- macros/contextString.pl +++ macros/contextString.pl @@ -80,6 +80,7 @@ sub Init { my $context = $main::context{String} = Parser::Context->getCopy("Numeric"); $context->{name} = "String"; + $context->{pattern}{number} = qr/^$/; $context->parens->clear(); $context->variables->clear(); $context->constants->clear(); Index: PGbasicmacros.pl =================================================================== RCS file: /webwork/cvs/system/pg/macros/PGbasicmacros.pl,v retrieving revision 1.47.2.2.2.6 retrieving revision 1.47.2.2.2.7 diff -Lmacros/PGbasicmacros.pl -Lmacros/PGbasicmacros.pl -u -r1.47.2.2.2.6 -r1.47.2.2.2.7 --- macros/PGbasicmacros.pl +++ macros/PGbasicmacros.pl @@ -998,22 +998,23 @@ my @in = @_; my $out = ''; my $permissionLevel = PG_restricted_eval(q!$main::envir{permissionLevel}!); - my $PRINT_FILE_NAMES_PERMISSION_LEVEL = PG_restricted_eval(q!$envir->{'PRINT_FILE_NAMES_PERMISSION_LEVEL'}!); + my $PRINT_FILE_NAMES_PERMISSION_LEVEL = (PG_restricted_eval(q!defined( $main::envir{'PRINT_FILE_NAMES_PERMISSION_LEVEL'} )!))? + PG_restricted_eval(q!$main::envir{'PRINT_FILE_NAMES_PERMISSION_LEVEL'}!) : 10000; # protect against undefined values my $printHintForInstructor = $permissionLevel >= $PRINT_FILE_NAMES_PERMISSION_LEVEL; my $showHint = PG_restricted_eval(q!$main::showHint!); - my $displayHint = PG_restricted_eval(q!$envir->{'displayHintsQ'}!); + my $displayHint = PG_restricted_eval(q!$main::envir{'displayHintsQ'}!); PG_restricted_eval(q!$main::hintExists =1!); PG_restricted_eval(q!$main::numOfAttempts = 0 unless defined($main::numOfAttempts);!); my $attempts = PG_restricted_eval(q!$main::numOfAttempts!); if ($displayMode eq 'TeX') { if ($printHintForInstructor) { - $out = join(' ',@in, "$BR(Show hint after $showHint attempts. ) $BR"); + $out = join(' ', "$BR(Show the student hint after $showHint attempts: ) $BR",@in); } else { $out = ''; # do nothing since hints are not available for download for students } } elsif ($printHintForInstructor) { # always print hints for instructor types - $out = join(' ',@in, "$BR(Show hint after $showHint attempts. )$BR "); + $out = join(' ', "$BR( Show the student hint after $showHint attempts. The current number of attempts is $attempts. )$BR $BBOLD HINT: $EBOLD ", @in); } elsif ( $displayHint and ( $attempts > $showHint )) { ## the second test above prevents a hint being shown if a doctored form is submitted Index: contextLimitedPolynomial.pl =================================================================== RCS file: /webwork/cvs/system/pg/macros/contextLimitedPolynomial.pl,v retrieving revision 1.5.4.2.2.1 retrieving revision 1.5.4.2.2.2 diff -Lmacros/contextLimitedPolynomial.pl -Lmacros/contextLimitedPolynomial.pl -u -r1.5.4.2.2.1 -r1.5.4.2.2.2 --- macros/contextLimitedPolynomial.pl +++ macros/contextLimitedPolynomial.pl @@ -234,7 +234,7 @@ sub checkStrict { my $self = shift; - $self->Error("You can only use '%s' between a coefficent and a variable in a polynomial",$self->{bop}); + $self->Error("You can only use '%s' between coefficents and variables in a polynomial",$self->{bop}); } ############################################## Index: AppletObjects.pl =================================================================== RCS file: /webwork/cvs/system/pg/macros/AppletObjects.pl,v retrieving revision 1.11.2.1.2.1 retrieving revision 1.11.2.1.2.2 diff -Lmacros/AppletObjects.pl -Lmacros/AppletObjects.pl -u -r1.11.2.1.2.1 -r1.11.2.1.2.2 --- macros/AppletObjects.pl +++ macros/AppletObjects.pl @@ -184,7 +184,7 @@ my $state_input_element = ($debugMode) ? $debug_input_element : qq!\n<input type="hidden" name = "$appletStateName" value ="$base_64_encoded_answer_value">!; my $reset_button_str = ($reset_button) ? - qq!<input type='button' value='return this question to its initial state' onClick="setAppletStateToRestart('$appletName')"><br/>! + qq!<input type='submit' name='previewAnswers' value='return this question to its initial state' onClick="setAppletStateToRestart('$appletName')"><br/>! : '' ; # <input type="button" value="reinitialize applet" onClick="getQE('$appletStateName').value='$base64_initialState'"/><br/> # always base64 encode the hidden answer value to prevent problems with quotes. Index: parserFormulaUpToConstant.pl =================================================================== RCS file: /webwork/cvs/system/pg/macros/parserFormulaUpToConstant.pl,v retrieving revision 1.11.2.1.2.3 retrieving revision 1.11.2.1.2.4 diff -Lmacros/parserFormulaUpToConstant.pl -Lmacros/parserFormulaUpToConstant.pl -u -r1.11.2.1.2.3 -r1.11.2.1.2.4 --- macros/parserFormulaUpToConstant.pl +++ macros/parserFormulaUpToConstant.pl @@ -173,12 +173,16 @@ # If constants aren't the same, substitute the professor's in the student answer. # $r = $r->substitute($r->{constant}=>$l->{constant}) unless $r->{constant} eq $l->{constant}; + # # Compare with adaptive parameters to see if $l + n0 C = $r for some n0. # my $adapt = $l->adapt; - my $equal = $adapt->cmp_compare($r,{}); + my $equal = Parser::Eval(sub {$adapt == $r}); $self->{adapt} = $self->{adapt}->inherit($adapt); # save the adapted value's flags + $self->{adapt}{test_values} = $adapt->{test_values}; # (these two are removed by inherit) + $self->{adapt}{test_adapt} = $adapt->{test_adapt}; + $_[1]->{test_values} = $r->{test_values}; # save these in student answer for diagnostics return -1 unless $equal; # # Check that n0 is non-zero (i.e., there is a multiple of C in the student answer) @@ -192,7 +196,6 @@ # sub adapt { my $self = shift; - my $adapt = $self->{adapt}->inherit($self); delete $adapt->{adapt}; return $self->adjustInherit($self->{adapt}); } @@ -262,7 +265,10 @@ # sub cmp_diagnostics { my $self = shift; - $self->inherit($self->{adapt})->SUPER::cmp_diagnostics(@_); + my $adapt = $self->inherit($self->{adapt}); + $adapt->{test_values} = $self->{adapt}{test_values}; # these aren't copied by inherit + $adapt->{test_adapt} = $self->{adapt}{test_adapt}; + $adapt->SUPER::cmp_diagnostics(@_); } # @@ -289,9 +295,7 @@ return unless $ans->{score} == 0 && !$ans->{isPreview}; return if $ans->{ans_message} || !$self->getFlag("showHints"); my $student = $ans->{student_value}; - $main::{_cmp_} = sub {return $ans->{correct_value} <=> $student}; # compare encodes the reason in the result - my $result = main::PG_restricted_eval('&{$main::{_cmp_}}'); - delete $main::{_cmp_}; + my $result = Parser::Eval(sub {return $ans->{correct_value} <=> $student}); # compare encodes the reason in the result $self->cmp_Error($ans,"Note: there is always more than one posibility") if $result == 2 || $result == 3; if ($result == 3) { my $context = $self->context; Index: parserPopUp.pl =================================================================== RCS file: /webwork/cvs/system/pg/macros/parserPopUp.pl,v retrieving revision 1.1.6.2.2.2 retrieving revision 1.1.6.2.2.3 diff -Lmacros/parserPopUp.pl -Lmacros/parserPopUp.pl -u -r1.1.6.2.2.2 -r1.1.6.2.2.3 --- macros/parserPopUp.pl +++ macros/parserPopUp.pl @@ -76,6 +76,7 @@ # sub new { my $self = shift; my $class = ref($self) || $self; + shift if Value::isContext($_[0]); # remove context, if given (it is not used) my $choices = shift; my $value = shift; Value::Error("A PopUp's first argument should be a list of menu items") unless ref($choices) eq 'ARRAY'; Index: contextPiecewiseFunction.pl =================================================================== RCS file: /webwork/cvs/system/pg/macros/contextPiecewiseFunction.pl,v retrieving revision 1.9.2.1 retrieving revision 1.9.2.1.2.1 diff -Lmacros/contextPiecewiseFunction.pl -Lmacros/contextPiecewiseFunction.pl -u -r1.9.2.1 -r1.9.2.1.2.1 --- macros/contextPiecewiseFunction.pl +++ macros/contextPiecewiseFunction.pl @@ -64,12 +64,25 @@ Context()->texStrings; BEGIN_TEXT - Suppose \(f(x)=$f\). Then \(f($a)\) = \{ans_rule(20)\}. + If \[f(x)=$f\] then \(f($a)\) = \{ans_rule(20)\}. END_TEXT Context()->normalStrings; ANS($f->eval(x=>$a)->cmp); +Normally when you use a piecewise function at the end of a sentence, +the period is placed at the end of the last case. Since + + \[ f(x) = $f \]. + +would put the period centered at the right-hand side of the function, +this is not what is desired. To get a period at the end of the last +case, use + + \[ f(x) = \{$f->with(final_period=>1)\} \] + +instead. + =cut loadMacros("MathObjects.pl"); @@ -699,7 +712,7 @@ # sub compareInterval { my $self = shift; my ($D,$f0,$f1) = @_; - my ($a,$b) = $D->value; $a = $a->value; $b = $b=>value; + my ($a,$b) = $D->value; $a = $a->value; $b = $b->value; return $f0 == $f1 if $D->{leftInfinite} && $D->{rightInfinite}; $a = $b - 2 if $D->{leftInfinite}; $b = $a + 2 if $D->{rightInfinite}; @@ -741,12 +754,13 @@ # sub string { my $self = shift; my @cases = (); + my $period = ($self->{final_period} ? "." : ""); foreach my $If (@{$self->{data}}) { my ($I,$f) = @{$If}; push(@cases,$f->string." if ".$I->string); } push(@cases,$self->{otherwise}->string) if defined $self->{otherwise}; - join(" else\n",@cases); + join(" else\n",@cases) . $period; } # @@ -754,13 +768,14 @@ # sub TeX { my $self = shift; my @cases = (); + my $period = ($self->{final_period} ? "." : ""); foreach my $If (@{$self->{data}}) { my ($I,$f) = @{$If}; push(@cases,'\displaystyle{'.$f->TeX."}&\\text{if}\\ ".$I->TeX); } if (scalar(@cases)) { push(@cases,'\displaystyle{'.$self->{otherwise}->TeX.'}&\text{otherwise}') if defined $self->{otherwise}; - return '\begin{cases}'.join('\cr'."\n",@cases).'\end{cases}'; + return '\begin{cases}'.join('\cr'."\n",@cases).$period.'\end{cases}'; } else { return $self->{otherwise}->TeX; } Index: dangerousMacros.pl =================================================================== RCS file: /webwork/cvs/system/pg/macros/dangerousMacros.pl,v retrieving revision 1.41.2.3.2.1 retrieving revision 1.41.2.3.2.2 diff -Lmacros/dangerousMacros.pl -Lmacros/dangerousMacros.pl -u -r1.41.2.3.2.1 -r1.41.2.3.2.2 --- macros/dangerousMacros.pl +++ macros/dangerousMacros.pl @@ -1076,6 +1076,7 @@ # .gif FILES in TeX mode ################################################################################ + $setNumber =~ s/\./_/g; ## extra dots confuse latex's graphics package if ($envir{texDisposition} eq "pdf") { # We're going to create PDF files with our TeX (using pdflatex), so we # need images in PNG format. @@ -1208,6 +1209,7 @@ # .png FILES in TeX mode ################################################################################ + $setNumber =~ s/\./_/g; ## extra dots confuse latex's graphics package if ($envir{texDisposition} eq "pdf") { # We're going to create PDF files with our TeX (using pdflatex), so we # need images in PNG format. what luck! they're already in PDF format! Index: contextInequalities.pl =================================================================== RCS file: /webwork/cvs/system/pg/macros/contextInequalities.pl,v retrieving revision 1.18.2.1 retrieving revision 1.18.2.1.2.1 diff -Lmacros/contextInequalities.pl -Lmacros/contextInequalities.pl -u -r1.18.2.1 -r1.18.2.1.2.1 --- macros/contextInequalities.pl +++ macros/contextInequalities.pl @@ -114,11 +114,11 @@ '+' => {class => "Inequalities::BOP::add"}, '-' => {class => "Inequalities::BOP::subtract"}, ); - $context->parens->set("(" => {type => "List", formIntervla => ']'}); # trap these later - $context->parens->set("[" => {type => "List", formIntervla => ')'}); # trap these later + $context->parens->set("(" => {type => "List", formInterval => ']'}); # trap these later + $context->parens->set("[" => {type => "List", formInterval => ')'}); # trap these later $context->strings->remove("NONE"); $context->constants->add(NONE=>Value::Set->new()); - $context->flags->set(noneWord => 'NONE'); + $context->flags->set(noneWord => 'NONE', showNotEquals => 1); $context->{parser}{Variable} = "Inequalities::Variable"; $context->{value}{'Interval()'} = "Inequalities::MakeInterval"; $context->{value}{Inequality} = "Inequalities::Inequality"; @@ -218,8 +218,8 @@ sub evalGreaterThan { my ($self,$a,$b) = @_; my $context = $self->context; my $I = Value::Infinity->new; - return $self->Package("Interval")->new($context,'(',$b,$I,')') if $self->{varPos} eq 'lop'; - return $self->Package("Interval")->new($context,'(',-$I,$a,')'); + return $self->Package("Interval")->new($context,'(',$b,$I,')')->with(reversed=>1) if $self->{varPos} eq 'lop'; + return $self->Package("Interval")->new($context,'(',-$I,$a,')')->with(reversed=>1); } sub evalLessThanOrEqualTo { @@ -232,8 +232,8 @@ sub evalGreaterThanOrEqualTo { my ($self,$a,$b) = @_; my $context = $self->context; my $I = Value::Infinity->new; - return $self->Package("Interval")->new($context,'[',$b,$I,')') if $self->{varPos} eq 'lop'; - return $self->Package("Interval")->new($context,'(',-$I,$a,']'); + return $self->Package("Interval")->new($context,'[',$b,$I,')')->with(reversed=>1) if $self->{varPos} eq 'lop'; + return $self->Package("Interval")->new($context,'(',-$I,$a,']')->with(reversed=>1); } sub evalEqualTo { @@ -249,7 +249,7 @@ return $self->Package("Union")->new($context, $self->Package("Interval")->new($context,'(',-$I,$x,')'), $self->Package("Interval")->new($context,'(',$x,$I,')') - ); + )->with(notEqual=>1); } # @@ -534,19 +534,28 @@ sub type {"Interval"} +sub updateParts { + my $self = shift; + $self->{leftInfinite} = 1 if $self->{data}[0]->{isInfinite}; + $self->{rightInfinite} = 1 if $self->{data}[1]->{isInfinite}; +} + sub string { my $self = shift; my ($a,$b,$open,$close) = $self->value; my $x = $self->{varName} || ($self->context->variables->names)[0]; $x = $context->{variables}{$x}{string} if defined $context->{variables}{$x}{string}; - my $left = ($open eq '(' ? ' < ' : ' <= '); - my $right = ($close eq ')' ? ' < ' : ' <= '); - my $inequality = ""; - $inequality .= $a->string.$left unless $self->{leftInfinite}; - $inequality .= $x; - $inequality .= $right.$b->string unless $self->{rightInfinite}; - $inequality = "-infinity < $x < infinity" if $inequality eq $x; - return $inequality; + if ($self->{leftInfinite}) { + return "-infinity < $x < infinity" if $self->{rightInfinite}; + return $b->string . ($close eq ')' ? ' > ' : ' >= ') . $x if $self->{reversed}; + return $x . ($close eq ')' ? ' < ' : ' <= ') . $b->string; + } elsif ($self->{rightInfinite}) { + return $x . ($open eq '(' ? ' > ' : ' >= ') . $a->string if $self->{reversed}; + return $a->string . ($open eq '(' ? ' < ' : ' <= ') . $x; + } else { + return $a->string . ($open eq '(' ? ' < ' : ' <= ') . + $x . ($close eq ')' ? ' < ' : ' <= ') . $b->string; + } } sub TeX { @@ -556,14 +565,17 @@ my $x = $self->{varName} || ($context->variables->names)[0]; $x = $context->{variables}{$x}{TeX} if defined $context->{variables}{$x}{TeX}; $x =~ s/^([^_]+)_?(\d+)$/$1_{$2}/; - my $left = ($open eq '(' ? ' < ' : ' \le '); - my $right = ($close eq ')' ? ' < ' : ' \le '); - my $inequality = ""; - $inequality .= $a->string.$left unless $self->{leftInfinite}; - $inequality .= $x; - $inequality .= $right.$b->string unless $self->{rightInfinite}; - $inequality = "-\\infty < $x < \\infty " if $inequality eq $x; - return $inequality; + if ($self->{leftInfinite}) { + return "-\\infty < $x < \\infty" if $self->{rightInfinite}; + return $b->TeX . ($close eq ')' ? ' > ' : ' \ge ') . $x if $self->{reversed}; + return $x . ($close eq ')' ? ' < ' : ' \le ') . $b->TeX; + } elsif ($self->{rightInfinite}) { + return $x . ($open eq '(' ? ' > ' : ' \ge ') . $a->TeX if $self->{reversed}; + return $a->TeX . ($open eq '(' ? ' < ' : ' \le ') . $x; + } else { + return $a->TeX . ($open eq '(' ? ' < ' : ' \le ') . + $x . ($close eq ')' ? ' < ' : ' \le ') . $b->TeX; + } } ################################################## @@ -607,26 +619,60 @@ sub string { my $self = shift; my $equation = shift; shift; shift; my $prec = shift; - my $op = ($equation->{context} || $self->context)->{operators}{'or'}; - my @intervals = (); + return $self->display("string",$equation,$prec); +} + +sub TeX { + my $self = shift; + my $equation = shift; shift; shift; my $prec = shift; + return $self->display("TeX",$equation,$prec); +} + +sub display { + my $self = shift; my $method = shift; my $equation = shift; my $prec = shift; + my $context = ($equation->{context} || $self->context); + my $X = $self->{varName} || ($context->variables->names)[0]; + $X = $context->{variables}{$X}{$method} if defined $context->{variables}{$X}{$method}; + $X =~ s/^([^_]+)_?(\d+)$/$1_{$2}/ if $method eq 'TeX'; + my $op = $context->{operators}{'or'}; + my ($and,$or,$le,$ge,$ne,$open,$close) = @{{ + string => [' and ',$op->{string} || ' or ',' <= ',' >= ',' != ','(',')'], + TeX => ['\hbox{ and }',$op->{TeX} || $op->{string} || '\hbox{ or }', + ' \le ',' \ge ',' \ne ','\left(','\right)'], + }->{$method}}; + my $showNE = $self->getFlag("showNotEquals",1); + my @intervals = (); my @points = (); my $interval; foreach my $x (@{$self->data}) { $x->{format} = $self->{format} if defined $self->{format}; - push(@intervals,$x->string($equation)) + if ($x->type eq 'Interval' && $showNE) { + if (defined($interval)) { + if ($interval->{data}[1] == $x->{data}[0]) { + push(@points,$X.$ne.$x->{data}[0]->$method($equation)); + $interval = $interval->with(isCopy=>1, data=>[$interval->value]) unless $interval->{isCopy}; + $interval->{data}[1] = $x->{data}[1]; + $interval->{rightInfinite} = 1 if $x->{rightInfinite}; + next; + } + push(@intervals,$self->joinAnd($interval,$method,$and,$equation,@points)); + } + $interval = $x; @points = (); next; + } + if (defined($interval)) { + push(@intervals,$self->joinAnd($interval,$method,$and,$equation,@points)); + $interval = undef; @points = (); + } + push(@intervals,$x->$method($equation)); } - my $string = join($op->{string} || ' or ',@intervals); - $string = '('.$string.')' if defined($prec) && $prec > ($op->{precedence} || 1.5); + push(@intervals,$self->joinAnd($interval,$method,$and,$equation,@points)) if defined($interval); + my $string = join($or,@intervals); + $string = $open.$string.$close if defined($prec) && $prec > ($op->{precedence} || 1.5); return $string; } -sub TeX { - my $self = shift; - my $equation = shift; shift; shift; my $prec = shift; - my $op = ($equation->{context} || $self->context)->{operators}{'or'}; - my @intervals = (); - foreach my $x (@{$self->data}) {push(@intervals,$x->TeX($equation))} - my $TeX = join($op->{TeX} || $op->{string} || '\hbox{ or }',@intervals); - $TeX = '\left('.$TeX.'\right)' if defined($prec) && $prec > ($op->{precedence} || 1.5); - return $TeX; +sub joinAnd { + my $self = shift; $interval = shift; $method = shift, my $and = shift; my $equation = shift; + unshift(@_,$interval->$method($equation)) unless $interval->{leftInfinite} && $interval->{rightInfinite}; + return join($and, @_); } ################################################## |
From: Mike G. v. a. <we...@ma...> - 2009-06-25 21:04:41
|
Log Message: ----------- merging changes from HEAD Tags: ---- rel-2-4-patches Modified Files: -------------- pg/lib: Parser.pm WWPlot.pm pg/lib/Parser: Value.pm pg/lib/Value: Matrix.pm Revision Data ------------- Index: WWPlot.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/WWPlot.pm,v retrieving revision 1.3 retrieving revision 1.3.8.1 diff -Llib/WWPlot.pm -Llib/WWPlot.pm -u -r1.3 -r1.3.8.1 --- lib/WWPlot.pm +++ lib/WWPlot.pm @@ -142,16 +142,22 @@ to access methods defined in GD but not supported directly by WWPlot. (See the documentation for GD.) -=item moveTo, lineTo +=item moveTo, lineTo, arrowTo $graph->moveTo($x,$y); $graph->lineTo($x,$y,$color); - -Moves to the point ($x, $y) (defined in real world coordinates) or draws a line from the -current position to the specified point ($x, $y) using the color $color. $color is the -name, e.g. 'white', of the color, not an index value or RGB specification. These are -low level call back routines used by the function, label and stamp objects to draw themselves. - + $graph->lineTo($x,$y,$color,$thickness); + $graph->lineTo($x,$y,$color,$thickness,'dashed'); + $graph->arrowTo($x,$y,$color); + $graph->arrowTo($x,$y,$color,$thickness); + $graph->arrowTo($x,$y,$color,$thickness,'dashed'); + +Moves to the point ($x, $y) (defined in real world coordinates) or draws a line or arrow +from the current position to the specified point ($x, $y) using the color $color. $color +is the name, e.g. 'white', of the color, not an index value or RGB specification. +$thickness gives the thickness of the line or arrow to draw. If 'dashed' is specified, +the line or arrow is rendered with a dashed line. These are low level call +back routines used by the function, label and stamp objects to draw themselves. =item ii, jj @@ -363,12 +369,25 @@ sub lineTo { my $self = shift; - my ($x,$y,$color) = @_; + my ($x,$y,$color, $w, $d) = @_; + $w = 1 if ! defined( $w ); + $d = 0 if ! defined( $d ); ## draw a dashed line? + $x=$self->ii($x); $y=$self->jj($y); $color = $self->{'colors'}{$color} if $color=~/[A-Za-z]+/ && defined($self->{'colors'}{$color}) ; # colors referenced by name works here. $color = $self->{'colors'}{'default_color'} unless defined($color); - $self->im->line(@{$self->position},$x,$y,$color); + + $self->im->setThickness( $w ); + if ( $d ) { + my @dashing = ( $color )x(4*$w*$w); + my @spacing = ( GD::gdTransparent )x(3*$w*$w); + $self->im->setStyle( @dashing, @spacing ); + $self->im->line(@{$self->position},$x,$y,GD::gdStyled); + } else { + $self->im->line(@{$self->position},$x,$y,$color); + } + $self->im->setThickness( 1 ); #warn "color is $color"; @{$self->position} = ($x,$y); } @@ -383,6 +402,52 @@ @{$self->position} = ( $x,$y ); } +sub arrowTo { + my $self = shift; + my ( $x1, $y1, $color, $w, $d ) = @_; + $w = 1 if ! defined( $w ); + $d = 0 if ! defined( $d ); + my $width = ( $w == 1 ) ? 2 : $w; + + $x1 = $self->ii($x1); + $y1 = $self->jj($y1); + $color = $self->{'colors'}{$color} if $color=~/[A-Za-z]+/ && defined($self->{'colors'}{$color}) ; + $color = $self->{'colors'}{'default_color'} unless defined($color); + + ## set thickness + $self->im->setThickness($w); + + my ($x0, $y0) = @{$self->position}; + my $dx = $x1 - $x0; + my $dy = $y1 - $y0; + my $len = sqrt($dx*$dx + $dy*$dy); + my $ux = $dx/$len; ## a unit vector in the direction of the arrow + my $uy = $dy/$len; + my $px = -1*$uy; ## a unit vector perpendicular + my $py = $ux; + my $hbx = $x1 - 5*$width*$ux; ## the base of the arrowhead + my $hby = $y1 - 5*$width*$uy; + my $head = new GD::Polygon; + $head->addPt($x1,$y1); + $head->addPt($hbx + 2*$width*$px, $hby + 2*$width*$py); + $head->addPt($hbx - 2*$width*$px, $hby - 2*$width*$py); + $self->im->filledPolygon( $head, $color ); + if ( $d ) { + my @dashing = ( $color )x(4*$w*$w); + my @spacing = ( GD::gdTransparent )x(3*$w*$w); + $self->im->setStyle( @dashing, @spacing ); + $self->im->line( $x0,$y0,$x1,$y1,GD::gdStyled); + } else { + $self->im->line( $x0,$y0,$x1,$y1,$color ); + } + + @{$self->position} = ( $x1, $y1 ); + + ## reset thickness + $self->im->setThickness(1); +} + + sub v_axis { my $self = shift; @{$self->{vaxis}}=@_; # y_value, color Index: Parser.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/Parser.pm,v retrieving revision 1.32.2.2.2.2 retrieving revision 1.32.2.2.2.3 diff -Llib/Parser.pm -Llib/Parser.pm -u -r1.32.2.2.2.2 -r1.32.2.2.2.3 --- lib/Parser.pm +++ lib/Parser.pm @@ -389,7 +389,9 @@ $self->Item("List")->new($self,[$top->makeList],$top->{isConstant},$paren, ($top->type eq 'Comma') ? $top->entryType : $top->typeRef, ($type ne 'start') ? ($self->top->{value},$type) : () )}; - } + } else { + $top->{value}{hadParens} = 1; + } $self->pop; $self->push($top); $self->CloseFn() if ($paren->{function} && $self->prev->{type} eq 'fn'); } elsif ($paren->{formInterval} eq $type && $self->top->{value}->length == 2) { Index: Value.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/Parser/Value.pm,v retrieving revision 1.17.6.2.2.1 retrieving revision 1.17.6.2.2.2 diff -Llib/Parser/Value.pm -Llib/Parser/Value.pm -u -r1.17.6.2.2.1 -r1.17.6.2.2.2 --- lib/Parser/Value.pm +++ lib/Parser/Value.pm @@ -46,8 +46,7 @@ # Set flags for the object # sub check { - my $self = shift; - my $type = $self->{type}; my $value = $self->{value}; + my $self = shift; my $value = $self->{value}; $self->{isZero} = $value->isZero; $self->{isOne} = $value->isOne; } Index: Matrix.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/Value/Matrix.pm,v retrieving revision 1.22.4.2.2.2 retrieving revision 1.22.4.2.2.3 diff -Llib/Value/Matrix.pm -Llib/Value/Matrix.pm -u -r1.22.4.2.2.2 -r1.22.4.2.2.3 --- lib/Value/Matrix.pm +++ lib/Value/Matrix.pm @@ -396,6 +396,7 @@ $d = scalar(@entries); @entries = (); } } + $TeX =~ s/\\cr\n$/\n/; return '\left'.$open.'\begin{array}{'.('c'x$d).'}'."\n".$TeX.'\end{array}\right'.$close; } |
From: Mike G. v. a. <we...@ma...> - 2009-06-25 20:54:34
|
Log Message: ----------- merging HEAD into 2-4-patches branch Tags: ---- rel-2-4-patches Modified Files: -------------- pg/macros: PGasu.pl compoundProblem.pl Added Files: ----------- pg/macros: contextFraction.pl contextOrdering.pl problemPanic.pl Revision Data ------------- Index: PGasu.pl =================================================================== RCS file: /webwork/cvs/system/pg/macros/PGasu.pl,v retrieving revision 1.7.2.1 retrieving revision 1.7.2.1.2.1 diff -Lmacros/PGasu.pl -Lmacros/PGasu.pl -u -r1.7.2.1 -r1.7.2.1.2.1 --- macros/PGasu.pl +++ macros/PGasu.pl @@ -118,6 +118,11 @@ This is useful if you want students to report the value of sin(pi/4), but you don't want to allow "sin(pi/4)" as the answer. +A similar effect can be accomplished with Contexts() by undefining +the trig functions. +See http://webwork.maa.org/wiki/Modifying_contexts_%28advanced%29#.282.29_Functions + + =cut # ^function no_trig_fun @@ -203,6 +208,10 @@ Second argument is optional, and tells us whether yes or no Third argument is the error message to produce (if any). +A similar effect can be accomplished with Contexts() by undefining +the trig functions. +See http://webwork.maa.org/wiki/Modifying_contexts_%28advanced%29 + =cut Index: compoundProblem.pl =================================================================== RCS file: /webwork/cvs/system/pg/macros/compoundProblem.pl,v retrieving revision 1.1.2.1 retrieving revision 1.1.2.1.2.1 diff -Lmacros/compoundProblem.pl -Lmacros/compoundProblem.pl -u -r1.1.2.1 -r1.1.2.1.2.1 --- macros/compoundProblem.pl +++ macros/compoundProblem.pl @@ -225,6 +225,9 @@ new_answers => "", # answer labels for THIS part ans_rule_count => 0, # the ans_rule count from previous parts new_ans_rule_count => 0, # the ans_rule count from THIS part + images_created => 0, # the image count from the precious parts + new_images_created => 0, # the image count from THIS part + imageName => "", # name of images_created image file score => 0, # the (weighted) score on this part total => 0, # the total on previous parts raw => 0, # raw score on this part @@ -284,7 +287,7 @@ main::RECORD_FORM_LABEL("_next"); main::RECORD_FORM_LABEL("_status"); $self->{status} = $self->decode; - $self->{isNew} = $main::inputs_ref->{_next} || ($main::inputs_ref->{submitAnswers} && + $self->{isNew} = $main::inputs_ref->{_next} || ($main::inputs_ref->{submitAnswers} && $main::inputs_ref->{submitAnswers} eq ($self->{nextLabel} || "Go on to Next Part")); if ($self->{isNew}) { $self->checkAnswers; @@ -301,6 +304,8 @@ sub initPart { my $self = shift; $main::ans_rule_count = $self->{status}{ans_rule_count}; + $main::images_created{$self->{status}{imageName}} = $self->{status}{images_created} + if $self->{status}{imageName}; main::install_problem_grader(\&compoundProblem::grader); $main::PG_FLAGS{compoundProblem} = $self; $self->initAnswers($self->{status}{answers}); @@ -360,6 +365,7 @@ $status->{answers} .= ';' if $status->{answers}; $status->{answers} .= $status->{new_answers}; $status->{ans_rule_count} = $status->{new_ans_rule_count}; + $status->{images_created} = $status->{new_images_created}; $status->{total} += $status->{score}; $status->{score} = $status->{raw} = 0; $status->{new_answers} = ''; @@ -392,6 +398,10 @@ return {%defaultStatus} unless $status; my @data = (); foreach my $hex (split(/(..)/,$status)) {push(@data,fromHex($hex)) if $hex ne ''} @data = split('\\|',join('',@data)); $status = {%defaultStatus}; + if (scalar(@data) == 8) { + # insert imageName, images_created, new_images_created, if missing + splice(@data,2,0,"",0); splice(@data,6,0,0); + } foreach my $id (main::lex_sort(keys(%defaultStatus))) {$status->{$id} = shift(@data)} return $status; } @@ -571,6 +581,10 @@ $status->{raw} = $result->{score}; $status->{score} = $result->{score}*$weight; $status->{new_ans_rule_count} = $main::ans_rule_count; + if (defined(%main::images_created)) { + $status->{imageName} = (keys %main::images_created)[0]; + $status->{new_images_created} = $main::images_created{$status->{imageName}}; + } $status->{new_answers} = join(';',@answers); my $data = quoteHTML($self->encode); @@ -623,3 +637,5 @@ return ($result,$state); } + +1; --- /dev/null +++ macros/problemPanic.pl @@ -0,0 +1,226 @@ +################################################################################ +# WeBWorK Online Homework Delivery System +# Copyright © 2009 The WeBWorK Project, http://openwebwork.sf.net/ +# $CVSHeader: pg/macros/problemPanic.pl,v 1.4.2.1 2009/06/25 20:40:39 gage Exp $ +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of either: (a) the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version, or (b) the "Artistic License" which comes with this package. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the +# Artistic License for more details. +################################################################################ + +=head1 NAME + +problemPanic.pl - Allow for a PANIC button that gives additional + hints, possibly costing some points. + +=head1 DESCRIPTION + +This file implements a mechanism for you to provide one or more "panic +button" that your students can use to get additional hints, at the +cost of a portion of their score. + +To include the button, use the command Panic::Button command within a +BEGIN_TEXT/END_TEXT block. E.g., + + BEGIN_TEXT + \{Panic::Button(label => "Request a Hint", penalty => .25)\} + (you will lose 25% of your points if you do) + END_TEXT + +When the student presses the hint button, the button will not longer +be available, and the "panic level" will be increased. This sets the +variable $panicked, which you can use to determine whether to include the +hints or not. For example + + if ($panicked) { + BEGIN_TEXT + Hint: You should factor the numerator and cancel + one of the factors with the denominator. + END_TEXT + } + +Note that you can create a "cascade" of hints by including a second +panic button in the hint received from the first button. This will +set $panic to 2 (panic level 2) and you can use that to include the +second hint. + + if ($panicked) { + BEGIN_TEXT + Hint: You should factor the numerator and cancel + one of the factors with the denominator. + $PAR + \{Panic::Button(label => "Another Hint", penalty => .25)\} + (costing an additional 25%) + END_TEXT + + if ($panicked > 1) { + BEGIN_TEXT + Additional Hint: one of the factors is \(x+$a)\). + END_TEXT + } + } + +You can add more buttons in a similar way. You can not have separate +buttons for separate hints that are NOT cascaded, however. (That may +be possible in future versions.) + +The Panic::Button command takes two optional parameters: + +=over + +=item S<C<< label => "text" >>> + +Sets the text to use for the button. The default is "Request a Hint". + +=item S<C<< penalty => percent >>> + +Specifies the number points to lose (as a number from 0 to 1) if this +hint is displayed. When more than one panic button is used, the +penalties are cumulative. That is, two penalties of .25 would produce +a total penalty of .5, so the student would lose half his points if +both hints were given. + +=back + +Once a hint is displayed, the panic button for that hint will no +longer be shown, and the hint will continue to be displayed as the +student submits new answers. + +A professor will be given a "Reset problem hints" checkbox at the +bottom of the problem, and can use that to request that the panic +level be reset back to 0. This also sets the score and the number of +attempts back to 0 as well, so this effectively resets the problem to +its original state. This is intended for use primarily during problem +development, but can be used to allow a student to get full credit for +a problem even after he or she has asked for a hint. + +To allow the grading penalties to work, you must include the command + + Panic::GradeWithPenalty; + +in order to install the panic-button grader. You should do this afer +setting the grader that you want to use for the problem itself, as the +panic grader will use the one that is installed at the time the +Panic::GradWithPenalty command is issued. + +=cut + +sub _problemPanic_init {Panic::Init()} + + +# +# The packge to contain the routines and data for the Panic buttons +# +package Panic; + +my $isTeX = 0; # true in hardcopy mode +my $allowReset = 0; # true if a professor is viewing the problem +my $buttonCount = 0; # number of panic buttons displayed so far +my @penalty = (0); # accummulated penalty values +my $grader; # problem's original grader + +# +# Allow resets if permission level is high enough. +# Look up the panic level and reset it if needed. +# Save the panic level for the next time through. +# +sub Init { + $main::permissionLevel = 0 unless defined $main::permissionLevel; + $allowReset = $main::permissionLevel > $main::PRINT_FILE_NAMES_PERMISSION_LEVEL; + $isTeX = ($main::displayMode eq 'TeX'); + unless ($isTeX) { + $main::panicked = $main::inputs_ref->{_panicked} || 0; + $main::panicked = 0 if $main::inputs_ref->{_panic_reset} && $allowReset; + main::TEXT(qq!<input type="hidden" name="_panicked" id="_panicked" value="$main::panicked" />!); + main::RECORD_FORM_LABEL("_panicked"); + } +} + +# +# Place a panic button on the page, if it's not hardcopy mode and its not at the wrong level. +# You can set the label, the penalty for taking this hint, and the panic level for this button. +# Use submitAnswers if it is before the due date, and checkAnswers otherwise. +# +sub Button { + $buttonCount++; + my %options = ( + label => "Request a Hint", + level => $buttonCount, + penalty => 0, + @_ + ); + my $label = $options{label}; + my $level = $options{level}; + $penalty[$buttonCount] = $penalty[$buttonCount] + $options{penalty}; + $penalty[$buttonCount] = 1 if $penalty[$buttonCount] > 1; + return if $isTeX || $main::panicked >= $level; + my $time = time(); + my $name = ($main::openDate <= $time && $time <= $main::dueDate ? "submitAnswers" : "checkAnswers"); + $value = quoteHTML($value); + return qq!<input type="submit" name="$name" value="$label" onclick="document.getElementById('_panicked').value++">!; +} + +# +# The reset button +# +sub ResetButton { + main::RECORD_FORM_LABEL("_panic_reset"); + return qq!<input type="checkbox" name="_panic_reset"> Reset problem hints!; +} + +# +# Handle HTML in the value +# +sub quoteHTML { + my $string = shift; + $string =~ s/&/\&/g; $string =~ s/"/\"/g; + $string =~ s/>/\>/g; $string =~ s/</\</g; + return $string; +} + +# +# Install the panic grader, saving the original one +# +sub GradeWithPenalty { + $grader = $main::PG_FLAGS{PROBLEM_GRADER_TO_USE} || \&main::avg_problem_grader; + main::install_problem_grader(\&Panic::grader); +} + +# +# The grader for the panic levels. +# +sub grader { + # + # Save the old score and call the original grader. + # Compute the penalized score, and save it, if it is better than the old score. + # Reset the values if we are resetting scores. + # + my $oldScore = $_[1]->{recorded_score} || 0; + my ($result,$state) = &{$grader}(@_); + $result->{score} *= 1-$penalty[$main::panicked]; + $state->{recorded_score} = ($result->{score} > $oldScore ? $result->{score} : $oldScore); + $state->{recorded_score} = $state->{num_of_incorrect_ans} = $state->{num_of_correct_ans} = 0 + if $main::inputs_ref->{_panic_reset} && $allowReset; + + # + # Add the problemPanic message and data + # + $result->{type} = "problemPanic ($result->{type})"; + if ($main::panicked) { + $result->{msg} .= '</i><p><b>Note:</b> <i>' if $result->{msg}; + $result->{msg} .= 'Your score was reduced by '.(int($penalty[$main::panicked]*100)).'%' + . ' because you accepted '.($main::panicked == 1 ? 'a hint.' : $main::panicked.' hints.'); + # + # Add the reset checkbox, if needed + # + $result->{msg} .= '<p>'.ResetButton() if $allowReset; + } + + return ($result,$state); +} --- /dev/null +++ macros/contextOrdering.pl @@ -0,0 +1,389 @@ +=head1 NAME + +contextOrdering.pl - Parses ordered lists of letters like "B > A = C > D" + +=head1 DESCRIPTION + +This context provides a structured way to parse and check answers that +are ordered lists of letters, where the letters are separated by +greater-than signs or equal signs. The only operators allowed are > +and =, and the only letters allowed are the ones you specify explicitly. + +To access the context, you must include + + loadMacros("contextOrdering.pl"); + +at the top of your problem file, and then specify the Ordering context: + + Context("Ordering"); + +There are two main ways to use the Ordering context. The first is to +use the Ordering() command to generate your ordering. This command +creates a context in which the proper letters are defined, and returns +a MathObject that represents the ordering you have provided. For +example, + + $ans = Ordering("B > A > C"); + +or + + $ans = Ordering(A => 2, B => 2.5, C => 1); + +would both produce the same ordering. The first form gives the +ordering as the student must type it, and the second gives the +ordering by specifying numeric values for the various letters that +induce the resulting order. Note that equality is determined using +the default tolerances for the Ordering context. You can change these +using commands like the following: + + Context("Ordering"); + Context()->flags->set(tolerance => .01, tolType => 'absolute'); + +If you want to allow lists of orderings, use the Ordering-List context: + + Context("Ordering-List"); + $ans = Ordering("A > B , B = C"); + +Note that each Ordering() call uses its own copy of the current +context. If you need to modify the actual context used, then use the +context() method of the resulting object. + +The second method of generating orderings is to declare the letters +you wish to use explicitly, and then build the Ordering objects using +the standard Compute() method: + + Context("Ordering"); + Letters("A","B","C","D"); + $a = Compute("A > B = C"); + $b = Compute("C > D"); + +Note that in this case, D is still a valid letter that students can +enter in response to an answer checker for $a, and similarly for A and +B with $b. Note also that both $a and $b use the same context, unlike +orderings produced by calls to the Ordering() function. Changes to +the current context WILL affect $a and $b. + +If the ordering contains duplicate letters (e.g., "A > B > A"), then a +warning message will be issued. If not all the letters are used by +the student, then that also produces a warning message. The latter +can be controlled by the showMissingLetterHints flag to the cmp() +method. For example: + + ANS(Ordering("A > B > C")->cmp(showMissingLetterHints => 0)); + +would prevent the message from being issued if the student submitted +just "A > B". + +=cut + +loadMacros("MathObjects.pl"); + +sub _contextOrdering_init {context::Ordering::Init()} + +########################################### +# +# The main Ordering routines +# + +package context::Ordering; + +# +# Here we set up the prototype contexts and define the needed +# functions in the main:: namespace. Some error messages are +# modified to read better for these contexts. +# +sub Init { + my $context = $main::context{Ordering} = Parser::Context->getCopy("Numeric"); + $context->{name} = "Ordering"; + $context->parens->clear(); + $context->variables->clear(); + $context->constants->clear(); + $context->operators->clear(); + $context->functions->clear(); + $context->strings->clear(); + $context->operators->add( + '>' => {precedence => 1.5, associativity => 'left', type => 'bin', class => 'context::Ordering::BOP::ordering'}, + '=' => {precedence => 1.7, associativity => 'left', type => 'bin', class => 'context::Ordering::BOP::ordering'}, + ); + $context->{parser}{String} = "context::Ordering::Parser::String"; + $context->{parser}{Value} = "context::Ordering::Parser::Value"; + $context->{value}{String} = "context::Ordering::Value::String"; + $context->{value}{Ordering} = "context::Ordering::Value::Ordering"; + $context->strings->add('='=>{hidden=>1},'>'=>{hidden=>1}); + $context->{error}{msg}{"Variable '%s' is not defined in this context"} = "'%s' is not defined in this context"; + $context->{error}{msg}{"Unexpected character '%s'"} = "Can't use '%s' in this context"; + $context->{error}{msg}{"Missing operand before '%s'"} = "Missing letter before '%s'"; + $context->{error}{msg}{"Missing operand after '%s'"} = "Missing letter after '%s'"; + + $context = $main::context{'Ordering-List'} = $context->copy; + $context->{name} = 'Ordering-List'; + $context->operators->redefine(',',from => "Full"); + $context->{value}{List} = "context::Ordering::Value::List"; + + main::PG_restricted_eval('sub Letters {context::Ordering::Letters(@_)}'); + main::PG_restricted_eval('sub Ordering {context::Ordering::Ordering(@_)}'); +} + +# +# A routine to set the letters allowed in this context. +# (Old letters are cleared, and > and = are allowed, but hidden, +# since they are used in the List() objects that implement the context). +# +sub Letters { + my $context = (Value::isContext($_[0]) ? shift : main::Context()); + my @strings; + foreach my $x (@_) {push(@strings, $x => {isLetter => 1, caseSensitive => 1})} + $context->strings->are(@strings); + $context->strings->add('='=>{hidden=>1},'>'=>{hidden=>1}); +} + +# +# Create orderings from strings or lists of letter => value pairs. +# A copy of the current context is created that contains the proper +# letters, and the correct string is created and parsed into an +# Ordering object. +# +sub Ordering { + my $context = main::Context()->copy; my $string; + Value->Error("The current context is not the Ordering context") + unless $context->{name} =~ m/Ordering/; + if (scalar(@_) == 1) { + $string = shift; + my $letters = $string; $letters =~ s/ //g; + context::Ordering::Letters($context,split(/[>=]/,$letters)); + } else { + my %letter = @_; my @letters = keys %letter; + context::Ordering::Letters($context,@letters); + foreach my $x (@letters) {$letter{$x} = Value::Real->new($context,$letter{$x})} + my @order = main::PGsort( + sub {$letter{$_[0]} == $letter{$_[1]} ? $_[0] lt $_[1] : $letter{$_[0]} > $letter{$_[1]}}, + @letters + ); + my $a = shift(@order); my $b; $string = $a; + while ($b = shift(@order)) { + $string .= ($letter{$a} == $letter{$b} ? " = " : " > ") . $b; + $a = $b; + } + } + return main::Formula($context,$string)->eval; +} + +############################################################# +# +# This is a Parser BOP used to create the Ordering objects +# used internally. They are actually lists with the operator +# and the two operands, and the comparisons is based on the +# standard list comparisons. The operands are either the strings +# for individual letters, or another Ordering object as a +# nested List. +# + +package context::Ordering::BOP::ordering; +our @ISA = ('Parser::BOP'); + +sub class {"Ordering"} + +sub isOrdering { + my $self = shift; my $obj = shift; my $class = $obj->class; + return $class eq 'Ordering' || $obj->{def}{isLetter}; +} + +sub _check { + my $self = shift; + $self->Error("Operands of %s must be letters",$self->{bop}) + unless $self->isOrdering($self->{lop}) && $self->isOrdering($self->{rop}); + $self->{letters} = $self->{lop}{letters}; # we modify {lop}{letters} this way, but that doesn't matter + foreach my $x (keys %{$self->{rop}{letters}}) { + if (defined($self->{letters}{$x})) { + $self->{ref} = $self->{rop}{letters}{$x}; + $self->Error("Each letter may appear only once in an ordering"); + } + $self->{letters}{$x} = $self->{rop}{letters}{$x}; + } +} + +sub _eval { + my $self = shift; + my $ordering = $self->Package("Ordering")->new($self->context,$self->{bop},@_); + $ordering->{letters} = $self->{letters}; + return $ordering; +} + +sub string { + my $self = shift; + return $self->{lop}->string." ".$self->{bop}." ".$self->{rop}->string; +} + +sub TeX { + my $self = shift; + return $self->{lop}->TeX." ".$self->{bop}." ".$self->{rop}->TeX; +} + + +############################################################# +# +# This is the Value object used to implement the list That represents +# one ordering operation. It is simply a normal Value::List with the +# operator as the first entry and the two operands as the remaing +# entries in the list. The new() method is overriden to make binary +# trees of equal operators into flat sorted lists. We override the +# List string and TeX methods so that they print correctly as binary +# operators. The cmp_equal method is overriden to make sure the that +# the lists are treated as a unit during answer checking. There is +# also a routine for adding letters to the object's context. +# + +package context::Ordering::Value::Ordering; +our @ISA = ('Value::List'); + +# +# Put all equal letters into one list and sort them +# +sub new { + my $self = shift; + my $context = (Value::isContext($_[0]) ? shift : $self->context); + my $bop = shift; my @letters = @_; + if ($bop eq '=') { + if (Value::classMatch($letters[0],'Ordering') && $letters[0]->{data}[0] eq '=') + {@letters = ($letters[0]->value,$letters[1]); shift @letters} + @letters = main::lex_sort(@letters); + } + return $self->SUPER::new($context,$bop,@letters); +} + +sub string { + my $self = shift; + my ($bop,@rest) = $self->value; + foreach my $x (@rest) {$x = $x->string}; + return join(" $bop ",@rest); +} + +sub TeX { + my $self = shift; + my ($bop,@rest) = $self->value; + foreach my $x (@rest) {$x = $x->TeX}; + return join(" $bop ",@rest); +} + +# +# Make sure we do comparison as a list of lists (rather than as the +# individual entries in the underlying Value::List that encodes +# the ordering) +# +sub cmp_equal { + my $self = shift; my $ans = $_[0]; + $ans->{typeMatch} = $ans->{firstElement} = $self; + $ans->{correct_formula} = $self->{equation}; + $self = $ans->{correct_value} = Value::List->make($self); + $ans->{student_value} = Value::List->make($ans->{student_value}) + if Value::classMatch($ans->{student_value},'Ordering'); + return $self->SUPER::cmp_equal(@_); +} + +sub cmp_defaults { + my $self = shift; + return ( + $self->SUPER::cmp_defaults(@_), + showMissingLetterHints => 1, + ); +} + +sub cmp_postprocess { + my $self = shift; my $ans = shift; + return if $ans->{isPreview} || $ans->{score} != 0; + $self->cmp_Error($ans,"Your ordering should include all the letters") + if $ans->{showMissingLetterHints} && + scalar(keys %{$ans->{correct_formula}{tree}{letters}}) != + scalar(keys %{$ans->{student_formula}{tree}{letters}}); +} + +# +# Add more letters to the ordering's context (so student answers +# can include them even if they aren't in the correct answer). +# +sub AddLetters { + my $self = shift; my $context = $self->context; + my @strings; + foreach my $x (@_) { + push(@strings, $x => {isLetter => 1, caseSensitive => 1}) + unless $context->strings->get($x); + } + $context->strings->add(@strings) if scalar(@strings); +} + +############################################################# +# +# This overrides the TeX method of the letters +# so that they don't print using the \rm font. +# + +package context::Ordering::Value::String; +our @ISA = ('Value::String'); + +sub TeX {shift->value} + + +############################################################# +# +# Override Parser classes so that we can check for repeated letters +# + +package context::Ordering::Parser::String; +our @ISA = ('Parser::String'); + +# +# Save the letters positional reference +# +sub new { + my $self = shift; + $self = $self->SUPER::new(@_); + $self->{letters}{$self->{value}} = $self->{ref} if $self->{def}{isLetter}; + return $self; +} + +######################### + +package context::Ordering::Parser::Value; +our @ISA = ('Parser::Value'); + +# +# Move letters to Value object +# +sub new { + my $self = shift; + $self = $self->SUPER::new(@_); + $self->{letters} = $self->{value}{letters} if defined $self->{value}{letters}; + return $self; +} + +# +# Return Ordering class if the object is one +# +sub class { + my $self = shift; + return "Ordering" if $self->{value}->classMatch('Ordering'); + return $self->SUPER::class; +} + +############################################################# +# +# This overrides the cmp_equal method to make sure that +# Ordering lists are put into nested lists (since the +# underlying ordering is a list, we don't want the +# list checker to test the individual parts of the list, +# but rather the list as a whole). +# + +package context::Ordering::Value::List; +our @ISA = ('Value::List'); + +sub cmp_equal { + my $self = shift; my $ans = $_[0]; + $ans->{student_value} = Value::List->make($ans->{student_value}) + if Value::classMatch($ans->{student_value},'Ordering'); + return $self->SUPER::cmp_equal(@_); +} + +############################################################# + +1; --- /dev/null +++ macros/contextFraction.pl @@ -0,0 +1,793 @@ +=head1 NAME + +contextFraction.pl - Implements a MathObject class for Fractions. + +=head1 DESCRIPTION + +This context implements a Fraction object that works like a Real, but +keeps the numerator and denominator separate. It provides methods for +reducing the fractions, and for allowing fractions with a whole-number +preceeding it, as in 4 1/2 for "four and one half". The answer +checker can require that students reduce their results, and there are +contexts that don't allow entery of decimal values (only fractions), +and that don't allow any operators or functions (other than division +and negation). + +To use these contexts, first load the contextFraction.pl file: + + loadMacros("contextFraction.pl"); + +and then select the appropriate context -- one of the following three: + + Context("Fraction"); + Context("Fraction-NoDecimals"); + Context("LimitedFraction"); + +The first is the most general, and allows fractions to be intermixed +with real numbers, so 1/2 + .5 would be allowed. Also, 1/2.5 is +allowed, though it produces a real number, not a fraction, since this +fraction class only implements fractions of integers. All operators +and functions are defined, so there are no restrictions on what is +allowed by the student. + +The second does not allow decimal numbers to be entered, but they can +still be produced as the result of function calls, or by named +constants such as "pi". For example, 1/sqrt(2) is allowed (and +produces a real number result). All functions and operations are +defined, and the only real difference between this and the previous +context is that decimal numbers can't be typed in explicitly. + +The third context limits the operations that can be performed: in +addition to not being able to type decimal numbers, no operations +other than division and negation are allowed, and no function calls at +all. Thus 1/sqrt(2) would be illegal, as would 1/2 + 2. The student +must enter a whole number or a fraction in this context. It is also +permissible to enter a whole number WITH a fraction, as in 2 1/2 for +"two and one half", or 5/2. + +You can use the Compute() function to generate fraction objects, or +the Fraction() constructor to make one explicitly. For example: + + Context("Fraction"); + $a = Compute("1/2"); + $b = Compute("4 - 1/6"); + $c = Compute("(4/9)^(1/2)"); + + Context("LimitedFraction"); + $d = Compute("4 2/3"); + $e = Compute("-1 1/2"); + + $f = Fraction(-2,5); + +Note that $c will be 2/3, $d will be 14/3, $e will be -3/2, and $f +will be -2/5. + +Once you have created a fraction object, you can use it as you would +any real number. For example: + + Context("Fraction"); + $a = Compute("1/2"); + $b = Compute("1/3"); + $c = $a - $b; + $d = asin($a); + $e = $b**2; + +Here $c will be the equivalent of Compute("1/6"), $d will be +equivalent to Compute("pi/6"), and $e will be the same as Compute("1/9"); + +You can an answer checker for a fraction in the same way as you do for +ALL MathObjects -- via its cmp() method: + + ANS(Compute("1/2")->cmp); + +or + + $b = Compute("1/2"); + ANS($b->cmp); + +There are several options to the cmp() method that control how the +answer checker will work. The first is controls whether unreduced +fractions are accepted as correct. Unreduced fractions are allowed in +the Fraction and Fraction-NoDecimals contexts, but not in the +LimitedFraction context. You can control this using the +studentsMustReduceFractions option: + + Context("Fraction"); + ANS(Compute("1/2")->cmp(studentsMustReduceFractions=>1)); + +or + + Context("LimitedFraction"); + ANS(Compute("1/2")->cmp(studentsMustReduceFractions=>0)); + +The second controls whether warnings are issued when students don't +reduce their answers, or to mark the answer incorrect silently. This +is specified by the showFractionReductionWarnings option. The default +is to report the warnings, but this option has an effect only when +studentsMustReduceFractions is 1, and so only in the LimitedFraction +context. For example, + + Context("LimitedFraction"); + ANS(Compute("1/2")->cmp(showFractionReductionWarnings=>0)); + +turns off these warnings. + +The final option, requireFraction, specifies whether a fraction MUST +be entered (e.g. one would have to enter 2/1 for a whole number). The +default is 0. + +In addition to these options for cmp(), there are Context flags that +control how fractions are handled. These include the following. + +=over + +=item S<C<< reduceFractions >>> + +This determines whether fractions are reduced automatically when they +are created. The default is to reduce fractions (except when +studentsMustReduceFractions is set), so Compute("4/6") would produce +the fraction 2/3. To leave fractions unreduced, set +reduceFractions=>0. The LimitedFraction context has +studentsMustReduceFractions set, so reduceFractions is unset +automatically for students, but not for correct answers, so +Fraction(2,4) would still produce 1/2, even though 2/4 would not be +allowed in a student answer. + +=item S<C<< strictFractions >>> + +This determines whether division is allowed only between integers or +not. If you want to prevent division from accepting non-integers, +then set strictFractions=>1 (and also strictMinus=>1 and +strictMultiplication=>1). These are all three 0 by default in the +Fraction and Fraction-NoDecimals contexts, but 1 in LimitedFraction. + +=item S<C<< allowProperFractions >>> + +This determines whether a space between a whole number and a fraction +is interpretted as implicit multiplication (as it usually would be in +WeBWorK), or as addition, allowing "4 1/2" to mean "4 and 1/2". By +default, it acts as multiplication in the Fraction and +Fraction-NoDecimals contexts, and as addition in LimitedFraction. If +you set allowProperFractions=>1 you should also set reduceConstants=>0. + +=item S<C<< requireProperFractions >>> + +This determines whether fractions MUST be entered as proper fractions. +It is 0 by default, meaning improper fractions are allowed. When set, +you will not be able to enter 5/2 as a fraction, but must use "2 1/2". +Set it to 1 only when you also set allowProperFractions, or you will +not be able to specify fractions bigger than one. It is off by +default in all three contexts. + +=item S<C<< showProperFractions >>> + +This controls whether fractions are displayed as proper fractions or +not. When set, 5/2 will be displayed as 2 1/2 in the answer preview +area, otherwise it will be displayed as 5/2. This flag is 0 by +default in the Fraction and Fraction-NoDecimals contexts, and 1 in +LimitedFraction. + +=back + +Fraction objects have two methods that can be useful when +reduceFractions is set to 0. The reduce() method will reduce a +fraction to lowest terms, and the isReduced() method returns true when +the fraction is reduced and false otherwise. + +If you wish to convert a fraction to its numeric (real number) form, +use the Real() constructor to coerce it to a real. E.g., + + $a = Compute("1/2"); + $r = Real($a); + +would set $r to the value 0.5. Similarly, use Fraction() to convert a +real number to (an approximating) fraction. E.g., + + $r = Real(.5); + $a = Fraction($r); + +would set $a to be 1/2. The fraction produced is good to about 6 +decimal places, so it can't be used for numbers that are too small. + +A side-effect of using the Fraction context is that fractions can be +used to take powers of negative numbers when the reduced form of the +fraction has an odd denominator. Thus (-8)^(1/3) will produce -2 as a +result, while in the standard Numeric context it would produce an +error. + +=cut + +sub _contextFraction_init {context::Fraction::Init()}; + +########################################################################### + +package context::Fraction; + +# +# Initialize the contexts and make the creator function. +# +sub Init { + my $context = $main::context{Fraction} = Parser::Context->getCopy("Numeric"); + $context->{name} = "Fraction"; + $context->{pattern}{signedNumber} .= '|-?\d+/\d+'; + $context->operators->set( + "/" => {class => "context::Fraction::BOP::divide"}, + "//" => {class => "context::Fraction::BOP::divide"}, + "/ " => {class => "context::Fraction::BOP::divide"}, + " /" => {class => "context::Fraction::BOP::divide"}, + "u-" => {class => "context::Fraction::UOP::minus"}, + " " => {precedence => 2.8, string => ' *'}, + " *" => {class => "context::Fraction::BOP::multiply", precedence => 2.8}, + # precedence is lower to get proper parens in string() and TeX() calls + " " => {precedence => 2.7, associativity => 'left', type => 'bin', string => ' ', + class => 'context::Fraction::BOP::multiply', TeX => [' ',' '], hidden => 1}, + ); + $context->flags->set( + reduceFractions => 1, + strictFractions => 0, strictMinus => 0, strictMultiplication => 0, + allowProperFractions => 0, # also set reduceConstants => 0 if you change this + requireProperFractions => 0, + showProperFractions => 0, + ); + $context->reduction->set('a/b' => 1,'a b/c' => 1, '0 a/b' => 1); + $context->{value}{Fraction} = "context::Fraction::Fraction"; + $context->{value}{Real} = "context::Fraction::Real"; + $context->{parser}{Value} = "context::Fraction::Value"; + $context->{parser}{Number} = "Parser::Legacy::LimitedNumeric::Number"; + + $context = $main::context{'Fraction-NoDecimals'} = $context->copy; + $context->{name} = "Fraction-NoDecimals"; + Parser::Number::NoDecimals($context); + + $context = $main::context{LimitedFraction} = $context->copy; + $context->{name} = "LimitedFraction"; + $context->operators->undefine( + '+', '-', '*', '* ', '^', '**', + 'U', '.', '><', 'u+', '!', '_', ',', + ); + $context->parens->undefine('|','{','['); + $context->functions->disable('All'); + $context->flags->set( + strictFractions => 1, strictMinus => 1, strictMultiplication => 1, + allowProperFractions => 1, reduceConstants => 0, + showProperFractions => 1, + ); + $context->{cmpDefaults}{Fraction} = {studentsMustReduceFractions => 1}; + + main::PG_restricted_eval('sub Fraction {Value->Package("Fraction()")->new(@_)};'); +} + +# +# Convert a real to a reduced fraction approximation +# +sub toFraction { + my $context = shift; my $x = shift; + my $Real = $context->Package("Real"); + my $d = 1000000; + my ($a,$b) = reduce(int($x*$d),$d); + return [$Real->make($a),$Real->make($b)]; +} + +# +# Greatest Common Divisor +# +sub gcd { + my $a = abs(shift); my $b = abs(shift); + ($a,$b) = ($b,$a) if $a < $b; + return $a if $b == 0; + my $r = $a % $b; + while ($r != 0) { + ($a,$b) = ($b,$r); + $r = $a % $b; + } + return $b; +} + +# +# Least Common Multiple +# +sub lcm { + my ($a,$b) = @_; + return ($a/gcd($a,$b))*$b; +} + + +# +# Reduced fraction +# +sub reduce { + my $a = shift; my $b = shift; + ($a,$b) = (-$a,-$b) if $b < 0; + my $gcd = gcd($a,$b); + return ($a/$gcd,$b/$gcd); +} + +########################################################################### + +package context::Fraction::BOP::divide; +our @ISA = ('Parser::BOP::divide'); + +# +# Create a Fraction or Real from the given data +# +sub _eval { + my $self = shift; my $context = $self->{equation}{context}; + return $_[0]/$_[1] if Value::isValue($_[0]) || Value::isValue($_[1]); + my $n = $context->Package("Fraction")->make($context,@_); + $n->{isHorizontal} = 1 if $self->{def}{noFrac}; + return $n; +} + +# +# When strictFraction is in effect, only allow division +# with integers and negative integers +# +sub _check { + my $self = shift; + $self->SUPER::_check; + return unless $self->context->flag("strictFractions"); + $self->Error("The numerator of a fraction must be an integer") + unless $self->{lop}->class =~ /INTEGER|MINUS/; + $self->Error("The denominator of a fraction must be a (non-negative) integer") + unless $self->{rop}->class eq 'INTEGER'; + $self->Error("The numerator must be less than the denominator in a proper fraction") + if $self->context->flag("requireProperFractions") && CORE::abs($self->{lop}->eval) >= CORE::abs($self->{rop}->eval); +} + +# +# Reduce the fraction, if it is one, otherwise do the usual reduce +# +sub reduce { + my $self = shift; + return $self->SUPER::reduce unless $self->class eq 'FRACTION'; + my $reduce = $self->{equation}{context}{reduction}; + return $self->{lop} if $self->{rop}{isOne} && $reduce->{'x/1'}; + $self->Error("Division by zero"), return $self if $self->{rop}{isZero}; + return $self->{lop} if $self->{lop}{isZero} && $reduce->{'0/x'}; + if ($reduce->{'a/b'}) { + my ($a,$b) = context::Fraction::reduce($self->{lop}->eval,$self->{rop}->eval); + if ($self->{lop}->class eq 'INTEGER') {$self->{lop}{value} = $a} else {$self->{lop}{op}{value} = -$a} + $self->{rop}{value} = $b; + } + return $self; +} + +# +# Display minus signs outside the fraction +# +sub TeX { + my $self = shift; my $bop = $self->{def}; + return $self->SUPER::TeX(@_) if $self->class ne 'FRACTION' || $bop->{noFrac}; + my ($precedence,$showparens,$position,$outerRight) = @_; + $showparens = '' unless defined($showparens); + my $addparens = + defined($precedence) && + ($showparens eq 'all' || ($precedence > $bop->{precedence} && $showparens ne 'nofractions') || + ($precedence == $bop->{precedence} && ($bop->{associativity} eq 'right' || $showparens eq 'same'))); + + my $TeX = $self->eval->TeX; + $TeX = '\left('.$TeX.'\right)' if ($addparens); + return $TeX; +} + +# +# Indicate if the value is a fraction or not +# +sub class { + my $self = shift; + return "FRACTION" if $self->{lop}->class =~ /INTEGER|MINUS/ && + $self->{rop}->class eq 'INTEGER'; + return $self->SUPER::class; +} + +########################################################################### + +package context::Fraction::BOP::multiply; +our @ISA = ('Parser::BOP::multiply'); + +# +# For proper fractions, add the integer to the fraction +# +sub _eval { + my ($self,$a,$b)= @_; + return ($a > 0 ? $a + $b : $a - $b); +} + +# +# If the implied multiplication represents a proper fraction with a +# preceeding integer, then switch to the proper fraction operator +# (for proper handling of string() and TeX() calls), otherwise, +# convert the object to a standard multiplication. +# +sub _check { + my $self = shift; + $self->SUPER::_check; + my $isFraction = 0; + if ($self->context->flag("allowProperFractions")) { + $isFraction = ($self->{lop}->class =~ /INTEGER|MINUS/ && !$self->{lop}{hadParens} && + $self->{rop}->class eq 'FRACTION' && !$self->{rop}{hadParens} && + $self->{rop}->eval >= 0); + } + if ($isFraction) { + $self->{bop} = " "; + $self->{def} = $self->context->{operators}{$self->{bop}}; + if ($self->{lop}->class eq 'MINUS') { + # + # Hack to replace BOP with unary negation of BOP. + # (When check() is changed to accept a return value, + # this will not be necessary.) + # + my $copy = bless {%$self}, ref($self); $copy->{lop} = $copy->{lop}{op}; + my $neg = $self->Item("UOP")->new($self->{equation},"u-",$copy); + map {delete $self->{$_}} (keys %$self); + map {$self->{$_} = $neg->{$_}} (keys %$neg); + bless $self, ref($neg); + } + } else { + $self->Error("Can't use implied multiplication in this context",$self->{bop}) + if $self->context->flag("strictMultiplication"); + bless $self, $ISA[0]; + } +} + +# +# Reduce the fraction +# +sub reduce { + my $self = shift; + my $reduce = $self->{equation}{context}{reduction}; + my ($a,($b,$c)) = (CORE::abs($self->{lop}->eval),$self->{rop}->eval->value); + if ($reduce->{'a b/c'}) { + ($b,$c) = context::Fraction::reduce($b,$c) if $reduce->{'a/b'}; + $a += int($b/$c); $b = $b % $c; + $self->{lop}{value} = $a; + $self->{rop}{lop}{value} = $b; + $self->{rop}{rop}{value} = $c; + return $self->{lop} if $b == 0 || $c == 1; + } + return $self->{rop} if $a == 0 && $reduce->{'0 a/b'}; + return $self; +} + +########################################################################### + +package context::Fraction::UOP::minus; +our @ISA = ('Parser::UOP::minus'); + +# +# For strict fractions, only allow minus on certain operands +# +sub _check { + my $self = shift; + $self->SUPER::_check; + $self->{hadParens} = 1 if $self->{op}{hadParens}; + return unless $self->context->flag("strictMinus"); + my $uop = $self->{def}{string} || $self->{uop}; + $self->Error("You can only use '%s' with (non-negative) numbers",$uop) + unless $self->{op}->class =~ /Number|INTEGER|FRACTION/; +} + +# +# class is MINUS if it is a negative number +# +sub class { + my $self = shift; + return "MINUS" if $self->{op}->class =~ /Number|INTEGER/; + $self->SUPER::class; +} + +########################################################################### + +package context::Fraction::Value; +our @ISA = ('Parser::Value'); + +# +# Indicate if the Value object is a fraction or not +# +sub class { + my $self = shift; + return "FRACTION" if $self->{value}->classMatch('Fraction'); + return $self->SUPER::class; +} + +########################################################################### + +package context::Fraction::Real; +our @ISA = ('Value::Real'); + +# +# Allow Real to convert Fractions to Reals +# +sub new { + my $self = shift; my $context = (Value::isContext($_[0]) ? shift : $self->context); + my $x = shift; + $x = $context->Package("Formula")->new($context,$x)->eval if ref($x) eq "" && $x =~ m!/!; + $x = $x->eval if scalar(@_) == 0 && Value::classMatch($x,'Fraction'); + $self->SUPER::new($context,$x,@_); +} + +# +# Since the signed number pattern now include fractions, we need to make sure +# we handle them when a real is made and it looks like a fraction +# +sub make { + my $self = shift; my $context = (Value::isContext($_[0]) ? shift : $self->context); + my $x = shift; + $x = $context->Package("Formula")->new($context,$x)->eval if ref($x) eq "" && $x =~ m!/!; + $x = $x->eval if scalar(@_) == 0 && Value::classMatch($x,'Fraction'); + $self->SUPER::make($context,$x,@_); +} + +########################################################################### +########################################################################### +# +# Implements the MathObject for fractions +# + +package context::Fraction::Fraction; +our @ISA = ('Value'); + +sub new { + my $self = shift; my $class = ref($self) || $self; + my $context = (Value::isContext($_[0]) ? shift : $self->context); + my $x = shift; $x = [$x,@_] if scalar(@_) > 0; + return $x->inContext($context) if Value::classMatch($x,'Fraction'); + $x = [$x] unless ref($x) eq 'ARRAY'; $x->[1] = 1 if scalar(@{$x}) == 1; + Value::Error("Can't convert ARRAY of length %d to %s",scalar(@{$x}),Value::showClass($self)) + unless (scalar(@{$x}) == 2); + $x->[0] = Value::makeValue($x->[0],context=>$context); + $x->[1] = Value::makeValue($x->[1],context=>$context); + return $x->[0] if Value::classMatch($x->[0],'Fraction') && scalar(@_) == 0; + $x = context::Fraction::toFraction($context,$x->[0]->value) if Value::isReal($x->[0]) && scalar(@_) == 0; + return $self->formula($x) if Value::isFormula($x->[0]) || Value::isFormula($x->[1]); + Value::Error("Fraction numerators must be integers") unless isInteger($x->[0]); + Value::Error("Fraction denominators must be integers") unless isInteger($x->[1]); + my ($a,$b) = ($x->[0]->value,$x->[1]->value); ($a,$b) = (-$a,-$b) if $b < 0; + Value::Error("Denominator can't be zero") if $b == 0; + ($a,$b) = context::Fraction::reduce($a,$b) if $context->flag("reduceFractions"); + bless {data => [$a,$b], context => $context}, $class; +} + +# +# Produce a real if one of the terms is not an integer +# otherwise produce a fraction. +# +sub make { + my $self = shift; my $class = ref($self) || $self; + my $context = (Value::isContext($_[0]) ? shift : $self->context); + push(@_,0) if scalar(@_) == 0; push(@_,1) if scalar(@_) == 1; + my ($a,$b) = @_; ($a,$b) = (-$a,-$b) if $b < 0; + return $context->Package("Real")->make($context,$a/$b) unless isInteger($a) && isInteger($b); + ($a,$b) = context::Fraction::reduce($a,$b) if $context->flag("reduceFractions"); + bless {data => [$a,$b], context => $context}, $class; +} + +# +# Promote to a fraction, allowing reals to be $x/1 even when +# not an integer (later $self->make() will produce a Real in +# that case) +# +sub promote { + my $self = shift; my $class = ref($self) || $self; + my $context = (Value::isContext($_[0]) ? shift : $self->context); + my $x = (scalar(@_) ? shift : $self); + if (scalar(@_) == 0) { + return $x->inContext($context) if ref($x) eq $class; + return (bless {data => [$x->value,1], context => $context}, $class) if Value::isReal($x); + return (bless {data => [$x,1], context => $context}, $class) if Value::matchNumber($x); + } + return $self->new($context,$x,@_); +} + + +# +# Create a new formula from the number +# +sub formula { + my $self = shift; my $value = shift; + my $formula = $self->Package("Formula")->blank($self->context); + my ($l,$r) = Value::toFormula($formula,@{$value}); + $formula->{tree} = $formula->Item("BOP")->new($formula,'/',$l,$r); + return $formula; +} + +# +# Return the real number type +# +sub typeRef {return $Value::Type{number}} +sub length {2} + +sub isZero {(shift)->{data}[0] == 0} +sub isOne {(shift)->eval == 1} + +# +# Return the real value +# +sub eval { + my $self = shift; + my ($a,$b) = $self->value; + return $a/$b; +} + +# +# parts are not Value objects, so don't transfer +# +sub transferFlags {} + +# +# Check if a value is an integer +# +sub isInteger { + my $n = shift; + $n = $n->value if Value::isReal($n); + return $n =~ m/^-?\d+$/; +}; + + +################################################## +# +# Binary operations +# + +sub add { + my ($self,$l,$r,$other) = Value::checkOpOrderWithPromote(@_); + my (($a,$b),($c,$d)) = ($l->value,$r->value); + my $M = context::Fraction::lcm($b,$d); + return $self->inherit($other)->make($a*($M/$b)+$c*($M/$d),$M); +} + +sub sub { + my ($self,$l,$r,$other) = Value::checkOpOrderWithPromote(@_); + my (($a,$b),($c,$d)) = ($l->value,$r->value); + my $M = context::Fraction::lcm($b,$d); + return $self->inherit($other)->make($a*($M/$b)-$c*($M/$d),$M); +} + +sub mult { + my ($self,$l,$r,$other) = Value::checkOpOrderWithPromote(@_); + my (($a,$b),($c,$d)) = ($l->value,$r->value); + return $self->inherit($other)->make($a*$c,$b*$d); +} + +sub div { + my ($self,$l,$r,$other) = Value::checkOpOrderWithPromote(@_); + my (($a,$b),($c,$d)) = ($l->value,$r->value); + Value::Error("Division by zero") if $c == 0; + return $self->inherit($other)->make($a*$d,$b*$c); +} + +sub power { + my ($self,$l,$r,$other) = Value::checkOpOrderWithPromote(@_); + my (($a,$b),($c,$d)) = ($l->value,$r->reduce->value); + ($a,$b,$c) = ($b,$a,-$c) if $c < 0; + my ($x,$y) = ($c == 1 ? ($a,$b) : ($a**$c,$b**$c)); + if ($d != 1) { + if ($x < 0 && $d % 2 == 1) {$x = -(-$x)**(1/$d)} else {$x = $x**(1/$d)}; + if ($y < 0 && $d % 2 == 1) {$y = -(-$y)**(1/$d)} else {$y = $y**(1/$d)}; + } + return $self->inherit($other)->make($x,$y) unless $x eq 'nan' || $y eq 'nan'; + Value::Error("Can't raise a negative number to a power") if $a*$b < 0; + Value::Error("Result of exponention is not a number"); +} + +sub compare { + my ($self,$l,$r) = Value::checkOpOrderWithPromote(@_); + return $l->eval <=> $r->eval; +} + +################################################## +# +# Numeric functions +# + +sub abs {my $self = shift; $self->make(CORE::abs($self->{data}[0]),CORE::abs($self->{data}[1]))} +sub neg {my $self = shift; $self->make(-($self->{data}[0]),$self->{data}[1])} +sub exp {my $self = shift; $self->make(CORE::exp($self->eval))} +sub log {my $self = shift; $self->make(CORE::log($self->eval))} +sub sqrt {my $self = shift; $self->make(CORE::sqrt($self->{data}[0]),CORE::sqrt($self->{data}[1]))} + +################################################## +# +# Trig functions +# + +sub sin {my $self = shift; $self->make(CORE::sin($self->eval))} +sub cos {my $self = shift; $self->make(CORE::cos($self->eval))} + +sub atan2 { + my ($self,$l,$r,$other) = Value::checkOpOrderWithPromote(@_); + return $self->inherit($other)->make(CORE::atan2($l->eval,$r->eval)); +} + +################################################## +# +# Utility +# + +sub reduce { + my $self = shift; + my ($a,$b) = context::Fraction::reduce($self->value); + return $self->make($a,$b); +} + +sub isReduced { + my $self = shift; + my (($a,$b),($c,$d)) = ($self->value,$self->reduce->value); + return $a == $c && $b == $d; +} + +################################################## +# +# Formatting +# + +sub string { + my $self = shift; my $equation = shift; my $prec = shift; + my ($a,$b) = @{$self->{data}}; my $n = ""; + return $a if $b == 1; + if ($self->getFlag("showProperFractions") && CORE::abs($a) > $b) + {$n = int($a/$b); $a = CORE::abs($a) % $b; $n .= " " unless $a == 0} + $n .= "$a/$b" unless $a == 0 && $n ne ''; + $n = "($n)" if defined $prec && $prec >= 1; + return $n; +} + +sub TeX { + my $self = shift; my $equation = shift; my $prec = shift; + my ($a,$b) = @{$self->{data}}; my $n = ""; + return $a if $b == 1; + if ($self->getFlag("showProperFractions") && CORE::abs($a) > $b) + {$n = int($a/$b); $a = CORE::abs($a) % $b; $n .= " " unless $a == 0} + my $s = ""; ($a,$s) = (-$a,"-") if $a < 0; + $n .= ($self->{isHorizontal} ? "$s$a/$b" : "${s}{\\textstyle\\frac{$a}{$b}}") + unless $a == 0 && $n ne ''; + $n = "\\left($n\\right)" if defined $prec && $prec >= 1; + return $n; +} + +########################################################################### +# +# Answer Checker +# + +sub cmp_defaults {( + shift->SUPER::cmp_defaults(@_), + ignoreInfinity => 1, + studentsMustReduceFractions => 0, + showFractionReduceWarnings => 1, + requireFraction => 0, +)} + +sub cmp_contextFlags { + my $self = shift; my $ans = shift; + return ( + $self->SUPER::cmp_contextFlags($ans), + reduceFractions => !$ans->{studentsMustReduceFractions}, + ); +} + +sub cmp_class {"a fraction of integers"} + +sub typeMatch { + my $self = shift; my $other = shift; my $ans = shift; + return 1 unless ref($other); + return 0 if Value::isFormula($other); + return 1 if $other->type eq 'Infinity' && $ans->{ignoreInfinity}; + return 0 if $ans->{requireFraction} && !$other->classMatch("Fraction"); + $self->type eq $other->type; +} + +sub cmp_postprocess { + my $self = shift; my $ans = shift; + my $student = $ans->{student_value}; + return if $ans->{isPreview} || + !$ans->{studentsMustReduceFractions} || + !Value::classMatch($student,'Fraction') || + $student->isReduced; + $ans->score(0); + $self->cmp_Error($ans,"Your fraction is not reduced") if $ans->{showFractionReduceWarnings}; +} + +########################################################################### + +1; |
From: Mike G. v. a. <we...@ma...> - 2009-06-25 17:42:53
|
Log Message: ----------- update warning when initializeAction() is called (change to initializeWWquestion() Modified Files: -------------- webwork2/htdocs/js: ww_applet_support.js Revision Data ------------- Index: ww_applet_support.js =================================================================== RCS file: /webwork/cvs/system/webwork2/htdocs/js/ww_applet_support.js,v retrieving revision 1.10 retrieving revision 1.11 diff -Lhtdocs/js/ww_applet_support.js -Lhtdocs/js/ww_applet_support.js -u -r1.10 -r1.11 --- htdocs/js/ww_applet_support.js +++ htdocs/js/ww_applet_support.js @@ -53,7 +53,7 @@ } function initializeAction() { // deprecated call -- removed - alert("You might be using an old template (stored at webwork2/conf/templates). The <body> tag in the system.template calls a function 'initializeAction()' -- this function name should be replaced by 'initializeWWquestion()'. Please update to a recent version of system.template"); + alert("You might be using an old template (stored at webwork2/conf/templates). The <body> tag in the system.template calls a function 'initializeAction()' instead of 'intializeWWquestion()'-- this function name should be replaced by 'initializeWWquestion()'. Please update to a recent version of system.template"); initializeWWquestion(); } |
From: Mike G. v. a. <we...@ma...> - 2009-06-25 17:41:30
|
Log Message: ----------- replace initializeAction() with initializeWWquestion() in body tag Modified Files: -------------- webwork2/conf/templates/math2: system.template Revision Data ------------- Index: system.template =================================================================== RCS file: /webwork/cvs/system/webwork2/conf/templates/math2/system.template,v retrieving revision 1.2 retrieving revision 1.3 diff -Lconf/templates/math2/system.template -Lconf/templates/math2/system.template -u -r1.2 -r1.3 --- conf/templates/math2/system.template +++ conf/templates/math2/system.template @@ -28,7 +28,7 @@ <title><!--#path style="text" text=" : " textonly="1"--></title> <!--#head--> </head> -<body bgcolor="white" onload="if (typeof(initializeAction) == 'function') {initializeAction()}"> +<body bgcolor="white" onload="if (typeof(initializeWWquestion) == 'function') {initializeWWquestion()}"> <div id="masthead"> <div id="loginstatus"> |
From: Mike G. v. a. <we...@ma...> - 2009-06-25 15:05:04
|
Log Message: ----------- Change the "return applet to virgin state" button into a submit button so that the action takes place immediately rather than after entering an answer Modified Files: -------------- pg/macros: AppletObjects.pl Revision Data ------------- Index: AppletObjects.pl =================================================================== RCS file: /webwork/cvs/system/pg/macros/AppletObjects.pl,v retrieving revision 1.20 retrieving revision 1.21 diff -Lmacros/AppletObjects.pl -Lmacros/AppletObjects.pl -u -r1.20 -r1.21 --- macros/AppletObjects.pl +++ macros/AppletObjects.pl @@ -184,7 +184,7 @@ my $state_input_element = ($debugMode) ? $debug_input_element : qq!\n<input type="hidden" name = "$appletStateName" value ="$base_64_encoded_answer_value">!; my $reset_button_str = ($reset_button) ? - qq!<input type='button' value='return this question to its initial state' onClick="setAppletStateToRestart('$appletName')"><br/>! + qq!<input type='submit' name='previewAnswers' value='return this question to its initial state' onClick="setAppletStateToRestart('$appletName')"><br/>! : '' ; # <input type="button" value="reinitialize applet" onClick="getQE('$appletStateName').value='$base64_initialState'"/><br/> # always base64 encode the hidden answer value to prevent problems with quotes. |
From: Mike G. v. a. <we...@ma...> - 2009-06-25 15:03:21
|
Log Message: ----------- Update information to instructor about hints in the problem. Modified Files: -------------- pg/macros: PGbasicmacros.pl Revision Data ------------- Index: PGbasicmacros.pl =================================================================== RCS file: /webwork/cvs/system/pg/macros/PGbasicmacros.pl,v retrieving revision 1.58 retrieving revision 1.59 diff -Lmacros/PGbasicmacros.pl -Lmacros/PGbasicmacros.pl -u -r1.58 -r1.59 --- macros/PGbasicmacros.pl +++ macros/PGbasicmacros.pl @@ -1002,7 +1002,7 @@ PG_restricted_eval(q!$main::envir{'PRINT_FILE_NAMES_PERMISSION_LEVEL'}!) : 10000; # protect against undefined values my $printHintForInstructor = $permissionLevel >= $PRINT_FILE_NAMES_PERMISSION_LEVEL; my $showHint = PG_restricted_eval(q!$main::showHint!); - my $displayHint = PG_restricted_eval(q!$envir{'displayHintsQ'}!); + my $displayHint = PG_restricted_eval(q!$main::envir{'displayHintsQ'}!); PG_restricted_eval(q!$main::hintExists =1!); PG_restricted_eval(q!$main::numOfAttempts = 0 unless defined($main::numOfAttempts);!); my $attempts = PG_restricted_eval(q!$main::numOfAttempts!); @@ -1014,7 +1014,7 @@ $out = ''; # do nothing since hints are not available for download for students } } elsif ($printHintForInstructor) { # always print hints for instructor types - $out = join(' ', "$BR( Show the student hint after $showHint attempts: )$BR $BBOLD HINT: $EBOLD ", @in); + $out = join(' ', "$BR( Show the student hint after $showHint attempts. The current number of attempts is $attempts. )$BR $BBOLD HINT: $EBOLD ", @in); } elsif ( $displayHint and ( $attempts > $showHint )) { ## the second test above prevents a hint being shown if a doctored form is submitted |
From: Mike G. v. a. <we...@ma...> - 2009-06-25 15:02:00
|
Log Message: ----------- update helpfile instructions Modified Files: -------------- webwork2/htdocs/helpFiles: InstructorPGProblemEditor.html Revision Data ------------- Index: InstructorPGProblemEditor.html =================================================================== RCS file: /webwork/cvs/system/webwork2/htdocs/helpFiles/InstructorPGProblemEditor.html,v retrieving revision 1.4 retrieving revision 1.5 diff -Lhtdocs/helpFiles/InstructorPGProblemEditor.html -Lhtdocs/helpFiles/InstructorPGProblemEditor.html -u -r1.4 -r1.5 --- htdocs/helpFiles/InstructorPGProblemEditor.html +++ htdocs/helpFiles/InstructorPGProblemEditor.html @@ -29,7 +29,7 @@ <h3>PG Problem Editor Help Page</h3> <blockquote> <p> -This page allows to edit the contents of PG problem files (templates) +This page allows one to edit the contents of PG problem files (templates) as well as set headers and other files. </p> <p> @@ -41,8 +41,40 @@ <dl> - <dt>| Manpages | macro list | pod docs | report problem bugs |</dt> - <dd>The first three links are to pages that give some information about writing problems: "Manpages" is an (incomplete) list of the most commonly used macros with a description of their parameters and how to use them; a complete list of macros is provided by the "macro list"; and details for many macros can be obtained using the "pod docs" link which links to documentation embedded in the macro files themselves. If you find an error in a library problem please use the "Report problem bugs" link to notify us so that we can fix it. You will need to register with an email address the first time you use bugzilla, but after that you will not. </dd> + <dt>| <a href="http://webwork.math.rochester.edu/docs/docs/pglanguage/manpages/">Manpages</a> + | <a href="http://devel.webwork.rochester.edu/twiki/bin/view/Webwork/PGmacrosByFile"> + macro list</a> + | <a href="http://webwork.maa.org/wiki/Category:Authors"> + authoring info and help</a> + | <a href="http://devel.webwork.rochester.edu/doc/cvs/pg_HEAD/"> + testing lab</a> + | <a href="http://devel.webwork.rochester.edu/doc/cvs/pg_HEAD/"> + pod docs</a> + | <a href="http://bugs.webwork.rochester.edu">Report problem bugs</a> + |</dt> + <dd>The first five links are to pages that give some information about writing problems: + + <dl> + <dt><a href="http://webwork.math.rochester.edu/docs/docs/pglanguage/manpages/">Manpages</a></dt> + <dd>This links to an (incomplete) list of the most commonly used macros with a description of their parameters and how to use them; </dd> + <dt><a href="http://devel.webwork.rochester.edu/twiki/bin/view/Webwork/PGmacrosByFile"> + macro list</a></dt> + <dd>This link provides a complete list of macros;</dd> + + <dt><a href="http://webwork.maa.org/wiki/Category:Authors"> + authoring info and help</a> </dt> + <dd>This link takes you to the authoring section of the WeBWorK wiki;</dd> + <dt><a href="http://devel.webwork.rochester.edu/doc/cvs/pg_HEAD/"> + testing lab</a></dt> + <dd>This links to a WeBWorK "problem" which allows you to try out fragments of PG code.</dd> + <dt><a href="http://devel.webwork.rochester.edu/doc/cvs/pg_HEAD/"> + pod docs</a></dt> + <dd>This link gives details for many macros. It links to documentation embedded in the macro files themselves;</dd> + <dt><a href="http://bugs.webwork.rochester.edu">Report problem bugs</a></dt> + <dd>If you find an error in a library problem please use the link to notify us so that we can fix it. You will need to register with an email address the first time you use bugzilla, but after that you will not. </dd> + </dl> + + </dd> <dt> The large text window</dt> <dd> This is where you edit the text of the problem template. Nothing happens until you pick an action (below) and click the "Take action!" button.</dd> @@ -50,7 +82,7 @@ <h4>Action items:</h4> <i>(If an action cannot be executed it will not appear as an action item.) </i> -</dl> +<dl> <dt> View using seed "12345" and display mode "image"</dt> <dd> Will bring up a window to show the results of your editing. It does not change the permanent file on the disk. You can view different versions of the same problem by changing the seed (any number will do). You can also change mannerin which the @@ -59,22 +91,23 @@ <dd> Add this problem as the last problem of an existing set, either as a problem or as the set header (the text that appears on the home page of a homework set). You can rearrange the order of the problems later using the "Hmwk Sets Editor"</dd> - <dt> Create a copy at [TMPL]/"path"...</dt> + <dt> Save as [TMPL]/"path"...</dt> <dd> Makes a new copy of the file you are editing at the location relative to the course's templates ([TMPL]) directory given by "path". - The checkbox allows you to replace the current problem with the new problem in the homework set. + The checkbox allows you to replace the current problem with the new problem in the current homework set. <p> Check the checkbox if you want to edit the current problem without changing the original problem file.</p> <p> Leave the checkbox unchecked if you are using the current problem as a model for a brand new problem. You can add the new file to the homework set from the Library Browser.</p> - <p>If the original problem could not be edited than you will have to change the path name - before you will be allowed to save the problem. (Adding "local/" to the beginning of the suggested path will work. All of your - edited files will then appear in a subdirectory named "local". A new problem whose path ends in blankProblem.pg should be given - a new name, for example, "myNewProblem.pg")</P.</dd> + <p>If the original problem can not be edited than the path name + must be changed in order to be allowed to save the problem. (Adding "local/" to the beginning of the original path is the default solution. All locally created and + edited files will then appear in a subdirectory named "local". </p> + <p>A new problem whose path ends in blankProblem.pg should be given a new name, for example, "myNewProblem.pg")</P></dd> <dt> Revert</dt> <dd> Reverts to the copy of the file saved on the disk -- all unsaved editing changes will be lost. This option only appears when you have making provisional edits using the first option "View..." above</dd> <dt> Take action</dt> - <dd> Executes the</dd> action item selected above. You can elect to have this action appear in a new window or not. + <dd> Executes the action item selected above. You can elect to have the results of this action appear in a new window. + </dd> </dl> </blockquote> |
From: dpvc v. a. <we...@ma...> - 2009-06-09 20:37:28
|
Log Message: ----------- Only shorten the labels for display, not internally.\nPlus removed trailing spaces and fixed a typo in the javascript Modified Files: -------------- pg/macros: parserRadioButtons.pl Revision Data ------------- Index: parserRadioButtons.pl =================================================================== RCS file: /webwork/cvs/system/pg/macros/parserRadioButtons.pl,v retrieving revision 1.12 retrieving revision 1.13 diff -Lmacros/parserRadioButtons.pl -Lmacros/parserRadioButtons.pl -u -r1.12 -r1.13 --- macros/parserRadioButtons.pl +++ macros/parserRadioButtons.pl @@ -2,12 +2,12 @@ # WeBWorK Online Homework Delivery System # Copyright © 2000-2007 The WeBWorK Project, http://openwebwork.sf.net/ # $CVSHeader$ -# +# # This program is free software; you can redistribute it and/or modify it under # the terms of either: (a) the GNU General Public License as published by the # Free Software Foundation; either version 2, or (at your option) any later # version, or (b) the "Artistic License" which comes with this package. -# +# # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the @@ -166,16 +166,16 @@ return $self; } -# +# # Given a choice, a label, or an index into the choices array, # return the label. -# +# sub findChoice { my $self = shift; my $value = shift; my $index = $self->Index($value); foreach my $i (0..scalar(@{$self->{choices}})-1) { my $label = $self->{labels}[$i]; my $choice = $self->{choices}[$i]; - $label = $self->makeLabel($choice) unless defined $label; + $label = $choice unless defined $label; return $label if $label eq $value || $index == $i || $choice eq $value; } return undef; @@ -198,10 +198,10 @@ # ans_radio_buttons() routine # sub choiceHash { - my $self = shift; my @radio = (); + my $self = shift; my @radio = (); my %labels; foreach my $i (0..scalar(@{$self->{choices}})-1) { my $label = $self->{labels}[$i]; my $choice = $self->{choices}[$i]; - $label = $self->makeLabel($choice) unless defined $label; + $label = $choice unless defined $label; push(@radio, $label,$choice); } return @radio; @@ -209,15 +209,14 @@ # # Create a label for the answer, either using the labels -# provided by the user, or by creating one from the answer +# provided by the author, or by creating one from the answer # string (restrict its length so that the results table # will not be overflowed). # -sub makeLabel { +sub labelText { my $self = shift; my $choice = shift; return $choice if length($choice) < $self->{maxLabelSize}; - my @words = split(/\b/,$choice); - my ($s,$e) = ('',''); + my @words = split(/\b/,$choice); my ($s,$e) = ('',''); do {$s .= shift(@words); $e = pop(@words) . $e} while length($s) + length($e) + 15 < $self->{maxLabelSize} && scalar(@words); return $s . " ... " . $e; @@ -236,8 +235,7 @@ # Print the JavaScript needed for uncheckable radio buttons # sub JavaScript { - return if $main::displayMode eq 'TeX'; - return if $jsPrinted; + return if $jsPrinted || $main::displayMode eq 'TeX'; main::TEXT( "\n<script>\n" . "if (window.ww == null) {var ww = {}}\n" . @@ -259,7 +257,7 @@ "}\n". "</script>\n" ); - $jsSPrinted = 1; + $jsPrinted = 1; } sub makeUncheckable { @@ -271,20 +269,20 @@ return @radio; } -# +# # Determine the order the choices should be in. -# +# sub orderedChoices { my $self = shift; my %choiceHash = $self->choiceHash; my @labels = keys %choiceHash; - + my @order = @{$self->{order} || []}; my @first = @{$self->{first} || []}; my @last = @{$self->{last} || []}; - + my @orderLabels; - + if (@order) { my %remainingChoices = %choiceHash; Value::Error("When using the 'order' option, you must list all possible choices.") @@ -302,7 +300,7 @@ my @firstLabels; my @lastLabels; my %remainingChoices = %choiceHash; - + foreach my $i (0..$#first) { my $label = $self->findChoice($first[$i]); Value::Error("Item $i of the 'first' option is not a choice.") @@ -312,7 +310,7 @@ push @firstLabels, $label; delete $remainingChoices{$label}; } - + foreach my $i (0..$#last) { my $label = $self->findChoice($last[$i]); Value::Error("Item $i of the 'last' option is not a choice.") @@ -330,7 +328,7 @@ # might we want to explicitly randomize these? @orderLabels = @labels; } - + my $label = $self->findChoice($self->{checked}); return map { ($_ eq $label ? "%$_" : $_) => $choiceHash{$_} } @orderLabels; } @@ -363,4 +361,11 @@ sub ans_rule {shift->buttons(@_)} sub named_ans_rule {shift->named_buttons(@_)} +sub cmp_postprocess { + my $self = shift; my $ans = shift; + my $text = $self->labelText($ans->{student_value}->value); + $ans->{preview_text_string} = $ans->{student_ans} = $text; + $ans->{preview_latex_string} = "\\hbox{$text}"; +} + 1; |
From: dpvc v. a. <we...@ma...> - 2009-06-09 12:11:44
|
Log Message: ----------- Put off adding % to label key until just before needed Modified Files: -------------- pg/macros: parserRadioButtons.pl Revision Data ------------- Index: parserRadioButtons.pl =================================================================== RCS file: /webwork/cvs/system/pg/macros/parserRadioButtons.pl,v retrieving revision 1.11 retrieving revision 1.12 diff -Lmacros/parserRadioButtons.pl -Lmacros/parserRadioButtons.pl -u -r1.11 -r1.12 --- macros/parserRadioButtons.pl +++ macros/parserRadioButtons.pl @@ -158,7 +158,7 @@ Value::Error("A RadioButton's second argument should be the correct button choice") unless defined($value) && $value ne ""; my $context = Parser::Context->getCopy("String"); - my %choiceHash = $self->choiceHash(1); + my %choiceHash = $self->choiceHash; $context->strings->add(map {$_=>{}} (keys %choiceHash)); $value = $self->correctChoice($value); $self = bless $context->Package("String")->new($context,$value)->with(choices => $choices, %options), $class; @@ -178,6 +178,7 @@ $label = $self->makeLabel($choice) unless defined $label; return $label if $label eq $value || $index == $i || $choice eq $value; } + return undef; } # @@ -197,15 +198,10 @@ # ans_radio_buttons() routine # sub choiceHash { - my $self = shift; my $noChecked = shift; - my @radio = (); - my $index = $self->Index($self->{checked}); - my $checked = $self->{checked}; $checked = "" unless defined $checked; - if ($noChecked) {$checked = ""; $index = -1} + my $self = shift; my @radio = (); foreach my $i (0..scalar(@{$self->{choices}})-1) { my $label = $self->{labels}[$i]; my $choice = $self->{choices}[$i]; $label = $self->makeLabel($choice) unless defined $label; - $label = "%$label" if $label eq $checked || $index == $i || $choice eq $checked; push(@radio, $label,$choice); } return @radio; @@ -283,9 +279,9 @@ my %choiceHash = $self->choiceHash; my @labels = keys %choiceHash; - my @order = @{$self->{order}}; - my @first = @{$self->{first}}; - my @last = @{$self->{last}}; + my @order = @{$self->{order} || []}; + my @first = @{$self->{first} || []}; + my @last = @{$self->{last} || []}; my @orderLabels; @@ -335,7 +331,8 @@ @orderLabels = @labels; } - return map { $_ => $choiceHash{$_} } @orderLabels; + my $label = $self->findChoice($self->{checked}); + return map { ($_ eq $label ? "%$_" : $_) => $choiceHash{$_} } @orderLabels; } # |
From: dpvc v. a. <we...@ma...> - 2009-06-07 13:05:30
|
Log Message: ----------- Don't do equivalence check if prefilters are erased Modified Files: -------------- pg/lib/Value: AnswerChecker.pm Revision Data ------------- Index: AnswerChecker.pm =================================================================== RCS file: /webwork/cvs/system/pg/lib/Value/AnswerChecker.pm,v retrieving revision 1.124 retrieving revision 1.125 diff -Llib/Value/AnswerChecker.pm -Llib/Value/AnswerChecker.pm -u -r1.124 -r1.125 --- lib/Value/AnswerChecker.pm +++ lib/Value/AnswerChecker.pm @@ -1648,6 +1648,7 @@ my $self = shift; my $ans = shift; $ans->{_filter_name} = "produce_equivalence_message"; return $ans if $ans->{ans_message}; # don't overwrite other messages + return $ans unless defined($ans->{prev_ans}); # if prefilters are erased, don't do this check my $context = $self->context; $ans->{prev_formula} = Parser::Formula($context,$ans->{prev_ans}); if (defined($ans->{prev_formula}) && defined($ans->{student_formula})) { |