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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 5283 - (download) (as text) (annotate)
Fri Aug 10 01:36:56 2007 UTC (5 years, 9 months ago) by sh002i
Original Path: branches/rel-2-4-dev/webwork-modperl/lib/WeBWorK/ContentGenerator/CourseAdmin.pm
File size: 74349 byte(s)
backport (sh002i): use $ce->{externalPrograms}{gzip} rather than 'gzip'.

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

aubreyja at gmail dot com
ViewVC Help
Powered by ViewVC 1.0.9