| 1 | ################################################################################ |
1 | ################################################################################ |
| 2 | # WeBWorK Online Homework Delivery System |
2 | # WeBWorK Online Homework Delivery System |
| 3 | # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/ |
3 | # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/ |
| 4 | # $CVSHeader: webwork2/lib/WeBWorK/ContentGenerator/CourseAdmin.pm,v 1.31 2004/10/10 21:04:47 sh002i Exp $ |
4 | # $CVSHeader: webwork-modperl/lib/WeBWorK/ContentGenerator/CourseAdmin.pm,v 1.33 2004/12/21 04:40:56 sh002i Exp $ |
| 5 | # |
5 | # |
| 6 | # This program is free software; you can redistribute it and/or modify it under |
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 |
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 |
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. |
9 | # version, or (b) the "Artistic License" which comes with this package. |
| … | |
… | |
| 27 | use warnings; |
27 | use warnings; |
| 28 | use CGI::Pretty qw(); |
28 | use CGI::Pretty qw(); |
| 29 | use Data::Dumper; |
29 | use Data::Dumper; |
| 30 | use File::Temp qw/tempfile/; |
30 | use File::Temp qw/tempfile/; |
| 31 | use WeBWorK::CourseEnvironment; |
31 | use WeBWorK::CourseEnvironment; |
|
|
32 | use IO::File; |
| 32 | use WeBWorK::Utils qw(cryptPassword writeLog); |
33 | use WeBWorK::Utils qw(cryptPassword writeLog listFilesRecursive); |
| 33 | use WeBWorK::Utils::CourseManagement qw(addCourse renameCourse deleteCourse listCourses); |
34 | use WeBWorK::Utils::CourseManagement qw(addCourse renameCourse deleteCourse listCourses); |
| 34 | use WeBWorK::Utils::DBImportExport qw(dbExport dbImport); |
35 | use WeBWorK::Utils::DBImportExport qw(dbExport dbImport); |
| 35 | |
36 | |
| 36 | # put the following database layouts at the top of the list, in this order |
37 | # put the following database layouts at the top of the list, in this order |
| 37 | our @DB_LAYOUT_ORDER = qw/sql_single gdbm sql/; |
38 | our @DB_LAYOUT_ORDER = qw/sql_single gdbm sql/; |
| … | |
… | |
| 171 | } |
172 | } |
| 172 | |
173 | |
| 173 | sub header { |
174 | sub header { |
| 174 | my ($self) = @_; |
175 | my ($self) = @_; |
| 175 | my $method_to_call = $self->{method_to_call}; |
176 | my $method_to_call = $self->{method_to_call}; |
| 176 | if (defined $method_to_call and $method_to_call eq "do_export_database") { |
177 | # if (defined $method_to_call and $method_to_call eq "do_export_database") { |
| 177 | my $r = $self->r; |
178 | # my $r = $self->r; |
| 178 | my $courseID = $r->param("export_courseID"); |
179 | # my $courseID = $r->param("export_courseID"); |
| 179 | $r->content_type("application/octet-stream"); |
180 | # $r->content_type("application/octet-stream"); |
| 180 | $r->header_out("Content-Disposition" => "attachment; filename=\"${courseID}_database.xml\""); |
181 | # $r->header_out("Content-Disposition" => "attachment; filename=\"${courseID}_database.xml\""); |
| 181 | $r->send_http_header; |
182 | # $r->send_http_header; |
| 182 | } else { |
183 | # } else { |
| 183 | $self->SUPER::header; |
184 | $self->SUPER::header; |
| 184 | } |
185 | # } |
| 185 | } |
186 | } |
| 186 | |
187 | |
| 187 | # sends: |
188 | # sends: |
| 188 | # |
189 | # |
| 189 | # HTTP/1.1 200 OK |
190 | # HTTP/1.1 200 OK |
| … | |
… | |
| 195 | |
196 | |
| 196 | sub content { |
197 | sub content { |
| 197 | my ($self) = @_; |
198 | my ($self) = @_; |
| 198 | my $method_to_call = $self->{method_to_call}; |
199 | my $method_to_call = $self->{method_to_call}; |
| 199 | if (defined $method_to_call and $method_to_call eq "do_export_database") { |
200 | if (defined $method_to_call and $method_to_call eq "do_export_database") { |
| 200 | $self->do_export_database; |
201 | #$self->do_export_database; |
|
|
202 | $self->SUPER::content; |
| 201 | } else { |
203 | } else { |
| 202 | $self->SUPER::content; |
204 | $self->SUPER::content; |
| 203 | } |
205 | } |
| 204 | } |
206 | } |
| 205 | |
207 | |
| … | |
… | |
| 215 | |
217 | |
| 216 | # check permissions |
218 | # check permissions |
| 217 | unless ($authz->hasPermissions($user, "create_and_delete_courses")) { |
219 | unless ($authz->hasPermissions($user, "create_and_delete_courses")) { |
| 218 | return ""; |
220 | return ""; |
| 219 | } |
221 | } |
|
|
222 | my $method_to_call = $self->{method_to_call}; |
|
|
223 | my $methodMessage =""; |
|
|
224 | |
|
|
225 | (defined($method_to_call) and $method_to_call eq "do_export_database") && do { |
|
|
226 | my @export_courseID = $r->param("export_courseID"); |
|
|
227 | my $course_ids = join(", ", @export_courseID); |
|
|
228 | $methodMessage = CGI::p("Exporting database for course(s) $course_ids"). |
|
|
229 | CGI::p(".... please wait.... |
|
|
230 | If your browser times out you will |
|
|
231 | still be able to download the exported database using the |
|
|
232 | file manager.").CGI::hr(); |
|
|
233 | }; |
|
|
234 | |
| 220 | |
235 | |
| 221 | print CGI::p({style=>"text-align: center"}, |
236 | print CGI::p({style=>"text-align: center"}, |
| 222 | CGI::a({href=>$self->systemLink($urlpath, params=>{subDisplay=>"add_course"})}, "Add Course"), |
237 | CGI::a({href=>$self->systemLink($urlpath, params=>{subDisplay=>"add_course"})}, "Add Course"), |
| 223 | " | ", |
238 | " | ", |
| 224 | CGI::a({href=>$self->systemLink($urlpath, params=>{subDisplay=>"rename_course"})}, "Rename Course"), |
239 | CGI::a({href=>$self->systemLink($urlpath, params=>{subDisplay=>"rename_course"})}, "Rename Course"), |
| … | |
… | |
| 226 | CGI::a({href=>$self->systemLink($urlpath, params=>{subDisplay=>"delete_course"})}, "Delete Course"), |
241 | CGI::a({href=>$self->systemLink($urlpath, params=>{subDisplay=>"delete_course"})}, "Delete Course"), |
| 227 | " | ", |
242 | " | ", |
| 228 | CGI::a({href=>$self->systemLink($urlpath, params=>{subDisplay=>"export_database"})}, "Export Database"), |
243 | CGI::a({href=>$self->systemLink($urlpath, params=>{subDisplay=>"export_database"})}, "Export Database"), |
| 229 | " | ", |
244 | " | ", |
| 230 | CGI::a({href=>$self->systemLink($urlpath, params=>{subDisplay=>"import_database"})}, "Import Database"), |
245 | CGI::a({href=>$self->systemLink($urlpath, params=>{subDisplay=>"import_database"})}, "Import Database"), |
|
|
246 | CGI::hr(), |
|
|
247 | $methodMessage, |
|
|
248 | |
| 231 | ); |
249 | ); |
| 232 | |
250 | |
| 233 | print CGI::hr(); |
251 | print CGI::p("The ability to import and to export databases is still under development. |
|
|
252 | It seems to work but it is <b>VERY</b> slow on large courses. You may prefer to |
|
|
253 | use webwork2/bin/wwdb or the mysql dump facility for archiving large courses. |
|
|
254 | Please send bug reports if you find errors. "); |
| 234 | |
255 | |
| 235 | my @errors = @{$self->{errors}}; |
256 | my @errors = @{$self->{errors}}; |
| 236 | my $method_to_call = $self->{method_to_call}; |
257 | |
| 237 | |
258 | |
| 238 | if (@errors) { |
259 | if (@errors) { |
| 239 | print CGI::div({class=>"ResultsWithError"}, |
260 | print CGI::div({class=>"ResultsWithError"}, |
| 240 | CGI::p("Please correct the following errors and try again:"), |
261 | CGI::p("Please correct the following errors and try again:"), |
| 241 | CGI::ul(CGI::li(\@errors)), |
262 | CGI::ul(CGI::li(\@errors)), |
| … | |
… | |
| 1297 | |
1318 | |
| 1298 | my @tables = keys %{$ce->{dbLayout}}; |
1319 | my @tables = keys %{$ce->{dbLayout}}; |
| 1299 | |
1320 | |
| 1300 | my $export_courseID = $r->param("export_courseID") || ""; |
1321 | my $export_courseID = $r->param("export_courseID") || ""; |
| 1301 | my @export_tables = $r->param("export_tables"); |
1322 | my @export_tables = $r->param("export_tables"); |
| 1302 | |
1323 | |
| 1303 | @export_tables = @tables unless @export_tables; |
1324 | @export_tables = @tables unless @export_tables; |
| 1304 | |
1325 | |
| 1305 | my @courseIDs = listCourses($ce); |
1326 | my @courseIDs = listCourses($ce); |
| 1306 | @courseIDs = sort @courseIDs; |
1327 | @courseIDs = sort @courseIDs; |
| 1307 | |
1328 | |
| … | |
… | |
| 1334 | CGI::scrolling_list( |
1355 | CGI::scrolling_list( |
| 1335 | -name => "export_courseID", |
1356 | -name => "export_courseID", |
| 1336 | -values => \@courseIDs, |
1357 | -values => \@courseIDs, |
| 1337 | -default => $export_courseID, |
1358 | -default => $export_courseID, |
| 1338 | -size => 10, |
1359 | -size => 10, |
| 1339 | -multiple => 0, |
1360 | -multiple => 1, |
| 1340 | -labels => \%courseLabels, |
1361 | -labels => \%courseLabels, |
| 1341 | ), |
1362 | ), |
| 1342 | ), |
1363 | ), |
| 1343 | ), |
1364 | ), |
| 1344 | CGI::Tr( |
1365 | CGI::Tr( |
| … | |
… | |
| 1365 | #my $ce = $r->ce; |
1386 | #my $ce = $r->ce; |
| 1366 | #my $db = $r->db; |
1387 | #my $db = $r->db; |
| 1367 | #my $authz = $r->authz; |
1388 | #my $authz = $r->authz; |
| 1368 | #my $urlpath = $r->urlpath; |
1389 | #my $urlpath = $r->urlpath; |
| 1369 | |
1390 | |
| 1370 | my $export_courseID = $r->param("export_courseID") || ""; |
1391 | my @export_courseID = $r->param("export_courseID") || (); |
| 1371 | my @export_tables = $r->param("export_tables"); |
1392 | my @export_tables = $r->param("export_tables"); |
| 1372 | |
1393 | |
| 1373 | my @errors; |
1394 | my @errors; |
| 1374 | |
1395 | |
| 1375 | if ($export_courseID eq "") { |
1396 | unless ( @export_courseID) { |
| 1376 | push @errors, "You must specify a course name."; |
1397 | push @errors, "You must specify at least one course name."; |
| 1377 | } |
1398 | } |
| 1378 | |
1399 | |
| 1379 | unless (@export_tables) { |
1400 | unless (@export_tables) { |
| 1380 | push @errors, "You must specify at least one table to export."; |
1401 | push @errors, "You must specify at least one table to export."; |
| 1381 | } |
1402 | } |
| … | |
… | |
| 1389 | my $ce = $r->ce; |
1410 | my $ce = $r->ce; |
| 1390 | #my $db = $r->db; |
1411 | #my $db = $r->db; |
| 1391 | #my $authz = $r->authz; |
1412 | #my $authz = $r->authz; |
| 1392 | my $urlpath = $r->urlpath; |
1413 | my $urlpath = $r->urlpath; |
| 1393 | |
1414 | |
| 1394 | my $export_courseID = $r->param("export_courseID"); |
1415 | my @export_courseID = $r->param("export_courseID"); |
| 1395 | my @export_tables = $r->param("export_tables"); |
1416 | my @export_tables = $r->param("export_tables"); |
| 1396 | |
1417 | |
|
|
1418 | foreach my $export_courseID (@export_courseID) { |
|
|
1419 | |
| 1397 | my $ce2 = WeBWorK::CourseEnvironment->new( |
1420 | my $ce2 = WeBWorK::CourseEnvironment->new( |
| 1398 | $ce->{webworkDirs}->{root}, |
1421 | $ce->{webworkDirs}->{root}, |
| 1399 | $ce->{webworkURLs}->{root}, |
1422 | $ce->{webworkURLs}->{root}, |
| 1400 | $ce->{pg}->{directories}->{root}, |
1423 | $ce->{pg}->{directories}->{root}, |
| 1401 | $export_courseID, |
1424 | $export_courseID, |
| 1402 | ); |
1425 | ); |
| 1403 | |
1426 | |
| 1404 | my $db2 = new WeBWorK::DB($ce2->{dbLayout}); |
1427 | my $db2 = new WeBWorK::DB($ce2->{dbLayout}); |
| 1405 | |
1428 | |
| 1406 | #my ($fh, $export_file) = tempfile("db_export_XXXXXX", DIR => $ce->{webworkDirs}->{tmp}); |
1429 | #my ($fh, $export_file) = tempfile("db_export_XXXXXX", DIR => $ce->{webworkDirs}->{tmp}); |
| 1407 | #my ($random_chars) = $export_file =~ m/db_export_(\w+)$/; |
1430 | #my ($random_chars) = $export_file =~ m/db_export_(\w+)$/; |
|
|
1431 | # export to the admin/templates directory |
|
|
1432 | my $exportFileName = "$export_courseID.exported.xml"; |
|
|
1433 | my $exportFilePath = $ce->{courseDirs}->{templates}."/$exportFileName"; |
|
|
1434 | # get a unique name |
|
|
1435 | my $number =1; |
|
|
1436 | while (-e "$exportFilePath.$number.gz") { |
|
|
1437 | $number++; |
|
|
1438 | last if $number>9; |
|
|
1439 | } |
|
|
1440 | if ($number<=9 ) { |
|
|
1441 | $exportFilePath = "$exportFilePath.$number"; |
|
|
1442 | $exportFileName = "$exportFileName.$number"; |
|
|
1443 | } else { |
|
|
1444 | $self->addbadmessage(CGI::p("There are more than 9 exported files for this course! Please |
|
|
1445 | remove some of these files.")); |
|
|
1446 | $exportFilePath = "$exportFilePath.999"; |
|
|
1447 | $exportFileName = "$exportFileName.999"; |
|
|
1448 | } |
| 1408 | |
1449 | |
|
|
1450 | my $outputFileHandle = new IO::File(">$exportFilePath") or warn "Unable to create $exportFilePath"; |
|
|
1451 | |
| 1409 | my @errors; |
1452 | my @errors; |
| 1410 | |
|
|
| 1411 | eval { |
1453 | eval { |
| 1412 | @errors = dbExport( |
1454 | @errors = dbExport( |
| 1413 | db => $db2, |
1455 | db => $db2, |
| 1414 | #xml => $fh, |
1456 | #xml => $fh, |
| 1415 | xml => *STDOUT, |
1457 | xml => $outputFileHandle, |
| 1416 | tables => \@export_tables, |
1458 | tables => \@export_tables, |
| 1417 | ); |
1459 | ); |
| 1418 | }; |
1460 | }; |
|
|
1461 | |
|
|
1462 | $outputFileHandle->close(); |
| 1419 | |
1463 | |
|
|
1464 | my $gzipMessage = system( 'gzip', $exportFilePath); |
|
|
1465 | if ( !$gzipMessage ) { |
|
|
1466 | $self->addgoodmessage(CGI::p( "Database saved to templates/$exportFileName.gzip. |
|
|
1467 | You may download it with the file manager.")); |
|
|
1468 | } else { |
|
|
1469 | $self->addbadmessage(CGI::p( "Failed to gzip file $exportFilePath")); |
|
|
1470 | } |
|
|
1471 | unlink $exportFilePath; |
|
|
1472 | } # end export of one course |
| 1420 | #push @errors, "Fatal exception: $@" if $@; |
1473 | #push @errors, "Fatal exception: $@" if $@; |
| 1421 | # |
1474 | # |
| 1422 | #if (@errors) { |
1475 | #if (@errors) { |
| 1423 | # print CGI::div({class=>"ResultsWithError"}, |
1476 | # print CGI::div({class=>"ResultsWithError"}, |
| 1424 | # CGI::p("An error occured while exporting the database of course $export_courseID:"), |
1477 | # CGI::p("An error occured while exporting the database of course $export_courseID:"), |
| … | |
… | |
| 1467 | $courseID, |
1520 | $courseID, |
| 1468 | ); |
1521 | ); |
| 1469 | $courseLabels{$courseID} = "$courseID (" . $tempCE->{dbLayoutName} . ")"; |
1522 | $courseLabels{$courseID} = "$courseID (" . $tempCE->{dbLayoutName} . ")"; |
| 1470 | } |
1523 | } |
| 1471 | |
1524 | |
|
|
1525 | # find databases: |
|
|
1526 | my $templatesDir = $ce->{courseDirs}->{templates}; |
|
|
1527 | my %probLibs = %{ $r->ce->{courseFiles}->{problibs} }; |
|
|
1528 | my $exempt_dirs = join("|", keys %probLibs); |
|
|
1529 | |
|
|
1530 | my @databaseFiles = listFilesRecursive( |
|
|
1531 | $templatesDir, |
|
|
1532 | qr/.\.exported\.xml\.\d*\.gz$/, # match these files #FIXME this is too restricive!! |
|
|
1533 | qr/^(?:$exempt_dirs|CVS)$/, # prune these directories |
|
|
1534 | 0, # match against file name only |
|
|
1535 | 1, # prune against path relative to $templatesDir |
|
|
1536 | ); |
|
|
1537 | |
|
|
1538 | my %databaseLabels = map { ($_ => $_) } @databaseFiles; |
|
|
1539 | |
|
|
1540 | ####### |
|
|
1541 | |
| 1472 | print CGI::h2("Import Database"); |
1542 | print CGI::h2("Import Database"); |
| 1473 | |
1543 | |
| 1474 | print CGI::start_form("POST", $r->uri, &CGI::MULTIPART); |
1544 | print CGI::start_form("POST", $r->uri, &CGI::MULTIPART); |
| 1475 | print $self->hidden_authen_fields; |
1545 | print $self->hidden_authen_fields; |
| 1476 | print $self->hidden_fields("subDisplay"); |
1546 | print $self->hidden_fields("subDisplay"); |
| 1477 | |
1547 | |
| 1478 | print CGI::table({class=>"FormLayout"}, |
1548 | print CGI::table({class=>"FormLayout"}, |
| 1479 | CGI::Tr( |
1549 | CGI::Tr( |
| 1480 | CGI::th({class=>"LeftHeader"}, "Database XML File:"), |
1550 | CGI::th({class=>"LeftHeader"}, "Database XML File:"), |
|
|
1551 | # CGI::td( |
|
|
1552 | # CGI::filefield( |
|
|
1553 | # -name => "import_file", |
|
|
1554 | # -size => 50, |
|
|
1555 | # ), |
|
|
1556 | # ), |
| 1481 | CGI::td( |
1557 | CGI::td( |
| 1482 | CGI::filefield( |
1558 | CGI::scrolling_list( |
| 1483 | -name => "import_file", |
1559 | -name => "import_file", |
|
|
1560 | -values => \@databaseFiles, |
|
|
1561 | -default => undef, |
| 1484 | -size => 50, |
1562 | -size => 10, |
| 1485 | ), |
1563 | -multiple => 0, |
|
|
1564 | -labels => \%databaseLabels, |
| 1486 | ), |
1565 | ), |
|
|
1566 | |
|
|
1567 | ) |
| 1487 | ), |
1568 | ), |
| 1488 | CGI::Tr( |
1569 | CGI::Tr( |
| 1489 | CGI::th({class=>"LeftHeader"}, "Tables to Import:"), |
1570 | CGI::th({class=>"LeftHeader"}, "Tables to Import:"), |
| 1490 | CGI::td( |
1571 | CGI::td( |
| 1491 | CGI::checkbox_group( |
1572 | CGI::checkbox_group( |
| … | |
… | |
| 1545 | #my $import_conflict = $r->param("import_conflict") || "skip"; # not checked |
1626 | #my $import_conflict = $r->param("import_conflict") || "skip"; # not checked |
| 1546 | |
1627 | |
| 1547 | my @errors; |
1628 | my @errors; |
| 1548 | |
1629 | |
| 1549 | if ($import_file eq "") { |
1630 | if ($import_file eq "") { |
| 1550 | push @errors, "You must specify a database file to upload."; |
1631 | push @errors, "You must specify a database file to import."; |
| 1551 | } |
1632 | } |
| 1552 | |
1633 | |
| 1553 | if ($import_courseID eq "") { |
1634 | if ($import_courseID eq "") { |
| 1554 | push @errors, "You must specify a course name."; |
1635 | push @errors, "You must specify a course name."; |
| 1555 | } |
1636 | } |
| … | |
… | |
| 1581 | $import_courseID, |
1662 | $import_courseID, |
| 1582 | ); |
1663 | ); |
| 1583 | |
1664 | |
| 1584 | my $db2 = new WeBWorK::DB($ce2->{dbLayout}); |
1665 | my $db2 = new WeBWorK::DB($ce2->{dbLayout}); |
| 1585 | |
1666 | |
|
|
1667 | # locate file |
|
|
1668 | my $templateDir = $ce->{courseDirs}->{templates}; |
|
|
1669 | my $filePath = "$templateDir/$import_file"; |
|
|
1670 | |
|
|
1671 | my $gunzipMessage = system( 'gunzip', $filePath); |
|
|
1672 | #FIXME |
|
|
1673 | #warn "gunzip ", $gunzipMessage; |
|
|
1674 | $filePath =~ s/\.gz$//; |
|
|
1675 | #warn "new file path is $filePath"; |
|
|
1676 | my $fileHandle = new IO::File("<$filePath"); |
| 1586 | # retrieve upload from upload cache |
1677 | # retrieve upload from upload cache |
| 1587 | my ($id, $hash) = split /\s+/, $import_file; |
1678 | # my ($id, $hash) = split /\s+/, $import_file; |
| 1588 | my $upload = WeBWorK::Upload->retrieve($id, $hash, |
1679 | # my $upload = WeBWorK::Upload->retrieve($id, $hash, |
| 1589 | dir => $ce->{webworkDirs}->{uploadCache} |
1680 | # dir => $ce->{webworkDirs}->{uploadCache} |
| 1590 | ); |
1681 | # ); |
| 1591 | |
1682 | |
| 1592 | my @errors; |
1683 | my @errors; |
| 1593 | |
1684 | |
| 1594 | eval { |
1685 | eval { |
| 1595 | @errors = dbImport( |
1686 | @errors = dbImport( |
| 1596 | db => $db2, |
1687 | db => $db2, |
| 1597 | xml => $upload->fileHandle, |
1688 | # xml => $upload->fileHandle, |
|
|
1689 | xml => $fileHandle, |
| 1598 | tables => \@import_tables, |
1690 | tables => \@import_tables, |
| 1599 | conflict => $import_conflict, |
1691 | conflict => $import_conflict, |
| 1600 | ); |
1692 | ); |
| 1601 | }; |
1693 | }; |
| 1602 | |
1694 | |
| 1603 | $upload->dispose; |
|
|
| 1604 | |
|
|
| 1605 | push @errors, "Fatal exception: $@" if $@; |
1695 | push @errors, "Fatal exception: $@" if $@; |
|
|
1696 | push @errors, $gunzipMessage if $gunzipMessage; |
| 1606 | |
1697 | |
| 1607 | if (@errors) { |
1698 | if (@errors) { |
| 1608 | print CGI::div({class=>"ResultsWithError"}, |
1699 | print CGI::div({class=>"ResultsWithError"}, |
| 1609 | CGI::p("An error occured while importing the database of course $import_courseID:"), |
1700 | CGI::p("An error occured while importing the database of course $import_courseID:"), |
| 1610 | CGI::ul(CGI::li(\@errors)), |
1701 | CGI::ul(CGI::li(\@errors)), |