From d6b5bd24dbc34bd298be649aa35960f90c53dce5 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Sat, 19 Aug 2023 11:51:08 -0500 Subject: [PATCH] Split the CourseIntegrityCheck.pm module into two parts. The first is CourseDBIntegrityCheck.pm that checks the database integrity and handles a database upgrade. The second is the CourseDirectoryIntegrityCheck that checks the directory structure and handles fixing that if not correct. This includes the improved directory upgrade method. --- bin/upgrade_admin_db.pl | 8 +- lib/WeBWorK/ContentGenerator/CourseAdmin.pm | 66 +++---- ...rityCheck.pm => CourseDBIntegrityCheck.pm} | 158 ++-------------- .../Utils/CourseDirectoryIntegrityCheck.pm | 173 ++++++++++++++++++ .../archive_course_confirm.html.ep | 2 +- .../CourseAdmin/upgrade_course_form.html.ep | 9 +- 6 files changed, 235 insertions(+), 181 deletions(-) rename lib/WeBWorK/Utils/{CourseIntegrityCheck.pm => CourseDBIntegrityCheck.pm} (66%) create mode 100644 lib/WeBWorK/Utils/CourseDirectoryIntegrityCheck.pm diff --git a/bin/upgrade_admin_db.pl b/bin/upgrade_admin_db.pl index de034f77ca..be8b355177 100755 --- a/bin/upgrade_admin_db.pl +++ b/bin/upgrade_admin_db.pl @@ -25,7 +25,7 @@ BEGIN use WeBWorK::CourseEnvironment; use WeBWorK::DB; -use WeBWorK::Utils::CourseIntegrityCheck; +use WeBWorK::Utils::CourseDBIntegrityCheck; # Update admin course my $upgrade_courseID = 'admin'; @@ -37,15 +37,15 @@ BEGIN # Create integrity checker my @update_report; -my $CIchecker = new WeBWorK::Utils::CourseIntegrityCheck(ce => $ce); +my $CIchecker = new WeBWorK::Utils::CourseDBIntegrityCheck($ce); # Add missing tables and missing fields to existing tables my ($tables_ok, $dbStatus) = $CIchecker->checkCourseTables($upgrade_courseID); my @schema_table_names = keys %$dbStatus; # update tables missing from database; my @tables_to_create = - grep { $dbStatus->{$_}->[0] == WeBWorK::Utils::CourseIntegrityCheck::ONLY_IN_A() } @schema_table_names; + grep { $dbStatus->{$_}->[0] == WeBWorK::Utils::CourseDBIntegrityCheck::ONLY_IN_A() } @schema_table_names; my @tables_to_alter = - grep { $dbStatus->{$_}->[0] == WeBWorK::Utils::CourseIntegrityCheck::DIFFER_IN_A_AND_B() } @schema_table_names; + grep { $dbStatus->{$_}->[0] == WeBWorK::Utils::CourseDBIntegrityCheck::DIFFER_IN_A_AND_B() } @schema_table_names; push(@update_report, $CIchecker->updateCourseTables($upgrade_courseID, [@tables_to_create])); for my $table_name (@tables_to_alter) { diff --git a/lib/WeBWorK/ContentGenerator/CourseAdmin.pm b/lib/WeBWorK/ContentGenerator/CourseAdmin.pm index 2c36bc6dba..1bad6ff34e 100644 --- a/lib/WeBWorK/ContentGenerator/CourseAdmin.pm +++ b/lib/WeBWorK/ContentGenerator/CourseAdmin.pm @@ -32,7 +32,8 @@ use WeBWorK::Debug; use WeBWorK::Utils qw(cryptPassword writeLog trim_spaces); use WeBWorK::Utils::CourseManagement qw(addCourse renameCourse retitleCourse deleteCourse listCourses archiveCourse unarchiveCourse initNonNativeTables); -use WeBWorK::Utils::CourseIntegrityCheck; +use WeBWorK::Utils::CourseDBIntegrityCheck; +use WeBWorK::Utils::CourseDirectoryIntegrityCheck qw(checkCourseDirectories updateCourseDirectories); use WeBWorK::DB; sub pre_header_initialize ($c) { @@ -525,7 +526,7 @@ sub rename_course_confirm ($c) { ) unless $c->param('rename_newCourseID_checkbox'); if ($ce2->{dbLayoutName}) { - my $CIchecker = WeBWorK::Utils::CourseIntegrityCheck->new(ce => $ce2); + my $CIchecker = WeBWorK::Utils::CourseDBIntegrityCheck->new($ce2); # Check database my ($tables_ok, $dbStatus) = $CIchecker->checkCourseTables($rename_oldCourseID); @@ -535,9 +536,9 @@ sub rename_course_confirm ($c) { if ($c->param('upgrade_course_tables')) { my @schema_table_names = keys %$dbStatus; my @tables_to_create = - grep { $dbStatus->{$_}->[0] == WeBWorK::Utils::CourseIntegrityCheck::ONLY_IN_A } @schema_table_names; + grep { $dbStatus->{$_}->[0] == WeBWorK::Utils::CourseDBIntegrityCheck::ONLY_IN_A } @schema_table_names; my @tables_to_alter = - grep { $dbStatus->{$_}->[0] == WeBWorK::Utils::CourseIntegrityCheck::DIFFER_IN_A_AND_B } + grep { $dbStatus->{$_}->[0] == WeBWorK::Utils::CourseDBIntegrityCheck::DIFFER_IN_A_AND_B } @schema_table_names; push(@upgrade_report, $CIchecker->updateCourseTables($rename_oldCourseID, [@tables_to_create])); for my $table_name (@tables_to_alter) { @@ -548,7 +549,7 @@ sub rename_course_confirm ($c) { } # Check directories - my ($directories_ok, $directory_report) = $CIchecker->checkCourseDirectories($ce2); + my ($directories_ok, $directory_report) = checkCourseDirectories($ce2); return $c->include( 'ContentGenerator/CourseAdmin/rename_course_confirm', @@ -1004,7 +1005,7 @@ sub archive_course_confirm ($c) { my $ce2 = WeBWorK::CourseEnvironment->new({ courseName => $archive_courseID }); if ($ce2->{dbLayoutName}) { - my $CIchecker = WeBWorK::Utils::CourseIntegrityCheck->new(ce => $ce2); + my $CIchecker = WeBWorK::Utils::CourseDBIntegrityCheck->new($ce2); # Check database my ($tables_ok, $dbStatus) = $CIchecker->checkCourseTables($archive_courseID); @@ -1014,9 +1015,9 @@ sub archive_course_confirm ($c) { if ($c->param('upgrade_course_tables')) { my @schema_table_names = keys %$dbStatus; my @tables_to_create = - grep { $dbStatus->{$_}->[0] == WeBWorK::Utils::CourseIntegrityCheck::ONLY_IN_A } @schema_table_names; + grep { $dbStatus->{$_}->[0] == WeBWorK::Utils::CourseDBIntegrityCheck::ONLY_IN_A } @schema_table_names; my @tables_to_alter = - grep { $dbStatus->{$_}->[0] == WeBWorK::Utils::CourseIntegrityCheck::DIFFER_IN_A_AND_B } + grep { $dbStatus->{$_}->[0] == WeBWorK::Utils::CourseDBIntegrityCheck::DIFFER_IN_A_AND_B } @schema_table_names; push(@upgrade_report, $CIchecker->updateCourseTables($archive_courseID, [@tables_to_create])); for my $table_name (@tables_to_alter) { @@ -1027,8 +1028,8 @@ sub archive_course_confirm ($c) { } # Update and check directories. - my $dir_update_messages = $c->param('upgrade_course_tables') ? $CIchecker->updateCourseDirectories : []; - my ($directories_ok, $directory_report) = $CIchecker->checkCourseDirectories($ce2); + my $dir_update_messages = $c->param('upgrade_course_tables') ? updateCourseDirectories($ce2) : []; + my ($directories_ok, $directory_report) = checkCourseDirectories($ce2); return $c->include( 'ContentGenerator/CourseAdmin/archive_course_confirm', @@ -1350,7 +1351,7 @@ sub upgrade_course_confirm ($c) { my $ce2 = WeBWorK::CourseEnvironment->new({ courseName => $upgrade_courseID }); # Create integrity checker - my $CIchecker = WeBWorK::Utils::CourseIntegrityCheck->new(ce => $ce2); + my $CIchecker = WeBWorK::Utils::CourseDBIntegrityCheck->new($ce2); # Report on database status my ($tables_ok, $dbStatus) = $CIchecker->checkCourseTables($upgrade_courseID); @@ -1414,7 +1415,7 @@ sub upgrade_course_confirm ($c) { } # Report on directory status - my ($directories_ok, $directory_report) = $CIchecker->checkCourseDirectories; + my ($directories_ok, $directory_report) = checkCourseDirectories($ce2); push(@$course_output, $c->tag('div', class => 'mb-2', $c->maketext('Directory structure:'))); push( @$course_output, @@ -1466,15 +1467,16 @@ sub do_upgrade_course ($c) { my $ce2 = WeBWorK::CourseEnvironment->new({ courseName => $upgrade_courseID }); # Create integrity checker - my $CIchecker = WeBWorK::Utils::CourseIntegrityCheck->new(ce => $ce2); + my $CIchecker = WeBWorK::Utils::CourseDBIntegrityCheck->new($ce2); # Add missing tables and missing fields to existing tables my ($tables_ok, $dbStatus) = $CIchecker->checkCourseTables($upgrade_courseID); my @schema_table_names = keys %$dbStatus; my @tables_to_create = - grep { $dbStatus->{$_}[0] == WeBWorK::Utils::CourseIntegrityCheck::ONLY_IN_A } @schema_table_names; + grep { $dbStatus->{$_}[0] == WeBWorK::Utils::CourseDBIntegrityCheck::ONLY_IN_A } @schema_table_names; my @tables_to_alter = - grep { $dbStatus->{$_}[0] == WeBWorK::Utils::CourseIntegrityCheck::DIFFER_IN_A_AND_B } @schema_table_names; + grep { $dbStatus->{$_}[0] == WeBWorK::Utils::CourseDBIntegrityCheck::DIFFER_IN_A_AND_B } + @schema_table_names; my @upgrade_report; push( @@ -1526,8 +1528,8 @@ sub do_upgrade_course ($c) { } # Add missing directories and prepare report on directory status - my $dir_update_messages = $CIchecker->updateCourseDirectories; # Needs more error messages - my ($directories_ok, $directory_report) = $CIchecker->checkCourseDirectories; + my $dir_update_messages = updateCourseDirectories($ce2); # Needs more error messages + my ($directories_ok, $directory_report) = checkCourseDirectories($ce2); # Show status my $course_report = $c->c; @@ -1563,8 +1565,8 @@ sub do_upgrade_course ($c) { 'li', $c->tag( 'span', - class => $_->[2] ? 'text-success' : 'text-danger', - $_->[1] + class => $_->[1] ? 'text-success' : 'text-danger', + $_->[0] ) ) } @$dir_update_messages @@ -2264,32 +2266,32 @@ sub do_registration ($c) { # Format a list of tables and fields in the database, and the status of each. sub formatReportOnDatabaseTables ($c, $dbStatus, $courseID = undef) { my %table_status_message = ( - WeBWorK::Utils::CourseIntegrityCheck::SAME_IN_A_AND_B => + WeBWorK::Utils::CourseDBIntegrityCheck::SAME_IN_A_AND_B => $c->tag('span', class => 'text-success me-2', $c->maketext('Table is ok')), - WeBWorK::Utils::CourseIntegrityCheck::ONLY_IN_A => $c->tag( + WeBWorK::Utils::CourseDBIntegrityCheck::ONLY_IN_A => $c->tag( 'span', class => 'text-danger me-2', $c->maketext('Table defined in schema but missing in database') ), - WeBWorK::Utils::CourseIntegrityCheck::ONLY_IN_B => $c->tag( + WeBWorK::Utils::CourseDBIntegrityCheck::ONLY_IN_B => $c->tag( 'span', class => 'text-danger me-2', $c->maketext('Table defined in database but missing in schema') ), - WeBWorK::Utils::CourseIntegrityCheck::DIFFER_IN_A_AND_B => $c->tag( + WeBWorK::Utils::CourseDBIntegrityCheck::DIFFER_IN_A_AND_B => $c->tag( 'span', class => 'text-danger me-2', $c->maketext('Schema and database table definitions do not agree') ) ); my %field_status_message = ( - WeBWorK::Utils::CourseIntegrityCheck::SAME_IN_A_AND_B => + WeBWorK::Utils::CourseDBIntegrityCheck::SAME_IN_A_AND_B => $c->tag('span', class => 'text-success me-2', $c->maketext('Field is ok')), - WeBWorK::Utils::CourseIntegrityCheck::ONLY_IN_A => + WeBWorK::Utils::CourseDBIntegrityCheck::ONLY_IN_A => $c->tag('span', class => 'text-danger me-2', $c->maketext('Field missing in database')), - WeBWorK::Utils::CourseIntegrityCheck::ONLY_IN_B => + WeBWorK::Utils::CourseDBIntegrityCheck::ONLY_IN_B => $c->tag('span', class => 'text-danger me-2', $c->maketext('Field missing in schema')), - WeBWorK::Utils::CourseIntegrityCheck::DIFFER_IN_A_AND_B => $c->tag( + WeBWorK::Utils::CourseDBIntegrityCheck::DIFFER_IN_A_AND_B => $c->tag( 'span', class => 'text-danger me-2', $c->maketext('Schema and database field definitions do not agree') @@ -2308,9 +2310,9 @@ sub formatReportOnDatabaseTables ($c, $dbStatus, $courseID = undef) { my $table_status = $dbStatus->{$table}[0]; push(@$table_report, $table . ': ', $table_status_message{$table_status}); - if ($table_status == WeBWorK::Utils::CourseIntegrityCheck::ONLY_IN_A) { + if ($table_status == WeBWorK::Utils::CourseDBIntegrityCheck::ONLY_IN_A) { $all_tables_ok = 0; - } elsif ($table_status == WeBWorK::Utils::CourseIntegrityCheck::ONLY_IN_B) { + } elsif ($table_status == WeBWorK::Utils::CourseDBIntegrityCheck::ONLY_IN_B) { $extra_database_tables = 1; push( @$table_report, @@ -2325,7 +2327,7 @@ sub formatReportOnDatabaseTables ($c, $dbStatus, $courseID = undef) { ) ) ) if defined $courseID; - } elsif ($table_status == WeBWorK::Utils::CourseIntegrityCheck::DIFFER_IN_A_AND_B) { + } elsif ($table_status == WeBWorK::Utils::CourseDBIntegrityCheck::DIFFER_IN_A_AND_B) { my %fieldInfo = %{ $dbStatus->{$table}[1] }; my $fields_report = $c->c; @@ -2333,7 +2335,7 @@ sub formatReportOnDatabaseTables ($c, $dbStatus, $courseID = undef) { my $field_status = $fieldInfo{$key}[0]; my $field_report = $c->c("$key: $field_status_message{$field_status}"); - if ($field_status == WeBWorK::Utils::CourseIntegrityCheck::ONLY_IN_B) { + if ($field_status == WeBWorK::Utils::CourseDBIntegrityCheck::ONLY_IN_B) { $extra_database_fields = 1; push( @$field_report, @@ -2353,7 +2355,7 @@ sub formatReportOnDatabaseTables ($c, $dbStatus, $courseID = undef) { ) ) ) if defined $courseID; - } elsif ($field_status == WeBWorK::Utils::CourseIntegrityCheck::ONLY_IN_A) { + } elsif ($field_status == WeBWorK::Utils::CourseDBIntegrityCheck::ONLY_IN_A) { $all_tables_ok = 0; } push(@$fields_report, $c->tag('li', $field_report->join(''))); diff --git a/lib/WeBWorK/Utils/CourseIntegrityCheck.pm b/lib/WeBWorK/Utils/CourseDBIntegrityCheck.pm similarity index 66% rename from lib/WeBWorK/Utils/CourseIntegrityCheck.pm rename to lib/WeBWorK/Utils/CourseDBIntegrityCheck.pm index a1c2984d8b..704e6bea78 100644 --- a/lib/WeBWorK/Utils/CourseIntegrityCheck.pm +++ b/lib/WeBWorK/Utils/CourseDBIntegrityCheck.pm @@ -13,25 +13,23 @@ # Artistic License for more details. ################################################################################ -package WeBWorK::Utils::CourseIntegrityCheck; +package WeBWorK::Utils::CourseDBIntegrityCheck; =head1 NAME -WeBWorK::Utils::CourseIntegrityCheck - check that course database tables agree -with database schema and that course directory structure is correct. +WeBWorK::Utils::CourseDBIntegrityCheck - Check that course database tables agree +with database schema. =cut use strict; use warnings; -use WeBWorK::Debug; -use WeBWorK::Utils qw/createDirectory/; use WeBWorK::Utils::CourseManagement qw/listCourses/; # Developer note: This file should not format messages in html. Instead return an array of tuples. Each tuple should # contain the message components, and the last element of the tuple should be 0 or 1 to indicate failure or success -# respectively. See the updateCourseTables, updateTableFields, and updateCourseDirectories methods. +# respectively. See the updateCourseTables and updateTableFields. # Constants describing the comparison of two hashes. use constant { @@ -42,23 +40,21 @@ use constant { }; sub new { - my ($invocant, %options) = @_; - my $self = bless {}, ref $invocant || $invocant; - - $self->{dbh} = DBI->connect( - $options{ce}{database_dsn}, - $options{ce}{database_username}, - $options{ce}{database_password}, - { - PrintError => 0, - RaiseError => 1, + my ($invocant, $ce) = @_; + return bless { + dbh => DBI->connect( + $ce->{database_dsn}, + $ce->{database_username}, + $ce->{database_password}, + { + PrintError => 0, + RaiseError => 1 + } + ), + ce => $ce, + db => WeBWorK::DB->new($ce->{dbLayouts}{ $ce->{dbLayoutName} }) }, - ); - - $self->{ce} = $options{ce}; - $self->{db} = WeBWorK::DB->new($self->{ce}{dbLayouts}{ $self->{ce}->{dbLayoutName} }); - - return $self; + ref $invocant || $invocant; } sub ce { return shift->{ce} } @@ -286,124 +282,6 @@ sub updateTableFields { return @messages; } -=head2 checkCourseDirectories - -Usage: C<< $CIchecker->checkCourseDirectories($courseName); >> - -Checks the course directories to make sure they exist and have the correct -permissions. - -=cut - -sub checkCourseDirectories { - my ($self) = @_; - my $ce = $self->{ce}; - - my @results; - my $directories_ok = 1; - - for my $dir (sort keys %{ $ce->{courseDirs} }) { - my $path = $ce->{courseDirs}{$dir}; - my $status = -e $path ? (-r $path ? 'r' : '-') . (-w _ ? 'w' : '-') . (-x _ ? 'x' : '-') : 'missing'; - - # All directories should be readable, writable and executable. - my $good = $status eq 'rwx'; - $directories_ok = 0 if !$good; - - push @results, [ $dir, $path, $good ]; - } - - return ($directories_ok, \@results); -} - -=head2 updateCourseDirectories - -Usage: C<< $CIchecker->updateCourseDirectories($courseName); >> - -Creates some course directories automatically. - -=cut - -# FIXME: This method needs work. It should give better messages, and should at least attempt to fix permissions if -# possible. It also should deal with some of the other course directories that it skips. - -sub updateCourseDirectories { - my $self = shift; - my $ce = $self->{ce}; - - my @courseDirectories = keys %{ $ce->{courseDirs} }; - - #FIXME this is hardwired for the time being. - my %updateable_directories = (html_temp => 1, mailmerge => 1, tmpEditFileDir => 1, hardcopyThemes => 1); - - my @messages; - - for my $dir (sort @courseDirectories) { - # Hack for upgrading the achievements directory. - if ($dir eq 'achievements') { - my $modelCourseAchievementsDir = "$ce->{webworkDirs}{courses}/modelCourse/templates/achievements"; - my $modelCourseAchievementsHtmlDir = "$ce->{webworkDirs}{courses}/modelCourse/html/achievements"; - my $courseAchievementsDir = $ce->{courseDirs}{achievements}; - my $courseAchievementsHtmlDir = $ce->{courseDirs}{achievements_html}; - my $courseTemplatesDir = $ce->{courseDirs}{templates}; - my $courseHtmlDir = $ce->{courseDirs}{html}; - unless (-e $modelCourseAchievementsDir && -e $modelCourseAchievementsHtmlDir) { - push( - @messages, - [ - 'Your modelCourse in the "courses" directory is out of date or missing. Please update it from ' - . 'webwork/webwork2/courses.dist directory before upgrading the other courses. Cannot find ' - . "MathAchievements directory $modelCourseAchievementsDir nor MathAchievements picture " - . "directory $modelCourseAchievementsHtmlDir", - 0 - ] - ); - } else { - unless (-e $courseAchievementsDir && -e $courseAchievementsHtmlDir) { - push(@messages, - [ "Attempting to update the achievements directory for $ce->{courseDirs}{root}", 1 ]); - if (-e $courseAchievementsDir) { - push(@messages, [ 'Achievements directory is already present', 1 ]); - } else { - system "cp -RPpi $modelCourseAchievementsDir $courseTemplatesDir"; - push(@messages, [ 'Achievements directory created', 1 ]); - } - if (-e $courseAchievementsHtmlDir) { - push(@messages, [ 'Achievements html directory is already present', 1 ]); - } else { - system "cp -RPpi $modelCourseAchievementsHtmlDir $courseHtmlDir "; - push(@messages, [ 'Achievements html directory created', 1 ]); - } - } - } - } - - next unless exists $updateable_directories{$dir}; - my $path = $ce->{courseDirs}{$dir}; - unless (-e $path) { # If the directory does not exist, create it. - my $parentDirectory = $path; - $parentDirectory =~ s|/$||; # Remove a trailing forward slash - $parentDirectory =~ s|/[^/]*$||; # Remove last node - my ($perms, $groupID) = (stat $parentDirectory)[ 2, 5 ]; - if (-w $parentDirectory) { - createDirectory($path, $perms, $groupID) - or push(@messages, [ "Failed to create directory at $path.", 0 ]); - } else { - push( - @messages, - [ - "Permissions error. Can't create directory at $path. " - . "Lack write permission on $parentDirectory.", - 0 - ] - ); - } - } - } - - return \@messages; -} - # Database locking utilities # Create a lock named 'webwork.dbugrade' that times out after 10 seconds. diff --git a/lib/WeBWorK/Utils/CourseDirectoryIntegrityCheck.pm b/lib/WeBWorK/Utils/CourseDirectoryIntegrityCheck.pm new file mode 100644 index 0000000000..3450d92c98 --- /dev/null +++ b/lib/WeBWorK/Utils/CourseDirectoryIntegrityCheck.pm @@ -0,0 +1,173 @@ +################################################################################ +# WeBWorK Online Homework Delivery System +# Copyright © 2000-2023 The WeBWorK Project, https://github.com/openwebwork +# +# This program is free software; you can redistribute it and/or modify it under +# the terms of either: (a) the GNU General Public License as published by the +# Free Software Foundation; either version 2, or (at your option) any later +# version, or (b) the "Artistic License" which comes with this package. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the +# Artistic License for more details. +################################################################################ + +package WeBWorK::Utils::CourseDirectoryIntegrityCheck; +use parent Exporter; + +=head1 NAME + +WeBWorK::Utils::CourseDirectoryIntegrityCheck - Check that course directory +structure is correct. + +=cut + +use strict; +use warnings; + +use Mojo::File qw(path); + +our @EXPORT_OK = qw(checkCourseDirectories updateCourseDirectories); + +# Developer note: This file should not format messages in html. Instead return an array of tuples. Each tuple should +# contain the message components, and the last element of the tuple should be 0 or 1 to indicate failure or success +# respectively. See the the updateCourseDirectories method. + +=head2 checkCourseDirectories + +Usage: C<< checkCourseDirectories($ce) >> + +Checks the course directories to make sure they exist and have the correct +permissions. + +=cut + +sub checkCourseDirectories { + my $ce = shift; + + my @results; + my $directories_ok = 1; + + for my $dir (sort keys %{ $ce->{courseDirs} }) { + my $path = $ce->{courseDirs}{$dir}; + my $status = -e $path ? (-r $path ? 'r' : '-') . (-w _ ? 'w' : '-') . (-x _ ? 'x' : '-') : 'missing'; + + # All directories should be readable, writable and executable. + my $good = $status eq 'rwx'; + $directories_ok = 0 if !$good; + + push @results, [ $dir, $path, $good ]; + } + + return ($directories_ok, \@results); +} + +=head2 updateCourseDirectories + +Usage: C<< updateCourseDirectories($ce) >> + +Check to see if all course directories exist and have the correct permissions. + +If a directory does not exist, then it is copied from the model course if the +corresponding directory exists in the model course, and is created otherwise. + +If the permissions are not correct, then an attempt is made to correct the +permissions. The permissions are expected to match the course root directory. +If the permissions of the course root directory are not correct, then that will +need to be manually fixed. This method does not check that. + +=cut + +sub updateCourseDirectories { + my $ce = shift; + + my @messages; + + # All $courseDirs keys should be listed here except for root. The keys of the $courseDirs hash can not be used + # directly for lack of order. The important thing for the order is that a directory that is a subdirectory of + # another (with the default settings) is listed after the directory containing it. + my @course_dirs = ( + 'templates', 'DATA', 'html', 'logs', + 'scoring', 'achievements', 'email', 'hardcopyThemes', + 'macros', 'tmpEditFileDir', 'mailmerge', 'achievements_html', + 'html_images', 'html_temp' + ); + + # These are the directories in the model course that can be copied if not found in this course. + my %model_course_dirs = ( + templates => 'templates', + html => 'html', + achievements => 'templates/achievements', + email => 'templates/email', + achievements_html => 'html/achievements' + ); + + my $permissions = path($ce->{courseDirs}{root})->stat->mode & 0777; + + for my $dir (@course_dirs) { + my $path = path($ce->{courseDirs}{$dir}); + next if -r $path && -w $path && -x $path; + + my $path_exists_initially = -e $path; + + # Create the directory if it doesn't exist. + if (!$path_exists_initially) { + eval { + $path->make_path({ mode => $permissions }); + push(@messages, [ "Created directory $path.", 1 ]); + }; + if ($@) { + push(@messages, [ "Failed to create directory $path.", 0 ]); + next; + } + } + + # Fix permissions if those are not correct. + if (($path->stat->mode & 0777) != $permissions) { + eval { + $path->chmod($permissions); + push(@messages, [ "Changed permissions for directory $path.", 1 ]); + }; + push(@messages, [ "Failed to change permissions for directory $path.", 0 ]) if $@; + } + + # If the path did not exist to begin with and there is a corresponding model course directory, + # then copy the contents of the model course directory. + if (!$path_exists_initially && $model_course_dirs{$dir}) { + my $modelCoursePath = "$ce->{webworkDirs}{courses}/modelCourse/$model_course_dirs{$dir}"; + if (!-r $modelCoursePath) { + push( + @messages, + [ + 'Your modelCourse in the "courses" directory is out of date or missing. Please update it from ' + . "the webwork2/courses.dist directory. Cannot find directory $modelCoursePath. The " + . "directory $path has been created, but may be missing the files it should contain.", + 0 + ] + ); + next; + } + + eval { + for (path($modelCoursePath)->list_tree({ dir => 1 })->each) { + my $destPath = $_ =~ s!$modelCoursePath!$path!r; + if (-l $_) { + symlink(readlink $_, $destPath); + } elsif (-d $_) { + path($destPath)->make_path({ mode => $permissions }); + } else { + $_->copy_to($destPath); + } + } + push(@messages, [ "Copied model course directory $modelCoursePath to $path.", 1 ]); + }; + push(@messages, [ "Failed to copy model course directory $modelCoursePath to $path: $@.", 0 ]) if $@; + } + + } + + return \@messages; +} + +1; diff --git a/templates/ContentGenerator/CourseAdmin/archive_course_confirm.html.ep b/templates/ContentGenerator/CourseAdmin/archive_course_confirm.html.ep index a5597adfd8..29b24becd5 100644 --- a/templates/ContentGenerator/CourseAdmin/archive_course_confirm.html.ep +++ b/templates/ContentGenerator/CourseAdmin/archive_course_confirm.html.ep @@ -39,7 +39,7 @@ % if ($directories_ok) { diff --git a/templates/ContentGenerator/CourseAdmin/upgrade_course_form.html.ep b/templates/ContentGenerator/CourseAdmin/upgrade_course_form.html.ep index 6b9df08fbc..9fa1d4947f 100644 --- a/templates/ContentGenerator/CourseAdmin/upgrade_course_form.html.ep +++ b/templates/ContentGenerator/CourseAdmin/upgrade_course_form.html.ep @@ -1,5 +1,6 @@ % use WeBWorK::Utils::CourseManagement qw(listCourses); -% use WeBWorK::Utils::CourseIntegrityCheck; +% use WeBWorK::Utils::CourseDBIntegrityCheck; +% use WeBWorK::Utils::CourseDirectoryIntegrityCheck qw(checkCourseDirectories); % use WeBWorK::CourseEnvironment; % % my @courseIDs = sort { lc($a) cmp lc($b) } listCourses($ce); @@ -27,9 +28,9 @@ % if ($@) { <%= maketext(q{Can't create course environment for [_1] because [_2]}, $courseID, $@) =%> % } - % my $CIchecker = WeBWorK::Utils::CourseIntegrityCheck->new(ce => $tempCE); - % my ($tables_ok) = $CIchecker->checkCourseTables($courseID); - % my ($directories_ok) = $CIchecker->checkCourseDirectories(); + % my $CDBIchecker = WeBWorK::Utils::CourseDBIntegrityCheck->new($tempCE); + % my ($tables_ok) = $CDBIchecker->checkCourseTables($courseID); + % my ($directories_ok) = checkCourseDirectories($tempCE); %
  • % if (!$tables_ok || !$directories_ok) {