[system] / trunk / webwork2 / lib / WeBWorK / ContentGenerator / CourseAdmin.pm Repository:
ViewVC logotype

View of /trunk/webwork2/lib/WeBWorK/ContentGenerator/CourseAdmin.pm

Parent Directory Parent Directory | Revision Log Revision Log


Revision 5221 - (download) (as text) (annotate)
Thu Jul 26 18:53:08 2007 UTC (5 years, 10 months ago) by sh002i
File size: 74644 byte(s)
Construct additional course environments using %WeBWorK::SeedCE, which
will always have all necessary values in it. The old way broke when we
moved the definitions of $webwork_courses_* and $webwork_htdocs_* out of
global.conf and into webwork.apache[2]-config (and therefore into
SeedCE).

    1 ################################################################################
    2 # WeBWorK Online Homework Delivery System
    3 # Copyright © 2000-2006 The WeBWorK Project, http://openwebwork.sf.net/
    4 # $CVSHeader: webwork2/lib/WeBWorK/ContentGenerator/CourseAdmin.pm,v 1.67 2007/07/21 19:13:03 sh002i 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 qw(-nosticky );
   29 use WeBWorK::CGI;
   30 use Data::Dumper;
   31 use File::Temp qw/tempfile/;
   32 use WeBWorK::CourseEnvironment;
   33 use IO::File;
   34 use WeBWorK::Debug;
   35 use WeBWorK::Utils qw(cryptPassword writeLog listFilesRecursive);
   36 use WeBWorK::Utils::CourseManagement qw(addCourse renameCourse deleteCourse listCourses archiveCourse
   37                                         listArchivedCourses unarchiveCourse);
   38 use WeBWorK::Utils::DBImportExport qw(dbExport dbImport);
   39 # needed for location management
   40 use Net::IP;
   41 
   42 use constant IMPORT_EXPORT_WARNING => "The ability to import and export
   43 databases is still under development. It seems to work but it is <b>VERY</b>
   44 slow on large courses.  You may prefer to use webwork2/bin/wwdb  or the mysql
   45 dump facility for archiving large courses. Please send bug reports if you find
   46 errors.";
   47 
   48 sub pre_header_initialize {
   49   my ($self) = @_;
   50   my $r = $self->r;
   51   my $ce = $r->ce;
   52   my $db = $r->db;
   53   my $authz = $r->authz;
   54   my $urlpath = $r->urlpath;
   55   my $user        = $r->param('user');
   56 
   57   # check permissions
   58   unless ($authz->hasPermissions($user, "create_and_delete_courses")) {
   59     $self->addmessage( CGI::div({class=>'ResultsWithError'},"$user is not authorized to create or delete courses") );
   60     return;
   61   }
   62 
   63   # get result and send to message
   64   my $status_message = $r->param("status_message");
   65   $self->addmessage(CGI::p("$status_message")) if $status_message;
   66 
   67   ## if the user is asking for the downloaded database...
   68   #if (defined $r->param("download_exported_database")) {
   69   # my $courseID = $r->param("export_courseID");
   70   # my $random_chars = $r->param("download_exported_database");
   71   #
   72   # die "courseID not specified" unless defined $courseID;
   73   # die "invalid file specification" unless $random_chars =~ m/^\w+$/;
   74   #
   75   # my $tempdir = $ce->{webworkDirs}->{tmp};
   76   # my $export_file = "$tempdir/db_export_$random_chars";
   77   #
   78   # $self->reply_with_file("application/xml", $export_file, "${courseID}_database.xml", 0);
   79   #
   80   # return "";
   81   #}
   82   #
   83   ## otherwise...
   84 
   85   my @errors;
   86   my $method_to_call;
   87 
   88   my $subDisplay = $r->param("subDisplay");
   89   if (defined $subDisplay) {
   90 
   91     if ($subDisplay eq "add_course") {
   92       if (defined $r->param("add_course")) {
   93         @errors = $self->add_course_validate;
   94         if (@errors) {
   95           $method_to_call = "add_course_form";
   96         } else {
   97           $method_to_call = "do_add_course";
   98         }
   99       } else {
  100         $method_to_call = "add_course_form";
  101       }
  102     }
  103 
  104     elsif ($subDisplay eq "rename_course") {
  105       if (defined $r->param("rename_course")) {
  106         @errors = $self->rename_course_validate;
  107         if (@errors) {
  108           $method_to_call = "rename_course_form";
  109         } else {
  110           $method_to_call = "do_rename_course";
  111         }
  112       } else {
  113         $method_to_call = "rename_course_form";
  114       }
  115     }
  116 
  117     elsif ($subDisplay eq "delete_course") {
  118       if (defined $r->param("delete_course")) {
  119         # validate or confirm
  120         @errors = $self->delete_course_validate;
  121         if (@errors) {
  122           $method_to_call = "delete_course_form";
  123         } else {
  124           $method_to_call = "delete_course_confirm";
  125         }
  126       } elsif (defined $r->param("confirm_delete_course")) {
  127         # validate and delete
  128         @errors = $self->delete_course_validate;
  129         if (@errors) {
  130           $method_to_call = "delete_course_form";
  131         } else {
  132           $method_to_call = "do_delete_course";
  133         }
  134       } else {
  135         # form only
  136         $method_to_call = "delete_course_form";
  137       }
  138     }
  139 
  140     elsif ($subDisplay eq "export_database") {
  141       if (defined $r->param("export_database")) {
  142         @errors = $self->export_database_validate;
  143         if (@errors) {
  144           $method_to_call = "export_database_form";
  145         } else {
  146           # we have to do something special here, since we're sending
  147           # the database as we export it. $method_to_call still gets
  148           # set here, but it gets caught by header() and content()
  149           # below instead of by body().
  150           $method_to_call = "do_export_database";
  151         }
  152       } else {
  153         $method_to_call = "export_database_form";
  154       }
  155     }
  156 
  157     elsif ($subDisplay eq "import_database") {
  158       if (defined $r->param("import_database")) {
  159         @errors = $self->import_database_validate;
  160         if (@errors) {
  161           $method_to_call = "import_database_form";
  162         } else {
  163           $method_to_call = "do_import_database";
  164         }
  165       } else {
  166         $method_to_call = "import_database_form";
  167       }
  168     }
  169 
  170     elsif ($subDisplay eq "archive_course") {
  171       if (defined $r->param("archive_course")) {
  172         # validate or confirm
  173         @errors = $self->archive_course_validate;
  174         if (@errors) {
  175           $method_to_call = "archive_course_form";
  176         } else {
  177           $method_to_call = "archive_course_confirm";
  178         }
  179       } elsif (defined $r->param("confirm_archive_course")) {
  180         # validate and archive
  181         @errors = $self->archive_course_validate;
  182         if (@errors) {
  183           $method_to_call = "archive_course_form";
  184         } else {
  185           $method_to_call = "do_archive_course";
  186         }
  187       } else {
  188         # form only
  189         $method_to_call = "archive_course_form";
  190       }
  191     }
  192     elsif ($subDisplay eq "unarchive_course") {
  193       if (defined $r->param("unarchive_course")) {
  194         # validate or confirm
  195         @errors = $self->unarchive_course_validate;
  196         if (@errors) {
  197           $method_to_call = "unarchive_course_form";
  198         } else {
  199           $method_to_call = "unarchive_course_confirm";
  200         }
  201       } elsif (defined $r->param("confirm_unarchive_course")) {
  202         # validate and archive
  203         @errors = $self->unarchive_course_validate;
  204         if (@errors) {
  205           $method_to_call = "unarchive_course_form";
  206         } else {
  207           $method_to_call = "do_unarchive_course";
  208         }
  209       } else {
  210         # form only
  211         $method_to_call = "unarchive_course_form";
  212       }
  213     }
  214     elsif ($subDisplay eq "manage_locations") {
  215       if (defined ($r->param("manage_location_action"))) {
  216         $method_to_call =
  217             $r->param("manage_location_action");
  218       }
  219       else{
  220         $method_to_call = "manage_location_form";
  221       }
  222     }
  223     else {
  224       @errors = "Unrecognized sub-display @{[ CGI::b($subDisplay) ]}.";
  225     }
  226   }
  227 
  228   $self->{errors} = \@errors;
  229   $self->{method_to_call} = $method_to_call;
  230 }
  231 
  232 sub header {
  233   my ($self) = @_;
  234   my $method_to_call = $self->{method_to_call};
  235 #   if (defined $method_to_call and $method_to_call eq "do_export_database") {
  236 #     my $r = $self->r;
  237 #     my $courseID = $r->param("export_courseID");
  238 #     $r->content_type("application/octet-stream");
  239 #     $r->header_out("Content-Disposition" => "attachment; filename=\"${courseID}_database.xml\"");
  240 #     $r->send_http_header;
  241 #   } else {
  242     $self->SUPER::header;
  243 # }
  244 }
  245 
  246 # sends:
  247 #
  248 # HTTP/1.1 200 OK
  249 # Date: Fri, 09 Jul 2004 19:05:55 GMT
  250 # Server: Apache/1.3.27 (Unix) mod_perl/1.27
  251 # Content-Disposition: attachment; filename="mth143_database.xml"
  252 # Connection: close
  253 # Content-Type: application/octet-stream
  254 
  255 sub content {
  256   my ($self) = @_;
  257   my $method_to_call = $self->{method_to_call};
  258   if (defined $method_to_call and $method_to_call eq "do_export_database") {
  259     #$self->do_export_database;
  260     $self->SUPER::content;
  261   } else {
  262     $self->SUPER::content;
  263   }
  264 }
  265 
  266 sub body {
  267   my ($self) = @_;
  268   my $r = $self->r;
  269   my $ce = $r->ce;
  270   my $db = $r->db;
  271   my $authz = $r->authz;
  272   my $urlpath = $r->urlpath;
  273 
  274   my $user = $r->param('user');
  275 
  276   # check permissions
  277   unless ($authz->hasPermissions($user, "create_and_delete_courses")) {
  278     return "";
  279   }
  280   my $method_to_call = $self->{method_to_call};
  281   my $methodMessage ="";
  282 
  283   (defined($method_to_call) and $method_to_call eq "do_export_database") && do {
  284       my @export_courseID = $r->param("export_courseID");
  285       my $course_ids = join(", ", @export_courseID);
  286     $methodMessage  = CGI::p("Exporting database for course(s) $course_ids").
  287     CGI::p(".... please wait....
  288     If your browser times out you will
  289     still be able to download the exported database using the
  290     file manager.").CGI::hr();
  291   };
  292 
  293 
  294   print CGI::p({style=>"text-align: center"},
  295     CGI::a({href=>$self->systemLink($urlpath, params=>{subDisplay=>"add_course",add_admin_users=>1,
  296                add_dbLayout=>'sql_single',
  297                add_templates_course => $ce->{siteDefaults}->{default_templates_course} ||""}
  298                )},
  299                "Add Course"
  300     ),
  301     " | ",
  302     CGI::a({href=>$self->systemLink($urlpath, params=>{subDisplay=>"rename_course"})}, "Rename Course"),
  303     " | ",
  304     CGI::a({href=>$self->systemLink($urlpath, params=>{subDisplay=>"delete_course"})}, "Delete Course"),
  305     " | ",
  306     CGI::a({href=>$self->systemLink($urlpath, params=>{subDisplay=>"export_database"})}, "Export Database"),
  307     " | ",
  308     CGI::a({href=>$self->systemLink($urlpath, params=>{subDisplay=>"import_database"})}, "Import Database"),
  309     " | ",
  310     CGI::a({href=>$self->systemLink($urlpath, params=>{subDisplay=>"archive_course"})}, "Archive Course"),
  311      "|",
  312     CGI::a({href=>$self->systemLink($urlpath, params=>{subDisplay=>"unarchive_course"})}, "Unarchive Course"),
  313      "|",
  314     CGI::a({href=>$self->systemLink($urlpath, params=>{subDisplay=>"manage_locations"})}, "Manage Locations"),
  315     CGI::hr(),
  316     $methodMessage,
  317 
  318   );
  319 
  320   my @errors = @{$self->{errors}};
  321 
  322 
  323   if (@errors) {
  324     print CGI::div({class=>"ResultsWithError"},
  325       CGI::p("Please correct the following errors and try again:"),
  326       CGI::ul(CGI::li(\@errors)),
  327     );
  328   }
  329 
  330   if (defined $method_to_call and $method_to_call ne "") {
  331     $self->$method_to_call;
  332   } else {
  333 
  334     print CGI::h2("Courses");
  335 
  336     print CGI::start_ol();
  337 
  338     my @courseIDs = listCourses($ce);
  339     foreach my $courseID (sort {lc($a) cmp lc($b) } @courseIDs) {
  340       next if $courseID eq "admin"; # done already above
  341       my $urlpath = $r->urlpath->newFromModule("WeBWorK::ContentGenerator::ProblemSets", courseID => $courseID);
  342       my $tempCE = new WeBWorK::CourseEnvironment({
  343         %WeBWorK::SeedCE,
  344         courseName => $courseID,
  345       });
  346       print CGI::li(CGI::a({href=>$self->systemLink($urlpath, authen => 0)}, $courseID),
  347         CGI::code(
  348           $tempCE->{dbLayoutName},
  349         ),
  350         (-r $tempCE->{courseFiles}->{environment}) ? "" : CGI::i(", missing course.conf"),
  351 
  352       );
  353 
  354     }
  355 
  356     print CGI::end_ol();
  357 
  358     print CGI::h2("Archived Courses");
  359     print CGI::start_ol();
  360 
  361     @courseIDs = listArchivedCourses($ce);
  362     foreach my $courseID (sort {lc($a) cmp lc($b) } @courseIDs) {
  363       print CGI::li($courseID),
  364     }
  365 
  366     print CGI::end_ol();
  367   }
  368   return "";
  369 }
  370 
  371 ################################################################################
  372 
  373 sub add_course_form {
  374   my ($self) = @_;
  375   my $r = $self->r;
  376   my $ce = $r->ce;
  377   #my $db = $r->db;
  378   #my $authz = $r->authz;
  379   #my $urlpath = $r->urlpath;
  380 
  381   my $add_courseID                     = $r->param("add_courseID") || "";
  382   my $add_courseTitle                  = $r->param("add_courseTitle") || "";
  383   my $add_courseInstitution            = $r->param("add_courseInstitution") || "";
  384 
  385   my $add_admin_users                  = $r->param("add_admin_users") || "";
  386 
  387   my $add_initial_userID               = $r->param("add_initial_userID") || "";
  388   my $add_initial_password             = $r->param("add_initial_password") || "";
  389   my $add_initial_confirmPassword      = $r->param("add_initial_confirmPassword") || "";
  390   my $add_initial_firstName            = $r->param("add_initial_firstName") || "";
  391   my $add_initial_lastName             = $r->param("add_initial_lastName") || "";
  392   my $add_initial_email                = $r->param("add_initial_email") || "";
  393 
  394   my $add_templates_course             = $r->param("add_templates_course") || "";
  395 
  396   my $add_dbLayout                     = $r->param("add_dbLayout") || "";
  397 
  398   my @dbLayouts = do {
  399     my @ordered_layouts;
  400     foreach my $layout (@{$ce->{dbLayout_order}}) {
  401       if (exists $ce->{dbLayouts}->{$layout}) {
  402         push @ordered_layouts, $layout;
  403       }
  404     }
  405 
  406     my %ordered_layouts; @ordered_layouts{@ordered_layouts} = ();
  407     my @other_layouts;
  408     foreach my $layout (keys %{ $ce->{dbLayouts} }) {
  409       unless (exists $ordered_layouts{$layout}) {
  410         push @other_layouts, $layout;
  411       }
  412     }
  413 
  414     (@ordered_layouts, @other_layouts);
  415   };
  416 
  417   my $ce2 = new WeBWorK::CourseEnvironment({
  418     %WeBWorK::SeedCE,
  419     courseName => "COURSENAME",
  420   });
  421 
  422   my @existingCourses = listCourses($ce);
  423   @existingCourses = sort { lc($a) cmp lc ($b) } @existingCourses; #make sort case insensitive
  424 
  425   print CGI::h2("Add Course");
  426 
  427   print CGI::start_form(-method=>"POST", -action=>$r->uri);
  428   print $self->hidden_authen_fields;
  429   print $self->hidden_fields("subDisplay");
  430 
  431   print CGI::p("Specify an ID, title, and institution for the new course. The course ID may contain only letters, numbers, hyphens, and underscores.");
  432 
  433   print CGI::table({class=>"FormLayout"},
  434     CGI::Tr({},
  435       CGI::th({class=>"LeftHeader"}, "Course ID:"),
  436       CGI::td(CGI::textfield(-name=>"add_courseID", -value=>$add_courseID, -size=>25)),
  437     ),
  438     CGI::Tr({},
  439       CGI::th({class=>"LeftHeader"}, "Course Title:"),
  440       CGI::td(CGI::textfield(-name=>"add_courseTitle", -value=>$add_courseTitle, -size=>25)),
  441     ),
  442     CGI::Tr({},
  443       CGI::th({class=>"LeftHeader"}, "Institution:"),
  444       CGI::td(CGI::textfield(-name=>"add_courseInstitution", -value=>$add_courseInstitution, -size=>25)),
  445     ),
  446   );
  447 
  448   print CGI::p("To add the WeBWorK administrators to the new course (as instructors) check the box below.");
  449   my @checked = ($add_admin_users) ?(checked=>1): ();  # workaround because CGI::checkbox seems to have a bug -- it won't default to checked.
  450   print CGI::p({},CGI::input({-type=>'checkbox', -name=>"add_admin_users", @checked }, "Add WeBWorK administrators to new course"));
  451 
  452   print CGI::p("To add an additional instructor to the new course, specify user information below. The user ID may contain only
  453   numbers, letters, hyphens, periods (dots), commas,and underscores.\n");
  454 
  455   print CGI::table({class=>"FormLayout"}, CGI::Tr({},
  456     CGI::td({},
  457       CGI::table({class=>"FormLayout"},
  458         CGI::Tr({},
  459           CGI::th({class=>"LeftHeader"}, "User ID:"),
  460           CGI::td(CGI::textfield(-name=>"add_initial_userID", -value=>$add_initial_userID, -size=>25)),
  461         ),
  462         CGI::Tr({},
  463           CGI::th({class=>"LeftHeader"}, "Password:"),
  464           CGI::td(CGI::password_field(-name=>"add_initial_password", -value=>$add_initial_password, -size=>25)),
  465         ),
  466         CGI::Tr({},
  467           CGI::th({class=>"LeftHeader"}, "Confirm Password:"),
  468           CGI::td(CGI::password_field(-name=>"add_initial_confirmPassword", -value=>$add_initial_confirmPassword, -size=>25)),
  469         ),
  470       ),
  471     ),
  472     CGI::td({},
  473       CGI::table({class=>"FormLayout"},
  474         CGI::Tr({},
  475           CGI::th({class=>"LeftHeader"}, "First Name:"),
  476           CGI::td(CGI::textfield(-name=>"add_initial_firstName", -value=>$add_initial_firstName, -size=>25)),
  477         ),
  478         CGI::Tr({},
  479           CGI::th({class=>"LeftHeader"}, "Last Name:"),
  480           CGI::td(CGI::textfield(-name=>"add_initial_lastName", -value=>$add_initial_lastName, -size=>25)),
  481         ),
  482         CGI::Tr({},
  483           CGI::th({class=>"LeftHeader"}, "Email Address:"),
  484           CGI::td(CGI::textfield(-name=>"add_initial_email", -value=>$add_initial_email, -size=>25)),
  485         ),
  486       ),
  487 
  488     ),
  489   ));
  490 
  491   print CGI::p("To copy problem templates from an existing course, select the course below.");
  492 
  493   print CGI::table({class=>"FormLayout"},
  494     CGI::Tr({},
  495       CGI::th({class=>"LeftHeader"}, "Copy templates from:"),
  496       CGI::td(
  497         CGI::popup_menu(
  498           -name => "add_templates_course",
  499           -values => [ "", @existingCourses ],
  500           -default => $add_templates_course,
  501           #-size => 10,
  502           #-multiple => 0,
  503           #-labels => \%courseLabels,
  504         ),
  505 
  506       ),
  507     ),
  508   );
  509 
  510 
  511 
  512   print CGI::p("Select a database layout below.");
  513   print CGI::start_table({class=>"FormLayout"});
  514 
  515   my %dbLayout_buttons;
  516   my $selected_dbLayout = defined $add_dbLayout ? $add_dbLayout : $ce->{dbLayout_order}[0];
  517   @dbLayout_buttons{@dbLayouts} = CGI::radio_group(-name=>"add_dbLayout",-values=>\@dbLayouts,-default=>$selected_dbLayout);
  518   foreach my $dbLayout (@dbLayouts) {
  519     my $dbLayoutLabel = (defined $ce->{dbLayout_descr}{$dbLayout})
  520       ? "$dbLayout - " . $ce->{dbLayout_descr}{$dbLayout}
  521       : "$dbLayout - no description provided in global.conf";
  522     print CGI::Tr({},
  523       CGI::td({width=>'20%'}, $dbLayout_buttons{$dbLayout}),
  524       CGI::td($dbLayoutLabel),
  525     );
  526   }
  527   print CGI::end_table();
  528   print CGI::p({style=>"text-align: left"}, CGI::submit(-name=>"add_course", -label=>"Add Course"));
  529 
  530   print CGI::end_form();
  531 }
  532 
  533 sub add_course_validate {
  534   my ($self) = @_;
  535   my $r = $self->r;
  536   my $ce = $r->ce;
  537   #my $db = $r->db;
  538   #my $authz = $r->authz;
  539   #my $urlpath = $r->urlpath;
  540 
  541   my $add_courseID                     = $r->param("add_courseID") || "";
  542   my $add_courseTitle                  = $r->param("add_courseTitle") || "";
  543   my $add_courseInstitution            = $r->param("add_courseInstitution") || "";
  544 
  545   my $add_admin_users                  = $r->param("add_admin_users") || "";
  546 
  547   my $add_initial_userID               = $r->param("add_initial_userID") || "";
  548   my $add_initial_password             = $r->param("add_initial_password") || "";
  549   my $add_initial_confirmPassword      = $r->param("add_initial_confirmPassword") || "";
  550   my $add_initial_firstName            = $r->param("add_initial_firstName") || "";
  551   my $add_initial_lastName             = $r->param("add_initial_lastName") || "";
  552   my $add_initial_email                = $r->param("add_initial_email") || "";
  553 
  554   my $add_templates_course             = $r->param("add_templates_course") || "";
  555 
  556   my $add_dbLayout                     = $r->param("add_dbLayout") || "";
  557 
  558   my @errors;
  559 
  560   if ($add_courseID eq "") {
  561     push @errors, "You must specify a course ID.";
  562   }
  563   unless ($add_courseID =~ /^[\w-]*$/) { # regex copied from CourseAdministration.pm
  564     push @errors, "Course ID may only contain letters, numbers, hyphens, and underscores.";
  565   }
  566   if (grep { $add_courseID eq $_ } listCourses($ce)) {
  567     push @errors, "A course with ID $add_courseID already exists.";
  568   }
  569   #if ($add_courseTitle eq "") {
  570   # push @errors, "You must specify a course title.";
  571   #}
  572   #if ($add_courseInstitution eq "") {
  573   # push @errors, "You must specify an institution for this course.";
  574   #}
  575 
  576   if ($add_initial_userID ne "") {
  577     if ($add_initial_password eq "") {
  578       push @errors, "You must specify a password for the initial instructor.";
  579     }
  580     if ($add_initial_confirmPassword eq "") {
  581       push @errors, "You must confirm the password for the initial instructor.";
  582     }
  583     if ($add_initial_password ne $add_initial_confirmPassword) {
  584       push @errors, "The password and password confirmation for the instructor must match.";
  585     }
  586     if ($add_initial_firstName eq "") {
  587       push @errors, "You must specify a first name for the initial instructor.";
  588     }
  589     if ($add_initial_lastName eq "") {
  590       push @errors, "You must specify a last name for the initial instructor.";
  591     }
  592     if ($add_initial_email eq "") {
  593       push @errors, "You must specify an email address for the initial instructor.";
  594     }
  595   }
  596 
  597   if ($add_dbLayout eq "") {
  598     push @errors, "You must select a database layout.";
  599   } else {
  600     if (exists $ce->{dbLayouts}->{$add_dbLayout}) {
  601       # we used to check for layout-specific fields here, but there aren't any layouts that require them
  602       # anymore. (in the future, we'll probably deal with this in layout-specific modules.)
  603     } else {
  604       push @errors, "The database layout $add_dbLayout doesn't exist.";
  605     }
  606   }
  607 
  608   return @errors;
  609 }
  610 
  611 sub do_add_course {
  612   my ($self) = @_;
  613   my $r = $self->r;
  614   my $ce = $r->ce;
  615   my $db = $r->db;
  616   my $authz = $r->authz;
  617   my $urlpath = $r->urlpath;
  618 
  619   my $add_courseID                     = $r->param("add_courseID") || "";
  620   my $add_courseTitle                  = $r->param("add_courseTitle") || "";
  621   my $add_courseInstitution            = $r->param("add_courseInstitution") || "";
  622 
  623   my $add_admin_users                  = $r->param("add_admin_users") || "";
  624 
  625   my $add_initial_userID               = $r->param("add_initial_userID") || "";
  626   my $add_initial_password             = $r->param("add_initial_password") || "";
  627   my $add_initial_confirmPassword      = $r->param("add_initial_confirmPassword") || "";
  628   my $add_initial_firstName            = $r->param("add_initial_firstName") || "";
  629   my $add_initial_lastName             = $r->param("add_initial_lastName") || "";
  630   my $add_initial_email                = $r->param("add_initial_email") || "";
  631 
  632   my $add_templates_course             = $r->param("add_templates_course") || "";
  633 
  634   my $add_dbLayout                     = $r->param("add_dbLayout") || "";
  635 
  636   my $ce2 = new WeBWorK::CourseEnvironment({
  637     %WeBWorK::SeedCE,
  638     courseName => $add_courseID,
  639   });
  640 
  641   my %courseOptions = ( dbLayoutName => $add_dbLayout );
  642 
  643   if ($add_initial_email ne "") {
  644     $courseOptions{allowedRecipients} = [ $add_initial_email ];
  645     # don't set feedbackRecipients -- this just gets in the way of the more
  646     # intelligent "receive_recipients" method.
  647     #$courseOptions{feedbackRecipients} = [ $add_initial_email ];
  648   }
  649 
  650   # this is kinda left over from when we had 'gdbm' and 'sql' database layouts
  651   # below this line, we would grab values from getopt and put them in this hash
  652   # but for now the hash can remain empty
  653   my %dbOptions;
  654 
  655   my @users;
  656 
  657   # copy users from current (admin) course if desired
  658   if ($add_admin_users ne "") {
  659     foreach my $userID ($db->listUsers) {
  660       if ($userID eq $add_initial_userID) {
  661         $self->addbadmessage( "User '$userID' will not be copied from admin course as it is the initial instructor.");
  662         next;
  663       }
  664       my $User            = $db->getUser($userID);
  665       my $Password        = $db->getPassword($userID);
  666       my $PermissionLevel = $db->getPermissionLevel($userID);
  667       push @users, [ $User, $Password, $PermissionLevel ]
  668              if $authz->hasPermissions($userID,"create_and_delete_courses");
  669              #only transfer the "instructors" in the admin course classlist.
  670     }
  671   }
  672 
  673   # add initial instructor if desired
  674   if ($add_initial_userID ne "") {
  675     my $User = $db->newUser(
  676       user_id       => $add_initial_userID,
  677       first_name    => $add_initial_firstName,
  678       last_name     => $add_initial_lastName,
  679       student_id    => $add_initial_userID,
  680       email_address => $add_initial_email,
  681       status        => "C",
  682     );
  683     my $Password = $db->newPassword(
  684       user_id  => $add_initial_userID,
  685       password => cryptPassword($add_initial_password),
  686     );
  687     my $PermissionLevel = $db->newPermissionLevel(
  688       user_id    => $add_initial_userID,
  689       permission => "10",
  690     );
  691     push @users, [ $User, $Password, $PermissionLevel ];
  692   }
  693 
  694   push @{$courseOptions{PRINT_FILE_NAMES_FOR}}, map { $_->[0]->user_id } @users;
  695 
  696   my %optional_arguments;
  697   if ($add_templates_course ne "") {
  698     $optional_arguments{templatesFrom} = $add_templates_course;
  699   }
  700 
  701   eval {
  702     addCourse(
  703       courseID      => $add_courseID,
  704       ce            => $ce2,
  705       courseOptions => \%courseOptions,
  706       dbOptions     => \%dbOptions,
  707       users         => \@users,
  708       %optional_arguments,
  709     );
  710   };
  711   if ($@) {
  712     my $error = $@;
  713     print CGI::div({class=>"ResultsWithError"},
  714       CGI::p("An error occured while creating the course $add_courseID:"),
  715       CGI::tt(CGI::escapeHTML($error)),
  716     );
  717     # get rid of any partially built courses
  718     # FIXME  -- this is too fragile
  719     unless ($error =~ /course exists/) {
  720       eval {
  721         deleteCourse(
  722           courseID   => $add_courseID,
  723           ce         => $ce2,
  724           dbOptions  => \%dbOptions,
  725         );
  726       }
  727     }
  728   } else {
  729       #log the action
  730       writeLog($ce, "hosted_courses", join("\t",
  731         "\tAdded",
  732         ( defined $add_courseInstitution ? $add_courseInstitution : "(no institution specified)" ),
  733         ( defined $add_courseTitle ? $add_courseTitle : "(no title specified)" ),
  734         $add_courseID,
  735         $add_initial_firstName,
  736         $add_initial_lastName,
  737         $add_initial_email,
  738       ));
  739       # add contact to admin course as student?
  740       # FIXME -- should we do this?
  741       if ($add_initial_userID ne "") {
  742           my $composite_id = "${add_initial_userID}_${add_courseID}"; # student id includes school name and contact
  743       my $User = $db->newUser(
  744       user_id       => $composite_id,          # student id includes school name and contact
  745       first_name    => $add_initial_firstName,
  746       last_name     => $add_initial_lastName,
  747       student_id    => $add_initial_userID,
  748       email_address => $add_initial_email,
  749       status        => "C",
  750       );
  751       my $Password = $db->newPassword(
  752         user_id  => $composite_id,
  753         password => cryptPassword($add_initial_password),
  754       );
  755       my $PermissionLevel = $db->newPermissionLevel(
  756         user_id    => $composite_id,
  757         permission => "0",
  758       );
  759       # add contact to admin course as student
  760       # or if this contact and course already exist in a dropped status
  761       # change the student's status to enrolled
  762       if (my $oldUser = $db->getUser($composite_id) ) {
  763         warn "Replacing old data for $composite_id  status: ". $oldUser->status;
  764         $db->deleteUser($composite_id);
  765       }
  766       eval { $db->addUser($User)                       }; warn $@ if $@;
  767       eval { $db->addPassword($Password)               }; warn $@ if $@;
  768       eval { $db->addPermissionLevel($PermissionLevel) }; warn $@ if $@;
  769     }
  770     print CGI::div({class=>"ResultsWithoutError"},
  771       CGI::p("Successfully created the course $add_courseID"),
  772     );
  773     my $newCoursePath = $urlpath->newFromModule("WeBWorK::ContentGenerator::ProblemSets",
  774       courseID => $add_courseID);
  775     my $newCourseURL = $self->systemLink($newCoursePath, authen => 0);
  776     print CGI::div({style=>"text-align: center"},
  777       CGI::a({href=>$newCourseURL}, "Log into $add_courseID"),
  778     );
  779   }
  780 
  781 
  782 }
  783 
  784 ################################################################################
  785 
  786 sub rename_course_form {
  787   my ($self) = @_;
  788   my $r = $self->r;
  789   my $ce = $r->ce;
  790   #my $db = $r->db;
  791   #my $authz = $r->authz;
  792   #my $urlpath = $r->urlpath;
  793 
  794   my $rename_oldCourseID     = $r->param("rename_oldCourseID")     || "";
  795   my $rename_newCourseID     = $r->param("rename_newCourseID")     || "";
  796 
  797   my @courseIDs = listCourses($ce);
  798   @courseIDs    = sort {lc($a) cmp lc ($b) } @courseIDs;
  799 
  800   my %courseLabels; # records... heh.
  801   foreach my $courseID (@courseIDs) {
  802     my $tempCE = new WeBWorK::CourseEnvironment({
  803       %WeBWorK::SeedCE,
  804       courseName => $courseID,
  805     });
  806     $courseLabels{$courseID} = "$courseID (" . $tempCE->{dbLayoutName} . ")";
  807   }
  808 
  809   print CGI::h2("Rename Course");
  810 
  811   print CGI::start_form(-method=>"POST", -action=>$r->uri);
  812   print $self->hidden_authen_fields;
  813   print $self->hidden_fields("subDisplay");
  814 
  815   print CGI::p("Select a course to rename.");
  816 
  817   print CGI::table({class=>"FormLayout"},
  818     CGI::Tr({},
  819       CGI::th({class=>"LeftHeader"}, "Course Name:"),
  820       CGI::td(
  821         CGI::scrolling_list(
  822           -name => "rename_oldCourseID",
  823           -values => \@courseIDs,
  824           -default => $rename_oldCourseID,
  825           -size => 10,
  826           -multiple => 0,
  827           -labels => \%courseLabels,
  828         ),
  829       ),
  830     ),
  831     CGI::Tr({},
  832       CGI::th({class=>"LeftHeader"}, "New Name:"),
  833       CGI::td(CGI::textfield(-name=>"rename_newCourseID", -value=>$rename_newCourseID, -size=>25)),
  834     ),
  835   );
  836 
  837   print CGI::end_table();
  838 
  839   print CGI::p({style=>"text-align: center"}, CGI::submit(-name=>"rename_course", -label=>"Rename Course"));
  840 
  841   print CGI::end_form();
  842 }
  843 
  844 sub rename_course_validate {
  845   my ($self) = @_;
  846   my $r = $self->r;
  847   my $ce = $r->ce;
  848   #my $db = $r->db;
  849   #my $authz = $r->authz;
  850   #my $urlpath = $r->urlpath;
  851 
  852   my $rename_oldCourseID     = $r->param("rename_oldCourseID")     || "";
  853   my $rename_newCourseID     = $r->param("rename_newCourseID")     || "";
  854 
  855   my @errors;
  856 
  857   if ($rename_oldCourseID eq "") {
  858     push @errors, "You must select a course to rename.";
  859   }
  860   if ($rename_newCourseID eq "") {
  861     push @errors, "You must specify a new name for the course.";
  862   }
  863   if ($rename_oldCourseID eq $rename_newCourseID) {
  864     push @errors, "Can't rename to the same name.";
  865   }
  866   unless ($rename_newCourseID =~ /^[\w-]*$/) { # regex copied from CourseAdministration.pm
  867     push @errors, "Course ID may only contain letters, numbers, hyphens, and underscores.";
  868   }
  869   if (grep { $rename_newCourseID eq $_ } listCourses($ce)) {
  870     push @errors, "A course with ID $rename_newCourseID already exists.";
  871   }
  872 
  873   my $ce2 = new WeBWorK::CourseEnvironment({
  874     %WeBWorK::SeedCE,
  875     courseName => $rename_oldCourseID,
  876   });
  877 
  878   return @errors;
  879 }
  880 
  881 sub do_rename_course {
  882   my ($self) = @_;
  883   my $r = $self->r;
  884   my $ce = $r->ce;
  885   my $db = $r->db;
  886   #my $authz = $r->authz;
  887   my $urlpath = $r->urlpath;
  888 
  889   my $rename_oldCourseID     = $r->param("rename_oldCourseID")     || "";
  890   my $rename_newCourseID     = $r->param("rename_newCourseID")     || "";
  891 
  892   my $ce2 = new WeBWorK::CourseEnvironment({
  893     %WeBWorK::SeedCE,
  894     courseName => $rename_oldCourseID,
  895   });
  896 
  897   my $dbLayoutName = $ce->{dbLayoutName};
  898 
  899   # this is kinda left over from when we had 'gdbm' and 'sql' database layouts
  900   # below this line, we would grab values from getopt and put them in this hash
  901   # but for now the hash can remain empty
  902   my %dbOptions;
  903 
  904   eval {
  905     renameCourse(
  906       courseID      => $rename_oldCourseID,
  907       ce            => $ce2,
  908       dbOptions     => \%dbOptions,
  909       newCourseID   => $rename_newCourseID,
  910     );
  911   };
  912   if ($@) {
  913     my $error = $@;
  914     print CGI::div({class=>"ResultsWithError"},
  915       CGI::p("An error occured while renaming the course $rename_oldCourseID to $rename_newCourseID:"),
  916       CGI::tt(CGI::escapeHTML($error)),
  917     );
  918   } else {
  919     print CGI::div({class=>"ResultsWithoutError"},
  920       CGI::p("Successfully renamed the course $rename_oldCourseID to $rename_newCourseID"),
  921     );
  922     my $newCoursePath = $urlpath->newFromModule("WeBWorK::ContentGenerator::ProblemSets",
  923       courseID => $rename_newCourseID);
  924     my $newCourseURL = $self->systemLink($newCoursePath, authen => 0);
  925     print CGI::div({style=>"text-align: center"},
  926       CGI::a({href=>$newCourseURL}, "Log into $rename_newCourseID"),
  927     );
  928   }
  929 }
  930 
  931 ################################################################################
  932 
  933 sub delete_course_form {
  934   my ($self) = @_;
  935   my $r = $self->r;
  936   my $ce = $r->ce;
  937   #my $db = $r->db;
  938   #my $authz = $r->authz;
  939   #my $urlpath = $r->urlpath;
  940 
  941   my $delete_courseID     = $r->param("delete_courseID")     || "";
  942 
  943   my @courseIDs = listCourses($ce);
  944   @courseIDs    = sort {lc($a) cmp lc ($b) } @courseIDs; #make sort case insensitive
  945 
  946   my %courseLabels; # records... heh.
  947   foreach my $courseID (@courseIDs) {
  948     my $tempCE = new WeBWorK::CourseEnvironment({
  949       %WeBWorK::SeedCE,
  950       courseName => $courseID,
  951     });
  952     $courseLabels{$courseID} = "$courseID (" . $tempCE->{dbLayoutName} . ")";
  953   }
  954 
  955   print CGI::h2("Delete Course");
  956 
  957   print CGI::start_form(-method=>"POST", -action=>$r->uri);
  958   print $self->hidden_authen_fields;
  959   print $self->hidden_fields("subDisplay");
  960 
  961   print CGI::p("Select a course to delete.");
  962 
  963   print CGI::table({class=>"FormLayout"},
  964     CGI::Tr({},
  965       CGI::th({class=>"LeftHeader"}, "Course Name:"),
  966       CGI::td(
  967         CGI::scrolling_list(
  968           -name => "delete_courseID",
  969           -values => \@courseIDs,
  970           -default => $delete_courseID,
  971           -size => 10,
  972           -multiple => 0,
  973           -labels => \%courseLabels,
  974         ),
  975       ),
  976     ),
  977   );
  978 
  979   print CGI::p({style=>"text-align: center"}, CGI::submit(-name=>"delete_course", -value=>"Delete Course"));
  980 
  981   print CGI::end_form();
  982 }
  983 
  984 sub delete_course_validate {
  985   my ($self) = @_;
  986   my $r = $self->r;
  987   my $ce = $r->ce;
  988   #my $db = $r->db;
  989   #my $authz = $r->authz;
  990   my $urlpath = $r->urlpath;
  991 
  992   my $delete_courseID     = $r->param("delete_courseID")     || "";
  993 
  994   my @errors;
  995 
  996   if ($delete_courseID eq "") {
  997     push @errors, "You must specify a course name.";
  998   } elsif ($delete_courseID eq $urlpath->arg("courseID")) {
  999     push @errors, "You cannot delete the course you are currently using.";
 1000   }
 1001 
 1002   my $ce2 = new WeBWorK::CourseEnvironment({
 1003     %WeBWorK::SeedCE,
 1004     courseName => $delete_courseID,
 1005   });
 1006 
 1007   return @errors;
 1008 }
 1009 
 1010 sub delete_course_confirm {
 1011   my ($self) = @_;
 1012   my $r = $self->r;
 1013   my $ce = $r->ce;
 1014   #my $db = $r->db;
 1015   #my $authz = $r->authz;
 1016   #my $urlpath = $r->urlpath;
 1017 
 1018   print CGI::h2("Delete Course");
 1019 
 1020   my $delete_courseID     = $r->param("delete_courseID")     || "";
 1021 
 1022   my $ce2 = new WeBWorK::CourseEnvironment({
 1023     %WeBWorK::SeedCE,
 1024     courseName => $delete_courseID,
 1025   });
 1026 
 1027   print CGI::p("Are you sure you want to delete the course " . CGI::b($delete_courseID)
 1028     . "? All course files and data will be destroyed. There is no undo available.");
 1029 
 1030   print CGI::start_form(-method=>"POST", -action=>$r->uri);
 1031   print $self->hidden_authen_fields;
 1032   print $self->hidden_fields("subDisplay");
 1033   print $self->hidden_fields(qw/delete_courseID/);
 1034 
 1035   print CGI::p({style=>"text-align: center"},
 1036     CGI::submit(-name=>"decline_delete_course", -label=>"Don't delete"),
 1037     "&nbsp;",
 1038     CGI::submit(-name=>"confirm_delete_course", -label=>"Delete"),
 1039   );
 1040 
 1041   print CGI::end_form();
 1042 }
 1043 
 1044 sub do_delete_course {
 1045   my ($self) = @_;
 1046   my $r = $self->r;
 1047   my $ce = $r->ce;
 1048   my $db = $r->db;
 1049   #my $authz = $r->authz;
 1050   #my $urlpath = $r->urlpath;
 1051 
 1052   my $delete_courseID     = $r->param("delete_courseID")     || "";
 1053 
 1054   my $ce2 = new WeBWorK::CourseEnvironment({
 1055     %WeBWorK::SeedCE,
 1056     courseName => $delete_courseID,
 1057   });
 1058 
 1059   # this is kinda left over from when we had 'gdbm' and 'sql' database layouts
 1060   # below this line, we would grab values from getopt and put them in this hash
 1061   # but for now the hash can remain empty
 1062   my %dbOptions;
 1063 
 1064   eval {
 1065     deleteCourse(
 1066       courseID => $delete_courseID,
 1067       ce => $ce2,
 1068       dbOptions => \%dbOptions,
 1069     );
 1070   };
 1071 
 1072   if ($@) {
 1073     my $error = $@;
 1074     print CGI::div({class=>"ResultsWithError"},
 1075       CGI::p("An error occured while deleting the course $delete_courseID:"),
 1076       CGI::tt(CGI::escapeHTML($error)),
 1077     );
 1078   } else {
 1079       # mark the contact person in the admin course as dropped.
 1080       # find the contact person for the course by searching the admin classlist.
 1081       my @contacts = grep /_$delete_courseID$/,  $db->listUsers;
 1082       if (@contacts) {
 1083       die "Incorrect number of contacts for the course $delete_courseID". join(" ", @contacts) if @contacts !=1;
 1084       #warn "contacts", join(" ", @contacts);
 1085       #my $composite_id = "${add_initial_userID}_${add_courseID}";
 1086       my $composite_id  = $contacts[0];
 1087 
 1088       # mark the contact person as dropped.
 1089       my $User = $db->getUser($composite_id);
 1090       my $status_name = 'Drop';
 1091       my $status_value = ($ce->status_name_to_abbrevs($status_name))[0];
 1092       $User->status($status_value);
 1093       $db->putUser($User);
 1094     }
 1095 
 1096     print CGI::div({class=>"ResultsWithoutError"},
 1097       CGI::p("Successfully deleted the course $delete_courseID."),
 1098     );
 1099      writeLog($ce, "hosted_courses", join("\t",
 1100         "\tDeleted",
 1101         "",
 1102         "",
 1103         $delete_courseID,
 1104       ));
 1105     print CGI::start_form(-method=>"POST", -action=>$r->uri);
 1106     print $self->hidden_authen_fields;
 1107     print $self->hidden_fields("subDisplay");
 1108 
 1109     print CGI::p({style=>"text-align: center"}, CGI::submit(-name=>"decline_delete_course", -value=>"OK"),);
 1110 
 1111     print CGI::end_form();
 1112   }
 1113 }
 1114 
 1115 ################################################################################
 1116 
 1117 sub export_database_form {
 1118   my ($self) = @_;
 1119   my $r = $self->r;
 1120   my $ce = $r->ce;
 1121   #my $db = $r->db;
 1122   #my $authz = $r->authz;
 1123   #my $urlpath = $r->urlpath;
 1124 
 1125   my @tables = keys %{$ce->{dbLayout}};
 1126 
 1127   my $export_courseID = $r->param("export_courseID") || "";
 1128   my @export_tables   = $r->param("export_tables");
 1129 
 1130   @export_tables = @tables unless @export_tables;
 1131 
 1132   my @courseIDs = listCourses($ce);
 1133   @courseIDs    = sort {lc($a) cmp lc ($b) } @courseIDs; #make sort case insensitive
 1134 
 1135   my %courseLabels; # records... heh.
 1136   foreach my $courseID (@courseIDs) {
 1137     my $tempCE = new WeBWorK::CourseEnvironment({
 1138       %WeBWorK::SeedCE,
 1139       courseName => $courseID,
 1140     });
 1141     $courseLabels{$courseID} = "$courseID (" . $tempCE->{dbLayoutName} . ")";
 1142   }
 1143 
 1144   print CGI::h2("Export Database");
 1145 
 1146   print CGI::p(IMPORT_EXPORT_WARNING);
 1147 
 1148   print CGI::start_form(-method=>"GET", -action=>$r->uri);
 1149   print $self->hidden_authen_fields;
 1150   print $self->hidden_fields("subDisplay");
 1151 
 1152   print CGI::p({},"Select a course to export the course's database. Please note
 1153   that exporting can take a very long time for a large course. If you have
 1154   shell access to the WeBWorK server, you may use the ", CGI::code("wwdb"), "
 1155   utility instead.");
 1156 
 1157   print CGI::table({class=>"FormLayout"},
 1158     CGI::Tr({},
 1159       CGI::th({class=>"LeftHeader"}, "Course Name:"),
 1160       CGI::td(
 1161         CGI::scrolling_list(
 1162           -name => "export_courseID",
 1163           -values => \@courseIDs,
 1164           -default => $export_courseID,
 1165           -size => 10,
 1166           -multiple => 1,
 1167           -labels => \%courseLabels,
 1168         ),
 1169       ),
 1170     ),
 1171     CGI::Tr({},
 1172       CGI::th({class=>"LeftHeader"}, "Tables to Export:"),
 1173       CGI::td({},
 1174         CGI::checkbox_group(
 1175           -name => "export_tables",
 1176           -values => \@tables,
 1177           -default => \@export_tables,
 1178           -linebreak => 1,
 1179         ),
 1180       ),
 1181     ),
 1182   );
 1183 
 1184   print CGI::p({style=>"text-align: center"}, CGI::submit(-name=>"export_database", -value=>"Export Database"));
 1185 
 1186   print CGI::end_form();
 1187 }
 1188 
 1189 sub export_database_validate {
 1190   my ($self) = @_;
 1191   my $r = $self->r;
 1192   #my $ce = $r->ce;
 1193   #my $db = $r->db;
 1194   #my $authz = $r->authz;
 1195   #my $urlpath = $r->urlpath;
 1196 
 1197   my @export_courseID = $r->param("export_courseID") || ();
 1198   my @export_tables   = $r->param("export_tables");
 1199 
 1200   my @errors;
 1201 
 1202   unless ( @export_courseID) {
 1203     push @errors, "You must specify at least one course name.";
 1204   }
 1205 
 1206   unless (@export_tables) {
 1207     push @errors, "You must specify at least one table to export.";
 1208   }
 1209 
 1210   return @errors;
 1211 }
 1212 
 1213 sub do_export_database {
 1214   my ($self) = @_;
 1215   my $r = $self->r;
 1216   my $ce = $r->ce;
 1217   #my $db = $r->db;
 1218   #my $authz = $r->authz;
 1219   my $urlpath = $r->urlpath;
 1220 
 1221   my @export_courseID = $r->param("export_courseID");
 1222   my @export_tables   = $r->param("export_tables");
 1223 
 1224   foreach my $export_courseID (@export_courseID) {
 1225 
 1226     my $ce2 = new WeBWorK::CourseEnvironment({
 1227       %WeBWorK::SeedCE,
 1228       courseName => $export_courseID,
 1229     });
 1230 
 1231     my $db2 = new WeBWorK::DB($ce2->{dbLayout});
 1232 
 1233     #my ($fh, $export_file) = tempfile("db_export_XXXXXX", DIR => $ce->{webworkDirs}->{tmp});
 1234     #my ($random_chars) = $export_file =~ m/db_export_(\w+)$/;
 1235     # export to the admin/templates directory
 1236     my $exportFileName = "$export_courseID.exported.xml";
 1237     my $exportFilePath = $ce->{courseDirs}->{templates}."/$exportFileName";
 1238     # get a unique name
 1239     my $number =1;
 1240     while (-e "$exportFilePath.$number.gz") {
 1241       $number++;
 1242       last if $number>9;
 1243     }
 1244     if ($number<=9 ) {
 1245       $exportFilePath = "$exportFilePath.$number";
 1246       $exportFileName = "$exportFileName.$number";
 1247     } else {
 1248       $self->addbadmessage(CGI::p("There are more than 9 exported files for this course! Please
 1249       remove some of these files."));
 1250       $exportFilePath = "$exportFilePath.999";
 1251       $exportFileName = "$exportFileName.999";
 1252     }
 1253 
 1254     my $outputFileHandle = new IO::File(">$exportFilePath") or warn "Unable to create $exportFilePath";
 1255 
 1256     my @errors;
 1257     eval {
 1258       @errors = dbExport(
 1259         db => $db2,
 1260         #xml => $fh,
 1261         xml => $outputFileHandle,
 1262         tables => \@export_tables,
 1263       );
 1264     };
 1265 
 1266     $outputFileHandle->close();
 1267 
 1268     my $gzipMessage = system($ce->{externalPrograms}{gzip}, $exportFilePath);
 1269     if ( !$gzipMessage ) {
 1270       $self->addgoodmessage(CGI::p( "Database saved to templates/$exportFileName.gz.
 1271       You may download it with the file manager."));
 1272     } else {
 1273       $self->addbadmessage(CGI::p( "Failed to gzip file $exportFilePath"));
 1274     }
 1275     unlink $exportFilePath;
 1276   } # end export of one course
 1277   #push @errors, "Fatal exception: $@" if $@;
 1278   #
 1279   #if (@errors) {
 1280   # print CGI::div({class=>"ResultsWithError"},
 1281   #   CGI::p("An error occured while exporting the database of course $export_courseID:"),
 1282   #   CGI::ul(CGI::li(\@errors)),
 1283   # );
 1284   #} else {
 1285   # print CGI::div({class=>"ResultsWithoutError"},
 1286   #   CGI::p("Export succeeded."),
 1287   # );
 1288   #
 1289   # print CGI::div({style=>"text-align: center"},
 1290   #   CGI::a({href=>$self->systemLink($urlpath, params=>{download_exported_database=>$random_chars, export_courseID=>undef})}, "Download Exported Database"),
 1291   # );
 1292   #}
 1293 }
 1294 
 1295 ################################################################################
 1296 
 1297 sub import_database_form {
 1298   my ($self) = @_;
 1299   my $r = $self->r;
 1300   my $ce = $r->ce;
 1301   #my $db = $r->db;
 1302   #my $authz = $r->authz;
 1303   #my $urlpath = $r->urlpath;
 1304 
 1305   my @tables = keys %{$ce->{dbLayout}};
 1306 
 1307   my $import_file     = $r->param("import_file")     || "";
 1308   my $import_courseID = $r->param("import_courseID") || "";
 1309   my @import_tables   = $r->param("import_tables");
 1310   my $import_conflict = $r->param("import_conflict") || "skip";
 1311 
 1312   @import_tables = @tables unless @import_tables;
 1313 
 1314   my @courseIDs = listCourses($ce);
 1315   @courseIDs    = sort {lc($a) cmp lc ($b) } @courseIDs; #make sort case insensitive
 1316 
 1317 
 1318   my %courseLabels; # records... heh.
 1319   foreach my $courseID (@courseIDs) {
 1320     my $tempCE = new WeBWorK::CourseEnvironment({
 1321       %WeBWorK::SeedCE,
 1322       courseName => $courseID,
 1323     });
 1324     $courseLabels{$courseID} = "$courseID (" . $tempCE->{dbLayoutName} . ")";
 1325   }
 1326 
 1327   # find databases:
 1328   my $templatesDir = $ce->{courseDirs}->{templates};
 1329   my $exempt_dirs = join("|", keys %{ $r->ce->{courseFiles}->{problibs} });
 1330 
 1331   my @databaseFiles = listFilesRecursive(
 1332     $templatesDir,
 1333     qr/.\.exported\.xml\.\d*\.gz$/, # match these files  #FIXME this is too restricive!!
 1334     qr/^(?:$exempt_dirs|CVS)$/, # prune these directories
 1335     0, # match against file name only
 1336     1, # prune against path relative to $templatesDir
 1337   );
 1338 
 1339   my %databaseLabels = map { ($_ => $_) } @databaseFiles;
 1340 
 1341   #######
 1342 
 1343   print CGI::h2("Import Database");
 1344 
 1345   print CGI::p(IMPORT_EXPORT_WARNING);
 1346 
 1347   print CGI::start_form(-method=>"POST", -action=>$r->uri, -enctype=>&CGI::MULTIPART);
 1348   print $self->hidden_authen_fields;
 1349   print $self->hidden_fields("subDisplay");
 1350 
 1351   print CGI::table({class=>"FormLayout"},
 1352     CGI::Tr({},
 1353       CGI::th({class=>"LeftHeader"}, "Database XML File:"),
 1354       CGI::td(
 1355         CGI::scrolling_list(
 1356           -name => "import_file",
 1357           -values => \@databaseFiles,
 1358           -default => undef,
 1359           -size => 10,
 1360           -multiple => 0,
 1361           -labels => \%databaseLabels,
 1362         ),
 1363 
 1364       )
 1365     ),
 1366     CGI::Tr({},
 1367       CGI::th({class=>"LeftHeader"}, "Tables to Import:"),
 1368       CGI::td(
 1369         CGI::checkbox_group(
 1370           -name => "import_tables",
 1371           -values => \@tables,
 1372           -default => \@import_tables,
 1373           -linebreak => 1,
 1374         ),
 1375       ),
 1376     ),
 1377     CGI::Tr({},
 1378       CGI::th({class=>"LeftHeader"}, "Import into Course:"),
 1379       CGI::td(
 1380         CGI::scrolling_list(
 1381           -name => "import_courseID",
 1382           -values => \@courseIDs,
 1383           -default => $import_courseID,
 1384           -size => 10,
 1385           -multiple => 0,
 1386           -labels => \%courseLabels,
 1387         ),
 1388       ),
 1389     ),
 1390     CGI::Tr({},
 1391       CGI::th({class=>"LeftHeader"}, "Conflicts:"),
 1392       CGI::td(
 1393         CGI::radio_group(
 1394           -name => "import_conflict",
 1395           -values => [qw/skip replace/],
 1396           -default => $import_conflict,
 1397           -linebreak=>'true',
 1398           -labels => {
 1399             skip => "Skip duplicate records",
 1400             replace => "Replace duplicate records",
 1401           },
 1402         ),
 1403       ),
 1404     ),
 1405   );
 1406 
 1407   print CGI::p({style=>"text-align: center"}, CGI::submit(-name=>"import_database", -value=>"Import Database"));
 1408 
 1409   print CGI::end_form();
 1410 }
 1411 
 1412 sub import_database_validate {
 1413   my ($self) = @_;
 1414   my $r = $self->r;
 1415   #my $ce = $r->ce;
 1416   #my $db = $r->db;
 1417   #my $authz = $r->authz;
 1418   #my $urlpath = $r->urlpath;
 1419 
 1420   my $import_file     = $r->param("import_file")     || "";
 1421   my $import_courseID = $r->param("import_courseID") || "";
 1422   my @import_tables   = $r->param("import_tables");
 1423   #my $import_conflict = $r->param("import_conflict") || "skip"; # not checked
 1424 
 1425   my @errors;
 1426 
 1427   if ($import_file eq "") {
 1428     push @errors, "You must specify a database file to import.";
 1429   }
 1430 
 1431   if ($import_courseID eq "") {
 1432     push @errors, "You must specify a course name.";
 1433   }
 1434 
 1435   unless (@import_tables) {
 1436     push @errors, "You must specify at least one table to import.";
 1437   }
 1438 
 1439   return @errors;
 1440 }
 1441 
 1442 sub do_import_database {
 1443   my ($self) = @_;
 1444   my $r = $self->r;
 1445   my $ce = $r->ce;
 1446   #my $db = $r->db;
 1447   #my $authz = $r->authz;
 1448   my $urlpath = $r->urlpath;
 1449 
 1450   my $import_file     = $r->param("import_file");
 1451   my $import_courseID = $r->param("import_courseID");
 1452   my @import_tables   = $r->param("import_tables");
 1453   my $import_conflict = $r->param("import_conflict") || "skip"; # need default -- not checked above
 1454 
 1455   my $ce2 = new WeBWorK::CourseEnvironment({
 1456     %WeBWorK::SeedCE,
 1457     courseName => $import_courseID,
 1458   });
 1459 
 1460   my $db2 = new WeBWorK::DB($ce2->{dbLayout});
 1461 
 1462   # locate file
 1463   my $templateDir = $ce->{courseDirs}->{templates};
 1464   my $filePath = "$templateDir/$import_file";
 1465 
 1466   my $gunzipMessage = system( 'gunzip', $filePath);
 1467   #FIXME
 1468   #warn "gunzip ", $gunzipMessage;
 1469   $filePath =~ s/\.gz$//;
 1470   #warn "new file path is $filePath";
 1471   my $fileHandle = new IO::File("<$filePath");
 1472   # retrieve upload from upload cache
 1473 #   my ($id, $hash) = split /\s+/, $import_file;
 1474 #   my $upload = WeBWorK::Upload->retrieve($id, $hash,
 1475 #     dir => $ce->{webworkDirs}->{uploadCache}
 1476 #   );
 1477 
 1478   my @errors;
 1479 
 1480   eval {
 1481     @errors = dbImport(
 1482       db => $db2,
 1483       # xml => $upload->fileHandle,
 1484       xml => $fileHandle,
 1485       tables => \@import_tables,
 1486       conflict => $import_conflict,
 1487     );
 1488   };
 1489 
 1490   push @errors, "Fatal exception: $@" if $@;
 1491   push @errors, $gunzipMessage if $gunzipMessage;
 1492 
 1493   if (@errors) {
 1494     print CGI::div({class=>"ResultsWithError"},
 1495       CGI::p("An error occured while importing the database of course $import_courseID:"),
 1496       CGI::ul(CGI::li(\@errors)),
 1497     );
 1498   } else {
 1499     print CGI::div({class=>"ResultsWithoutError"},
 1500       CGI::p("Import succeeded."),
 1501     );
 1502   }
 1503 }
 1504 ################################################################################
 1505 
 1506 sub archive_course_form {
 1507   my ($self) = @_;
 1508   my $r = $self->r;
 1509   my $ce = $r->ce;
 1510   #my $db = $r->db;
 1511   #my $authz = $r->authz;
 1512   #my $urlpath = $r->urlpath;
 1513 
 1514   my $archive_courseID     = $r->param("archive_courseID")     || "";
 1515 
 1516   my @courseIDs = listCourses($ce);
 1517   @courseIDs    = sort {lc($a) cmp lc ($b) } @courseIDs; #make sort case insensitive
 1518 
 1519   my %courseLabels; # records... heh.
 1520   foreach my $courseID (@courseIDs) {
 1521     my $tempCE = new WeBWorK::CourseEnvironment({
 1522       %WeBWorK::SeedCE,
 1523       courseName => $courseID,
 1524     });
 1525     $courseLabels{$courseID} = "$courseID (" . $tempCE->{dbLayoutName} . ")";
 1526   }
 1527 
 1528   print CGI::h2("archive Course");
 1529 
 1530   print CGI::p(
 1531     "Creates a gzipped tar archive (.tar.gz) of a course in the WeBWorK
 1532     courses directory. Before archiving, the course database is dumped into
 1533     a subdirectory of the course's DATA directory. Currently the archive
 1534     facility is only available for mysql databases. It depends on the
 1535     mysqldump application."
 1536   );
 1537 
 1538   print CGI::start_form(-method=>"POST", -action=>$r->uri);
 1539   print $self->hidden_authen_fields;
 1540   print $self->hidden_fields("subDisplay");
 1541 
 1542   print CGI::p("Select a course to archive.");
 1543 
 1544   print CGI::table({class=>"FormLayout"},
 1545     CGI::Tr({},
 1546       CGI::th({class=>"LeftHeader"}, "Course Name:"),
 1547       CGI::td(
 1548         CGI::scrolling_list(
 1549           -name => "archive_courseID",
 1550           -values => \@courseIDs,
 1551           -default => $archive_courseID,
 1552           -size => 10,
 1553           -multiple => 0,
 1554           -labels => \%courseLabels,
 1555         ),
 1556       ),
 1557 
 1558     ),
 1559     CGI::Tr({},
 1560       CGI::th({class=>"LeftHeader"}, "Delete course:"),
 1561       CGI::td({-style=>'color:red'}, CGI::checkbox({
 1562                           -name=>'delete_course',
 1563                           -checked=>0,
 1564                           -value => 1,
 1565                           -label =>'Delete course after archiving. Caution there is no undo!',
 1566                          },
 1567              ),
 1568       ),
 1569     )
 1570   );
 1571 
 1572   print CGI::p({style=>"text-align: center"}, CGI::submit(-name=>"archive_course", -value=>"archive Course"));
 1573 
 1574   print CGI::end_form();
 1575 }
 1576 
 1577 sub archive_course_validate {
 1578   my ($self) = @_;
 1579   my $r = $self->r;
 1580   my $ce = $r->ce;
 1581   #my $db = $r->db;
 1582   #my $authz = $r->authz;
 1583   my $urlpath = $r->urlpath;
 1584 
 1585   my $archive_courseID     = $r->param("archive_courseID")     || "";
 1586 
 1587   my @errors;
 1588 
 1589   if ($archive_courseID eq "") {
 1590     push @errors, "You must specify a course name.";
 1591   } elsif ($archive_courseID eq $urlpath->arg("courseID")) {
 1592     push @errors, "You cannot archive the course you are currently using.";
 1593   }
 1594 
 1595   #my $ce2 = new WeBWorK::CourseEnvironment({
 1596   # %WeBWorK::SeedCE,
 1597   # courseName => $archive_courseID,
 1598   #});
 1599 
 1600   return @errors;
 1601 }
 1602 
 1603 sub archive_course_confirm {
 1604   my ($self) = @_;
 1605   my $r = $self->r;
 1606   my $ce = $r->ce;
 1607   #my $db = $r->db;
 1608   #my $authz = $r->authz;
 1609   #my $urlpath = $r->urlpath;
 1610 
 1611   print CGI::h2("archive Course");
 1612 
 1613   my $archive_courseID     = $r->param("archive_courseID")     || "";
 1614   my $delete_course_flag   = $r->param("delete_course")        || "";
 1615 
 1616   my $ce2 = new WeBWorK::CourseEnvironment({
 1617     %WeBWorK::SeedCE,
 1618     courseName => $archive_courseID,
 1619   });
 1620 
 1621   if ($ce2->{dbLayoutName} ) {
 1622     print CGI::p("Are you sure you want to archive the course " . CGI::b($archive_courseID)
 1623     . "? ");
 1624     print(CGI::p({-style=>'color:red; font-weight:bold'}, "Are you sure that you want to delete the course ".
 1625     CGI::b($archive_courseID). " after archiving?  This cannot be undone!")) if $delete_course_flag;
 1626 
 1627 
 1628   }
 1629 
 1630   print CGI::start_form(-method=>"POST", -action=>$r->uri);
 1631   print $self->hidden_authen_fields;
 1632   print $self->hidden_fields("subDisplay");
 1633   print $self->hidden_fields(qw/archive_courseID delete_course/);
 1634 
 1635   print CGI::p({style=>"text-align: center"},
 1636     CGI::submit(-name=>"decline_archive_course", -value=>"Don't archive"),
 1637     "&nbsp;",
 1638     CGI::submit(-name=>"confirm_archive_course", -value=>"archive"),
 1639   );
 1640 
 1641   print CGI::end_form();
 1642 }
 1643 
 1644 sub do_archive_course {
 1645   my ($self) = @_;
 1646   my $r = $self->r;
 1647   my $ce = $r->ce;
 1648   my $db = $r->db;
 1649   #my $authz = $r->authz;
 1650   #my $urlpath = $r->urlpath;
 1651 
 1652   my $archive_courseID     = $r->param("archive_courseID")     || "";
 1653   my $delete_course_flag   = $r->param("delete_course")        || "";
 1654 
 1655   my $ce2 = new WeBWorK::CourseEnvironment({
 1656     %WeBWorK::SeedCE,
 1657     courseName => $archive_courseID,
 1658   });
 1659 
 1660   # this is kinda left over from when we had 'gdbm' and 'sql' database layouts
 1661   # below this line, we would grab values from getopt and put them in this hash
 1662   # but for now the hash can remain empty
 1663   my %dbOptions;
 1664 
 1665   eval {
 1666     archiveCourse(
 1667       courseID => $archive_courseID,
 1668       ce => $ce2,
 1669       dbOptions => \%dbOptions,
 1670     );
 1671   };
 1672 
 1673   if ($@) {
 1674     my $error = $@;
 1675     print CGI::div({class=>"ResultsWithError"},
 1676       CGI::p("An error occured while archiving the course $archive_courseID:"),
 1677       CGI::tt(CGI::escapeHTML($error)),
 1678     );
 1679   } else {
 1680     print CGI::div({class=>"ResultsWithoutError"},
 1681       CGI::p("Successfully archived the course $archive_courseID"),
 1682     );
 1683      writeLog($ce, "hosted_courses", join("\t",
 1684         "\tarchived",
 1685         "",
 1686         "",
 1687         $archive_courseID,
 1688       ));
 1689 
 1690     if ($delete_course_flag) {
 1691       eval {
 1692         deleteCourse(
 1693           courseID => $archive_courseID,
 1694           ce => $ce2,
 1695           dbOptions => \%dbOptions,
 1696         );
 1697       };
 1698 
 1699       if ($@) {
 1700         my $error = $@;
 1701         print CGI::div({class=>"ResultsWithError"},
 1702           CGI::p("An error occured while deleting the course $archive_courseID:"),
 1703           CGI::tt(CGI::escapeHTML($error)),
 1704         );
 1705       } else {
 1706         # mark the contact person in the admin course as dropped.
 1707         # find the contact person for the course by searching the admin classlist.
 1708         my @contacts = grep /_$archive_courseID$/,  $db->listUsers;
 1709         if (@contacts) {
 1710           die "Incorrect number of contacts for the course $archive_courseID". join(" ", @contacts) if @contacts !=1;
 1711           #warn "contacts", join(" ", @contacts);
 1712           #my $composite_id = "${add_initial_userID}_${add_courseID}";
 1713           my $composite_id  = $contacts[0];
 1714 
 1715           # mark the contact person as dropped.
 1716           my $User = $db->getUser($composite_id);
 1717           my $status_name = 'Drop';
 1718           my $status_value = ($ce->status_name_to_abbrevs($status_name))[0];
 1719           $User->status($status_value);
 1720           $db->putUser($User);
 1721         }
 1722 
 1723         print CGI::div({class=>"ResultsWithoutError"},
 1724           CGI::p("Successfully deleted the course $archive_courseID."),
 1725         );
 1726       }
 1727 
 1728 
 1729     }
 1730 
 1731 #     print CGI::start_form(-method=>"POST", -action=>$r->uri);
 1732 #     print $self->hidden_authen_fields;
 1733 #     print $self->hidden_fields("subDisplay");
 1734 #
 1735 #     print CGI::p({style=>"text-align: center"}, CGI::submit("decline_archive_course", "OK"),);
 1736 #
 1737 #     print CGI::end_form();
 1738   }
 1739 }
 1740 
 1741 ##########################################################################
 1742 
 1743 sub unarchive_course_form {
 1744   my ($self) = @_;
 1745   my $r = $self->r;
 1746   my $ce = $r->ce;
 1747   #my $db = $r->db;
 1748   #my $authz = $r->authz;
 1749   #my $urlpath = $r->urlpath;
 1750 
 1751   my $unarchive_courseID     = $r->param("unarchive_courseID")     || "";
 1752 
 1753   # First find courses which have been archived.
 1754   my @courseIDs = listArchivedCourses($ce);
 1755   @courseIDs    = sort {lc($a) cmp lc ($b) } @courseIDs; #make sort case insensitive
 1756 
 1757   my %courseLabels; # records... heh.
 1758   foreach my $courseID (@courseIDs) {
 1759         $courseLabels{$courseID} = $courseID;
 1760   }
 1761 
 1762   print CGI::h2("Unarchive Course");
 1763 
 1764   print CGI::p(
 1765     "Restores a course from a gzipped tar archive (.tar.gz). After
 1766     unarchiving, the course database is restored from a subdirectory of the
 1767     course's DATA directory. Currently the archive facility is only
 1768     available for mysql databases. It depends on the mysqldump application."
 1769   );
 1770 
 1771   print CGI::start_form(-method=>"POST", -action=>$r->uri);
 1772   print $self->hidden_authen_fields;
 1773   print $self->hidden_fields("subDisplay");
 1774 
 1775   print CGI::p("Select a course to unarchive.");
 1776 
 1777   print CGI::table({class=>"FormLayout"},
 1778     CGI::Tr({},
 1779       CGI::th({class=>"LeftHeader"}, "Course Name:"),
 1780       CGI::td(
 1781         CGI::scrolling_list(
 1782           -name => "unarchive_courseID",
 1783           -values => \@courseIDs,
 1784           -default => $unarchive_courseID,
 1785           -size => 10,
 1786           -multiple => 0,
 1787           -labels => \%courseLabels,
 1788         ),
 1789       ),
 1790     ),
 1791 
 1792     CGI::Tr({},
 1793         CGI::th({class=>"LeftHeader"}, CGI::checkbox(-name => "create_newCourseID",-default=>'',-value=>1, -label=>'New Name:')),
 1794         CGI::td(CGI::textfield(-name=>"new_courseID", -value=>'', -size=>25)),
 1795       ),
 1796   );
 1797 
 1798   print CGI::p({style=>"text-align: center"}, CGI::submit(-name=>"unarchive_course", -value=>"Unarchive Course"));
 1799 
 1800   print CGI::end_form();
 1801 }
 1802 
 1803 sub unarchive_course_validate {
 1804   my ($self) = @_;
 1805   my $r = $self->r;
 1806   my $ce = $r->ce;
 1807   #my $db = $r->db;
 1808   #my $authz = $r->authz;
 1809   my $urlpath = $r->urlpath;
 1810 
 1811   my $unarchive_courseID                   = $r->param("unarchive_courseID")             || "";
 1812   my $create_newCourseID                   = $r->param("create_newCourseID")      || "";
 1813   my $new_courseID                         = $r->param("new_courseID")    || "";
 1814   my @errors;
 1815   #by default we use the archive name for the course
 1816   my $courseID = $unarchive_courseID; $courseID =~ s/\.tar\.gz$//;
 1817 
 1818   if ( $create_newCourseID) {
 1819     $courseID = $new_courseID;
 1820   }
 1821   debug(" unarchive_courseID $unarchive_courseID new_courseID $new_courseID ");
 1822 
 1823   if ($courseID eq "") {
 1824     push @errors, "You must specify a course name.";
 1825   } elsif ( -d $ce->{webworkDirs}->{courses}."/$courseID" ) {
 1826       #Check that a directory for this course doesn't already exist
 1827     push @errors, "A directory already exists with the name $courseID.
 1828      You must first delete this existing course before you can unarchive.";
 1829   }
 1830 
 1831 
 1832   return @errors;
 1833 }
 1834 
 1835 sub unarchive_course_confirm {
 1836   my ($self) = @_;
 1837   my $r = $self->r;
 1838   my $ce = $r->ce;
 1839   #my $db = $r->db;
 1840   #my $authz = $r->authz;
 1841   #my $urlpath = $r->urlpath;
 1842 
 1843   print CGI::h2("Unarchive Course");
 1844 
 1845   my $unarchive_courseID                    = $r->param("unarchive_courseID")     || "";
 1846   my $create_newCourseID                    = $r->param("create_newCourseID")      || "";
 1847   my $new_courseID                          = $r->param("new_courseID")           || "";
 1848 
 1849   my $courseID = $unarchive_courseID; $courseID =~ s/\.tar\.gz$//;
 1850 
 1851   if ( $create_newCourseID) {
 1852     $courseID = $new_courseID;
 1853   }
 1854 
 1855     debug(" unarchive_courseID $unarchive_courseID new_courseID $new_courseID ");
 1856 
 1857   print CGI::start_form(-method=>"POST", -action=>$r->uri);
 1858     print CGI::p($unarchive_courseID," to course ",
 1859                CGI::input({-name=>'new_courseID', -value=>$courseID})
 1860   );
 1861 
 1862   print $self->hidden_authen_fields;
 1863   print $self->hidden_fields("subDisplay");
 1864   print $self->hidden_fields(qw/unarchive_courseID create_newCourseID/);
 1865 
 1866   print CGI::p({style=>"text-align: center"},
 1867     CGI::submit(-name=>"decline_unarchive_course", -value=>"Don't unarchive"),
 1868     "&nbsp;",
 1869     CGI::submit(-name=>"confirm_unarchive_course", -value=>"unarchive"),
 1870   );
 1871 
 1872   print CGI::end_form();
 1873 }
 1874 
 1875 sub do_unarchive_course {
 1876   my ($self) = @_;
 1877   my $r = $self->r;
 1878   my $ce = $r->ce;
 1879   #my $db = $r->db;
 1880   #my $authz = $r->authz;
 1881   my $urlpath = $r->urlpath;
 1882   my $new_courseID           = $r->param("new_courseID")           || "";
 1883   my $unarchive_courseID     = $r->param("unarchive_courseID")     || "";
 1884 
 1885   my $old_courseID   = $unarchive_courseID; $old_courseID =~ s/.tar.gz//;
 1886 
 1887   #eval {
 1888     unarchiveCourse(
 1889       newCourseID => $new_courseID,
 1890       oldCourseID => $old_courseID,
 1891       archivePath =>$ce->{webworkDirs}->{courses}."/$unarchive_courseID",
 1892       ce => $ce,
 1893     );
 1894   #};
 1895 
 1896   if ($@) {
 1897     my $error = $@;
 1898     print CGI::div({class=>"ResultsWithError"},
 1899       CGI::p("An error occured while archiving the course $unarchive_courseID:"),
 1900       CGI::tt(CGI::escapeHTML($error)),
 1901     );
 1902   } else {
 1903     print CGI::div({class=>"ResultsWithoutError"},
 1904       CGI::p("Successfully unarchived  $unarchive_courseID to the course $new_courseID"),
 1905     );
 1906      writeLog($ce, "hosted_courses", join("\t",
 1907         "\tunarchived",
 1908         "",
 1909         "",
 1910         "$unarchive_courseID to $new_courseID",
 1911       ));
 1912 
 1913     my $newCoursePath = $urlpath->newFromModule("WeBWorK::ContentGenerator::ProblemSets",
 1914       courseID => $new_courseID);
 1915     my $newCourseURL = $self->systemLink($newCoursePath, authen => 0);
 1916     print CGI::div({style=>"text-align: center"},
 1917       CGI::a({href=>$newCourseURL}, "Log into $new_courseID"),
 1918     );
 1919   }
 1920 }
 1921 
 1922 ################################################################################
 1923 ## location management routines; added by DG [Danny Ginn] 20070215
 1924 ## revised by glarose
 1925 
 1926 sub manage_location_form {
 1927   my ($self) = @_;
 1928   my $r = $self->r;
 1929   my $ce = $r->ce;
 1930   my $db = $r->db;
 1931   #my $authz = $r->authz;
 1932   my $urlpath = $r->urlpath;
 1933 
 1934   # get a list of all existing locations
 1935   my @locations = sort {lc($a->location_id) cmp lc($b->location_id)}
 1936     $db->getAllLocations();
 1937   my %locAddr = map {$_->location_id => [ $db->listLocationAddresses($_->location_id) ]} @locations;
 1938 
 1939   my @locationIDs = map { $_->location_id } @locations;
 1940 
 1941   print CGI::h2("Manage Locations");
 1942 
 1943   print CGI::p({},CGI::strong("Currently defined locations are listed below."));
 1944 
 1945   print CGI::start_form(-method=>"POST", -action=>$r->uri);
 1946   print $self->hidden_authen_fields;
 1947   print $self->hidden_fields("subDisplay");
 1948 
 1949   # get a list of radio buttons to select an action
 1950   my @actionRadios =
 1951     CGI::radio_group(-name => "manage_location_action",
 1952          -values => ["edit_location_form",
 1953                "add_location_handler",
 1954                "delete_location_handler"],
 1955          -labels => { edit_location_form => "",
 1956                 add_location_handler => "",
 1957                 delete_location_handler => "", },
 1958          -default => $r->param("manage_location_action") ? $r->param("manage_location_action") : 'none');
 1959 
 1960   print CGI::start_table({});
 1961   print CGI::Tr({}, CGI::th({-colspan=>4,-align=>"left"},
 1962           "Select an action to perform:"));
 1963 
 1964   # edit action
 1965   print CGI::Tr({},
 1966     CGI::td({},[ $actionRadios[0], "Edit Location:" ]),
 1967     CGI::td({-colspan=>2, -align=>"left"},
 1968       CGI::div({-style=>"width:25%;"},
 1969           CGI::popup_menu(-name=>"edit_location",
 1970           -values=>[@locationIDs]))) );
 1971   # create action
 1972   print CGI::Tr({},
 1973     CGI::td({-align=>"left"},[ $actionRadios[1],
 1974       "Create Location:" ]),
 1975     CGI::td({-colspan=>2},
 1976       "Location name: " .
 1977       CGI::textfield(-name=>"new_location_name",
 1978                -size=>"10",
 1979                -default=>$r->param("new_location_name")?$r->param("new_location_name"):'')));
 1980   print CGI::Tr({valign=>'top'},
 1981     CGI::td({}, ["&nbsp;", "Location description:"]),
 1982     CGI::td({-colspan=>2},
 1983       CGI::textfield(-name=>"new_location_description",
 1984                -size=>"50",
 1985                -default=>$r->param("new_location_description")?$r->param("new_location_description"):'')) );
 1986   print CGI::Tr({}, CGI::td({},"&nbsp;"),
 1987     CGI::td({-colspan=>3}, "Addresses for new location " .
 1988       "(enter one per line, as single IP addresses " .
 1989       "(e.g., 192.168.1.101), address masks (e.g., " .
 1990       "192.168.1.0/24), or IP ranges (e.g., " .
 1991       "192.168.1.101-192.168.1.150)):"));
 1992   print CGI::Tr({}, CGI::td({}, "&nbsp;"),
 1993     CGI::td({-colspan=>3},
 1994       CGI::textarea({-name=>"new_location_addresses",
 1995                -rows=>5, -columns=>28,
 1996                -default=>$r->param("new_location_addresses")?$r->param("new_location_addresses"):''})));
 1997 
 1998   # delete action
 1999   print CGI::Tr({},
 2000     CGI::td({-colspan=>4},
 2001       CGI::div({-class=>"ResultsWithError"},
 2002          CGI::em({}, "Deletion deletes all location " .
 2003            "data and related addresses, and is" .
 2004 
 2005            " not undoable!"))));
 2006   print CGI::Tr({},
 2007     CGI::td({},
 2008       [ $actionRadios[2],
 2009         CGI::div({-class=>"ResultsWithError"},
 2010            "Delete location:") ]),
 2011     CGI::td({-colspan=>2},
 2012       CGI::popup_menu(-name=>"delete_location",
 2013           -values=>["",
 2014               "selected_locations",
 2015               @locationIDs],
 2016           -labels=>{selected_locations => "locations selected below",
 2017               "" => "no location"}) .
 2018       CGI::span({-style=>"color:#C33;"}, "  Confirm: ") .
 2019       CGI::checkbox({-name=>"delete_confirm",
 2020                -value=>"true",
 2021                -label=>""}) ) );
 2022   print CGI::end_table();
 2023 
 2024   print CGI::p({}, CGI::submit(-name=>"manage_locations", -value=>"Take Action!"));
 2025 
 2026   # existing location table
 2027   # FIXME: the styles for this table should be off in a stylesheet
 2028   #    somewhere
 2029   print CGI::start_div({align=>"center"}),
 2030     CGI::start_table({border=>1, cellpadding=>2});
 2031   print CGI::Tr({style=>"background-color:#e0e0e0;font-size:92%", align=>"left"},
 2032           CGI::th({}, ["Select", "Location", "Description",
 2033            "Addresses"]));
 2034   foreach my $loc ( @locations ) {
 2035     my $editAddr = $self->systemLink($urlpath, params=>{subDisplay=>"manage_locations", manage_location_action=>"edit_location_form", edit_location=>$loc->location_id});
 2036     print CGI::Tr({valign=>'top',style=>"background-color:#eeeeee;"},
 2037             CGI::td({style=>'font-size:85%;'},
 2038               [ CGI::checkbox(-name=>"delete_selected",
 2039                   -value=>$loc->location_id,
 2040                   -label=>''),
 2041           CGI::a({href=>$editAddr}, $loc->location_id),
 2042           $loc->description,
 2043           join(', ', @{$locAddr{$loc->location_id}}) ]));
 2044   }
 2045   print CGI::end_table(), CGI::end_div();
 2046   print CGI::end_form();
 2047 
 2048 
 2049 }
 2050 
 2051 sub add_location_handler {
 2052   my $self = shift();
 2053   my $r = $self->r;
 2054   my $db = $r->db;
 2055 
 2056   # the location data we're to add
 2057   my $locationID = $r->param("new_location_name");
 2058   my $locationDescr = $r->param("new_location_description");
 2059   my $locationAddr = $r->param("new_location_addresses");
 2060   # break the addresses up
 2061   $locationAddr =~ s/\s*-\s*/-/g;
 2062   $locationAddr =~ s/\s*\/\s*/\//g;
 2063   my @addresses = split(/\s+/, $locationAddr);
 2064 
 2065   # sanity checks
 2066   my $badAddr = '';
 2067   foreach my $addr ( @addresses ) {
 2068     unless ( new Net::IP($addr) ) {
 2069       $badAddr .= "$addr, ";
 2070       $locationAddr =~ s/$addr\n//s;
 2071     }
 2072   }
 2073   $badAddr =~ s/, $//;
 2074 
 2075   # a check to be sure that the location addresses don't already
 2076   #    exist
 2077   my $badLocAddr;
 2078   if ( ! $badAddr && $locationID ) {
 2079     if ( $db->countLocationAddresses( $locationID ) ) {
 2080       my @allLocAddr = $db->listLocationAddresses($locationID);
 2081       foreach my $addr ( @addresses ) {
 2082         $badLocAddr .= "$addr, "
 2083           if ( grep {/^$addr$/} @allLocAddr );
 2084       }
 2085       $badLocAddr =~ s/, $//;
 2086     }
 2087   }
 2088 
 2089   if ( ! @addresses || ! $locationID || ! $locationDescr ) {
 2090     print CGI::div({-class=>"ResultsWithError"},
 2091              "Missing required input data. Please check " .
 2092              "that you have filled in all of the create " .
 2093              "location fields and resubmit.");
 2094   } elsif ( $badAddr ) {
 2095     $r->param("new_location_addresses", $locationAddr);
 2096     print CGI::div({-class=>"ResultsWithError"},
 2097              "Address(es) $badAddr is(are) not in a " .
 2098              "recognized form.  Please check your " .
 2099              "data entry and resubmit.");
 2100   } elsif ( $db->existsLocation( $locationID ) ) {
 2101     print CGI::div({-class=>"ResultsWithError"},
 2102              "A location with the name $locationID " .
 2103              "already exists in the database.  Did " .
 2104              "you mean to edit that location instead?");
 2105   } elsif ( $badLocAddr ) {
 2106     print CGI::div({-class=>"ResultsWithError"},
 2107              "Address(es) $badLocAddr already exist " .
 2108              "in the database.  THIS SHOULD NOT HAPPEN!  " .
 2109              "Please double check the integrity of " .
 2110              "the WeBWorK database before continuing.");
 2111   } else {
 2112     # add the location
 2113     my $locationObj = $db->newLocation;
 2114     $locationObj->location_id( $locationID );
 2115     $locationObj->description( $locationDescr );
 2116     $db->addLocation( $locationObj );
 2117 
 2118     # and add the addresses
 2119     foreach my $addr ( @addresses ) {
 2120       my $locationAddress = $db->newLocationAddress;
 2121       $locationAddress->location_id($locationID);
 2122       $locationAddress->ip_mask($addr);
 2123 
 2124       $db->addLocationAddress( $locationAddress );
 2125     }
 2126 
 2127     # we've added the location, so clear those param
 2128     #    entries
 2129     $r->param('manage_location_action','none');
 2130     $r->param('new_location_name','');
 2131     $r->param('new_location_description','');
 2132     $r->param('new_location_addresses','');
 2133 
 2134     print CGI::div({-class=>"ResultsWithoutError"},
 2135              "Location $locationID has been created, " .
 2136              "with addresses " . join(', ', @addresses) .
 2137              ".");
 2138   }
 2139 
 2140   $self->manage_location_form;
 2141 }
 2142 
 2143 sub delete_location_handler {
 2144   my $self = shift;
 2145   my $r = $self->r;
 2146   my $db = $r->db;
 2147 
 2148   # what location are we deleting?
 2149   my $locationID = $r->param("delete_location");
 2150   # check for selected deletions if appropriate
 2151   my @delLocations = ( $locationID );
 2152   if ( $locationID eq 'selected_locations' ) {
 2153     @delLocations = $r->param("delete_selected");
 2154     $locationID = @delLocations;
 2155   }
 2156   # are we sure?
 2157   my $confirm = $r->param("delete_confirm");
 2158 
 2159   my $badID;
 2160   if ( ! $locationID ) {
 2161     print CGI::div({-class=>"ResultsWithError"},
 2162              "Please provide a location name " .
 2163              "to delete.");
 2164 
 2165   } elsif ( $badID = $self->existsLocations_helper( @delLocations ) ) {
 2166     print CGI::div({-class=>"ResultsWithError"},
 2167              "No location with name $badID " .
 2168              "exists in the database.");
 2169 
 2170   } elsif ( ! $confirm || $confirm ne 'true' ) {
 2171     print CGI::div({-class=>"ResultsWithError"},
 2172              "Location deletion requires confirmation.");
 2173   } else {
 2174     foreach ( @delLocations ) {
 2175       $db->deleteLocation( $_ );
 2176     }
 2177     print CGI::div({-class=>"ResultsWithoutError"},
 2178              "Location" . (@delLocations > 1 ? 's ' : ' ') .
 2179              join(', ', @delLocations) .
 2180              (@delLocations > 1 ? ' have ' : ' has ' ) .
 2181              'been deleted.');
 2182     $r->param('manage_location_action','none');
 2183     $r->param('delete_location','');
 2184   }
 2185   $self->manage_location_form;
 2186 }
 2187 sub existsLocations_helper {
 2188   my ($self, @locations) = @_;
 2189   my $db = $self->r->db;
 2190   foreach ( @locations ) {
 2191     return $_ if ( ! $db->existsLocation($_) );
 2192   }
 2193   return 0;
 2194 }
 2195 
 2196 sub edit_location_form {
 2197   my $self = shift;
 2198   my $r = $self->r;
 2199   my $db = $r->db;
 2200 
 2201   my $locationID = $r->param("edit_location");
 2202   if ( $db->existsLocation( $locationID ) ) {
 2203     my $location = $db->getLocation($locationID);
 2204     # this doesn't give that nice a sort for IP addresses,
 2205     #    b/c there's the problem with 192.168.1.168 sorting
 2206     #    ahead of 192.168.1.2.  we could do better if we
 2207     #    either invoked Net::IP in the sort routine, or if
 2208     #    we insisted on dealing only with IPv4.  rather than
 2209     #    deal with either of those, we'll leave this for now
 2210     my @locAddresses = sort { $a cmp $b }
 2211       $db->listLocationAddresses($locationID);
 2212 
 2213     print CGI::h2("Editing location ", $locationID);
 2214 
 2215     print CGI::p({},"Edit the current value of the location ",
 2216            "description, if desired, then add and select ",
 2217            "addresses to delete, and then click the ",
 2218            "\"Take Action\" button to make all of your ",
 2219            "changes.  Or, click \"Manage Locations\" ",
 2220            "above to make no changes and return to the ",
 2221            "Manage Locations page.");
 2222 
 2223     print CGI::start_form(-method=>"POST",
 2224               -action=>$r->uri);
 2225     print $self->hidden_authen_fields;
 2226     print $self->hidden_fields("subDisplay");
 2227     print CGI::hidden(-name=>'edit_location',
 2228           -default=>$locationID);
 2229     print CGI::hidden(-name=>'manage_location_action',
 2230           -default=>'edit_location_handler');
 2231 
 2232     print CGI::start_table();
 2233     print CGI::Tr({-valign=>'top'},
 2234       CGI::td({-colspan=>3},
 2235         "Location description: ", CGI::br(),
 2236         CGI::textfield(-name=>"location_description",
 2237                  -size=>"50",
 2238                  -default=>$location->description)));
 2239     print CGI::Tr({-valign=>'top'},
 2240       CGI::td({-width=>"50%"},
 2241         "Addresses to add to the location " .
 2242         "(enter one per line, as single IP addresses " .
 2243         "(e.g., 192.168.1.101), address masks " .
 2244         "(e.g., 192.168.1.0/24), or IP ranges " .
 2245         "(e.g., 192.168.1.101-192.168.1.150)):" .
 2246         CGI::br() .
 2247         CGI::textarea({-name=>"new_location_addresses",
 2248                  -rows=>5, -columns=>28})),
 2249       CGI::td({}, "&nbsp;"),
 2250       CGI::td({-width=>"50%"},
 2251         "Existing addresses for the location are " .
 2252         "given in the scrolling list below.  Select " .
 2253         "addresses from the list to delete them:" .
 2254         CGI::br() .
 2255         CGI::scrolling_list(-name=>'delete_location_addresses',
 2256                 -values=>[@locAddresses],
 2257                 -size=>8,
 2258                 -multiple=>'multiple') .
 2259         CGI::br() . "or: " .
 2260         CGI::checkbox(-name=>'delete_all_addresses',
 2261                 -value=>'true',
 2262                 -label=>'Delete all existing addresses')
 2263          ));
 2264 
 2265     print CGI::end_table();
 2266 
 2267     print CGI::p({},CGI::submit(-value=>'Take Action!'));
 2268 
 2269   } else {
 2270     print CGI::div({-class=>"ResultsWithError"},
 2271              "Location $locationID does not exist " .
 2272              "in the WeBWorK database.  Please check " .
 2273              "your input (perhaps you need to reload " .
 2274              "the location management page?).");
 2275 
 2276     $self->manage_location_form;
 2277   }
 2278 }
 2279 
 2280 sub edit_location_handler {
 2281   my $self = shift;
 2282   my $r = $self->r;
 2283   my $db = $r->db;
 2284 
 2285   my $locationID = $r->param("edit_location");
 2286   my $locationDesc = $r->param("location_description");
 2287   my $addAddresses = $r->param("new_location_addresses");
 2288   my @delAddresses = $r->param("delete_location_addresses");
 2289   my $deleteAll = $r->param("delete_all_addresses");
 2290 
 2291   # gut check
 2292   if ( ! $locationID ) {
 2293     print CGI::div({-class=>"ResultsWithError"},
 2294              "No location specified to edit?! " .
 2295              "Please check your input data.");
 2296     $self->manage_location_form;
 2297 
 2298   } elsif ( ! $db->existsLocation( $locationID ) ) {
 2299     print CGI::div({-class=>"ResultsWithError"},
 2300              "Location $locationID does not exist " .
 2301              "in the WeBWorK database.  Please check " .
 2302              "your input (perhaps you need to reload " .
 2303              "the location management page?).");
 2304     $self->manage_location_form;
 2305   } else {
 2306     my $location = $db->getLocation($locationID);
 2307 
 2308     # get the current location addresses.  if we're deleting
 2309     #   all of the existing addresses, we don't use this list
 2310     #   to determine which addresses to add, however.
 2311     my @currentAddr = $db->listLocationAddresses($locationID);
 2312     my @compareAddr = ( ! $deleteAll || $deleteAll ne 'true' )
 2313       ? @currentAddr : ();
 2314 
 2315     my $doneMsg = '';
 2316 
 2317     if ($locationDesc && $location->description ne $locationDesc) {
 2318       $location->description($locationDesc);
 2319       $db->putLocation($location);
 2320       $doneMsg .= CGI::p({},"Updated location description.");
 2321     }
 2322     # get the actual addresses to add out of the text field
 2323     $addAddresses =~ s/\s*-\s*/-/g;
 2324     $addAddresses =~ s/\s*\/\s*/\//g;
 2325     my @addAddresses = split(/\s+/, $addAddresses);
 2326 
 2327     # make sure that we're adding and deleting only those
 2328     #    addresses that are not yet/currently in the location
 2329     #    addresses
 2330     my @toAdd = ();  my @noAdd = ();
 2331     my @toDel = ();  my @noDel = ();
 2332     foreach my $addr ( @addAddresses ) {
 2333       if (grep {/^$addr$/} @compareAddr) {push(@noAdd,$addr);}
 2334       else { push(@toAdd, $addr); }
 2335     }
 2336     if ( $deleteAll && $deleteAll eq 'true' ) {
 2337       @toDel = @currentAddr;
 2338     } else {
 2339       foreach my $addr ( @delAddresses ) {
 2340         if (grep {/^$addr$/} @currentAddr) {
 2341           push(@toDel,$addr);
 2342         } else { push(@noDel, $addr); }
 2343       }
 2344     }
 2345 
 2346     # and make sure that all of the addresses we're adding are
 2347     #    a sensible form
 2348     my $badAddr = '';
 2349     foreach my $addr ( @toAdd ) {
 2350       unless ( new Net::IP($addr) ) {
 2351         $badAddr .= "$addr, ";
 2352       }
 2353     }
 2354     $badAddr =~ s/, $//;
 2355 
 2356     # delete addresses first, because we allow deletion of
 2357     #    all existing addresses, then addition of addresses.
 2358     #    note that we don't allow deletion and then addition
 2359     #    of the same address normally, however; in that case
 2360     #    we'll end up just deleting the address.
 2361     foreach ( @toDel ) {
 2362       $db->deleteLocationAddress($locationID, $_);
 2363     }
 2364     foreach ( @toAdd ) {
 2365       my $locAddr = $db->newLocationAddress;
 2366       $locAddr->location_id($locationID);
 2367       $locAddr->ip_mask($_);
 2368 
 2369       $db->addLocationAddress($locAddr);
 2370     }
 2371 
 2372     my $addrMsg = '';
 2373     $addrMsg .= "Deleted addresses " . join(', ', @toDel) .
 2374       " from location." . CGI::br() if ( @toDel );
 2375     $addrMsg .= "Added addresses " . join(', ', @toAdd) .
 2376       " to location $locationID.  " if ( @toAdd );
 2377 
 2378     my $badMsg = '';
 2379     $badMsg .= 'Address(es) ' . join(', ', @noAdd) .
 2380       " in the add list is(are) already in the " .
 2381       "location $locationID, and so were " .
 2382       "skipped." . CGI::br() if ( @noAdd );
 2383     $badMsg .= "Address(es) $badAddr is(are) not in a " .
 2384       "recognized form.  Please check your data " .
 2385       "entry and try again." . CGI::br() if ( $badAddr );
 2386     $badMsg .= 'Address(es) ' . join(', ', @noDel) .
 2387       " in the delete list is(are) not in the " .
 2388       "location $locationID, and so were " .
 2389       "skipped." if ( @noDel );
 2390 
 2391     print CGI::div({-class=>"ResultsWithError"}, $badMsg)
 2392       if ( $badMsg );
 2393     if ( $doneMsg || $addrMsg ) {
 2394       print CGI::div({-class=>"ResultsWithoutError"},
 2395                CGI::p({}, $doneMsg, $addrMsg));
 2396     } else {
 2397       print CGI::div({-class=>"ResultsWithError"},
 2398                "No valid changes submitted for ",
 2399                "location $locationID.");
 2400     }
 2401 
 2402     $self->edit_location_form;
 2403   }
 2404 }
 2405 
 2406 ################################################################################
 2407 1;

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9