[system] / branches / rel-2-4-patches / webwork2 / lib / WeBWorK / ContentGenerator / CourseAdmin.pm Repository:
ViewVC logotype

View of /branches/rel-2-4-patches/webwork2/lib/WeBWorK/ContentGenerator/CourseAdmin.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 3377 - (download) (as text) (annotate)
Thu Jul 14 13:15:27 2005 UTC (7 years, 10 months ago) by glarose
Original Path: trunk/webwork2/lib/WeBWorK/ContentGenerator/CourseAdmin.pm
File size: 54144 byte(s)
Preliminary commit of changes to add Gateway module.
This adds to WeBWorK
 - the ability to create versioned, timed problem sets ("gateway tests")
   for which all problems are displayed on a single page ("versioned"
   means that students can get multiple versions of the problem set),
 - the ability to create sets that draw problems from groups of
   problems, and
 - the ability to create sets that require a proctor login to start
   and grade.
Sets can be defined as gateway tests or proctored gateway tests from
the ProblemSetDetail page.

Not quite bug-free yet.  Known bugs include handling of problem values
on the Student Progress page (I think this may be a problem with
changing from sql database format where all entries were 'text' to
sql_single in ver 2.1, where they are integer), and a division by zero
error on the grades page (which may be the same problem).

Tests with a number of attempts per version greater than one haven't
been carefully tested, nor has scoring of gateway tests.

    1 ################################################################################
    2 # WeBWorK Online Homework Delivery System
    3 # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/
    4 # $CVSHeader: webwork2/lib/WeBWorK/ContentGenerator/CourseAdmin.pm,v 1.35 2005/06/10 15:59:52 gage Exp $
    5 #
    6 # This program is free software; you can redistribute it and/or modify it under
    7 # the terms of either: (a) the GNU General Public License as published by the
    8 # Free Software Foundation; either version 2, or (at your option) any later
    9 # version, or (b) the "Artistic License" which comes with this package.
   10 #
   11 # This program is distributed in the hope that it will be useful, but WITHOUT
   12 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
   13 # FOR A PARTICULAR PURPOSE.  See either the GNU General Public License or the
   14 # Artistic License for more details.
   15 ################################################################################
   16 
   17 package WeBWorK::ContentGenerator::CourseAdmin;
   18 use base qw(WeBWorK::ContentGenerator);
   19 
   20 =head1 NAME
   21 
   22 WeBWorK::ContentGenerator::CourseAdmin - Add, rename, and delete courses.
   23 
   24 =cut
   25 
   26 use strict;
   27 use warnings;
   28 use CGI::Pretty qw();
   29 use Data::Dumper;
   30 use File::Temp qw/tempfile/;
   31 use WeBWorK::CourseEnvironment;
   32 use IO::File;
   33 use WeBWorK::Utils qw(cryptPassword writeLog listFilesRecursive);
   34 use WeBWorK::Utils::CourseManagement qw(addCourse renameCourse deleteCourse listCourses);
   35 use WeBWorK::Utils::DBImportExport qw(dbExport dbImport);
   36 
   37 # put the following database layouts at the top of the list, in this order
   38 our @DB_LAYOUT_ORDER = qw/sql_single gdbm sql/;
   39 
   40 our %DB_LAYOUT_DESCRIPTIONS = (
   41   gdbm => "Deprecated. Uses GDBM databases to record WeBWorK data. Use this layout if the course must be used with WeBWorK 1.x.",
   42   sql => "Deprecated. Uses a separate SQL database to record WeBWorK data for each course.",
   43   sql_single => "Uses a single SQL database to record WeBWorK data for all courses using this layout. This is the recommended layout for new courses.",
   44 );
   45 
   46 sub pre_header_initialize {
   47   my ($self) = @_;
   48   my $r = $self->r;
   49   my $ce = $r->ce;
   50   my $db = $r->db;
   51   my $authz = $r->authz;
   52   my $urlpath = $r->urlpath;
   53   my $user        = $r->param('user');
   54 
   55   # check permissions
   56   unless ($authz->hasPermissions($user, "create_and_delete_courses")) {
   57     $self->addmessage( CGI::div({class=>'ResultsWithError'},"$user is not authorized to create or delete courses") );
   58     return;
   59   }
   60 
   61   # get result and send to message
   62   my $status_message = $r->param("status_message");
   63   $self->addmessage(CGI::p("$status_message")) if $status_message;
   64 
   65   ## if the user is asking for the downloaded database...
   66   #if (defined $r->param("download_exported_database")) {
   67   # my $courseID = $r->param("export_courseID");
   68   # my $random_chars = $r->param("download_exported_database");
   69   #
   70   # die "courseID not specified" unless defined $courseID;
   71   # die "invalid file specification" unless $random_chars =~ m/^\w+$/;
   72   #
   73   # my $tempdir = $ce->{webworkDirs}->{tmp};
   74   # my $export_file = "$tempdir/db_export_$random_chars";
   75   #
   76   # $self->reply_with_file("application/xml", $export_file, "${courseID}_database.xml", 0);
   77   #
   78   # return "";
   79   #}
   80   #
   81   ## otherwise...
   82 
   83   my @errors;
   84   my $method_to_call;
   85 
   86   my $subDisplay = $r->param("subDisplay");
   87   if (defined $subDisplay) {
   88 
   89     if ($subDisplay eq "add_course") {
   90       if (defined $r->param("add_course")) {
   91         @errors = $self->add_course_validate;
   92         if (@errors) {
   93           $method_to_call = "add_course_form";
   94         } else {
   95           $method_to_call = "do_add_course";
   96         }
   97       } else {
   98         $method_to_call = "add_course_form";
   99       }
  100     }
  101 
  102     elsif ($subDisplay eq "rename_course") {
  103       if (defined $r->param("rename_course")) {
  104         @errors = $self->rename_course_validate;
  105         if (@errors) {
  106           $method_to_call = "rename_course_form";
  107         } else {
  108           $method_to_call = "do_rename_course";
  109         }
  110       } else {
  111         $method_to_call = "rename_course_form";
  112       }
  113     }
  114 
  115     elsif ($subDisplay eq "delete_course") {
  116       if (defined $r->param("delete_course")) {
  117         # validate or confirm
  118         @errors = $self->delete_course_validate;
  119         if (@errors) {
  120           $method_to_call = "delete_course_form";
  121         } else {
  122           $method_to_call = "delete_course_confirm";
  123         }
  124       } elsif (defined $r->param("confirm_delete_course")) {
  125         # validate and delete
  126         @errors = $self->delete_course_validate;
  127         if (@errors) {
  128           $method_to_call = "delete_course_form";
  129         } else {
  130           $method_to_call = "do_delete_course";
  131         }
  132       } else {
  133         # form only
  134         $method_to_call = "delete_course_form";
  135       }
  136     }
  137 
  138     elsif ($subDisplay eq "export_database") {
  139       if (defined $r->param("export_database")) {
  140         @errors = $self->export_database_validate;
  141         if (@errors) {
  142           $method_to_call = "export_database_form";
  143         } else {
  144           # we have to do something special here, since we're sending
  145           # the database as we export it. $method_to_call still gets
  146           # set here, but it gets caught by header() and content()
  147           # below instead of by body().
  148           $method_to_call = "do_export_database";
  149         }
  150       } else {
  151         $method_to_call = "export_database_form";
  152       }
  153     }
  154 
  155     elsif ($subDisplay eq "import_database") {
  156       if (defined $r->param("import_database")) {
  157         @errors = $self->import_database_validate;
  158         if (@errors) {
  159           $method_to_call = "import_database_form";
  160         } else {
  161           $method_to_call = "do_import_database";
  162         }
  163       } else {
  164         $method_to_call = "import_database_form";
  165       }
  166     }
  167 
  168     else {
  169       @errors = "Unrecognized sub-display @{[ CGI::b($subDisplay) ]}.";
  170     }
  171 
  172   }
  173 
  174   $self->{errors} = \@errors;
  175   $self->{method_to_call} = $method_to_call;
  176 }
  177 
  178 sub header {
  179   my ($self) = @_;
  180   my $method_to_call = $self->{method_to_call};
  181 #   if (defined $method_to_call and $method_to_call eq "do_export_database") {
  182 #     my $r = $self->r;
  183 #     my $courseID = $r->param("export_courseID");
  184 #     $r->content_type("application/octet-stream");
  185 #     $r->header_out("Content-Disposition" => "attachment; filename=\"${courseID}_database.xml\"");
  186 #     $r->send_http_header;
  187 #   } else {
  188     $self->SUPER::header;
  189 # }
  190 }
  191 
  192 # sends:
  193 #
  194 # HTTP/1.1 200 OK
  195 # Date: Fri, 09 Jul 2004 19:05:55 GMT
  196 # Server: Apache/1.3.27 (Unix) mod_perl/1.27
  197 # Content-Disposition: attachment; filename="mth143_database.xml"
  198 # Connection: close
  199 # Content-Type: application/octet-stream
  200 
  201 sub content {
  202   my ($self) = @_;
  203   my $method_to_call = $self->{method_to_call};
  204   if (defined $method_to_call and $method_to_call eq "do_export_database") {
  205     #$self->do_export_database;
  206     $self->SUPER::content;
  207   } else {
  208     $self->SUPER::content;
  209   }
  210 }
  211 
  212 sub body {
  213   my ($self) = @_;
  214   my $r = $self->r;
  215   my $ce = $r->ce;
  216   my $db = $r->db;
  217   my $authz = $r->authz;
  218   my $urlpath = $r->urlpath;
  219 
  220   my $user = $r->param('user');
  221 
  222   # check permissions
  223   unless ($authz->hasPermissions($user, "create_and_delete_courses")) {
  224     return "";
  225   }
  226   my $method_to_call = $self->{method_to_call};
  227   my $methodMessage ="";
  228 
  229   (defined($method_to_call) and $method_to_call eq "do_export_database") && do {
  230       my @export_courseID = $r->param("export_courseID");
  231       my $course_ids = join(", ", @export_courseID);
  232     $methodMessage  = CGI::p("Exporting database for course(s) $course_ids").
  233     CGI::p(".... please wait....
  234     If your browser times out you will
  235     still be able to download the exported database using the
  236     file manager.").CGI::hr();
  237   };
  238 
  239 
  240   print CGI::p({style=>"text-align: center"},
  241     CGI::a({href=>$self->systemLink($urlpath, params=>{subDisplay=>"add_course"})}, "Add Course"),
  242     " | ",
  243     CGI::a({href=>$self->systemLink($urlpath, params=>{subDisplay=>"rename_course"})}, "Rename Course"),
  244     " | ",
  245     CGI::a({href=>$self->systemLink($urlpath, params=>{subDisplay=>"delete_course"})}, "Delete Course"),
  246     " | ",
  247     CGI::a({href=>$self->systemLink($urlpath, params=>{subDisplay=>"export_database"})}, "Export Database"),
  248     " | ",
  249     CGI::a({href=>$self->systemLink($urlpath, params=>{subDisplay=>"import_database"})}, "Import Database"),
  250     CGI::hr(),
  251     $methodMessage,
  252 
  253   );
  254 
  255   print CGI::p("The ability to import and to export databases is still under development.
  256    It seems to work but it is <b>VERY</b> slow on large courses.  You may prefer to
  257    use webwork2/bin/wwdb  or the mysql dump facility for archiving large courses.
  258    Please send bug reports if you find errors.  ");
  259 
  260   my @errors = @{$self->{errors}};
  261 
  262 
  263   if (@errors) {
  264     print CGI::div({class=>"ResultsWithError"},
  265       CGI::p("Please correct the following errors and try again:"),
  266       CGI::ul(CGI::li(\@errors)),
  267     );
  268   }
  269 
  270   if (defined $method_to_call and $method_to_call ne "") {
  271     $self->$method_to_call;
  272   }
  273 
  274   return "";
  275 }
  276 
  277 ################################################################################
  278 
  279 sub add_course_form {
  280   my ($self) = @_;
  281   my $r = $self->r;
  282   my $ce = $r->ce;
  283   #my $db = $r->db;
  284   #my $authz = $r->authz;
  285   #my $urlpath = $r->urlpath;
  286 
  287   my $add_courseID                     = $r->param("add_courseID") || "";
  288   my $add_courseTitle                  = $r->param("add_courseTitle") || "";
  289   my $add_courseInstitution            = $r->param("add_courseInstitution") || "";
  290 
  291   my $add_admin_users                  = $r->param("add_admin_users") || "";
  292 
  293   my $add_initial_userID               = $r->param("add_initial_userID") || "";
  294   my $add_initial_password             = $r->param("add_initial_password") || "";
  295   my $add_initial_confirmPassword      = $r->param("add_initial_confirmPassword") || "";
  296   my $add_initial_firstName            = $r->param("add_initial_firstName") || "";
  297   my $add_initial_lastName             = $r->param("add_initial_lastName") || "";
  298   my $add_initial_email                = $r->param("add_initial_email") || "";
  299 
  300   my $add_templates_course             = $r->param("add_templates_course") || "";
  301 
  302   my $add_dbLayout                     = $r->param("add_dbLayout") || "";
  303   my $add_sql_host                     = $r->param("add_sql_host") || "";
  304   my $add_sql_port                     = $r->param("add_sql_port") || "";
  305   my $add_sql_username                 = $r->param("add_sql_username") || "";
  306   my $add_sql_password                 = $r->param("add_sql_password") || "";
  307   my $add_sql_database                 = $r->param("add_sql_database") || "";
  308   my $add_sql_wwhost                   = $r->param("add_sql_wwhost") || "";
  309   my $add_gdbm_globalUserID            = $r->param("add_gdbm_globalUserID") || "";
  310 
  311   my @dbLayouts = do {
  312     my @ordered_layouts;
  313     foreach my $layout (@DB_LAYOUT_ORDER) {
  314       if (exists $ce->{dbLayouts}->{$layout}) {
  315         push @ordered_layouts, $layout;
  316       }
  317     }
  318 
  319     my %ordered_layouts; @ordered_layouts{@ordered_layouts} = ();
  320     my @other_layouts;
  321     foreach my $layout (keys %{ $ce->{dbLayouts} }) {
  322       unless (exists $ordered_layouts{$layout}) {
  323         push @other_layouts, $layout;
  324       }
  325     }
  326 
  327     (@ordered_layouts, @other_layouts);
  328   };
  329 
  330   my $ce2 = WeBWorK::CourseEnvironment->new(
  331     $ce->{webworkDirs}->{root},
  332     $ce->{webworkURLs}->{root},
  333     $ce->{pg}->{directories}->{root},
  334     "COURSENAME",
  335   );
  336 
  337   my $dbi_source = do {
  338     # find the most common SQL source (stolen from CourseManagement.pm)
  339     my %sources;
  340     foreach my $table (keys %{ $ce2->{dbLayouts}->{sql} }) {
  341       $sources{$ce2->{dbLayouts}->{sql}->{$table}->{source}}++;
  342     }
  343     my $source;
  344     if (keys %sources > 1) {
  345       foreach my $curr (keys %sources) {
  346         $source = $curr if not defined $source or
  347           $sources{$curr} > $sources{$source};
  348       }
  349     } else {
  350       ($source) = keys %sources;
  351     }
  352     $source;
  353   };
  354 
  355   my @existingCourses = listCourses($ce);
  356   @existingCourses = sort @existingCourses;
  357 
  358   print CGI::h2("Add Course");
  359 
  360   print CGI::start_form("POST", $r->uri);
  361   print $self->hidden_authen_fields;
  362   print $self->hidden_fields("subDisplay");
  363 
  364   print CGI::p("Specify an ID, title, and institution for the new course. The course ID may contain only letters, numbers, hyphens, and underscores.");
  365 
  366   print CGI::table({class=>"FormLayout"},
  367     CGI::Tr(
  368       CGI::th({class=>"LeftHeader"}, "Course ID:"),
  369       CGI::td(CGI::textfield("add_courseID", $add_courseID, 25)),
  370     ),
  371     CGI::Tr(
  372       CGI::th({class=>"LeftHeader"}, "Course Title:"),
  373       CGI::td(CGI::textfield("add_courseTitle", $add_courseTitle, 25)),
  374     ),
  375     CGI::Tr(
  376       CGI::th({class=>"LeftHeader"}, "Institution:"),
  377       CGI::td(CGI::textfield("add_courseInstitution", $add_courseInstitution, 25)),
  378     ),
  379   );
  380 
  381   print CGI::p("To add the WeBWorK administrators to the new course (as instructors) check the box below.");
  382 
  383   print CGI::p(CGI::checkbox("add_admin_users", $add_admin_users, "on", "Add WeBWorK administrators to new course"));
  384 
  385   print CGI::p("To add an additional instructor to the new course, specify user information below. The user ID may contain only numbers, letters, hyphens, and underscores.");
  386 
  387   print CGI::table({class=>"FormLayout"}, CGI::Tr(
  388     CGI::td(
  389       CGI::table({class=>"FormLayout"},
  390         CGI::Tr(
  391           CGI::th({class=>"LeftHeader"}, "User ID:"),
  392           CGI::td(CGI::textfield("add_initial_userID", $add_initial_userID, 25)),
  393         ),
  394         CGI::Tr(
  395           CGI::th({class=>"LeftHeader"}, "Password:"),
  396           CGI::td(CGI::password_field("add_initial_password", $add_initial_password, 25)),
  397         ),
  398         CGI::Tr(
  399           CGI::th({class=>"LeftHeader"}, "Confirm Password:"),
  400           CGI::td(CGI::password_field("add_initial_confirmPassword", $add_initial_confirmPassword, 25)),
  401         ),
  402       ),
  403     ),
  404     CGI::td(
  405       CGI::table({class=>"FormLayout"},
  406         CGI::Tr(
  407           CGI::th({class=>"LeftHeader"}, "First Name:"),
  408           CGI::td(CGI::textfield("add_initial_firstName", $add_initial_firstName, 25)),
  409         ),
  410         CGI::Tr(
  411           CGI::th({class=>"LeftHeader"}, "Last Name:"),
  412           CGI::td(CGI::textfield("add_initial_lastName", $add_initial_lastName, 25)),
  413         ),
  414         CGI::Tr(
  415           CGI::th({class=>"LeftHeader"}, "Email Address:"),
  416           CGI::td(CGI::textfield("add_initial_email", $add_initial_email, 25)),
  417         ),
  418       ),
  419 
  420     ),
  421   ));
  422 
  423   print CGI::p("To copy problem templates from an existing course, select the course below.");
  424 
  425   print CGI::table({class=>"FormLayout"},
  426     CGI::Tr(
  427       CGI::th({class=>"LeftHeader"}, "Copy templates from:"),
  428       CGI::td(
  429         CGI::popup_menu(
  430           -name => "add_templates_course",
  431           -values => [ "", @existingCourses ],
  432           -default => $add_templates_course,
  433           #-size => 10,
  434           #-multiple => 0,
  435           #-labels => \%courseLabels,
  436         ),
  437 
  438       ),
  439     ),
  440   );
  441 
  442   print CGI::p("Select a database layout below.");
  443 
  444   foreach my $dbLayout (@dbLayouts) {
  445     print CGI::start_table({class=>"FormLayout"});
  446 
  447     my $dbLayoutLabel = (defined $DB_LAYOUT_DESCRIPTIONS{$dbLayout})
  448       ? "$dbLayout - $DB_LAYOUT_DESCRIPTIONS{$dbLayout}"
  449       : $dbLayout;
  450 
  451     # we generate singleton radio button tags ourselves because it's too much of a pain to do it with CGI.pm
  452     print CGI::Tr(
  453       CGI::td({style=>"text-align: right"},
  454         '<input type="radio" name="add_dbLayout" value="' . $dbLayout . '"'
  455         . ($add_dbLayout eq $dbLayout ? " checked" : "") . ' />',
  456       ),
  457       CGI::td($dbLayoutLabel),
  458     );
  459 
  460     print CGI::start_Tr();
  461     print CGI::td(); # for indentation :(
  462     print CGI::start_td();
  463 
  464     if ($dbLayout eq "sql") {
  465       print CGI::start_table({class=>"FormLayout"});
  466       print CGI::Tr(CGI::td({colspan=>2},
  467           "Enter the user ID and password for an SQL account with sufficient permissions to create a new database."
  468         )
  469       );
  470       print CGI::Tr(
  471         CGI::th({class=>"LeftHeader"}, "SQL Admin Username:"),
  472         CGI::td(CGI::textfield("add_sql_username", $add_sql_username, 25)),
  473       );
  474       print CGI::Tr(
  475         CGI::th({class=>"LeftHeader"}, "SQL Admin Password:"),
  476         CGI::td(CGI::password_field("add_sql_password", $add_sql_password, 25)),
  477       );
  478 
  479       print CGI::Tr(CGI::td({colspan=>2},
  480           "The optionial SQL settings you enter below must match the settings in the DBI source"
  481           . " specification " . CGI::tt($dbi_source) . ". Replace " . CGI::tt("COURSENAME")
  482           . " with the course name you entered above."
  483         )
  484       );
  485       print CGI::Tr(
  486         CGI::th({class=>"LeftHeader"}, "SQL Server Host:"),
  487         CGI::td(
  488           CGI::textfield("add_sql_host", $add_sql_host, 25),
  489           CGI::br(),
  490           CGI::small("Leave blank to use the default host."),
  491         ),
  492       );
  493       print CGI::Tr(
  494         CGI::th({class=>"LeftHeader"}, "SQL Server Port:"),
  495         CGI::td(
  496           CGI::textfield("add_sql_port", $add_sql_port, 25),
  497           CGI::br(),
  498           CGI::small("Leave blank to use the default port."),
  499         ),
  500       );
  501 
  502       print CGI::Tr(
  503         CGI::th({class=>"LeftHeader"}, "SQL Database Name:"),
  504         CGI::td(
  505           CGI::textfield("add_sql_database", $add_sql_database, 25),
  506           CGI::br(),
  507           CGI::small("Leave blank to use the name ", CGI::tt("webwork_COURSENAME"), "."),
  508         ),
  509       );
  510       print CGI::Tr(
  511         CGI::th({class=>"LeftHeader"}, "WeBWorK Host:"),
  512         CGI::td(
  513           CGI::textfield("add_sql_wwhost", $add_sql_wwhost || "localhost", 25),
  514           CGI::br(),
  515           CGI::small("If the SQL server does not run on the same host as WeBWorK, enter the host name of the WeBWorK server as seen by the SQL server."),
  516         ),
  517       );
  518       print CGI::end_table();
  519     } elsif ($dbLayout eq "gdbm") {
  520       print CGI::start_table({class=>"FormLayout"});
  521       print CGI::Tr(
  522         CGI::th({class=>"LeftHeader"}, "GDBM Global User ID:"),
  523         CGI::td(CGI::textfield("add_gdbm_globalUserID", $add_gdbm_globalUserID || "global_user", 25)),
  524       );
  525       print CGI::end_table();
  526     }
  527 
  528     print CGI::end_td();
  529     print CGI::end_Tr();
  530     print CGI::end_table();
  531   }
  532 
  533   print CGI::p({style=>"text-align: center"}, CGI::submit("add_course", "Add Course"));
  534 
  535   print CGI::end_form();
  536 }
  537 
  538 sub add_course_validate {
  539   my ($self) = @_;
  540   my $r = $self->r;
  541   my $ce = $r->ce;
  542   #my $db = $r->db;
  543   #my $authz = $r->authz;
  544   #my $urlpath = $r->urlpath;
  545 
  546   my $add_courseID                     = $r->param("add_courseID") || "";
  547   my $add_courseTitle                  = $r->param("add_courseTitle") || "";
  548   my $add_courseInstitution            = $r->param("add_courseInstitution") || "";
  549 
  550   my $add_admin_users                  = $r->param("add_admin_users") || "";
  551 
  552   my $add_initial_userID               = $r->param("add_initial_userID") || "";
  553   my $add_initial_password             = $r->param("add_initial_password") || "";
  554   my $add_initial_confirmPassword      = $r->param("add_initial_confirmPassword") || "";
  555   my $add_initial_firstName            = $r->param("add_initial_firstName") || "";
  556   my $add_initial_lastName             = $r->param("add_initial_lastName") || "";
  557   my $add_initial_email                = $r->param("add_initial_email") || "";
  558 
  559   my $add_templates_course             = $r->param("add_templates_course") || "";
  560 
  561   my $add_dbLayout                     = $r->param("add_dbLayout") || "";
  562   my $add_sql_host                     = $r->param("add_sql_host") || "";
  563   my $add_sql_port                     = $r->param("add_sql_port") || "";
  564   my $add_sql_username                 = $r->param("add_sql_username") || "";
  565   my $add_sql_password                 = $r->param("add_sql_password") || "";
  566   my $add_sql_database                 = $r->param("add_sql_database") || "";
  567   my $add_sql_wwhost                   = $r->param("add_sql_wwhost") || "";
  568   my $add_gdbm_globalUserID            = $r->param("add_gdbm_globalUserID") || "";
  569 
  570   my @errors;
  571 
  572   if ($add_courseID eq "") {
  573     push @errors, "You must specify a course ID.";
  574   }
  575   unless ($add_courseID =~ /^[\w-]*$/) { # regex copied from CourseAdministration.pm
  576     push @errors, "Course ID may only contain letters, numbers, hyphens, and underscores.";
  577   }
  578   if (grep { $add_courseID eq $_ } listCourses($ce)) {
  579     push @errors, "A course with ID $add_courseID already exists.";
  580   }
  581   if ($add_courseTitle eq "") {
  582     push @errors, "You must specify a course title.";
  583   }
  584   if ($add_courseInstitution eq "") {
  585     push @errors, "You must specify an institution for this course.";
  586   }
  587 
  588   if ($add_initial_userID ne "") {
  589     if ($add_initial_password eq "") {
  590       push @errors, "You must specify a password for the initial instructor.";
  591     }
  592     if ($add_initial_confirmPassword eq "") {
  593       push @errors, "You must confirm the password for the initial instructor.";
  594     }
  595     if ($add_initial_password ne $add_initial_confirmPassword) {
  596       push @errors, "The password and password confirmation for the instructor must match.";
  597     }
  598     if ($add_initial_firstName eq "") {
  599       push @errors, "You must specify a first name for the initial instructor.";
  600     }
  601     if ($add_initial_lastName eq "") {
  602       push @errors, "You must specify a last name for the initial instructor.";
  603     }
  604     if ($add_initial_email eq "") {
  605       push @errors, "You must specify an email address for the initial instructor.";
  606     }
  607   }
  608 
  609   if ($add_dbLayout eq "") {
  610     push @errors, "You must select a database layout.";
  611   } else {
  612     if (exists $ce->{dbLayouts}->{$add_dbLayout}) {
  613       if ($add_dbLayout eq "sql") {
  614         push @errors, "You must specify the SQL admin username." if $add_sql_username eq "";
  615         push @errors, "You must specify the WeBWorK host." if $add_sql_wwhost eq "";
  616       } elsif ($add_dbLayout eq "gdbm") {
  617         push @errors, "You must specify the GDBM global user ID." if $add_gdbm_globalUserID eq "";
  618       }
  619     } else {
  620       push @errors, "The database layout $add_dbLayout doesn't exist.";
  621     }
  622   }
  623 
  624   return @errors;
  625 }
  626 
  627 sub do_add_course {
  628   my ($self) = @_;
  629   my $r = $self->r;
  630   my $ce = $r->ce;
  631   my $db = $r->db;
  632   #my $authz = $r->authz;
  633   my $urlpath = $r->urlpath;
  634 
  635   my $add_courseID                     = $r->param("add_courseID") || "";
  636   my $add_courseTitle                  = $r->param("add_courseTitle") || "";
  637   my $add_courseInstitution            = $r->param("add_courseInstitution") || "";
  638 
  639   my $add_admin_users                  = $r->param("add_admin_users") || "";
  640 
  641   my $add_initial_userID               = $r->param("add_initial_userID") || "";
  642   my $add_initial_password             = $r->param("add_initial_password") || "";
  643   my $add_initial_confirmPassword      = $r->param("add_initial_confirmPassword") || "";
  644   my $add_initial_firstName            = $r->param("add_initial_firstName") || "";
  645   my $add_initial_lastName             = $r->param("add_initial_lastName") || "";
  646   my $add_initial_email                = $r->param("add_initial_email") || "";
  647 
  648   my $add_templates_course             = $r->param("add_templates_course") || "";
  649 
  650   my $add_dbLayout                     = $r->param("add_dbLayout") || "";
  651   my $add_sql_host                     = $r->param("add_sql_host") || "";
  652   my $add_sql_port                     = $r->param("add_sql_port") || "";
  653   my $add_sql_username                 = $r->param("add_sql_username") || "";
  654   my $add_sql_password                 = $r->param("add_sql_password") || "";
  655   my $add_sql_database                 = $r->param("add_sql_database") || "";
  656   my $add_sql_wwhost                   = $r->param("add_sql_wwhost") || "";
  657   my $add_gdbm_globalUserID            = $r->param("add_gdbm_globalUserID") || "";
  658 
  659   my $ce2 = WeBWorK::CourseEnvironment->new(
  660     $ce->{webworkDirs}->{root},
  661     $ce->{webworkURLs}->{root},
  662     $ce->{pg}->{directories}->{root},
  663     $add_courseID,
  664   );
  665 
  666   my %courseOptions = ( dbLayoutName => $add_dbLayout );
  667 
  668   if ($add_initial_email ne "") {
  669     $courseOptions{allowedRecipients} = [ $add_initial_email ];
  670     # don't set feedbackRecipients -- this just gets in the way of the more
  671     # intelligent "receive_recipients" method.
  672     #$courseOptions{feedbackRecipients} = [ $add_initial_email ];
  673   }
  674 
  675   if ($add_dbLayout eq "gdbm") {
  676     $courseOptions{globalUserID} = $add_gdbm_globalUserID if $add_gdbm_globalUserID ne "";
  677   }
  678 
  679   my %dbOptions;
  680   if ($add_dbLayout eq "sql") {
  681     $dbOptions{host}     = $add_sql_host if $add_sql_host ne "";
  682     $dbOptions{port}     = $add_sql_port if $add_sql_port ne "";
  683     $dbOptions{username} = $add_sql_username;
  684     $dbOptions{password} = $add_sql_password;
  685     $dbOptions{database} = $add_sql_database || "webwork_$add_courseID";
  686     $dbOptions{wwhost}   = $add_sql_wwhost;
  687   }
  688 
  689   my @users;
  690 
  691   # copy users from current (admin) course if desired
  692   if ($add_admin_users ne "") {
  693     foreach my $userID ($db->listUsers) {
  694       if ($userID eq $add_initial_userID) {
  695         $self->addbadmessage( "User '$userID' will not be copied from admin course as it is the initial instructor.");
  696         next;
  697       }
  698       my $User            = $db->getUser($userID);
  699       my $Password        = $db->getPassword($userID);
  700       my $PermissionLevel = $db->getPermissionLevel($userID);
  701       push @users, [ $User, $Password, $PermissionLevel ];
  702     }
  703   }
  704 
  705   # add initial instructor if desired
  706   if ($add_initial_userID ne "") {
  707     my $User = $db->newUser(
  708       user_id       => $add_initial_userID,
  709       first_name    => $add_initial_firstName,
  710       last_name     => $add_initial_lastName,
  711       student_id    => $add_initial_userID,
  712       email_address => $add_initial_email,
  713       status        => "C",
  714     );
  715     my $Password = $db->newPassword(
  716       user_id  => $add_initial_userID,
  717       password => cryptPassword($add_initial_password),
  718     );
  719     my $PermissionLevel = $db->newPermissionLevel(
  720       user_id    => $add_initial_userID,
  721       permission => "10",
  722     );
  723     push @users, [ $User, $Password, $PermissionLevel ];
  724   }
  725 
  726   push @{$courseOptions{PRINT_FILE_NAMES_FOR}}, map { $_->[0]->user_id } @users;
  727 
  728   my %optional_arguments;
  729   if ($add_templates_course ne "") {
  730     $optional_arguments{templatesFrom} = $add_templates_course;
  731   }
  732 
  733   eval {
  734     addCourse(
  735       courseID      => $add_courseID,
  736       ce            => $ce2,
  737       courseOptions => \%courseOptions,
  738       dbOptions     => \%dbOptions,
  739       users         => \@users,
  740       %optional_arguments,
  741     );
  742   };
  743   if ($@) {
  744     my $error = $@;
  745     print CGI::div({class=>"ResultsWithError"},
  746       CGI::p("An error occured while creating the course $add_courseID:"),
  747       CGI::tt(CGI::escapeHTML($error)),
  748     );
  749     # get rid of any partially built courses
  750     # FIXME  -- this is too fragile
  751     unless ($error =~ /course exists/) {
  752       eval {
  753         deleteCourse(
  754           courseID   => $add_courseID,
  755           ce         => $ce2,
  756           dbOptions  => \%dbOptions,
  757         );
  758       }
  759     }
  760   } else {
  761       #log the action
  762       writeLog($ce, "hosted_courses", join("\t",
  763         "\tAdded",
  764         $add_courseInstitution,
  765         $add_courseTitle,
  766         $add_courseID,
  767         $add_initial_firstName,
  768         $add_initial_lastName,
  769         $add_initial_email,
  770       ));
  771       # add contact to admin course as student?
  772       # FIXME -- should we do this?
  773     print CGI::div({class=>"ResultsWithoutError"},
  774       CGI::p("Successfully created the course $add_courseID"),
  775     );
  776     my $newCoursePath = $urlpath->newFromModule("WeBWorK::ContentGenerator::ProblemSets",
  777       courseID => $add_courseID);
  778     my $newCourseURL = $self->systemLink($newCoursePath, authen => 0);
  779     print CGI::div({style=>"text-align: center"},
  780       CGI::a({href=>$newCourseURL}, "Log into $add_courseID"),
  781     );
  782   }
  783 
  784 
  785 }
  786 
  787 ################################################################################
  788 
  789 sub rename_course_form {
  790   my ($self) = @_;
  791   my $r = $self->r;
  792   my $ce = $r->ce;
  793   #my $db = $r->db;
  794   #my $authz = $r->authz;
  795   #my $urlpath = $r->urlpath;
  796 
  797   my $rename_oldCourseID     = $r->param("rename_oldCourseID")     || "";
  798   my $rename_newCourseID     = $r->param("rename_newCourseID")     || "";
  799 
  800   my $rename_sql_host        = $r->param("rename_sql_host")        || "";
  801   my $rename_sql_port        = $r->param("rename_sql_port")        || "";
  802   my $rename_sql_username    = $r->param("rename_sql_username")    || "";
  803   my $rename_sql_password    = $r->param("rename_sql_password")    || "";
  804   my $rename_sql_oldDatabase = $r->param("rename_sql_oldDatabase") || "";
  805   my $rename_sql_newDatabase = $r->param("rename_sql_newDatabase") || "";
  806   my $rename_sql_wwhost      = $r->param("rename_sql_wwhost")      || "";
  807 
  808   my @courseIDs = listCourses($ce);
  809   @courseIDs    = sort @courseIDs;
  810 
  811   my %courseLabels; # records... heh.
  812   foreach my $courseID (@courseIDs) {
  813     my $tempCE = WeBWorK::CourseEnvironment->new(
  814       $ce->{webworkDirs}->{root},
  815       $ce->{webworkURLs}->{root},
  816       $ce->{pg}->{directories}->{root},
  817       $courseID,
  818     );
  819     $courseLabels{$courseID} = "$courseID (" . $tempCE->{dbLayoutName} . ")";
  820   }
  821 
  822   print CGI::h2("Rename Course");
  823 
  824   print CGI::start_form("POST", $r->uri);
  825   print $self->hidden_authen_fields;
  826   print $self->hidden_fields("subDisplay");
  827 
  828   print CGI::p("Select a course to rename.");
  829 
  830   print CGI::table({class=>"FormLayout"},
  831     CGI::Tr(
  832       CGI::th({class=>"LeftHeader"}, "Course Name:"),
  833       CGI::td(
  834         CGI::scrolling_list(
  835           -name => "rename_oldCourseID",
  836           -values => \@courseIDs,
  837           -default => $rename_oldCourseID,
  838           -size => 10,
  839           -multiple => 0,
  840           -labels => \%courseLabels,
  841         ),
  842       ),
  843     ),
  844     CGI::Tr(
  845       CGI::th({class=>"LeftHeader"}, "New Name:"),
  846       CGI::td(CGI::textfield("rename_newCourseID", $rename_newCourseID, 25)),
  847     ),
  848   );
  849 
  850   print CGI::p(
  851     "If the course's database layout (indicated in parentheses above) is "
  852     . CGI::b("sql") . ", supply the SQL connections information requested below."
  853   );
  854 
  855   print CGI::start_table({class=>"FormLayout"});
  856   print CGI::Tr(CGI::td({colspan=>2},
  857       "Enter the user ID and password for an SQL account with sufficient permissions to create and delete databases."
  858     )
  859   );
  860   print CGI::Tr(
  861     CGI::th({class=>"LeftHeader"}, "SQL Admin Username:"),
  862     CGI::td(CGI::textfield("rename_sql_username", $rename_sql_username, 25)),
  863   );
  864   print CGI::Tr(
  865     CGI::th({class=>"LeftHeader"}, "SQL Admin Password:"),
  866     CGI::td(CGI::password_field("rename_sql_password", $rename_sql_password, 25)),
  867   );
  868 
  869   print CGI::Tr(
  870     CGI::th({class=>"LeftHeader"}, "SQL Server Host:"),
  871     CGI::td(
  872       CGI::textfield("rename_sql_host", $rename_sql_host, 25),
  873       CGI::br(),
  874       CGI::small("Leave blank to use the default host."),
  875     ),
  876   );
  877   print CGI::Tr(
  878     CGI::th({class=>"LeftHeader"}, "SQL Server Port:"),
  879     CGI::td(
  880       CGI::textfield("rename_sql_port", $rename_sql_port, 25),
  881       CGI::br(),
  882       CGI::small("Leave blank to use the default port."),
  883     ),
  884   );
  885 
  886   print CGI::Tr(
  887     CGI::th({class=>"LeftHeader"}, "SQL Current Database Name:"),
  888     CGI::td(
  889       CGI::textfield("rename_sql_database", $rename_sql_oldDatabase, 25),
  890       CGI::br(),
  891       CGI::small("Leave blank to use the name ", CGI::tt("webwork_COURSENAME"), "."),
  892     ),
  893   );
  894   print CGI::Tr(
  895     CGI::th({class=>"LeftHeader"}, "SQL New Database Name:"),
  896     CGI::td(
  897       CGI::textfield("rename_sql_database", $rename_sql_newDatabase, 25),
  898       CGI::br(),
  899       CGI::small("Leave blank to use the name ", CGI::tt("webwork_COURSENAME"), "."),
  900     ),
  901   );
  902   print CGI::Tr(
  903     CGI::th({class=>"LeftHeader"}, "WeBWorK Host:"),
  904     CGI::td(
  905       CGI::textfield("rename_sql_wwhost", $rename_sql_wwhost || "localhost", 25),
  906       CGI::br(),
  907       CGI::small("If the SQL server does not run on the same host as WeBWorK, enter the host name of the WeBWorK server as seen by the SQL server."),
  908     ),
  909   );
  910   print CGI::end_table();
  911 
  912   print CGI::p({style=>"text-align: center"}, CGI::submit("rename_course", "Rename Course"));
  913 
  914   print CGI::end_form();
  915 }
  916 
  917 sub rename_course_validate {
  918   my ($self) = @_;
  919   my $r = $self->r;
  920   my $ce = $r->ce;
  921   #my $db = $r->db;
  922   #my $authz = $r->authz;
  923   #my $urlpath = $r->urlpath;
  924 
  925   my $rename_oldCourseID     = $r->param("rename_oldCourseID")     || "";
  926   my $rename_newCourseID     = $r->param("rename_newCourseID")     || "";
  927 
  928   my $rename_sql_host        = $r->param("rename_sql_host")        || "";
  929   my $rename_sql_port        = $r->param("rename_sql_port")        || "";
  930   my $rename_sql_username    = $r->param("rename_sql_username")    || "";
  931   my $rename_sql_password    = $r->param("rename_sql_password")    || "";
  932   my $rename_sql_oldDatabase = $r->param("rename_sql_oldDatabase") || "";
  933   my $rename_sql_newDatabase = $r->param("rename_sql_newDatabase") || "";
  934   my $rename_sql_wwhost      = $r->param("rename_sql_wwhost")      || "";
  935 
  936   my @errors;
  937 
  938   if ($rename_oldCourseID eq "") {
  939     push @errors, "You must select a course to rename.";
  940   }
  941   if ($rename_newCourseID eq "") {
  942     push @errors, "You must specify a new name for the course.";
  943   }
  944   if ($rename_oldCourseID eq $rename_newCourseID) {
  945     push @errors, "Can't rename to the same name.";
  946   }
  947   unless ($rename_newCourseID =~ /^[\w-]*$/) { # regex copied from CourseAdministration.pm
  948     push @errors, "Course ID may only contain letters, numbers, hyphens, and underscores.";
  949   }
  950   if (grep { $rename_newCourseID eq $_ } listCourses($ce)) {
  951     push @errors, "A course with ID $rename_newCourseID already exists.";
  952   }
  953 
  954   my $ce2 = WeBWorK::CourseEnvironment->new(
  955     $ce->{webworkDirs}->{root},
  956     $ce->{webworkURLs}->{root},
  957     $ce->{pg}->{directories}->{root},
  958     $rename_oldCourseID,
  959   );
  960 
  961   if ($ce2->{dbLayoutName} eq "sql") {
  962     push @errors, "You must specify the SQL admin username." if $rename_sql_username eq "";
  963     #push @errors, "You must specify the SQL admin password." if $rename_sql_password eq "";
  964     #push @errors, "You must specify the current SQL database name." if $rename_sql_oldDatabase eq "";
  965     #push @errors, "You must specify the new SQL database name." if $rename_sql_newDatabase eq "";
  966   }
  967 
  968   return @errors;
  969 }
  970 
  971 sub do_rename_course {
  972   my ($self) = @_;
  973   my $r = $self->r;
  974   my $ce = $r->ce;
  975   my $db = $r->db;
  976   #my $authz = $r->authz;
  977   my $urlpath = $r->urlpath;
  978 
  979   my $rename_oldCourseID     = $r->param("rename_oldCourseID")     || "";
  980   my $rename_newCourseID     = $r->param("rename_newCourseID")     || "";
  981 
  982   my $rename_sql_host        = $r->param("rename_sql_host")        || "";
  983   my $rename_sql_port        = $r->param("rename_sql_port")        || "";
  984   my $rename_sql_username    = $r->param("rename_sql_username")    || "";
  985   my $rename_sql_password    = $r->param("rename_sql_password")    || "";
  986   my $rename_sql_oldDatabase = $r->param("rename_sql_oldDatabase") || "";
  987   my $rename_sql_newDatabase = $r->param("rename_sql_newDatabase") || "";
  988   my $rename_sql_wwhost      = $r->param("rename_sql_wwhost")      || "";
  989 
  990   my $ce2 = WeBWorK::CourseEnvironment->new(
  991     $ce->{webworkDirs}->{root},
  992     $ce->{webworkURLs}->{root},
  993     $ce->{pg}->{directories}->{root},
  994     $rename_oldCourseID,
  995   );
  996 
  997   my $dbLayoutName = $ce->{dbLayoutName};
  998 
  999   my %dbOptions;
 1000   if ($dbLayoutName eq "sql") {
 1001     $dbOptions{host}         = $rename_sql_host if $rename_sql_host ne "";
 1002     $dbOptions{port}         = $rename_sql_port if $rename_sql_port ne "";
 1003     $dbOptions{username}     = $rename_sql_username;
 1004     $dbOptions{password}     = $rename_sql_password;
 1005     $dbOptions{old_database} = $rename_sql_oldDatabase || "webwork_$rename_oldCourseID";
 1006     $dbOptions{new_database} = $rename_sql_newDatabase || "webwork_$rename_newCourseID";
 1007     $dbOptions{wwhost}       = $rename_sql_wwhost;
 1008   }
 1009 
 1010   eval {
 1011     renameCourse(
 1012       courseID      => $rename_oldCourseID,
 1013       ce            => $ce2,
 1014       dbOptions     => \%dbOptions,
 1015       newCourseID   => $rename_newCourseID,
 1016     );
 1017   };
 1018   if ($@) {
 1019     my $error = $@;
 1020     print CGI::div({class=>"ResultsWithError"},
 1021       CGI::p("An error occured while renaming the course $rename_oldCourseID to $rename_newCourseID:"),
 1022       CGI::tt(CGI::escapeHTML($error)),
 1023     );
 1024   } else {
 1025     print CGI::div({class=>"ResultsWithoutError"},
 1026       CGI::p("Successfully renamed the course $rename_oldCourseID to $rename_newCourseID"),
 1027     );
 1028     my $newCoursePath = $urlpath->newFromModule("WeBWorK::ContentGenerator::ProblemSets",
 1029       courseID => $rename_newCourseID);
 1030     my $newCourseURL = $self->systemLink($newCoursePath, authen => 0);
 1031     print CGI::div({style=>"text-align: center"},
 1032       CGI::a({href=>$newCourseURL}, "Log into $rename_newCourseID"),
 1033     );
 1034   }
 1035 }
 1036 
 1037 ################################################################################
 1038 
 1039 sub delete_course_form {
 1040   my ($self) = @_;
 1041   my $r = $self->r;
 1042   my $ce = $r->ce;
 1043   #my $db = $r->db;
 1044   #my $authz = $r->authz;
 1045   #my $urlpath = $r->urlpath;
 1046 
 1047   my $delete_courseID     = $r->param("delete_courseID")     || "";
 1048   my $delete_sql_host     = $r->param("delete_sql_host")     || "";
 1049   my $delete_sql_port     = $r->param("delete_sql_port")     || "";
 1050   my $delete_sql_username = $r->param("delete_sql_username") || "";
 1051   my $delete_sql_password = $r->param("delete_sql_password") || "";
 1052   my $delete_sql_database = $r->param("delete_sql_database")    || "";
 1053 
 1054   my @courseIDs = listCourses($ce);
 1055   @courseIDs    = sort @courseIDs;
 1056 
 1057   my %courseLabels; # records... heh.
 1058   foreach my $courseID (@courseIDs) {
 1059     my $tempCE = WeBWorK::CourseEnvironment->new(
 1060       $ce->{webworkDirs}->{root},
 1061       $ce->{webworkURLs}->{root},
 1062       $ce->{pg}->{directories}->{root},
 1063       $courseID,
 1064     );
 1065     $courseLabels{$courseID} = "$courseID (" . $tempCE->{dbLayoutName} . ")";
 1066   }
 1067 
 1068   print CGI::h2("Delete Course");
 1069 
 1070   print CGI::start_form("POST", $r->uri);
 1071   print $self->hidden_authen_fields;
 1072   print $self->hidden_fields("subDisplay");
 1073 
 1074   print CGI::p("Select a course to delete.");
 1075 
 1076   print CGI::table({class=>"FormLayout"},
 1077     CGI::Tr(
 1078       CGI::th({class=>"LeftHeader"}, "Course Name:"),
 1079       CGI::td(
 1080         CGI::scrolling_list(
 1081           -name => "delete_courseID",
 1082           -values => \@courseIDs,
 1083           -default => $delete_courseID,
 1084           -size => 10,
 1085           -multiple => 0,
 1086           -labels => \%courseLabels,
 1087         ),
 1088       ),
 1089     ),
 1090   );
 1091 
 1092   print CGI::p(
 1093     "If the course's database layout (indicated in parentheses above) is "
 1094     . CGI::b("sql") . ", supply the SQL connections information requested below."
 1095   );
 1096 
 1097   print CGI::start_table({class=>"FormLayout"});
 1098   print CGI::Tr(CGI::td({colspan=>2},
 1099       "Enter the user ID and password for an SQL account with sufficient permissions to delete an existing database."
 1100     )
 1101   );
 1102   print CGI::Tr(
 1103     CGI::th({class=>"LeftHeader"}, "SQL Admin Username:"),
 1104     CGI::td(CGI::textfield("delete_sql_username", $delete_sql_username, 25)),
 1105   );
 1106   print CGI::Tr(
 1107     CGI::th({class=>"LeftHeader"}, "SQL Admin Password:"),
 1108     CGI::td(CGI::password_field("delete_sql_password", $delete_sql_password, 25)),
 1109   );
 1110 
 1111   #print CGI::Tr(CGI::td({colspan=>2},
 1112   #   "The optionial SQL settings you enter below must match the settings in the DBI source"
 1113   #   . " specification " . CGI::tt($dbi_source) . ". Replace " . CGI::tt("COURSENAME")
 1114   #   . " with the course name you entered above."
 1115   # )
 1116   #);
 1117   print CGI::Tr(
 1118     CGI::th({class=>"LeftHeader"}, "SQL Server Host:"),
 1119     CGI::td(
 1120       CGI::textfield("delete_sql_host", $delete_sql_host, 25),
 1121       CGI::br(),
 1122       CGI::small("Leave blank to use the default host."),
 1123     ),
 1124   );
 1125   print CGI::Tr(
 1126     CGI::th({class=>"LeftHeader"}, "SQL Server Port:"),
 1127     CGI::td(
 1128       CGI::textfield("delete_sql_port", $delete_sql_port, 25),
 1129       CGI::br(),
 1130       CGI::small("Leave blank to use the default port."),
 1131     ),
 1132   );
 1133 
 1134   print CGI::Tr(
 1135     CGI::th({class=>"LeftHeader"}, "SQL Database Name:"),
 1136     CGI::td(
 1137       CGI::textfield("delete_sql_database", $delete_sql_database, 25),
 1138       CGI::br(),
 1139       CGI::small("Leave blank to use the name ", CGI::tt("webwork_COURSENAME"), "."),
 1140     ),
 1141   );
 1142   print CGI::end_table();
 1143 
 1144   print CGI::p({style=>"text-align: center"}, CGI::submit("delete_course", "Delete Course"));
 1145 
 1146   print CGI::end_form();
 1147 }
 1148 
 1149 sub delete_course_validate {
 1150   my ($self) = @_;
 1151   my $r = $self->r;
 1152   my $ce = $r->ce;
 1153   #my $db = $r->db;
 1154   #my $authz = $r->authz;
 1155   my $urlpath = $r->urlpath;
 1156 
 1157   my $delete_courseID     = $r->param("delete_courseID")     || "";
 1158   my $delete_sql_host     = $r->param("delete_sql_host")     || "";
 1159   my $delete_sql_port     = $r->param("delete_sql_port")     || "";
 1160   my $delete_sql_username = $r->param("delete_sql_username") || "";
 1161   my $delete_sql_password = $r->param("delete_sql_password") || "";
 1162   my $delete_sql_database = $r->param("delete_sql_database") || "";
 1163 
 1164   my @errors;
 1165 
 1166   if ($delete_courseID eq "") {
 1167     push @errors, "You must specify a course name.";
 1168   } elsif ($delete_courseID eq $urlpath->arg("courseID")) {
 1169     push @errors, "You cannot delete the course you are currently using.";
 1170   }
 1171 
 1172   my $ce2 = WeBWorK::CourseEnvironment->new(
 1173     $ce->{webworkDirs}->{root},
 1174     $ce->{webworkURLs}->{root},
 1175     $ce->{pg}->{directories}->{root},
 1176     $delete_courseID,
 1177   );
 1178 
 1179   if ($ce2->{dbLayoutName} eq "sql") {
 1180     push @errors, "You must specify the SQL admin username." if $delete_sql_username eq "";
 1181     #push @errors, "You must specify the SQL admin password." if $delete_sql_password eq "";
 1182     #push @errors, "You must specify the SQL database name." if $delete_sql_database eq "";
 1183   }
 1184 
 1185   return @errors;
 1186 }
 1187 
 1188 sub delete_course_confirm {
 1189   my ($self) = @_;
 1190   my $r = $self->r;
 1191   my $ce = $r->ce;
 1192   #my $db = $r->db;
 1193   #my $authz = $r->authz;
 1194   #my $urlpath = $r->urlpath;
 1195 
 1196   print CGI::h2("Delete Course");
 1197 
 1198   my $delete_courseID     = $r->param("delete_courseID")     || "";
 1199   my $delete_sql_host     = $r->param("delete_sql_host")     || "";
 1200   my $delete_sql_port     = $r->param("delete_sql_port")     || "";
 1201   my $delete_sql_database = $r->param("delete_sql_database") || "";
 1202 
 1203   my $ce2 = WeBWorK::CourseEnvironment->new(
 1204     $ce->{webworkDirs}->{root},
 1205     $ce->{webworkURLs}->{root},
 1206     $ce->{pg}->{directories}->{root},
 1207     $delete_courseID,
 1208   );
 1209 
 1210   if ($ce2->{dbLayoutName} eq "sql") {
 1211     print CGI::p("Are you sure you want to delete the course " . CGI::b($delete_courseID)
 1212     . "? All course files and data and the following database will be destroyed."
 1213     . " There is no undo available.");
 1214 
 1215     print CGI::table({class=>"FormLayout"},
 1216       CGI::Tr(
 1217         CGI::th({class=>"LeftHeader"}, "SQL Server Host:"),
 1218         CGI::td($delete_sql_host || "system default"),
 1219       ),
 1220       CGI::Tr(
 1221         CGI::th({class=>"LeftHeader"}, "SQL Server Port:"),
 1222         CGI::td($delete_sql_port || "system default"),
 1223       ),
 1224       CGI::Tr(
 1225         CGI::th({class=>"LeftHeader"}, "SQL Database Name:"),
 1226         CGI::td($delete_sql_database || "webwork_$delete_courseID"),
 1227       ),
 1228     );
 1229   } else {
 1230     print CGI::p("Are you sure you want to delete the course " . CGI::b($delete_courseID)
 1231       . "? All course files and data will be destroyed. There is no undo available.");
 1232   }
 1233 
 1234   print CGI::start_form("POST", $r->uri);
 1235   print $self->hidden_authen_fields;
 1236   print $self->hidden_fields("subDisplay");
 1237   print $self->hidden_fields(qw/delete_courseID delete_sql_host delete_sql_port delete_sql_username delete_sql_password delete_sql_database/);
 1238 
 1239   print CGI::p({style=>"text-align: center"},
 1240     CGI::submit("decline_delete_course", "Don't delete"),
 1241     "&nbsp;",
 1242     CGI::submit("confirm_delete_course", "Delete"),
 1243   );
 1244 
 1245   print CGI::end_form();
 1246 }
 1247 
 1248 sub do_delete_course {
 1249   my ($self) = @_;
 1250   my $r = $self->r;
 1251   my $ce = $r->ce;
 1252   #my $db = $r->db;
 1253   #my $authz = $r->authz;
 1254   #my $urlpath = $r->urlpath;
 1255 
 1256   my $delete_courseID     = $r->param("delete_courseID")     || "";
 1257   my $delete_sql_host     = $r->param("delete_sql_host")     || "";
 1258   my $delete_sql_port     = $r->param("delete_sql_port")     || "";
 1259   my $delete_sql_username = $r->param("delete_sql_username") || "";
 1260   my $delete_sql_password = $r->param("delete_sql_password") || "";
 1261   my $delete_sql_database = $r->param("delete_sql_database") || "";
 1262 
 1263   my $ce2 = WeBWorK::CourseEnvironment->new(
 1264     $ce->{webworkDirs}->{root},
 1265     $ce->{webworkURLs}->{root},
 1266     $ce->{pg}->{directories}->{root},
 1267     $delete_courseID,
 1268   );
 1269 
 1270   my %dbOptions;
 1271   if ($ce2->{dbLayoutName} eq "sql") {
 1272     $dbOptions{host}     = $delete_sql_host if $delete_sql_host ne "";
 1273     $dbOptions{port}     = $delete_sql_port if $delete_sql_port ne "";
 1274     $dbOptions{username} = $delete_sql_username;
 1275     $dbOptions{password} = $delete_sql_password;
 1276     $dbOptions{database} = $delete_sql_database || "webwork_$delete_courseID";
 1277   }
 1278 
 1279   eval {
 1280     deleteCourse(
 1281       courseID => $delete_courseID,
 1282       ce => $ce2,
 1283       dbOptions => \%dbOptions,
 1284     );
 1285   };
 1286 
 1287   if ($@) {
 1288     my $error = $@;
 1289     print CGI::div({class=>"ResultsWithError"},
 1290       CGI::p("An error occured while deleting the course $delete_courseID:"),
 1291       CGI::tt(CGI::escapeHTML($error)),
 1292     );
 1293   } else {
 1294     print CGI::div({class=>"ResultsWithoutError"},
 1295       CGI::p("Successfully deleted the course $delete_courseID."),
 1296     );
 1297      writeLog($ce, "hosted_courses", join("\t",
 1298         "\tDeleted",
 1299         "",
 1300         "",
 1301         $delete_courseID,
 1302       ));
 1303     print CGI::start_form("POST", $r->uri);
 1304     print $self->hidden_authen_fields;
 1305     print $self->hidden_fields("subDisplay");
 1306 
 1307     print CGI::p({style=>"text-align: center"}, CGI::submit("decline_delete_course", "OK"),);
 1308 
 1309     print CGI::end_form();
 1310   }
 1311 }
 1312 
 1313 ################################################################################
 1314 
 1315 sub export_database_form {
 1316   my ($self) = @_;
 1317   my $r = $self->r;
 1318   my $ce = $r->ce;
 1319   #my $db = $r->db;
 1320   #my $authz = $r->authz;
 1321   #my $urlpath = $r->urlpath;
 1322 
 1323   my @tables = keys %{$ce->{dbLayout}};
 1324 
 1325   my $export_courseID = $r->param("export_courseID") || "";
 1326   my @export_tables   = $r->param("export_tables");
 1327 
 1328   @export_tables = @tables unless @export_tables;
 1329 
 1330   my @courseIDs = listCourses($ce);
 1331   @courseIDs    = sort @courseIDs;
 1332 
 1333   my %courseLabels; # records... heh.
 1334   foreach my $courseID (@courseIDs) {
 1335     my $tempCE = WeBWorK::CourseEnvironment->new(
 1336       $ce->{webworkDirs}->{root},
 1337       $ce->{webworkURLs}->{root},
 1338       $ce->{pg}->{directories}->{root},
 1339       $courseID,
 1340     );
 1341     $courseLabels{$courseID} = "$courseID (" . $tempCE->{dbLayoutName} . ")";
 1342   }
 1343 
 1344   print CGI::h2("Export Database");
 1345 
 1346   print CGI::start_form("GET", $r->uri);
 1347   print $self->hidden_authen_fields;
 1348   print $self->hidden_fields("subDisplay");
 1349 
 1350   print CGI::p("Select a course to export the course's database. Please note
 1351   that exporting can take a very long time for a large course. If you have
 1352   shell access to the WeBWorK server, you may use the ", CGI::code("wwdb"), "
 1353   utility instead.");
 1354 
 1355   print CGI::table({class=>"FormLayout"},
 1356     CGI::Tr(
 1357       CGI::th({class=>"LeftHeader"}, "Course Name:"),
 1358       CGI::td(
 1359         CGI::scrolling_list(
 1360           -name => "export_courseID",
 1361           -values => \@courseIDs,
 1362           -default => $export_courseID,
 1363           -size => 10,
 1364           -multiple => 1,
 1365           -labels => \%courseLabels,
 1366         ),
 1367       ),
 1368     ),
 1369     CGI::Tr(
 1370       CGI::th({class=>"LeftHeader"}, "Tables to Export:"),
 1371       CGI::td(
 1372         CGI::checkbox_group(
 1373           -name => "export_tables",
 1374           -values => \@tables,
 1375           -default => \@export_tables,
 1376           -linebreak => 1,
 1377         ),
 1378       ),
 1379     ),
 1380   );
 1381 
 1382   print CGI::p({style=>"text-align: center"}, CGI::submit("export_database", "Export Database"));
 1383 
 1384   print CGI::end_form();
 1385 }
 1386 
 1387 sub export_database_validate {
 1388   my ($self) = @_;
 1389   my $r = $self->r;
 1390   #my $ce = $r->ce;
 1391   #my $db = $r->db;
 1392   #my $authz = $r->authz;
 1393   #my $urlpath = $r->urlpath;
 1394 
 1395   my @export_courseID = $r->param("export_courseID") || ();
 1396   my @export_tables   = $r->param("export_tables");
 1397 
 1398   my @errors;
 1399 
 1400   unless ( @export_courseID) {
 1401     push @errors, "You must specify at least one course name.";
 1402   }
 1403 
 1404   unless (@export_tables) {
 1405     push @errors, "You must specify at least one table to export.";
 1406   }
 1407 
 1408   return @errors;
 1409 }
 1410 
 1411 sub do_export_database {
 1412   my ($self) = @_;
 1413   my $r = $self->r;
 1414   my $ce = $r->ce;
 1415   #my $db = $r->db;
 1416   #my $authz = $r->authz;
 1417   my $urlpath = $r->urlpath;
 1418 
 1419   my @export_courseID = $r->param("export_courseID");
 1420   my @export_tables   = $r->param("export_tables");
 1421 
 1422   foreach my $export_courseID (@export_courseID) {
 1423 
 1424     my $ce2 = WeBWorK::CourseEnvironment->new(
 1425       $ce->{webworkDirs}->{root},
 1426       $ce->{webworkURLs}->{root},
 1427       $ce->{pg}->{directories}->{root},
 1428       $export_courseID,
 1429     );
 1430 
 1431     my $db2 = new WeBWorK::DB($ce2->{dbLayout});
 1432 
 1433     #my ($fh, $export_file) = tempfile("db_export_XXXXXX", DIR => $ce->{webworkDirs}->{tmp});
 1434     #my ($random_chars) = $export_file =~ m/db_export_(\w+)$/;
 1435     # export to the admin/templates directory
 1436     my $exportFileName = "$export_courseID.exported.xml";
 1437     my $exportFilePath = $ce->{courseDirs}->{templates}."/$exportFileName";
 1438     # get a unique name
 1439     my $number =1;
 1440     while (-e "$exportFilePath.$number.gz") {
 1441       $number++;
 1442       last if $number>9;
 1443     }
 1444     if ($number<=9 ) {
 1445       $exportFilePath = "$exportFilePath.$number";
 1446       $exportFileName = "$exportFileName.$number";
 1447     } else {
 1448       $self->addbadmessage(CGI::p("There are more than 9 exported files for this course! Please
 1449       remove some of these files."));
 1450       $exportFilePath = "$exportFilePath.999";
 1451       $exportFileName = "$exportFileName.999";
 1452     }
 1453 
 1454     my $outputFileHandle = new IO::File(">$exportFilePath") or warn "Unable to create $exportFilePath";
 1455 
 1456     my @errors;
 1457     eval {
 1458       @errors = dbExport(
 1459         db => $db2,
 1460         #xml => $fh,
 1461         xml => $outputFileHandle,
 1462         tables => \@export_tables,
 1463       );
 1464     };
 1465 
 1466     $outputFileHandle->close();
 1467 
 1468     my $gzipMessage = system( 'gzip', $exportFilePath);
 1469     if ( !$gzipMessage ) {
 1470       $self->addgoodmessage(CGI::p( "Database saved to templates/$exportFileName.gzip.
 1471       You may download it with the file manager."));
 1472     } else {
 1473       $self->addbadmessage(CGI::p( "Failed to gzip file $exportFilePath"));
 1474     }
 1475     unlink $exportFilePath;
 1476   } # end export of one course
 1477   #push @errors, "Fatal exception: $@" if $@;
 1478   #
 1479   #if (@errors) {
 1480   # print CGI::div({class=>"ResultsWithError"},
 1481   #   CGI::p("An error occured while exporting the database of course $export_courseID:"),
 1482   #   CGI::ul(CGI::li(\@errors)),
 1483   # );
 1484   #} else {
 1485   # print CGI::div({class=>"ResultsWithoutError"},
 1486   #   CGI::p("Export succeeded."),
 1487   # );
 1488   #
 1489   # print CGI::div({style=>"text-align: center"},
 1490   #   CGI::a({href=>$self->systemLink($urlpath, params=>{download_exported_database=>$random_chars, export_courseID=>undef})}, "Download Exported Database"),
 1491   # );
 1492   #}
 1493 }
 1494 
 1495 ################################################################################
 1496 
 1497 sub import_database_form {
 1498   my ($self) = @_;
 1499   my $r = $self->r;
 1500   my $ce = $r->ce;
 1501   #my $db = $r->db;
 1502   #my $authz = $r->authz;
 1503   #my $urlpath = $r->urlpath;
 1504 
 1505   my @tables = keys %{$ce->{dbLayout}};
 1506 
 1507   my $import_file     = $r->param("import_file")     || "";
 1508   my $import_courseID = $r->param("import_courseID") || "";
 1509   my @import_tables   = $r->param("import_tables");
 1510   my $import_conflict = $r->param("import_conflict") || "skip";
 1511 
 1512   @import_tables = @tables unless @import_tables;
 1513 
 1514   my @courseIDs = listCourses($ce);
 1515   @courseIDs    = sort @courseIDs;
 1516 
 1517 
 1518   my %courseLabels; # records... heh.
 1519   foreach my $courseID (@courseIDs) {
 1520     my $tempCE = WeBWorK::CourseEnvironment->new(
 1521       $ce->{webworkDirs}->{root},
 1522       $ce->{webworkURLs}->{root},
 1523       $ce->{pg}->{directories}->{root},
 1524       $courseID,
 1525     );
 1526     $courseLabels{$courseID} = "$courseID (" . $tempCE->{dbLayoutName} . ")";
 1527   }
 1528 
 1529   # find databases:
 1530   my $templatesDir = $ce->{courseDirs}->{templates};
 1531   my %probLibs = %{ $r->ce->{courseFiles}->{problibs} };
 1532   my $exempt_dirs = join("|", keys %probLibs);
 1533 
 1534   my @databaseFiles = listFilesRecursive(
 1535     $templatesDir,
 1536     qr/.\.exported\.xml\.\d*\.gz$/, # match these files  #FIXME this is too restricive!!
 1537     qr/^(?:$exempt_dirs|CVS)$/, # prune these directories
 1538     0, # match against file name only
 1539     1, # prune against path relative to $templatesDir
 1540   );
 1541 
 1542   my %databaseLabels = map { ($_ => $_) } @databaseFiles;
 1543 
 1544   #######
 1545 
 1546   print CGI::h2("Import Database");
 1547 
 1548   print CGI::start_form("POST", $r->uri, &CGI::MULTIPART);
 1549   print $self->hidden_authen_fields;
 1550   print $self->hidden_fields("subDisplay");
 1551 
 1552   print CGI::table({class=>"FormLayout"},
 1553     CGI::Tr(
 1554       CGI::th({class=>"LeftHeader"}, "Database XML File:"),
 1555 #       CGI::td(
 1556 #         CGI::filefield(
 1557 #           -name => "import_file",
 1558 #           -size => 50,
 1559 #         ),
 1560 #       ),
 1561       CGI::td(
 1562         CGI::scrolling_list(
 1563           -name => "import_file",
 1564           -values => \@databaseFiles,
 1565           -default => undef,
 1566           -size => 10,
 1567           -multiple => 0,
 1568           -labels => \%databaseLabels,
 1569         ),
 1570 
 1571       )
 1572     ),
 1573     CGI::Tr(
 1574       CGI::th({class=>"LeftHeader"}, "Tables to Import:"),
 1575       CGI::td(
 1576         CGI::checkbox_group(
 1577           -name => "import_tables",
 1578           -values => \@tables,
 1579           -default => \@import_tables,
 1580           -linebreak => 1,
 1581         ),
 1582       ),
 1583     ),
 1584     CGI::Tr(
 1585       CGI::th({class=>"LeftHeader"}, "Import into Course:"),
 1586       CGI::td(
 1587         CGI::scrolling_list(
 1588           -name => "import_courseID",
 1589           -values => \@courseIDs,
 1590           -default => $import_courseID,
 1591           -size => 10,
 1592           -multiple => 0,
 1593           -labels => \%courseLabels,
 1594         ),
 1595       ),
 1596     ),
 1597     CGI::Tr(
 1598       CGI::th({class=>"LeftHeader"}, "Conflicts:"),
 1599       CGI::td(
 1600         CGI::radio_group(
 1601           -name => "import_conflict",
 1602           -values => [qw/skip replace/],
 1603           -default => $import_conflict,
 1604           -linebreak=>'true',
 1605           -labels => {
 1606             skip => "Skip duplicate records",
 1607             replace => "Replace duplicate records",
 1608           },
 1609         ),
 1610       ),
 1611     ),
 1612   );
 1613 
 1614   print CGI::p({style=>"text-align: center"}, CGI::submit("import_database", "Import Database"));
 1615 
 1616   print CGI::end_form();
 1617 }
 1618 
 1619 sub import_database_validate {
 1620   my ($self) = @_;
 1621   my $r = $self->r;
 1622   #my $ce = $r->ce;
 1623   #my $db = $r->db;
 1624   #my $authz = $r->authz;
 1625   #my $urlpath = $r->urlpath;
 1626 
 1627   my $import_file     = $r->param("import_file")     || "";
 1628   my $import_courseID = $r->param("import_courseID") || "";
 1629   my @import_tables   = $r->param("import_tables");
 1630   #my $import_conflict = $r->param("import_conflict") || "skip"; # not checked
 1631 
 1632   my @errors;
 1633 
 1634   if ($import_file eq "") {
 1635     push @errors, "You must specify a database file to import.";
 1636   }
 1637 
 1638   if ($import_courseID eq "") {
 1639     push @errors, "You must specify a course name.";
 1640   }
 1641 
 1642   unless (@import_tables) {
 1643     push @errors, "You must specify at least one table to import.";
 1644   }
 1645 
 1646   return @errors;
 1647 }
 1648 
 1649 sub do_import_database {
 1650   my ($self) = @_;
 1651   my $r = $self->r;
 1652   my $ce = $r->ce;
 1653   #my $db = $r->db;
 1654   #my $authz = $r->authz;
 1655   my $urlpath = $r->urlpath;
 1656 
 1657   my $import_file     = $r->param("import_file");
 1658   my $import_courseID = $r->param("import_courseID");
 1659   my @import_tables   = $r->param("import_tables");
 1660   my $import_conflict = $r->param("import_conflict") || "skip"; # need default -- not checked above
 1661 
 1662   my $ce2 = WeBWorK::CourseEnvironment->new(
 1663     $ce->{webworkDirs}->{root},
 1664     $ce->{webworkURLs}->{root},
 1665     $ce->{pg}->{directories}->{root},
 1666     $import_courseID,
 1667   );
 1668 
 1669   my $db2 = new WeBWorK::DB($ce2->{dbLayout});
 1670 
 1671   # locate file
 1672   my $templateDir = $ce->{courseDirs}->{templates};
 1673   my $filePath = "$templateDir/$import_file";
 1674 
 1675   my $gunzipMessage = system( 'gunzip', $filePath);
 1676   #FIXME
 1677   #warn "gunzip ", $gunzipMessage;
 1678   $filePath =~ s/\.gz$//;
 1679   #warn "new file path is $filePath";
 1680   my $fileHandle = new IO::File("<$filePath");
 1681   # retrieve upload from upload cache
 1682 #   my ($id, $hash) = split /\s+/, $import_file;
 1683 #   my $upload = WeBWorK::Upload->retrieve($id, $hash,
 1684 #     dir => $ce->{webworkDirs}->{uploadCache}
 1685 #   );
 1686 
 1687   my @errors;
 1688 
 1689   eval {
 1690     @errors = dbImport(
 1691       db => $db2,
 1692       # xml => $upload->fileHandle,
 1693       xml => $fileHandle,
 1694       tables => \@import_tables,
 1695       conflict => $import_conflict,
 1696     );
 1697   };
 1698 
 1699   push @errors, "Fatal exception: $@" if $@;
 1700   push @errors, $gunzipMessage if $gunzipMessage;
 1701 
 1702   if (@errors) {
 1703     print CGI::div({class=>"ResultsWithError"},
 1704       CGI::p("An error occured while importing the database of course $import_courseID:"),
 1705       CGI::ul(CGI::li(\@errors)),
 1706     );
 1707   } else {
 1708     print CGI::div({class=>"ResultsWithoutError"},
 1709       CGI::p("Import succeeded."),
 1710     );
 1711   }
 1712 }
 1713 
 1714 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9