From: Mike G. v. a. <we...@ma...> - 2005-06-10 02:21:09
|
Log Message: ----------- Merging changes from rel-2-1-3 back into rel-2-1-patches Tags: ---- rel-2-1-patches Modified Files: -------------- webwork2: README webwork2/conf: devel.apache-config.dist global.conf.dist webwork.apache-config.dist webwork2/courses: adminClasslist.lst defaultClasslist.lst webwork2/doc/parser/problems: sample05.pg sample21.pg webwork2/htdocs/css: ur.css webwork2/lib/WeBWorK: ContentGenerator.pm DB.pm PG.pm Utils.pm webwork2/lib/WeBWorK/ContentGenerator: CourseAdmin.pm Login.pm Problem.pm ProblemSet.pm webwork2/lib/WeBWorK/ContentGenerator/Instructor: ProblemSetList.pm Scoring.pm SetsAssignedToUser.pm Stats.pm StudentProgress.pm UsersAssignedToSet.pm webwork2/lib/WeBWorK/DB/Record: Key.pm Password.pm PermissionLevel.pm Problem.pm Set.pm User.pm UserProblem.pm UserSet.pm webwork2/lib/WeBWorK/DB/Schema: SQL.pm webwork2/lib/WeBWorK/PG: Local.pm webwork2/lib/WeBWorK/Utils: CourseManagement.pm DBImportExport.pm webwork2/lib/WeBWorK/Utils/CourseManagement: sql_single.pm Added Files: ----------- webwork2/bin: ww_db_v2_to_v3 webwork2/clients: README hello_world_soap_client.pl hello_world_xmlrpc_client.pl input.txt webwork_soap_client.pl webwork_xmlrpc_client.pl webwork2/conf/snippets: blankProblem.pg webwork2/courses/modelCourse: course.conf webwork2/lib: MySOAP.pm RQP.pm WebworkWebservice.pm webwork2/lib/WeBWorK: DBv3.pm webwork2/lib/WeBWorK/DBv3: NormalizerMixin.pm Utils.pm webwork2/lib/WebworkWebservice: LibraryActions.pm MathTranslators.pm RenderProblem.pm Revision Data ------------- Index: Problem.pm =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D RCS file: /webwork/cvs/system/webwork2/lib/WeBWorK/DB/Record/Problem.pm,v retrieving revision 1.5 retrieving revision 1.5.8.1 diff -Llib/WeBWorK/DB/Record/Problem.pm -Llib/WeBWorK/DB/Record/Problem.p= m -u -r1.5 -r1.5.8.1 --- lib/WeBWorK/DB/Record/Problem.pm +++ lib/WeBWorK/DB/Record/Problem.pm @@ -45,4 +45,12 @@ max_attempts )} =20 +sub SQL_TYPES {qw( + BLOB + INT + TEXT + INT + INT +)} + 1; Index: User.pm =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D RCS file: /webwork/cvs/system/webwork2/lib/WeBWorK/DB/Record/User.pm,v retrieving revision 1.5 retrieving revision 1.5.8.1 diff -Llib/WeBWorK/DB/Record/User.pm -Llib/WeBWorK/DB/Record/User.pm -u -= r1.5 -r1.5.8.1 --- lib/WeBWorK/DB/Record/User.pm +++ lib/WeBWorK/DB/Record/User.pm @@ -53,4 +53,16 @@ comment )} =20 +sub SQL_TYPES {qw( + BLOB + TEXT + TEXT + TEXT + TEXT + TEXT + TEXT + TEXT + TEXT +)} + 1; Index: Key.pm =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D RCS file: /webwork/cvs/system/webwork2/lib/WeBWorK/DB/Record/Key.pm,v retrieving revision 1.5 retrieving revision 1.5.8.1 diff -Llib/WeBWorK/DB/Record/Key.pm -Llib/WeBWorK/DB/Record/Key.pm -u -r1= .5 -r1.5.8.1 --- lib/WeBWorK/DB/Record/Key.pm +++ lib/WeBWorK/DB/Record/Key.pm @@ -41,4 +41,10 @@ timestamp )} =20 +sub SQL_TYPES {qw( + BLOB + TEXT + TEXT +)} + 1; Index: PermissionLevel.pm =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D RCS file: /webwork/cvs/system/webwork2/lib/WeBWorK/DB/Record/PermissionLe= vel.pm,v retrieving revision 1.6 retrieving revision 1.6.8.1 diff -Llib/WeBWorK/DB/Record/PermissionLevel.pm -Llib/WeBWorK/DB/Record/P= ermissionLevel.pm -u -r1.6 -r1.6.8.1 --- lib/WeBWorK/DB/Record/PermissionLevel.pm +++ lib/WeBWorK/DB/Record/PermissionLevel.pm @@ -40,4 +40,9 @@ permission )} =20 +sub SQL_TYPES {qw( + BLOB + INT +)} + 1; Index: Password.pm =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D RCS file: /webwork/cvs/system/webwork2/lib/WeBWorK/DB/Record/Password.pm,= v retrieving revision 1.4 retrieving revision 1.4.8.1 diff -Llib/WeBWorK/DB/Record/Password.pm -Llib/WeBWorK/DB/Record/Password= .pm -u -r1.4 -r1.4.8.1 --- lib/WeBWorK/DB/Record/Password.pm +++ lib/WeBWorK/DB/Record/Password.pm @@ -39,4 +39,9 @@ password )} =20 +sub SQL_TYPES {qw( + BLOB + TEXT +)} + 1; Index: Set.pm =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D RCS file: /webwork/cvs/system/webwork2/lib/WeBWorK/DB/Record/Set.pm,v retrieving revision 1.8 retrieving revision 1.8.4.1 diff -Llib/WeBWorK/DB/Record/Set.pm -Llib/WeBWorK/DB/Record/Set.pm -u -r1= .8 -r1.8.4.1 --- lib/WeBWorK/DB/Record/Set.pm +++ lib/WeBWorK/DB/Record/Set.pm @@ -49,4 +49,14 @@ published )} =20 +sub SQL_TYPES {qw( + BLOB + TEXT + TEXT + BIGINT + BIGINT + BIGINT + INT +)} + 1; Index: UserSet.pm =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D RCS file: /webwork/cvs/system/webwork2/lib/WeBWorK/DB/Record/UserSet.pm,v retrieving revision 1.7 retrieving revision 1.7.4.1 diff -Llib/WeBWorK/DB/Record/UserSet.pm -Llib/WeBWorK/DB/Record/UserSet.p= m -u -r1.7 -r1.7.4.1 --- lib/WeBWorK/DB/Record/UserSet.pm +++ lib/WeBWorK/DB/Record/UserSet.pm @@ -53,4 +53,16 @@ published )} =20 +sub SQL_TYPES {( + "BLOB", + "BLOB", + "INT NOT NULL PRIMARY KEY AUTO_INCREMENT", + "TEXT", + "TEXT", + "BIGINT", + "BIGINT", + "BIGINT", + "INT" +)} + 1; Index: UserProblem.pm =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D RCS file: /webwork/cvs/system/webwork2/lib/WeBWorK/DB/Record/UserProblem.= pm,v retrieving revision 1.4 retrieving revision 1.4.8.1 diff -Llib/WeBWorK/DB/Record/UserProblem.pm -Llib/WeBWorK/DB/Record/UserP= roblem.pm -u -r1.4 -r1.4.8.1 --- lib/WeBWorK/DB/Record/UserProblem.pm +++ lib/WeBWorK/DB/Record/UserProblem.pm @@ -60,4 +60,22 @@ num_incorrect )} =20 +# Should value be float instead of text? + +sub SQL_TYPES {qw( + BLOB + BLOB + INT + TEXT + INT + INT + INT + TEXT + INT + TEXT + INT + INT +)} + + 1; --- /dev/null +++ lib/WeBWorK/DBv3/Utils.pm @@ -0,0 +1,115 @@ +########################################################################= ######## +# WeBWorK Online Homework Delivery System +# Copyright =A9 2000-2003 The WeBWorK Project, http://openwebwork.sf.net= / +# $CVSHeader: webwork2/lib/WeBWorK/DBv3/Utils.pm,v 1.2.2.1 2005/06/10 02= :18:25 gage Exp $ +#=20 +# This program is free software; you can redistribute it and/or modify i= t under +# the terms of either: (a) the GNU General Public License as published b= y the +# Free Software Foundation; either version 2, or (at your option) any la= ter +# version, or (b) the "Artistic License" which comes with this package. +#=20 +# This program is distributed in the hope that it will be useful, but WI= THOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or = FITNESS +# FOR A PARTICULAR PURPOSE. See either the GNU General Public License o= r the +# Artistic License for more details. +########################################################################= ######## + +package WeBWorK::DBv3::Utils; +use base qw(Exporter); + +=3Dhead1 NAME + +WeBWorK::DBv3::Utils - useful utilities for WWDBv3. + +=3Dcut + +use strict; +use warnings; +use DBI; +use Fcntl qw/:DEFAULT :flock/; + +use constant GET_VERSION =3D> "SELECT `val` FROM `setting` WHERE `name`=3D= 'db_version'"; +use constant INCR_VERSION =3D> "UPDATE `setting` SET `val`=3D`val`+1 WHE= RE `name`=3D'db_version'"; +use constant DB_VERSION =3D> 1; +use constant DELTAS =3D> [ + q/ # DUMMY VALUE FOR 0 /, + q/ # DUMMY VALUE FOR 1 /, +]; + +our @EXPORT =3D qw( + upgrade_schema +); + +=3Dhead1 FUNCTIONS + +=3Dhead2 upgrade_schema + + upgrade_schema($dbh, $lockfile) + +This is a private subroutine, but it has interesting behavior and is the= refore +documented here. It should only be called by WeBWorK::DBv3. + +Checks the 'db_version' setting in the C<setting> table of the specified= WWDBv3 +database. If it is less than the current version (defined by the constan= t +C<DB_VERSION> in this file), deltas are applied to the database to updat= e it. + +A lockfile is used to prevent concurrent execution of this subroutine. H= owever, +it does not protect against concurrent execution on the same database fr= om +separate machines. + +Any database error causes an exception to be thrown. + +=3Dcut + +sub upgrade_schema { + my ($dbh, $lockfile) =3D @_; + my $dsn =3D "dbi:" . $dbh->{Driver}->{Name} . ":" . $dbh->{Name}; +=09 + # use the upgrade_lock to protect this critical section + local *LOCK; + sysopen LOCK, $lockfile, O_RDONLY|O_CREAT + or die "failed to sysopen WWDBv3 upgrade lock '$lockfile' with flags '= O_RDONLY|OCREAT': $!"; + flock LOCK, LOCK_EX + or die "failed to flock WWDBv3 upgrade lock '$lockfile' with flags 'LO= CK_EX': $!"; +=09 + my @record =3D $dbh->selectrow_array(GET_VERSION); + if (@record) { + my $db_version =3D $record[0]; + if ($db_version !~ /^-?\d+$/) { + warn "System setting 'db_version' in WWDBv3 database '$dsn' has non-n= umeric value '$db_version'. Assuming database schema is up-to-date.\n"; + } elsif ($db_version =3D=3D DB_VERSION) { + # database is fine :) + } elsif ($db_version < 1) { + warn "System setting 'db_version' in WWDBv3 database '$dsn' has nonse= nsical value '$db_version'. Assuming database schema is up-to-date.\n"; + } elsif ($db_version > DB_VERSION) { + warn "System setting 'db_version' in WWDBv3 database '$dsn' has futur= e value '$db_version'. Assuming database schema is up-to-date.\n"; + } else { + warn "WWDBv3 schema at version '$db_version', current version is '@{[= DB_VERSION]}'. Upgrade required.\n"; + =09 + foreach my $version ($db_version+1 .. DB_VERSION) { + my $delta =3D DELTAS->[$version]; + =09 + unless ($dbh->do($delta)) { + warn "Failed to apply schema delta '$version' to WWDBv3 database '$= dsn'. Bailing out. DBI error: $DBI::errstr"; + last; + } + =09 + unless ($dbh->do(INCR_VERSION)) { + warn "Failed to increment system setting 'db_version' in WWDBv3 dat= abase '$dsn'. Bailing out. DBI error: $DBI::errstr"; + last; + } + =09 + warn "Upgraded WWDBv3 schema to version '$version'.\n"; + } + } + } else { + # Value doesn't exist yet. We could add it, but there's no sensible + # default since we can't tell what state the database is in otherwise. + warn "System setting 'db_version' not found in WWDBv3 database '$dsn'.= Assuming database schema is up-to-date.\n"; + } +=09 + # we're done, disconnect and unlock + close LOCK; +} + +1; --- /dev/null +++ lib/WeBWorK/DBv3/NormalizerMixin.pm @@ -0,0 +1,153 @@ +package WeBWorK::DBv3::NormalizerMixin; + +=3Dhead1 NAME + +WeBWorK::DBv3NormalizerMixin - Mixin to add/call inhertiable normalizers. + +=3Dhead1 SYNOPSIS + + package My::DB; + use base "Class::DBI"; + use WeBWorK::DBv3::NormalizerMixin; +=20 + # overload Class::DBI's empty normalize_column_values method to use cal= l_normalizer(). + sub normalize_column_values { + my ($self, $column_values) =3D @_; + =09 + my @errors; + =09 + foreach my $column (keys %$column_values) { + #warn "callig normalizers for column '$column'.\n"; + eval { $self->call_normalizer($column_values, $column) }; + push @errors, $column =3D> $@ if $@; + } + =09 + return unless @errors; + $self->_croak( + "normalize_column_values error: " . join(" ", @errors), + method =3D> "normalize_column_values", + data =3D> { @errors }, + ); + } +=20 + package My::DB::SomeTable; +=20 + # ... other Class::DBI stuff here ... +=20 + # add normalizers for various fields + __PACKAGE__->add_normalizer(field =3D> \&normalizer_sub); + +=3Dcut + +use strict; +use warnings; +use Class::Data::Inheritable; +use Carp (); + +sub import { + my ($invocant) =3D @_; + my $pkg =3D caller(0); +=09 + # XXX 5.005_03 isa() is broken with MI + unless ($pkg->can('mk_classdata')) { + no strict 'refs'; + push @{"$pkg\::ISA"}, 'Class::Data::Inheritable'; + } + + $pkg->mk_classdata('__normalizers'); + + # export mixin methods + no strict 'refs'; + my @methods =3D qw(add_normalizer call_normalizer); + *{"$pkg\::$_"} =3D \&{$_} for @methods; +} + +sub add_normalizer { + my ($invocant, @new) =3D @_; +=09 + my $normalizers =3D __fetch_normalizers($invocant) || {}; + my %normalizers =3D __deep_dereference($normalizers); +=09 + while (my ($column, $code) =3D splice @new, 0, 2) { + __validate_field($invocant, $column); + Carp::croak('add_normalizer() needs coderef') unless ref($code) eq 'CO= DE'; + push @{$normalizers{$column}}, $code; + } +=09 + __store_normalizers($invocant, \%normalizers); +} + +sub call_normalizer { + my ($invocant, $column_values, $column) =3D @_; +=09 + my $normalizers =3D __fetch_normalizers($invocant) || return; + if (exists $normalizers->{$column}) { + foreach my $code (@{$normalizers->{$column}}) { + #warn "call_normalizer: old value of column '$column': '", $column_va= lues->{$column}, "'.\n"; + $column_values->{$column} =3D $code->($column_values->{$column}); + #warn "call_normalizer: new value of column '$column': '", $column_va= lues->{$column}, "'.\n"; + } + } else { + __validate_field($invocant, $column); + } +} + +########################################################################= ######## + +sub __validate_field { + my ($invocant, $column) =3D @_; + Carp::croak("$column is not valid field for " . (ref($invocant) || $inv= ocant)) + unless $invocant->find_column($column) ? 1 : ""; +} + +sub __fetch_normalizers { + my ($invocant) =3D @_; +=09 + if (ref $invocant) { + # called with an instance, use the instance's normalizers + return $invocant->{__normalizers}; + } else { + # called with a class, use the class's normalizers + return $invocant->__normalizers; + } +} + +sub __store_normalizers { + my ($invocant, $normalizers) =3D @_; +=09 + if (ref $invocant) { + # called with an instance, use the instance's normalizers + $invocant->{__normalizers} =3D $normalizers; + } else { + # called with a class, use the class's normalizers + $invocant->__normalizers($normalizers); + } +} + +# straight from Class::Trigger -- two-level copy of hash-of-arrays. +sub __deep_dereference { + my $hashref =3D shift; + my %copy; + while (my($key, $arrayref) =3D each %$hashref) { + $copy{$key} =3D [ @$arrayref ]; + } + return %copy; +} + +########################################################################= ######## + +=3Dhead1 AUTHOR + +Written by Sam Hathaway, sh002i (at) math.rochester.edu. + +Based on Class::Trigger, which says: + + Original idea by Tony Bowden <to...@ka...> in Class::DBI. +=20 + Code by Tatsuhiko Miyagawa <miy...@bu...>. +=20 + Patches by Tim Buce <Tim...@po...>. + +=3Dcut + +1; Index: Local.pm =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D RCS file: /webwork/cvs/system/webwork2/lib/WeBWorK/PG/Local.pm,v retrieving revision 1.15 retrieving revision 1.15.2.1 diff -Llib/WeBWorK/PG/Local.pm -Llib/WeBWorK/PG/Local.pm -u -r1.15 -r1.15= .2.1 --- lib/WeBWorK/PG/Local.pm +++ lib/WeBWorK/PG/Local.pm @@ -95,8 +95,7 @@ # set the directory hash #warn "PG: setting the directory hash\n"; $translator->rh_directories({ - courseScriptsDirectory =3D> $ce->{pg}->{directories}->{macros}, - macroDirectory =3D> $ce->{courseDirs}->{macros}, + macrosPath =3D> $ce->{courseDirs}->{macrosPath}, templateDirectory =3D> $ce->{courseDirs}->{templates}, tempDirectory =3D> $ce->{courseDirs}->{html_temp}, }); @@ -180,17 +179,39 @@ =09 # store the problem source #warn "PG: storing the problem source\n"; - my $sourceFile =3D $problem->source_file; - $sourceFile =3D $ce->{courseDirs}->{templates}."/".$sourceFile - unless ($sourceFile =3D~ /^\//); - eval { $translator->source_string(readFile($sourceFile)) }; - if ($@) { + my $source =3D''; + my $sourceFilePath =3D ''; + my $readErrors =3D undef; + if (ref($translationOptions->{r_source}) ) { + # the source for the problem is already given to us as a reference to = a string + $source =3D ${$translationOptions->{r_source}}; + } else { + # the source isn't given to us so we need to read it=20 + # from a file defined by the problem + =20 + # we grab the sourceFilePath from the problem + $sourceFilePath =3D $problem->source_file; + =09 + # the path to the source file is usually given relative to the=20 + # the templates directory. Unless the path starts with / assume + # that it is relative to the templates directory + =20 + $sourceFilePath =3D $ce->{courseDirs}->{templates}."/" + .$sourceFilePath unless ($sourceFilePath =3D~ /^\//); + #now grab the source + eval {$source =3D readFile($sourceFilePath) }; + $readErrors =3D $@ if $@; + } + # put the source into the translator object + eval { $translator->source_string( $source ) } unless $readErrors; + $readErrors .=3D"\n $@ " if $@; + if ($readErrors) { # well, we couldn't get the problem source, for some reason. return bless { translator =3D> $translator, head_text =3D> "",=20 body_text =3D> <<EOF, -WeBWorK::Utils::readFile($sourceFile) says:=20 +WeBWorK::Utils::readFile($sourceFilePath) says:=20 $@ EOF answers =3D> {}, Index: webwork.apache-config.dist =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D RCS file: /webwork/cvs/system/webwork2/conf/webwork.apache-config.dist,v retrieving revision 1.4 retrieving revision 1.4.6.1 diff -Lconf/webwork.apache-config.dist -Lconf/webwork.apache-config.dist = -u -r1.4 -r1.4.6.1 --- conf/webwork.apache-config.dist +++ conf/webwork.apache-config.dist @@ -28,6 +28,9 @@ # Set this variable to the path to your WeBWorK installation. my $webwork_dir =3D "/opt/webwork2"; =20 +$ENV{WEBWORK_ROOT} =3D $webwork_dir; #allows the XMLRPC modules to find + # the webwork root directory + # This code reads global.conf and extracts the remaining configuration # variables. There is no need to modify it. eval "use lib '$webwork_dir/lib'"; die $@ if $@; @@ -40,6 +43,16 @@ my $webwork_courses_url =3D $ce->{webwork_courses_url}; my $webwork_courses_dir =3D $ce->{webwork_courses_dir}; eval "use lib '$pg_dir/lib'"; die $@ if $@; + +########################################## +# allows Webservice access to WeBWorK +########################################## + +eval "use WebworkWebservice";die $@ if $@; #FIXME, is there anoth= er way to initialize this? +eval "use RQP"; die $@ if $@; + +########################################## + $WeBWorK::SeedCE{webwork_dir} =3D $webwork_dir; =20 # Between the line below and the "EOF" line are the three configuration = stanzas @@ -73,7 +86,9 @@ #=20 Alias $webwork_htdocs_url $webwork_htdocs_dir <Directory $webwork_htdocs_dir> - Options None + Order Allow,Deny + Allow from All + Options FollowSymLinks AllowOverride None </Directory> =20 @@ -81,10 +96,49 @@ #=20 AliasMatch $webwork_courses_url/([^/]*)/(.*) $webwork_courses_dir/\$1/ht= ml/\$2 <Directory $webwork_courses_dir/*/html> + Order Allow,Deny + Allow from All Options FollowSymLinks AllowOverride None </Directory> =20 + ########## XMLRPC installation ########## +<Location /mod_xmlrpc> + SetHandler perl-script + PerlHandler Apache::XMLRPC::Lite + PerlSetVar dispatch_to "WebworkXMLRPC" + PerlSetVar options "compress_threshold =3D> 10000" + Order Allow,Deny + Allow from All +</Location> + ########## RQP installation ########## +=20 +#<Location /rqp> +# SetHandler perl-script +# PerlHandler Apache::SOAP +# PerlSetVar dispatch_to "RQP" +# PerlSetVar options "compress_threshold =3D> 10000" +# Order Allow,Deny +# Allow from All +#</Location> + +<Location /rqp> + SetHandler perl-script + PerlHandler MySOAP + Order Allow,Deny + Allow from All +</Location> + +########## SOAP installation ############ +<Location /mod_soap> + SetHandler perl-script + PerlHandler Apache::SOAP + PerlSetVar dispatch_to "WebworkXMLRPC"=20 + PerlSetVar options "compress_threshold =3D> 10000" + Order Allow,Deny + Allow from All +</Location> + EOF =20 </Perl> Index: global.conf.dist =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D RCS file: /webwork/cvs/system/webwork2/conf/global.conf.dist,v retrieving revision 1.111.2.5 retrieving revision 1.111.2.6 diff -Lconf/global.conf.dist -Lconf/global.conf.dist -u -r1.111.2.5 -r1.1= 11.2.6 --- conf/global.conf.dist +++ conf/global.conf.dist @@ -256,9 +256,13 @@ # The set header is displayed on the problem set page. It is a PG file. $webworkFiles{screenSnippets}{setHeader} =3D "$webworkDirs{conf}/= snippets/setHeader.pg"; # screenSetHeader.pg" =20 + # A PG template for creation of new problems. $webworkFiles{screenSnippets}{blankProblem} =3D "$webworkDirs{conf}/s= nippets/blankProblem.pg"; # screenSetHeader.pg" =20 +# A site info "message of the day" file +$webworkFiles{site_info} =3D "$webworkDirs{htdocs}= /site_info.txt"; + ########################################################################= ######## # Course-specific files ########################################################################= ######## @@ -299,7 +303,22 @@ }; =20 ########################################################################= ######## -# Database options +# Database options (WWDBv3) +########################################################################= ######## + +# The four arguments passed to the DBI::connect() method. See the DBI ma= nual for +# more information. +$wwdbv3_settings{dsn} =3D "dbi:mysql:wwdbv3"; +$wwdbv3_settings{user} =3D "wwdbv3"; +$wwdbv3_settings{pass} =3D "xyzzy"; +$wwdbv3_settings{attr} =3D {}; + +# WWDBv3 needs a lock file to prevent concurrent database upgrades. The = file +# will be locked with flock(). +$wwdbv3_settings{upgrade_lock} =3D "$webworkDirs{tmp}/wwdbv3_upgrade.loc= k"; + +########################################################################= ######## +# Database options (WWDBv2) ########################################################################= ######## =20 # Several database are defined in the file conf/database.conf and stored= in the @@ -425,7 +444,8 @@ my $nobody =3D undef; =20 %permissionLevels =3D ( - login =3D> $student, + + login =3D> $guest, report_bugs =3D> $student, submit_feedback =3D> $student, change_password =3D> $student, @@ -461,9 +481,9 @@ avoid_recording_answers =3D> $ta, check_answers_before_open_date =3D> $ta, check_answers_after_open_date_with_attempts =3D> $ta, - check_answers_after_open_date_without_attempts =3D> $student, - check_answers_after_due_date =3D> $student, - check_answers_after_answer_date =3D> $student, + check_answers_after_open_date_without_attempts =3D> $guest, + check_answers_after_due_date =3D> $guest, + check_answers_after_answer_date =3D> $guest, record_answers_when_acting_as_student =3D> $nobody, # "record_answers_when_acting_as_student" takes precedence # over the following for professors acting as students: @@ -597,6 +617,16 @@ $pg{directories}{lib} =3D "$pg{directories}{root}/lib"; $pg{directories}{macros} =3D "$pg{directories}{root}/macros"; =20 +# +# The macro file search path. Each directory in this list is seached +# (in this order) by loadMacros() when it looks for a .pl file. +# +$pg{directories}{macrosPath} =3D [ + ".", # search the problem file's directory + $courseDirs{macros}, + $pg{directories}{macros}, +]; + ##### "Special" PG environment variables. (Stuff that doesn't fit in any= where else.) =20 # Users for whom to print the file name of the PG file being processed. @@ -622,7 +652,7 @@ [qw(Exporter)], [qw(GD)], =09 - [qw(AlgParser AlgParserWithImplicitExpand Expr ExprWithImplicitExpand)]= , + [qw(AlgParser AlgParserWithImplicitExpand Expr ExprWithImplicitExpand u= tf8)], [qw(AnswerHash AnswerEvaluator)], [qw(WWPlot)], # required by Circle (and others) [qw(Circle)], Index: devel.apache-config.dist =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D RCS file: /webwork/cvs/system/webwork2/conf/devel.apache-config.dist,v retrieving revision 1.4 retrieving revision 1.4.2.1 diff -Lconf/devel.apache-config.dist -Lconf/devel.apache-config.dist -u -= r1.4 -r1.4.2.1 --- conf/devel.apache-config.dist +++ conf/devel.apache-config.dist @@ -26,8 +26,8 @@ # central location. #=20 # The second part is the stock webwork.apache-config file that is used f= or -# normal installations. Customize this file, setting $webwork_dir approp= riatly -# for your development server. +# normal installations. Customize this file, setting the $webwork_url, +# $webwork_dir, $pg_dir, etc. appropriatly for your development server. #=20 # The third part is this file. It contains the user-specific directives = that are # specific to each developer's server. @@ -59,7 +59,7 @@ $Group =3D $group_name; =20 # It will listen on a port equal to the UID of the user who starts it + = 10000. -$Port =3D $> + 10000; +$Port =3D $> + 7000; # effectively picks a port between 8000 and 8999 = since uid's are 1000+ =20 # Email address of server administator. $ServerAdmin =3D "$user_name\@$host_name"; @@ -101,3 +101,5 @@ # Stick any local additions down here ########################################################################= ######## =20 +#Alias /segue /home/gage/webwork/webwork-modperl/htdocs/segue +#Alias /moodle /home/gage/webwork/webwork-modperl/htdocs/moodle Index: SQL.pm =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D RCS file: /webwork/cvs/system/webwork2/lib/WeBWorK/DB/Schema/SQL.pm,v retrieving revision 1.24 retrieving revision 1.24.2.1 diff -Llib/WeBWorK/DB/Schema/SQL.pm -Llib/WeBWorK/DB/Schema/SQL.pm -u -r1= .24 -r1.24.2.1 --- lib/WeBWorK/DB/Schema/SQL.pm +++ lib/WeBWorK/DB/Schema/SQL.pm @@ -368,7 +368,11 @@ next unless defined $part; =09 $where .=3D " AND" unless $first; - $where .=3D " BINARY $name=3D?"; +# $where .=3D " BINARY $name=3D?"; + $where .=3D " $name=3D?"; ## Make lookups case insensitive. Otherwi= se + ## indices seem not to be used which slows things + ## down drastically. See =20 + ## ope...@li... discussion push @used_keyparts, $part; =09 $first =3D 0; Index: README =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D RCS file: /webwork/cvs/system/webwork2/README,v retrieving revision 1.10.2.3 retrieving revision 1.10.2.4 diff -LREADME -LREADME -u -r1.10.2.3 -r1.10.2.4 --- README +++ README @@ -1,4 +1,4 @@ - +this is a test WeBWorK = =20 Online Homework Delivery System = =20 Version 2.1.2 = =20 --- /dev/null +++ bin/ww_db_v2_to_v3 @@ -0,0 +1,703 @@ +#!/usr/bin/env perl +########################################################################= ######## +# WeBWorK Online Homework Delivery System +# Copyright =A9 2000-2003 The WeBWorK Project, http://openwebwork.sf.net= / +# $CVSHeader: webwork2/bin/ww_db_v2_to_v3,v 1.5.2.1 2005/06/10 02:18:20 = gage Exp $ +#=20 +# This program is free software; you can redistribute it and/or modify i= t under +# the terms of either: (a) the GNU General Public License as published b= y the +# Free Software Foundation; either version 2, or (at your option) any la= ter +# version, or (b) the "Artistic License" which comes with this package. +#=20 +# This program is distributed in the hope that it will be useful, but WI= THOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or = FITNESS +# FOR A PARTICULAR PURPOSE. See either the GNU General Public License o= r the +# Artistic License for more details. +########################################################################= ######## + +=3Dhead1 NAME + +ww_db_v2_to_v3 - convert a WWDBv2 database to a WWDBv3 database. + +=3Dhead1 SYNOPSIS + + ww_db_v2_to_v3 -crsuv course ... + +=3Dhead1 DESCRIPTION + +Copies course data from legacy WWDBv2 database(s) to a WWDBv3 database. = This may +take a long time. + +You must disallow login to the WeBWorK system while the transfer is taki= ng +place. To disable logins for all courses, set the permission level neces= sary for +C<login> to $nobody in F<global.conf>. (It is usually set to $student.) + +=3Dhead1 OPTIONS + +=3Dover + +=3Ditem -c + +If an error occurs while copying a course's data, continue copying with = the next +course. + +=3Ditem -r + +Update role table in WWDBv3 database from permission level information i= n +F<global.conf>. + +=3Ditem -s + +Update status table in WWDBv3 database from the status information in +F<global.conf>. + +=3Ditem -u + +When importing a user that already exists in the WWDBv3 database, replac= e the +existing information (including the password) with the information in th= e user +record being imported. + +If this option is not specified, existing users are not updated. + +=3Ditem -v + +Verbose operation. + +=3Ditem course ... + +Data from these courses will be copied. + +=3Dback + +=3Dhead1 BEHAVIOR + +=3Dhead2 ROLES + +=3Dover + +=3Ditem * + +Roles created with the -r switch are created as system-wide roles. + +=3Ditem * + +Roles are created by observing the %permissionLevels hash in F<global.co= nf>, and +collecting the privileges granted at each permission level into sets. Ea= ch set +of privileges becomes a WWDBv3 role record. + +=3Ditem * + +When a role with the same set of permissions already exists in the WWDBv= 3 +database, a new one is not created. + +=3Dback + +=3Dhead2 STATUSES + +=3Dover + +=3Ditem * + +Statuses created with the -s switch are created as system-wide statuses. + +=3Ditem * + +Statuses are created by observing the %{$siteDefaults{status}} hash in +F<global.conf>, and=20 + +=3Ditem * + +A status named "Enrolled" is imported into the database with the +C<allow_course_access>, C<include_in_assignment>, C<include_in_stats>, a= nd +C<include_in_scoring> flags set. + +=3Ditem * + +A status named "Audit" is imported into the database with the +C<allow_course_access>, C<include_in_assignment>, and C<include_in_stats= >, flags +set, and the C<include_in_scoring> flag unset. + +=3Ditem * + +A status named "Drop" is imported into the database with the +C<allow_course_access>, C<include_in_assignment>, C<include_in_stats>, a= nd +C<include_in_scoring> flags unset. + +=3Ditem * + +Statuses with other names are imported into the database with the same f= lags set +as the "Enrolled" flag. + +=3Dback + +=3Dhead2 USERS + +=3Dover + +=3Ditem * + +WWDBv2 user IDs are converted to login IDs. + +=3Ditem * + +Users with the same v2 user ID in different courses are assumed to be th= e same +user. + +=3Ditem * + +A user's permission level is used to determine the role to assign to the= ir v3 +participant record. (See L<ROLES>.) If the user has an empty permission = level, +they are assigned the role associated with permission level "0". + +=3Ditem * + +A user's status abbreviation is used to determine the status to assign t= o their +v3 participant record. (See L<STATUSES>.) If the user has an empty statu= s, they +are assigned the status "Enrolled". + +=3Ditem * + +If a user has a non-empty section or recitation, their v3 participant re= cord +will be assigned to the section or recitation with a matching name. + +=3Dback + +=3Dcut + +use strict; +use warnings; +use Data::Dumper; +use DateTime; +use Getopt::Std; + +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::DB; +use WeBWorK::DBv3; + +# map statuses from course environment to sets of status privileges +use constant STATUS_MAP =3D> { + Enrolled =3D> { allow_course_access =3D> 1, include_in_assignment =3D> = 1, include_in_stats =3D> 1, include_in_scoring =3D> 1 }, + Audit =3D> { allow_course_access =3D> 1, include_in_assignment =3D> = 1, include_in_stats =3D> 1, include_in_scoring =3D> 0 }, + Drop =3D> { allow_course_access =3D> 0, include_in_assignment =3D> = 0, include_in_stats =3D> 0, include_in_scoring =3D> 0 }, +}; + +use constant DEFAULT_STATUS =3D> "C"; +use constant DEFAULT_PERMISSION_LEVEL =3D> "0"; + +our ($opt_c, $opt_r, $opt_s, $opt_u, $opt_v); +getopts("crsuv"); + +sub debug { print STDERR @_ if $opt_v } +sub usage { print STDERR "usage: $0 [-crsuv] course ...\n"; exit 1 } + +main(@ARGV); + +sub main { + my (@courseIDs) =3D @_; +=09 + usage() unless @courseIDs; +=09 + my $ce =3D WeBWorK::CourseEnvironment->new({webwork_dir =3D> $ENV{WEBWO= RK_ROOT}}); +=09 + WeBWorK::DBv3::init($ce->{wwdbv3_settings}); +=09 + my %abbrev_to_status_id =3D set_up_statuses($ce->{siteDefaults}{status}= ); + warn "abbrev_to_status_id: ", Dumper(\%abbrev_to_status_id); +=09 + my %level_to_role_id =3D set_up_roles($ce->{permissionLevels}); + warn "level_to_role_id: ", Dumper(\%level_to_role_id); +=09 + foreach my $courseID (@courseIDs) { + eval { copy_course_data($courseID, \%abbrev_to_status_id, \%level_to_r= ole_id) }; + if ($@) { + warn "An error occured while copying data from course '$courseID':\n\= n$@\n\n"; + if ($opt_c) { + warn "Continuing with the next course...\n"; + } else { + warn "Exiting.\n"; + exit 2; + } + } + } +} + +########################################################################= ######## + +sub reverse_hash { + my (%hash) =3D @_; +=09 + my %reverse_hash; + foreach my $key (keys %hash) { + my $value =3D $hash{$key}; + if (defined $value and not ref $value) { + push @{ $reverse_hash{$value} }, $key; + #} else { + # my $val_string =3D defined $value ? $value : "UNDEF"; + # warn "pair ( $key =3D> $val_string ) skipped.\n"; + } + } +=09 + return %reverse_hash; +} + +sub listeq { + my ($a, $b) =3D @_; + return "" unless @$a =3D=3D @$b; + for (my $i =3D 0; $i < @$a; $i++) { + return "" unless $a->[$i] eq $b->[$i]; + } + return 1; +} + +sub is_empty { + my ($val) =3D @_; + return (not defined $val or $val eq ""); +} + +########################################################################= ######## + +sub set_up_roles { + my ($permissionLevels) =3D @_; + my %permissionLevels =3D %$permissionLevels; +=09 + my %level_to_role_id; +=09 + # reverse the permission levels hash, resulting in a hash mapping + # permissions levels to arrayrefs containing privileges + my %levels =3D reverse_hash(%permissionLevels); +=09 + # copy up the privileges at each level to the next-higher level + # also sort each level + my @level_names =3D sort { $a <=3D> $b } keys %levels; + foreach my $i (0 .. $#level_names-1) { + my $this_level =3D $level_names[$i]; + my $next_level =3D $level_names[$i+1]; + push @{ $levels{$next_level} }, @{ $levels{$this_level} }; + } +=09 + # sort the privileges in each level + debug("I found the following permission levels:\n"); + foreach my $level (keys %levels) { + my @sorted =3D sort @{ $levels{$level} }; + $levels{$level} =3D [ @sorted ]; + debug("\t$level =3D> @sorted\n"); + } +=09 + # keep track of role names so we know if we need to rename any of our n= ew ones + my %role_names; +=09 + # look at existing roles to see if we can avoid adding some new ones + my $i =3D retrieve_all WeBWorK::DBv3::Role; + while (my $Role =3D $i->next) { + $role_names{$Role->name} =3D 1; + my @role_privs =3D sort $Role->privs_list; + =09 + foreach my $level (keys %levels) { + if (listeq($levels{$level}, \@role_privs)) { + debug("Permission level '$level' is already represented as role '", + $Role->name, "' (ID $Role) -- skipping.\n"); + delete $levels{$level}; + $level_to_role_id{$level} =3D $Role->id; + } + } + } +=09 + if ($opt_r) { + debug("Updating role table (as per -r switch).\n"); + foreach my $level (keys %levels) { + my $name =3D "Legacy permission level $level"; + if (exists $role_names{$name}) { + my $i =3D 2; + while (1) { + my $try_name =3D "$name (#$i)"; + if (not exists $role_names{$try_name}) { + $name =3D $try_name; + last; + } + } + } + =09 + my @privs =3D @{ $levels{$level} }; + =09 + my $Role =3D create WeBWorK::DBv3::Role({name =3D> $name}); + $Role->privs_list(@privs); + $Role->update; + debug("Added role '", $Role->name, "' (ID $Role) with privileges '@pr= ivs'.\n"); + $level_to_role_id{$level} =3D $Role->id; + } + } else { + debug("Not updating role table (as per lack of -r switch).\n"); + debug("I might run into users with permission levels that don't map to= roles later.\n"); + } +=09 + return %level_to_role_id; +} + +sub set_up_statuses { + my ($abbrevs) =3D @_; + my %abbrevs =3D %$abbrevs; +=09 + my %abbrev_to_status_id; +=09 + # reverse the statuses hash, resulting in a hash mapping statuses to + # arrayrefs containing abbreviations + my %statuses =3D reverse_hash(%abbrevs); +=09 + # look at existing statuses to see if we can avoid adding some new ones + my $i =3D retrieve_all WeBWorK::DBv3::Status; + while (my $Status =3D $i->next) { + if (exists $statuses{$Status->name}) { + debug("Status '", $Status->name, "' (ID $Status) already exists in th= e database -- skipping.\n"); + # add entries mapping abbreviations to the ID of this status + foreach my $abbrev (@{$statuses{$Status->name}}) { + $abbrev_to_status_id{$abbrev} =3D $Status->id; + } + =09 + delete $statuses{$Status->name}; + } + } +=09 + if ($opt_s) { + debug("Updating status table (as per -s switch).\n"); + foreach my $status (keys %statuses) { + my %flags; + %flags =3D %{ STATUS_MAP->{$status} } if exists STATUS_MAP->{$status}= ; + my $Status =3D create WeBWorK::DBv3::Status({name =3D> $status, %flag= s}); + =09 + my @flags =3D grep { $flags{$_} } keys %flags; + debug("Added status '", $Status->name, "' (ID $Status) with flags '@f= lags'.\n"); + =09 + # add entries mapping abbreviations to the ID of this status + foreach my $abbrev (@{$statuses{$status}}) { + $abbrev_to_status_id{$abbrev} =3D $Status->id; + } + } + } else { + debug("Not updating status table (as per lack of -s switch).\n"); + debug("I might run into users with status abbreviations that don't map= to statuses later.\n"); + } +=09 + return %abbrev_to_status_id; +} + +########################################################################= ######## + +sub copy_course_data { + my ($courseID, $abbrev_to_status_id, $level_to_role_id) =3D @_; +=09 + debug("Processing course '$courseID'...\n"); +=09 + my $course_ce =3D WeBWorK::CourseEnvironment->new({ + webwork_dir =3D> $ENV{WEBWORK_ROOT}, + courseName =3D> $courseID, + }); +=09 + my $course_db =3D WeBWorK::DB->new($course_ce->{dbLayout}); +=09 + debug("Adding course '$courseID' to v3 DB.\n"); + my $v3Course =3D eval { create WeBWorK::DBv3::Course({name =3D> $course= ID}) }; + $@ =3D~ /Duplicate entry/ and die "Course '$courseID' exists in v3 DB.\= n"; + $@ and die $@; +=09 + copy_users($course_db, $v3Course, $abbrev_to_status_id, $level_to_role_= id); +=09 + # { $globalSetID =3D> [ $v3AbsSet->id, { $globalProblemID =3D> $vAbsPro= b->id, ... }, ... } + my %global_set_id_to_abstract_set_data =3D copy_abstract_data($course_d= b, $v3Course); +} + +########################################################################= ######## + +sub copy_users { + my ($course_db, $v3Course, $abbrev_to_status_id, $level_to_role_id) =3D= @_; +=09 + my $DefaultStatus =3D find_status(DEFAULT_STATUS, $abbrev_to_status_id)= ; + die "Default status '", DEFAULT_STATUS, "' does not correspond to any v= 3 status.\n" + unless $DefaultStatus; +=09 + my $DefaultRole =3D find_role(DEFAULT_PERMISSION_LEVEL, $level_to_role_= id); + die "Default permission level '", DEFAULT_PERMISSION_LEVEL, "' does not= correspond to any v3 role.\n" + unless $DefaultRole; +=09 + my @userIDs =3D $course_db->listUsers; + my %Users; @Users{@userIDs} =3D $course_db->getUsers(@userIDs); + my %Passwords; @Passwords{@userIDs} =3D $course_db->getPasswords(@userI= Ds); + my %PermissionLevels; @PermissionLevels{@userIDs} =3D $course_db->getPe= rmissionLevels(@userIDs); +=09 + foreach my $userID (keys %Users) { + my $User =3D $Users{$userID}; + my $Password =3D $Passwords{$userID}; + my $PermissionLevel =3D $PermissionLevels{$userID}; + =09 + unless (defined $User) { + debug("User record for user ID '$userID' not found -- skipping.\n"); + next; + } + =09 + debug("Processing user '$userID'...\n"); + =09 + # create/update user record + my ($v3User) =3D search WeBWorK::DBv3::User(login_id =3D> $userID); + if ($v3User) { + debug("A user with login_id '$userID' exists in v3 database -- "); + if ($opt_u) { + # password record might not exist (annoying...) + my $password =3D defined $Password ? $Password->password : ""; + =09 + debug("updating (as per -u switch).\n"); + $v3User->first_name($User->first_name) unless is_empty($User->first_= name); + $v3User->last_name($User->first_name) unless is_empty($User->last_na= me); + $v3User->email_address($User->email_address) unless is_empty($User->= email_address); + $v3User->student_id($User->student_id) unless is_empty($User->studen= t_id); + $v3User->password($password) unless is_empty($password); + $v3User->update; + } else { + debug("not updating (as per lack of -u switch).\n"); + } + } else { + # password record might not exist (annoying...) + my $password =3D defined $Password ? $Password->password : ""; + =09 + debug("No user with login_id '$userID' exists in v3 database -- addin= g.\n"); + $v3User =3D create WeBWorK::DBv3::User({ + first_name =3D> $User->first_name, + last_name =3D> $User->last_name, + email_address =3D> $User->email_address, + student_id =3D> $User->student_id, + login_id =3D> $User->user_id, + password =3D> $password, + }); + } + =09 + # get status + my $v3Status =3D find_status($User->status, $abbrev_to_status_id); + unless ($v3Status) { + debug("Using default status '", $DefaultStatus->name, "'.\n"); + $v3Status =3D $DefaultStatus; + } + =09 + # get role + my $level =3D defined $PermissionLevel ? $PermissionLevel->permission = : ""; + my $v3Role =3D find_role($level, $level_to_role_id); + unless ($v3Role) { + debug("Using default role '", $DefaultRole->name, "'.\n"); + $v3Role =3D $DefaultRole; + } + =09 + # find/create section record + my $section =3D $User->section; + my $v3Section; + if (is_empty($section)) { + debug("This user has section '$section'.\n"); + ($v3Section) =3D search WeBWorK::DBv3::Section(course =3D> $v3Course,= name =3D> $section); + if ($v3Section) { + debug("This corresponds to existing section ID $v3Section in v3 data= base.\n"); + } else { + debug("No corresponding section exists in v3 DB -- adding.\n"); + $v3Section =3D create WeBWorK::DBv3::Section({ + course =3D> $v3Course, + name =3D> $section, + }); + debug("Added section '", $v3Section->name, "' (ID $v3Section).\n"); + } + } else { + debug("This user has no section.\n"); + } + =09 + # find/create recitation record + my $recitation =3D $User->recitation; + my $v3Recitation; + if (is_empty($recitation)) { + debug("This user has recitation '$recitation'.\n"); + ($v3Recitation) =3D search WeBWorK::DBv3::Recitation(course =3D> $v3= Course, name =3D> $User->recitation); + if ($v3Recitation) { + debug("This correponds to existing recitation ID $v3Recitation in v3= database.\n"); + } else { + debug("No corresponding recitation exists in v3 DB -- adding.\n"); + $v3Recitation =3D create WeBWorK::DBv3::Recitation({ + course =3D> $v3Course, + name =3D> $User->recitation, + }); + debug("Added recitation '", $v3Recitation->name, "' (ID $v3Recitatio= n).\n"); + } + } else { + debug("This user has no recitation.\n"); + } + =09 + # create participant record + debug("Adding participant record for user '$userID'..."); + #my $sectionID =3D $v3Section->id if defined $v3Section; + #my $recitationID =3D $v3Recitation->id if defined $v3Recitation; + my $v3Participant =3D create WeBWorK::DBv3::Participant({ + course =3D> $v3Course, + user =3D> $v3User, + status =3D> $v3Status, + role =3D> $v3Role, + section =3D> $v3Section, + recitation =3D> $v3Recitation, + comment =3D> $User->comment, + }); + debug(" added participant ID $v3Participant.\n"); + } +} + +sub find_status { + my ($status, $abbrev_to_status_id) =3D @_; +=09 + return if is_empty($status); +=09 + my $v3Status_id =3D $abbrev_to_status_id->{$status}; + my $v3Status; + if (defined $v3Status_id) { + #debug("Status '$status' maps to v3 status ID '$v3Status_id'.\n"); + $v3Status =3D retrieve WeBWorK::DBv3::Status($v3Status_id); + } else { + #debug("Status '$status' doesn't map to any v3 status.\n"); + } +=09 + return $v3Status; +} + +sub find_role { + my ($level, $level_to_role_id) =3D @_; +=09 + return if is_empty($level); +=09 + my $v3Role_id =3D $level_to_role_id->{$level}; + my $v3Role; + if (defined $v3Role_id) { + #debug("Permission level '$level' maps to v3 role ID '$v3Role_id'.\n")= ; + $v3Role =3D retrieve WeBWorK::DBv3::Role($v3Role_id); + } else { + #debug("Permission level '$level' doesn't map to any v3 role.\n"); + } +=09 + return $v3Role; +} + +########################################################################= ######## + +sub copy_abstract_data { + my ($course_db, $v3Course) =3D @_; +=09 + my %global_set_id_to_abstract_set_data; +=09 + my @globalSetIDs =3D $course_db->listGlobalSets; + my %GlobalSets; @GlobalSets{@globalSetIDs} =3D $course_db->getGlobalSet= s(@globalSetIDs); +=09 + foreach my $globalSetID (keys %GlobalSets) { + my $GlobalSet =3D $GlobalSets{$globalSetID}; + =09 + unless (defined $GlobalSet) { + debug("Global set record for global set ID '$globalSetID' not found -= - skipping.\n"); + next; + } + =09 + debug("Processing global set '$globalSetID'...\n"); + =09 + # set up some fields that need setting up + # (if the conditional is false, the variable is left undefined) + =09 + # convert empty strings to undefined values + my $set_header =3D $GlobalSet->set_header unless is_empty($GlobalSet->= set_header); + my $hardcopy_header =3D $GlobalSet->hardcopy_header unless is_empty($G= lobalSet->hardcopy_header); + =09 + # convert=20 + my $open_date =3D DateTime->from_epoch(epoch =3D> $GlobalSet->open_dat= e); + my $due_date =3D DateTime->from_epoch(epoch =3D> $GlobalSet->due_date)= ; + my $answer_date =3D DateTime->from_epoch(epoch =3D> $GlobalSet->answer= _date); + =09 + # create abstract_set record + debug("Adding abstract_set record for global set '$globalSetID'..."); + my $v3AbsSet =3D create WeBWorK::DBv3::AbstractSet({ + course =3D> $v3Course, + name =3D> $GlobalSet->set_id, + set_header =3D> $set_header, + hardcopy_header =3D> $hardcopy_header, + open_date =3D> $open_date, + due_date =3D> $due_date, + answer_date =3D> $answer_date, + published =3D> $GlobalSet->published, + }); + debug(" added abstract_set ID '$v3AbsSet'.\n"); + =09 + =09 + my %problem_mapping; + =09 + my @globalProblemIDs =3D sort { $a <=3D> $b } $course_db->listGlobalPr= oblems($globalSetID); + warn "globalProblemIDs=3D@globalProblemIDs\n"; + my %GlobalProblems; @GlobalProblems{@globalProblemIDs} + =3D $course_db->getGlobalProblems(map { [ $globalSetID, $_ ] } @globa= lProblemIDs); + =09 + my @problem_order; + =09 + foreach my $globalProblemID (@globalProblemIDs) { + my $GlobalProblem =3D $GlobalProblems{$globalProblemID}; + =09 + unless (defined $GlobalProblem) { + warn "Global problem record for global problem ID '$globalProblemID'= in set ID '$globalSetID' not found -- skipping.\n"; + next; + } + =09 + debug("Processing global problem '$globalProblemID'...\n"); + =09 + # convert max_attempts of -1 to undef + my $max_attempts_per_version =3D $GlobalProblem->max_attempts + if $GlobalProblem->max_attempts >=3D 0; + =09 + # create abstract_problem record + debug("Adding abstract_set record for global problem '$globalProblemI= D'..."); + my $v3AbsProb =3D create WeBWorK::DBv3::AbstractProblem({ + abstract_set =3D> $v3AbsSet, + name =3D> "Legacy problem $globalProblemID", + source_type =3D> "file", + source_file =3D> $GlobalProblem->source_file, + weight =3D> $GlobalProblem->value, + max_attempts_per_version =3D> $max_attempts_per_version, + version_creation_interval =3D> undef, + versions_per_interval =3D> 1, + version_due_date_offset =3D> undef, + version_answer_date_offset =3D> undef, + }); + debug(" added abstract_problem ID '$v3AbsProb'.\n"); + =09 + push @problem_order, $v3AbsProb->id; + =09 + $problem_mapping{$globalProblemID} =3D $v3AbsProb->id; + } + =09 + # update problem order + debug("Setting problem order to: '@problem_order'..."); + $v3AbsSet->problem_order_list(@problem_order); + $v3AbsSet->update; + debug(" done.\n"); + =09 + $global_set_id_to_abstract_set_data{$globalSetID} =3D [ $v3AbsSet->id,= \%problem_mapping ]; + } +=09 + return %global_set_id_to_abstract_set_data; +} + +########################################################################= ######## + +sub copy_assignment_data { + my ($course_db, $v3Course, $global_set_id_to_abstract_set_data) =3D @_; +=09 + my $participant_iter =3D WeBWorK::DBv3::Participant->search(course =3D>= $v3Course); +=09 + while (my $Participant =3D $participant_iter->next) { + my @userSetIDs =3D $course_db->listUserSets($Participant->user->login_= id); + $v3AbsSet-> + } +} + +sub copy_single_assignment { + my ($course_db, $v3Course, $v3Participant, $v3AbsSet, $global_set_id_to= _abstract_set_data) =3D @_; +=09 +=09 +} --- /dev/null +++ clients/input.txt @@ -0,0 +1,99 @@ +##DESCRIPTION +## A very simple first problem +##ENDDESCRIPTION +##KEYWORDS('algebra') +DOCUMENT(); # This should be the first executable line in the pro= blem. +loadMacros( +"PG.pl", +"PGbasicmacros.pl", +"PGchoicemacros.pl", +"PGanswermacros.pl", +"PGauxiliaryFunctions.pl" +); + +TEXT(&beginproblem); +$showPartialCorrectAnswers =3D 1; +$a =3D random(-10,-1,1); +$b =3D random(1,11,1); +$c =3D random(1,11,1); +$d =3D random(1,11,1); + +BEGIN_TEXT +$PAR +displayMode is $displayMode $BR +$PAR +This problem demonstrates how you enter numerical answers into WeBWorK. = $PAR +Evaluate the expression \(3($a )($b -$c -2($d ))\): + + \{ ans_rule(10) \} + +$BR +END_TEXT +$ans =3D 3*($a)*($b-$c-2*($d)); + +&ANS(strict_num_cmp($ans)); + +BEGIN_TEXT + +In the case above you need to enter a number, since we're testing whethe= r you can multiply +out these numbers. (You can use a calculator if you want.)=20 +$PAR +For most problems, you will be able to get WeBWorK to +do some of the work for you. For example +$BR +Calculate ($a) * ($b): \{ ans_rule()\} +$BR +END_TEXT +$ans =3D $a*$b; + +&ANS(std_num_cmp($ans)); + +BEGIN_TEXT +The asterisk is what most computers use to denote multiplication and you= can use this with WeBWorK.=20 +But WeBWorK will also allow use to use a space to denote multiplication. +You can either \($a * $b\) or \{$a*$b\} or even \($a \ $b\). All will w= ork. Try them. =20 +$PAR +Now try calculating the sine of 45 degrees ( that's sine of pi over 4 in= radians +and numerically sin(pi/4) equals \{1/sqrt(2)\} or, more precisely, \(1/= \sqrt{2} \) ). =20 +You can enter this as sin(pi/4) , as=20 +sin(3.1415926/4), as 1/sqrt(2), as 2**(-.5), etc. This is because WeBWor= K knows about=20 +functions like sin and sqrt (square root). (Note: exponents +can be indicated by either a "caret" or **). Try it.$BR \( \sin(\pi/4) = =3D \) \{ ans_rule(20) \}$PAR + Here's the=20 +\{ +htmlLink(qq!http://webwork.math.rochester.edu/webwork_system_html/docs/d= ocs/pglanguage/availablefunctions.html!,"list=20 +of the functions") \} + which WeBWorK understands. WeBWorK ALWAYS uses radian mode for trig f= unctions.=20 + $PAR +END_TEXT + +&ANS( std_num_cmp(sin(3.1415926/4)) ); +BEGIN_TEXT +You can also use juxtaposition to denote multiplication. E.g. enter \( 2= \sin(3\pi/2) \). +You can enter this as 2*sin(3*pi/2) or more simply as 2sin(3pi/2). Try = it: $BR=20 +\{ ans_rule(20) \}$PAR + +END_TEXT + +$pi =3D 4*atan(1); +&ANS( std_num_cmp(2*sin(3*$pi/2)) ); + +BEGIN_TEXT +Sometimes you need to use ( )'s to make your meaning clear. E.g. 1/2+3 i= s 3.5, but 1/(2+3) is .2 Why? +Try entering both and use the ${LQ}Preview${RQ} button below to see the = difference. In addition to +( )'s, you can also use [ ]'s and $LB ${RB}'s. $BR +\{ ans_rule(20) \}$PAR +END_TEXT + +&ANS( std_num_cmp(.2)); + +BEGIN_TEXT +You can always try to enter answers and let WeBWorK do the calculating.=20 +WeBWorK will tell you if the problem requires a strict numerical answer.= =20 +The way we use WeBWorK in this class there is no penalty for getting an = answer wrong. What counts +is that you get the answer right eventually (before the due date). For = complicated answers, +you should use the ${LQ}Preview${RQ} button to check for syntax errors a= nd also to check that the answer +you enter is really what you think it is. +END_TEXT + +ENDDOCUMENT(); # This should be the last executable line in the p= roblem. --- /dev/null +++ clients/webwork_xmlrpc_client.pl @@ -0,0 +1,277 @@ +#!/usr/local/bin/perl -w + +use XMLRPC::Lite; + +# configuration section +use constant HOSTURL =3D> 'devel.webwork.rochester.edu';=20 +use constant HOSTPORT =3D> 8002; +use constant TRANSPORT_METHOD =3D> 'XMLRPC::Lite'; +use constant REQUEST_CLASS =3D>'WebworkXMLRPC'; # WebworkXMLRPC is use= d for soap also!! +use constant REQUEST_URI =3D>'mod_xmlrpc'; +my @COMMANDS =3D qw( listLibraries renderProblem ); #listLib readFi= le tex2pdf=20 + +# $pg{displayModes} =3D [ +# "plainText", # display raw TeX for math expressions +# "formattedText", # format math expressions using TtH +# "images", # display math expressions as images generated by dv= ipng +# "jsMath", # render TeX math expressions on the client side usi= ng jsMath +# "asciimath", # render TeX math expressions on the client side usi= ng ASCIIMathML +# ]; +use constant DISPLAYMODE =3D> 'images'; + +# end configuration section +use MIME::Base64 qw( encode_base64 decode_base64); + + +print STDERR "inputs are ", join (" | ", @ARGV), "\n"; +our $source; + +if (@ARGV) { + my $command =3D $ARGV[0]; + =20 + warn "executing WebworkXMLRPC.$command"; + $source =3D (defined $ARGV[1]) ? `cat $ARGV[1]` : '' ; + xmlrpcCall($command); + + +} else { + + print STDERR "Useage: .xmlrpc_client4.pl command file_name\n"; + print STDERR "For example: .xmlrpc_client4.pl renderProblem input.txt= \n"; + print STDERR "For example: .xmlrpc_client4.pl listLibraries \n"; + print STDERR "Commands are: ", join(" ", @COMMANDS), "\n"; +=09 +} + + + +sub xmlrpcCall { + my $command =3D shift; + $command =3D 'listLibraries' unless $command; + + my $requestResult =3D TRANSPORT_METHOD + #->uri('http://'.HOSTURL.':'.HOSTPORT.'/'.REQUEST_CLASS) + -> proxy('http://'.HOSTURL.':'.HOSTPORT.'/'.REQUEST_URI); + =09 + my $test =3D [3,4,5,6]; =20 + my $input =3D setInputTable(); + print "displayMode=3D",$input->{envir}->{displayMode},"\n"; + local( $result); + # use eval to catch errors + eval { $result =3D $requestResult->call(REQUEST_CLASS.'.'.$command,$i= nput) }; + print STDERR "There were a lot of errors\n" if $@; + print "Errors: \n $@\n End Errors\n" if $@; +=09 + print "result is|", ref($result),"|"; +=09 + unless (ref($result) and $result->fault) { + =20 + if (ref($result->result())=3D~/HASH/ and defined($result->result()->= {text}) ) { + $result->result()->{text} =3D decode_base64($result->result()->{tex= t}); + } + print pretty_print_rh($result->result()),"\n"; #$result->result() + } else { + print 'oops ', join ', ', + $result->faultcode, + $result->faultstring; + } +} + =20 +sub source { + encode_base64($source); +} +sub pretty_print_rh {=20 + shift if UNIVERSAL::isa($_[0] =3D> __PACKAGE__); + my $rh =3D shift; + my $indent =3D shift || 0; + my $out =3D ""; + my $type =3D ref($rh); + + if (defined($type) and $type) { + $out .=3D " type =3D $type; "; + } elsif (! defined($rh )) { + $out .=3D " type =3D UNDEFINED; "; + } + return $out." " unless defined($rh); +=09 + if ( ref($rh) =3D~/HASH/ or "$rh" =3D~/HASH/ ) { + $out .=3D "{\n"; + $indent++; + foreach my $key (sort keys %{$rh}) { + $out .=3D " "x$indent."$key =3D> " . pretty_print_rh( $rh->{$key}, = $indent ) . "\n"; + } + $indent--; + $out .=3D "\n"." "x$indent."}\n"; + + } elsif (ref($rh) =3D~ /ARRAY/ or "$rh" =3D~/ARRAY/) { + $out .=3D " ( "; + foreach my $elem ( @{$rh} ) { + $out .=3D pretty_print_rh($elem, $indent); + =09 + } + $out .=3D " ) \n"; + } elsif ( ref($rh) =3D~ /SCALAR/ ) { + $out .=3D "scalar reference ". ${$rh}; + } elsif ( ref($rh) =3D~/Base64/ ) { + $out .=3D "base64 reference " .$$rh; + } else { + $out .=3D $rh; + } +=09 + return $out." "; +} + +sub setInputTable_for_listLib { + $out =3D { + #password =3D> 'geometry', + pw =3D> 'geometry', + set =3D> 'set0', + library_name =3D> 'rochesterLibrary', + command =3D> 'all', + }; + + $out; +} +sub setInputTable { + $out =3D { + #password =3D> 'geometry', + pw =3D> 'geometry', + set =3D> 'set0', + library_name =3D> 'rochesterLibrary', + command =3D> 'all', + answer_form_submitted =3D> 1, + course =3D> 'daemon_course', + extra_packages_to_load =3D> [qw( AlgParserWithImplicitExpand Expr + ExprWithImplicitExpand AnswerEvaluator + AnswerEvaluatorMaker=20 + )], + mode =3D> 'HTML_dpng', + modules_to_evaluate =3D> [ qw(=20 +Exporter + +DynaLoader + + =09 +GD +WWPlot +Fun +Circle +Label + + =09 +PGrandom +Units +Hermite + +List + + =09 +Match +Multiple +Select + + =09 +AlgParser + +AnswerHash + + =09 +Fraction +VectorField + + =09 +Complex1 +Complex + + =09 +MatrixReal1 Matrix + + =09 +Distr... [truncated message content] |