From ef76631f2ae5590ca1b2bee8d8fa52e33da70356 Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Mon, 18 Dec 2023 22:14:46 -0800 Subject: [PATCH 1/9] be able to copy more things from a course when adding a new course --- conf/defaults.config | 14 +- lib/WeBWorK/ContentGenerator/CourseAdmin.pm | 53 +++-- lib/WeBWorK/Utils/CourseManagement.pm | 191 ++++++++++++++---- .../ContentGenerator/Base/admin_links.html.ep | 10 +- .../CourseAdmin/add_course_form.html.ep | 57 +++++- templates/HelpFiles/AdminAddCourse.html.ep | 12 +- 6 files changed, 259 insertions(+), 78 deletions(-) diff --git a/conf/defaults.config b/conf/defaults.config index 3015dc5eee..4173c685a5 100644 --- a/conf/defaults.config +++ b/conf/defaults.config @@ -693,13 +693,17 @@ $courseFiles{logs}{activity_log} = ''; # Site defaults (Usually overridden in localOverrides.conf) ################################################################################ -# The default_templates_course is used by default to create a new course. -# The contents of the templates directory are copied from this course -# to the new course being created. -$siteDefaults{default_templates_course} ="modelCourse"; +# The default_copy_from_course is used by default when creating a new course. +# Its templates folder, html folder, course.conf file, and simple.conf file +# might be copied into a new course. This course might not be a true course; +# it might only have a directory structure and no presense in the database. +# If it is a real course, then also its title, institution, non-student users, +# achievements, and sets can be copied. +$siteDefaults{default_copy_from_course} ="modelCourse"; # Provide a list of model courses which are not real courses, but from which -# the templates for a new course can be copied. +# the templates for a new course can be copied. This list helps exclude such +# non-real courses when that is appopriate. $modelCoursesForCopy = [ "modelCourse" ]; ################################################################################ diff --git a/lib/WeBWorK/ContentGenerator/CourseAdmin.pm b/lib/WeBWorK/ContentGenerator/CourseAdmin.pm index 568f145cd4..e732cfeab7 100644 --- a/lib/WeBWorK/ContentGenerator/CourseAdmin.pm +++ b/lib/WeBWorK/ContentGenerator/CourseAdmin.pm @@ -294,21 +294,29 @@ sub do_add_course ($c) { my $db = $c->db; my $authz = $c->authz; - my $add_courseID = trim_spaces($c->param('new_courseID')) || ''; - my $add_courseTitle = trim_spaces($c->param('add_courseTitle')) || ''; - my $add_courseInstitution = trim_spaces($c->param('add_courseInstitution')) || ''; - - my $add_admin_users = trim_spaces($c->param('add_admin_users')) || ''; - - my $add_initial_userID = trim_spaces($c->param('add_initial_userID')) || ''; - my $add_initial_password = trim_spaces($c->param('add_initial_password')) || ''; - my $add_initial_confirmPassword = trim_spaces($c->param('add_initial_confirmPassword')) || ''; - my $add_initial_firstName = trim_spaces($c->param('add_initial_firstName')) || ''; - my $add_initial_lastName = trim_spaces($c->param('add_initial_lastName')) || ''; - my $add_initial_email = trim_spaces($c->param('add_initial_email')) || ''; - - my $add_templates_course = trim_spaces($c->param('add_templates_course')) || ''; - my $add_config_file = trim_spaces($c->param('add_config_file')) || ''; + my $add_courseID = trim_spaces($c->param('new_courseID')) // ''; + my $add_courseTitle = trim_spaces($c->param('add_courseTitle')) // ''; + my $add_courseInstitution = trim_spaces($c->param('add_courseInstitution')) // ''; + + my $add_admin_users = $c->param('add_admin_users') || ''; + + my $add_initial_userID = trim_spaces($c->param('add_initial_userID')) // ''; + my $add_initial_password = trim_spaces($c->param('add_initial_password')) // ''; + my $add_initial_confirmPassword = trim_spaces($c->param('add_initial_confirmPassword')) // ''; + my $add_initial_firstName = trim_spaces($c->param('add_initial_firstName')) // ''; + my $add_initial_lastName = trim_spaces($c->param('add_initial_lastName')) // ''; + my $add_initial_email = trim_spaces($c->param('add_initial_email')) // ''; + + my $copy_from_course = trim_spaces($c->param('copy_from_course')) // ''; + + my $copy_templates_html = $c->param('copy_templates_html') || ''; + my $copy_simple_config_file = $c->param('copy_simple_config_file') || ''; + my $copy_config_file = $c->param('copy_config_file') || ''; + my $copy_non_students = $c->param('copy_non_students') || ''; + my $copy_sets = $c->param('copy_sets') || ''; + my $copy_achievements = $c->param('copy_achievements') || ''; + my $copy_title = $c->param('copy_title') || ''; + my $copy_institution = $c->param('copy_institution') || ''; my $add_dbLayout = trim_spaces($c->param('add_dbLayout')) || ''; @@ -365,11 +373,16 @@ sub do_add_course ($c) { # Include any optional arguments, including a template course and the course title and course institution. my %optional_arguments; - if ($add_templates_course ne '') { - $optional_arguments{templatesFrom} = $add_templates_course; - } - if ($add_config_file ne '') { - $optional_arguments{copySimpleConfig} = $add_config_file; + if ($copy_from_course ne '') { + $optional_arguments{copyFrom} = $copy_from_course; + $optional_arguments{copyTemplatesHtml} = $copy_templates_html; + $optional_arguments{copySimpleConfig} = $copy_simple_config_file; + $optional_arguments{copyConfig} = $copy_config_file; + $optional_arguments{copyNonStudents} = $copy_non_students; + $optional_arguments{copySets} = $copy_sets; + $optional_arguments{copyAchievements} = $copy_achievements; + $optional_arguments{copyTitle} = $copy_title; + $optional_arguments{copyInstitution} = $copy_institution; } if ($add_courseTitle ne '') { $optional_arguments{courseTitle} = $add_courseTitle; diff --git a/lib/WeBWorK/Utils/CourseManagement.pm b/lib/WeBWorK/Utils/CourseManagement.pm index dc811f42e6..da56103408 100644 --- a/lib/WeBWorK/Utils/CourseManagement.pm +++ b/lib/WeBWorK/Utils/CourseManagement.pm @@ -144,18 +144,27 @@ sub listArchivedCourses { %options must contain: - courseID => $courseID, - ce => $ce, - courseOptions => $courseOptions, - users => $users + courseID => course ID for the new course, + ce => a course environment for the new course, + courseOptions => hash ref explained below + users => array ref explained below %options may contain: - templatesFrom => $templatesCourseID, - courseTitle => $courseTitle - courseInstitution => $courseInstitution + copyFrom => some course ID to copy things from, + courseTitle => a title for the new course + courseInstitution => institution for the new course + copyTemplatesHtml => boolean + copySimpleConfig => boolean + copyConfig => boolean + copyNonStudents => boolean + copySets => boolean + copyAchievements => boolean + copyTitle => boolean + copyInstitution => boolean + -Create a new course named $courseID. +Create a new course with ID $courseID. $ce is a WeBWorK::CourseEnvironment object that describes the new course's environment. @@ -174,8 +183,18 @@ PermissionLevel record for a single user: These users are added to the course. -$templatesCourseID indicates the ID of a course from which the contents of the -templates directory will be copied to the new course. +C indicates the ID of a course from which various things may be +copied into the new course. Which things are copied are controlled by the +boolean options: + + * copyTemplatesHtml (contents of Templates and HTML folders) + * copySimpleConfig (simple.conf file) + * copyConfig (course.conf file) + * copyNonStudents (all non-student users, their permission level, and password) + * copySets (all global sets, global set locations, and global problems) + * copyAchievements (all achievements) + * copyTitle (the course title, which will override courseTitle) + * copyInstitution (the course institution, which will override courseInstitution) =cut @@ -189,6 +208,7 @@ sub addCourse { } my $courseID = $options{courseID}; + my $sourceCourse = $options{copyFrom} // ''; my $ce = $options{ce}; my %courseOptions = %{ $options{courseOptions} }; my @users = exists $options{users} ? @{ $options{users} } : (); @@ -306,15 +326,42 @@ sub addCourse { ##### step 3: populate course database ##### + # db object for course to copy things from + my $db0; + if ( + $sourceCourse ne '' + && !(grep { $sourceCourse eq $_ } @{ $ce->{modelCoursesForCopy} }) + && ($options{copyNonStudents} + || $options{copySets} + || $options{copyAchievements} + || $options{copyTitle} + || $options{copyInstitution}) + ) + { + my $ce0 = WeBWorK::CourseEnvironment->new({ courseName => $sourceCourse }); + $db0 = WeBWorK::DB->new($ce0->{dbLayouts}{$dbLayoutName}); + } + + # add users (users that were directly passed to addCourse() as well as those copied from a source course) if ($ce->{dbLayouts}{$dbLayoutName}{user}{params}{non_native}) { debug("not adding users to the course database: 'user' table is non-native.\n"); } else { - # see above - #my $db = WeBWorK::DB->new($ce->{dbLayouts}->{$dbLayoutName}); + if ($db0 && $options{copyNonStudents}) { + my @non_student_ids = + map {@$_} ($db0->listPermissionLevelsWhere({ permission => { not_like => '0' } }, 'user_id')); + my %original_users = map { $_->[0]{user_id} => 1 } (@users); + + for my $user_id (@non_student_ids) { + next if $original_users{$user_id}; + my @User = $db0->getUsersWhere({ user_id => $user_id }); + my @Password = $db0->getPasswordsWhere({ user_id => $user_id }); + my @PermissionLevel = $db0->getPermissionLevelsWhere({ user_id => $user_id }); + push @users, [ $User[0], $Password[0], $PermissionLevel[0] ]; + } + } foreach my $userTriple (@users) { my ($User, $Password, $PermissionLevel) = @$userTriple; - eval { $db->addUser($User) }; warn $@ if $@; eval { $db->addPassword($Password) }; @@ -324,11 +371,56 @@ sub addCourse { } } - if (exists $options{courseTitle}) { - $db->setSettingValue('courseTitle', $options{courseTitle}); + # add sets + if ($db0 && $options{copySets}) { + if ($ce->{dbLayouts}{$dbLayoutName}{set}{params}{non_native}) { + debug("not adding sets to the course database: 'set' table is non-native.\n"); + } else { + my @set_ids = $db0->listGlobalSets; + for my $set_id (@set_ids) { + eval { $db->addGlobalSet($db0->getGlobalSet($set_id)) }; + warn $@ if $@; + + my @Problem = $db0->getGlobalProblemsWhere({ set_id => $set_id }); + for my $problem (@Problem) { + eval { $db->addGlobalProblem($problem) }; + warn $@ if $@; + } + + my @Location = $db0->getGlobalSetLocationsWhere({ set_id => $set_id }); + for my $location (@Location) { + eval { $db->addGlobalSetLocation($location) }; + warn $@ if $@; + } + } + } } - if (exists $options{courseInstitution}) { - $db->setSettingValue('courseInstitution', $options{courseInstitution}); + + # add achievements + if ($db0 && $options{copyAchievements}) { + if ($ce->{dbLayouts}{$dbLayoutName}{achievement}{params}{non_native}) { + debug("not adding achievements to the course database: 'achievement' table is non-native.\n"); + } else { + my @achievement_ids = $db0->listAchievements; + for my $achievement_id (@achievement_ids) { + eval { $db->addAchievement($db0->getAchievement($achievement_id)) }; + warn $@ if $@; + } + } + } + + # copy title and/or institution if requested + if ($db0 && ($options{copyTitle} || $options{copyInstitution})) { + if ($ce->{dbLayouts}{$dbLayoutName}{setting}{params}{non_native}) { + debug("not copying settings to the course database: 'setting' table is non-native.\n"); + } else { + $db->setSettingValue('courseTitle', $db0->getSettingValue('courseTitle')) if ($options{copyTitle}); + $db->setSettingValue('courseInstitution', $db0->getSettingValue('{courseInstitution')) + if ($options{copyInstitution}); + } + } else { + $db->setSettingValue('courseTitle', $options{courseTitle}) if (exists $options{courseTitle}); + $db->setSettingValue('courseInstitution', $options{courseInstitution}) if (exists $options{courseInstitution}); } ##### step 4: write course.conf file ##### @@ -339,40 +431,59 @@ sub addCourse { writeCourseConf($fh, $ce, %courseOptions); close $fh; - ##### step 5: copy templates, html, and simple.conf if desired ##### + ##### step 5: copy templates, html, simple.conf, course.conf if desired ##### - if (exists $options{templatesFrom}) { - my $sourceCourse = $options{templatesFrom}; - my $sourceCE = WeBWorK::CourseEnvironment->new({ get_SeedCE($ce), courseName => $sourceCourse }); - my $sourceDir = $sourceCE->{courseDirs}->{templates}; - ## copy templates ## - if (-d $sourceDir) { - my $destDir = $ce->{courseDirs}{templates}; - warn "Failed to copy templates from course '$sourceCourse': $! " unless dircopy("$sourceDir", $destDir); - } else { - warn - "Failed to copy templates from course '$sourceCourse': templates directory '$sourceDir' does not exist.\n"; - } - ## copy html ## - ## this copies the html/tmp directory as well which is not optimal - $sourceDir = $sourceCE->{courseDirs}->{html}; - if (-d $sourceDir) { - warn "Failed to copy html from course '$sourceCourse': $!" - unless dircopy($sourceDir, $ce->{courseDirs}{html}); - } else { - warn "Failed to copy html from course '$sourceCourse': html directory '$sourceDir' does not exist.\n"; + if ($sourceCourse ne '') { + my $sourceCE = WeBWorK::CourseEnvironment->new({ get_SeedCE($ce), courseName => $sourceCourse }); + my $sourceDir = $sourceCE->{courseDirs}{templates}; + if ($options{copyTemplatesHtml}) { + ## copy templates ## + if (-d $sourceDir) { + my $destDir = $ce->{courseDirs}{templates}; + warn "Failed to copy templates from course '$sourceCourse': $! " unless dircopy("$sourceDir", $destDir); + } else { + warn + "Failed to copy templates from course '$sourceCourse': templates directory '$sourceDir' does not exist.\n"; + } + ## copy html ## + ## this copies the html/tmp directory as well which is not optimal + $sourceDir = $sourceCE->{courseDirs}{html}; + if (-d $sourceDir) { + warn "Failed to copy html from course '$sourceCourse': $!" + unless dircopy($sourceDir, $ce->{courseDirs}{html}); + } else { + warn "Failed to copy html from course '$sourceCourse': html directory '$sourceDir' does not exist.\n"; + } } ## copy config files ## # this copies the simple.conf file if desired - if (exists $options{copySimpleConfig}) { - my $sourceFile = $sourceCE->{courseFiles}->{simpleConfig}; + if ($options{copySimpleConfig}) { + my $sourceFile = $sourceCE->{courseFiles}{simpleConfig}; if (-e $sourceFile) { eval { Mojo::File->new($sourceFile)->copy_to($ce->{courseFiles}) }; warn "Failed to copy simple.conf from course '$sourceCourse': $@" if $@; } } + # this copies the course.conf file if desired + if ($options{copyConfig}) { + my $sourceFile = $sourceCE->{courseFiles}{environment}; + if (-e $sourceFile) { + my $destFile = $ce->{courseFiles}{environment}; + my $cp_cmd = + join(" ", ("2>&1", $ce->{externalPrograms}{cp}, shell_quote($sourceFile), shell_quote($destFile))); + my $cp_out = readpipe $cp_cmd; + if ($?) { + my $exit = $? >> 8; + my $signal = $? & 127; + my $core = $? & 128; + warn "Failed to copy course.conf from course '$sourceCourse' " + . "with command '$cp_cmd' (exit=$exit signal=$signal core=$core): $cp_out\n"; + } + } + } } + } ################################################################################ diff --git a/templates/ContentGenerator/Base/admin_links.html.ep b/templates/ContentGenerator/Base/admin_links.html.ep index 34ce03942b..6199915ef4 100644 --- a/templates/ContentGenerator/Base/admin_links.html.ep +++ b/templates/ContentGenerator/Base/admin_links.html.ep @@ -16,9 +16,15 @@ % maketext('Add Courses'), % { % add_admin_users => 1, - % add_config_file => 1, + % copy_from_course => $ce->{siteDefaults}{default_copy_from_course} || '', + % copy_templates_html => 1, + % copy_simple_config_file => 1, + % copy_non_students => 1, + % copy_sets => 1, + % copy_achievements => 1, + % copy_title => 1, + % copy_institution => 1, % add_dbLayout => 'sql_single', - % add_templates_course => $ce->{siteDefaults}{default_templates_course} || '' % } % ], % [ 'rename_course', maketext('Rename Courses') ], diff --git a/templates/ContentGenerator/CourseAdmin/add_course_form.html.ep b/templates/ContentGenerator/CourseAdmin/add_course_form.html.ep index 50d91c9523..7bc5ce0c6b 100644 --- a/templates/ContentGenerator/CourseAdmin/add_course_form.html.ep +++ b/templates/ContentGenerator/CourseAdmin/add_course_form.html.ep @@ -103,27 +103,72 @@
- <%= maketext('To copy the templates and html folders from an existing course, select the course below.') =%> + <%= maketext('To copy components from an existing course, select the course and check which components to copy.') =%>
% my @existingCourses = sort { lc($a) cmp lc($b) } listCourses($ce); % unshift(@existingCourses, sort { lc($a) cmp lc($b) } @{ $ce->{modelCoursesForCopy} }); % - <%= label_for add_templates_course => maketext('Copy from:'), + <%= label_for copy_from_course => maketext('Copy Components From:'), class => 'col-auto col-form-label fw-bold' =%>
- <%= select_field add_templates_course => [ + <%= select_field copy_from_course => [ [ maketext('No Course') => '' ], map { [ $_ => $_] } @existingCourses ], - id => 'add_templates_course', + id => 'copy_from_course', class => 'form-select' =%>
+
+ Copy These Components: +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
diff --git a/templates/HelpFiles/AdminAddCourse.html.ep b/templates/HelpFiles/AdminAddCourse.html.ep index 8da9cd4c15..b34d3288a9 100644 --- a/templates/HelpFiles/AdminAddCourse.html.ep +++ b/templates/HelpFiles/AdminAddCourse.html.ep @@ -26,12 +26,14 @@ . 'Unchecking this option will make it so WeBWorK administrators cannot directly login to the course.') =%>

- <%= maketext('The simple configuration file is the name of the configuration file created when using the "Course ' - . 'Configuration" page to configure course options. This option sets if this configuration file is copied ' - . 'from the old course or not and instead use the server defaults.') =%> -

-

<%= maketext('Enter the details of the course instructor to be added when the course is created. This user ' . 'will also be added to the admin course with the username "userID_courseID" so you can manage and ' . 'email the instructors of the courses on the server.') =%>

+

+ <%= maketext('You may choose a course to copy components from. Select the course and which components to copy. ' + . 'If the course is not a true course (like the modelCourse) then only the templates and html folders, ' + . 'and the simple and course config files can be copied. The "simple config" file contains the settings ' + . 'made in the "Course Config" page. The "course config" file should only be copied if you know what you ' + . 'are doing.') =%> +

From 174a0df0afa033999fb95641e67579709219dda0 Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Tue, 19 Dec 2023 09:34:28 -0800 Subject: [PATCH 2/9] add a select-all checkbox to add course page --- lib/WeBWorK/ContentGenerator/CourseAdmin.pm | 21 +++-------------- .../ContentGenerator/Base/admin_links.html.ep | 14 ++++------- .../CourseAdmin/add_course_form.html.ep | 23 ++++++++++++------- 3 files changed, 22 insertions(+), 36 deletions(-) diff --git a/lib/WeBWorK/ContentGenerator/CourseAdmin.pm b/lib/WeBWorK/ContentGenerator/CourseAdmin.pm index e732cfeab7..60d2ae9bf7 100644 --- a/lib/WeBWorK/ContentGenerator/CourseAdmin.pm +++ b/lib/WeBWorK/ContentGenerator/CourseAdmin.pm @@ -309,15 +309,6 @@ sub do_add_course ($c) { my $copy_from_course = trim_spaces($c->param('copy_from_course')) // ''; - my $copy_templates_html = $c->param('copy_templates_html') || ''; - my $copy_simple_config_file = $c->param('copy_simple_config_file') || ''; - my $copy_config_file = $c->param('copy_config_file') || ''; - my $copy_non_students = $c->param('copy_non_students') || ''; - my $copy_sets = $c->param('copy_sets') || ''; - my $copy_achievements = $c->param('copy_achievements') || ''; - my $copy_title = $c->param('copy_title') || ''; - my $copy_institution = $c->param('copy_institution') || ''; - my $add_dbLayout = trim_spaces($c->param('add_dbLayout')) || ''; my $ce2 = WeBWorK::CourseEnvironment->new({ courseName => $add_courseID }); @@ -374,15 +365,9 @@ sub do_add_course ($c) { # Include any optional arguments, including a template course and the course title and course institution. my %optional_arguments; if ($copy_from_course ne '') { - $optional_arguments{copyFrom} = $copy_from_course; - $optional_arguments{copyTemplatesHtml} = $copy_templates_html; - $optional_arguments{copySimpleConfig} = $copy_simple_config_file; - $optional_arguments{copyConfig} = $copy_config_file; - $optional_arguments{copyNonStudents} = $copy_non_students; - $optional_arguments{copySets} = $copy_sets; - $optional_arguments{copyAchievements} = $copy_achievements; - $optional_arguments{copyTitle} = $copy_title; - $optional_arguments{copyInstitution} = $copy_institution; + %optional_arguments = map { $_ => 1 } ($c->param('copy_component')); + $optional_arguments{copyFrom} = $copy_from_course; + $optional_arguments{copyConfig} = $c->param('copy_config_file') || ''; } if ($add_courseTitle ne '') { $optional_arguments{courseTitle} = $add_courseTitle; diff --git a/templates/ContentGenerator/Base/admin_links.html.ep b/templates/ContentGenerator/Base/admin_links.html.ep index 6199915ef4..3a242b28c3 100644 --- a/templates/ContentGenerator/Base/admin_links.html.ep +++ b/templates/ContentGenerator/Base/admin_links.html.ep @@ -15,16 +15,10 @@ % 'add_course', % maketext('Add Courses'), % { - % add_admin_users => 1, - % copy_from_course => $ce->{siteDefaults}{default_copy_from_course} || '', - % copy_templates_html => 1, - % copy_simple_config_file => 1, - % copy_non_students => 1, - % copy_sets => 1, - % copy_achievements => 1, - % copy_title => 1, - % copy_institution => 1, - % add_dbLayout => 'sql_single', + % add_admin_users => 1, + % copy_from_course => $ce->{siteDefaults}{default_copy_from_course} || '', + % copy_component => 'copyTemplatesHtml', + % add_dbLayout => 'sql_single' % } % ], % [ 'rename_course', maketext('Rename Courses') ], diff --git a/templates/ContentGenerator/CourseAdmin/add_course_form.html.ep b/templates/ContentGenerator/CourseAdmin/add_course_form.html.ep index 7bc5ce0c6b..3ffc2131d5 100644 --- a/templates/ContentGenerator/CourseAdmin/add_course_form.html.ep +++ b/templates/ContentGenerator/CourseAdmin/add_course_form.html.ep @@ -125,49 +125,56 @@
+
+
+
From e4d0e4ea0456e26ac0e16ce2355ec63114466457 Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Wed, 20 Dec 2023 13:26:14 -0800 Subject: [PATCH 3/9] remove non_native table check from new course add features --- lib/WeBWorK/Utils/CourseManagement.pm | 61 +++++++++++---------------- 1 file changed, 24 insertions(+), 37 deletions(-) diff --git a/lib/WeBWorK/Utils/CourseManagement.pm b/lib/WeBWorK/Utils/CourseManagement.pm index da56103408..bb3c7fcb19 100644 --- a/lib/WeBWorK/Utils/CourseManagement.pm +++ b/lib/WeBWorK/Utils/CourseManagement.pm @@ -163,7 +163,6 @@ sub listArchivedCourses { copyTitle => boolean copyInstitution => boolean - Create a new course with ID $courseID. $ce is a WeBWorK::CourseEnvironment object that describes the new course's @@ -349,10 +348,10 @@ sub addCourse { if ($db0 && $options{copyNonStudents}) { my @non_student_ids = map {@$_} ($db0->listPermissionLevelsWhere({ permission => { not_like => '0' } }, 'user_id')); - my %original_users = map { $_->[0]{user_id} => 1 } (@users); + my %user_args = map { $_->[0]{user_id} => 1 } (@users); for my $user_id (@non_student_ids) { - next if $original_users{$user_id}; + next if $user_args{$user_id}; my @User = $db0->getUsersWhere({ user_id => $user_id }); my @Password = $db0->getPasswordsWhere({ user_id => $user_id }); my @PermissionLevel = $db0->getPermissionLevelsWhere({ user_id => $user_id }); @@ -373,54 +372,42 @@ sub addCourse { # add sets if ($db0 && $options{copySets}) { - if ($ce->{dbLayouts}{$dbLayoutName}{set}{params}{non_native}) { - debug("not adding sets to the course database: 'set' table is non-native.\n"); - } else { - my @set_ids = $db0->listGlobalSets; - for my $set_id (@set_ids) { - eval { $db->addGlobalSet($db0->getGlobalSet($set_id)) }; - warn $@ if $@; + my @set_ids = $db0->listGlobalSets; + for my $set_id (@set_ids) { + eval { $db->addGlobalSet($db0->getGlobalSet($set_id)) }; + warn $@ if $@; - my @Problem = $db0->getGlobalProblemsWhere({ set_id => $set_id }); - for my $problem (@Problem) { - eval { $db->addGlobalProblem($problem) }; - warn $@ if $@; - } + my @Problem = $db0->getGlobalProblemsWhere({ set_id => $set_id }); + for my $problem (@Problem) { + eval { $db->addGlobalProblem($problem) }; + warn $@ if $@; + } - my @Location = $db0->getGlobalSetLocationsWhere({ set_id => $set_id }); - for my $location (@Location) { - eval { $db->addGlobalSetLocation($location) }; - warn $@ if $@; - } + my @Location = $db0->getGlobalSetLocationsWhere({ set_id => $set_id }); + for my $location (@Location) { + eval { $db->addGlobalSetLocation($location) }; + warn $@ if $@; } } } # add achievements if ($db0 && $options{copyAchievements}) { - if ($ce->{dbLayouts}{$dbLayoutName}{achievement}{params}{non_native}) { - debug("not adding achievements to the course database: 'achievement' table is non-native.\n"); - } else { - my @achievement_ids = $db0->listAchievements; - for my $achievement_id (@achievement_ids) { - eval { $db->addAchievement($db0->getAchievement($achievement_id)) }; - warn $@ if $@; - } + my @achievement_ids = $db0->listAchievements; + for my $achievement_id (@achievement_ids) { + eval { $db->addAchievement($db0->getAchievement($achievement_id)) }; + warn $@ if $@; } } # copy title and/or institution if requested - if ($db0 && ($options{copyTitle} || $options{copyInstitution})) { - if ($ce->{dbLayouts}{$dbLayoutName}{setting}{params}{non_native}) { - debug("not copying settings to the course database: 'setting' table is non-native.\n"); + for my $setting ('Title', 'Institution') { + if ($db0 && $options{"copy$setting"}) { + $db->setSettingValue("course$setting", $db0->getSettingValue("course$setting")) + if ($options{"copy$setting"}); } else { - $db->setSettingValue('courseTitle', $db0->getSettingValue('courseTitle')) if ($options{copyTitle}); - $db->setSettingValue('courseInstitution', $db0->getSettingValue('{courseInstitution')) - if ($options{copyInstitution}); + $db->setSettingValue("course$setting", $options{"course$setting"}) if (exists $options{"course$setting"}); } - } else { - $db->setSettingValue('courseTitle', $options{courseTitle}) if (exists $options{courseTitle}); - $db->setSettingValue('courseInstitution', $options{courseInstitution}) if (exists $options{courseInstitution}); } ##### step 4: write course.conf file ##### From f9e99e6affceb7075d8c15d8922e5992764382c4 Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Sun, 31 Dec 2023 10:46:20 -0800 Subject: [PATCH 4/9] also assign sets and achievements to users copied from old course --- lib/WeBWorK/Utils/CourseManagement.pm | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/lib/WeBWorK/Utils/CourseManagement.pm b/lib/WeBWorK/Utils/CourseManagement.pm index bb3c7fcb19..498a019058 100644 --- a/lib/WeBWorK/Utils/CourseManagement.pm +++ b/lib/WeBWorK/Utils/CourseManagement.pm @@ -39,6 +39,7 @@ use WeBWorK::CourseEnvironment; use WeBWorK::DB; use WeBWorK::Debug; use WeBWorK::Utils qw(runtime_use readDirectory surePathToFile); +use WeBWorK::Utils::Instructor qw(assignSetsToUsers); our @EXPORT_OK = qw( listCourses @@ -347,7 +348,9 @@ sub addCourse { } else { if ($db0 && $options{copyNonStudents}) { my @non_student_ids = - map {@$_} ($db0->listPermissionLevelsWhere({ permission => { not_like => '0' } }, 'user_id')); + map {@$_} ($db0->listPermissionLevelsWhere( + { permission => { not_like => '0' }, user_id => { not_like => 'set_id:%' } }, 'user_id' + )); my %user_args = map { $_->[0]{user_id} => 1 } (@users); for my $user_id (@non_student_ids) { @@ -389,6 +392,13 @@ sub addCourse { warn $@ if $@; } } + if ($options{copyNonStudents}) { + foreach my $userTriple (@users) { + my $user_id = $userTriple->[0]{user_id}; + my @user_sets = $db0->listUserSets($user_id); + assignSetsToUsers($db, $ce, \@user_sets, [$user_id]); + } + } } # add achievements @@ -398,6 +408,18 @@ sub addCourse { eval { $db->addAchievement($db0->getAchievement($achievement_id)) }; warn $@ if $@; } + if ($options{copyNonStudents}) { + foreach my $userTriple (@users) { + my $user_id = $userTriple->[0]{user_id}; + my @user_achievements = $db0->listUserAchievements($user_id); + for my $achievement_id (@user_achievements) { + my $userAchievement = $db->newUserAchievement(); + $userAchievement->user_id($user_id); + $userAchievement->achievement_id($achievement_id); + $db->addUserAchievement($userAchievement); + } + } + } } # copy title and/or institution if requested @@ -470,7 +492,6 @@ sub addCourse { } } - } ################################################################################ From 2873722228f4702066c29072af4685e10e2a8cb1 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Tue, 9 Jan 2024 23:02:00 -0500 Subject: [PATCH 5/9] Suggestions from my review of #2290. --- lib/WeBWorK/ContentGenerator/CourseAdmin.pm | 9 +- lib/WeBWorK/Utils/CourseManagement.pm | 48 +++--- .../CourseAdmin/add_course_form.html.ep | 137 +++++++++--------- 3 files changed, 100 insertions(+), 94 deletions(-) diff --git a/lib/WeBWorK/ContentGenerator/CourseAdmin.pm b/lib/WeBWorK/ContentGenerator/CourseAdmin.pm index 60d2ae9bf7..eaf966ac16 100644 --- a/lib/WeBWorK/ContentGenerator/CourseAdmin.pm +++ b/lib/WeBWorK/ContentGenerator/CourseAdmin.pm @@ -298,8 +298,6 @@ sub do_add_course ($c) { my $add_courseTitle = trim_spaces($c->param('add_courseTitle')) // ''; my $add_courseInstitution = trim_spaces($c->param('add_courseInstitution')) // ''; - my $add_admin_users = $c->param('add_admin_users') || ''; - my $add_initial_userID = trim_spaces($c->param('add_initial_userID')) // ''; my $add_initial_password = trim_spaces($c->param('add_initial_password')) // ''; my $add_initial_confirmPassword = trim_spaces($c->param('add_initial_confirmPassword')) // ''; @@ -318,7 +316,7 @@ sub do_add_course ($c) { my @users; # copy users from current (admin) course if desired - if ($add_admin_users ne '') { + if ($c->param('add_admin_users')) { for my $userID ($db->listUsers) { if ($userID eq $add_initial_userID) { $c->addbadmessage($c->maketext( @@ -365,9 +363,8 @@ sub do_add_course ($c) { # Include any optional arguments, including a template course and the course title and course institution. my %optional_arguments; if ($copy_from_course ne '') { - %optional_arguments = map { $_ => 1 } ($c->param('copy_component')); - $optional_arguments{copyFrom} = $copy_from_course; - $optional_arguments{copyConfig} = $c->param('copy_config_file') || ''; + %optional_arguments = map { $_ => 1 } $c->param('copy_component'); + $optional_arguments{copyFrom} = $copy_from_course; } if ($add_courseTitle ne '') { $optional_arguments{courseTitle} = $add_courseTitle; diff --git a/lib/WeBWorK/Utils/CourseManagement.pm b/lib/WeBWorK/Utils/CourseManagement.pm index 498a019058..df8fd12450 100644 --- a/lib/WeBWorK/Utils/CourseManagement.pm +++ b/lib/WeBWorK/Utils/CourseManagement.pm @@ -326,8 +326,8 @@ sub addCourse { ##### step 3: populate course database ##### - # db object for course to copy things from - my $db0; + # database and course environment objects for the course to copy things from. + my ($db0, $ce0); if ( $sourceCourse ne '' && !(grep { $sourceCourse eq $_ } @{ $ce->{modelCoursesForCopy} }) @@ -338,7 +338,7 @@ sub addCourse { || $options{copyInstitution}) ) { - my $ce0 = WeBWorK::CourseEnvironment->new({ courseName => $sourceCourse }); + $ce0 = WeBWorK::CourseEnvironment->new({ courseName => $sourceCourse }); $db0 = WeBWorK::DB->new($ce0->{dbLayouts}{$dbLayoutName}); } @@ -347,11 +347,15 @@ sub addCourse { debug("not adding users to the course database: 'user' table is non-native.\n"); } else { if ($db0 && $options{copyNonStudents}) { + # If the course.conf file is being copied, then the student role from the source course needs to be used, + # as the role might be customized in that file. my @non_student_ids = - map {@$_} ($db0->listPermissionLevelsWhere( - { permission => { not_like => '0' }, user_id => { not_like => 'set_id:%' } }, 'user_id' - )); - my %user_args = map { $_->[0]{user_id} => 1 } (@users); + map {@$_} ($db0->listPermissionLevelsWhere({ + permission => + { '!=' => $options{copyConfig} ? $ce0->{userRoles}{student} : $ce->{userRoles}{student} }, + user_id => { not_like => 'set_id:%' } + })); + my %user_args = map { $_->[0]{user_id} => 1 } @users; for my $user_id (@non_student_ids) { next if $user_args{$user_id}; @@ -432,20 +436,24 @@ sub addCourse { } } - ##### step 4: write course.conf file ##### + ##### step 4: write course.conf file (unless that is going to be copied from a source course) ##### - my $courseEnvFile = $ce->{courseFiles}->{environment}; - open my $fh, ">:utf8", $courseEnvFile - or die "failed to open $courseEnvFile for writing.\n"; - writeCourseConf($fh, $ce, %courseOptions); - close $fh; + unless ($sourceCourse ne '' && $options{copyConfig}) { + my $courseEnvFile = $ce->{courseFiles}{environment}; + open my $fh, ">:utf8", $courseEnvFile + or die "failed to open $courseEnvFile for writing.\n"; + writeCourseConf($fh, $ce, %courseOptions); + close $fh; + } ##### step 5: copy templates, html, simple.conf, course.conf if desired ##### if ($sourceCourse ne '') { - my $sourceCE = WeBWorK::CourseEnvironment->new({ get_SeedCE($ce), courseName => $sourceCourse }); - my $sourceDir = $sourceCE->{courseDirs}{templates}; + my $sourceCE = WeBWorK::CourseEnvironment->new({ get_SeedCE($ce), courseName => $sourceCourse }); + if ($options{copyTemplatesHtml}) { + my $sourceDir = $sourceCE->{courseDirs}{templates}; + ## copy templates ## if (-d $sourceDir) { my $destDir = $ce->{courseDirs}{templates}; @@ -454,8 +462,8 @@ sub addCourse { warn "Failed to copy templates from course '$sourceCourse': templates directory '$sourceDir' does not exist.\n"; } + ## copy html ## - ## this copies the html/tmp directory as well which is not optimal $sourceDir = $sourceCE->{courseDirs}{html}; if (-d $sourceDir) { warn "Failed to copy html from course '$sourceCourse': $!" @@ -464,8 +472,10 @@ sub addCourse { warn "Failed to copy html from course '$sourceCourse': html directory '$sourceDir' does not exist.\n"; } } + ## copy config files ## - # this copies the simple.conf file if desired + + # this copies the simple.conf file if desired if ($options{copySimpleConfig}) { my $sourceFile = $sourceCE->{courseFiles}{simpleConfig}; if (-e $sourceFile) { @@ -473,7 +483,8 @@ sub addCourse { warn "Failed to copy simple.conf from course '$sourceCourse': $@" if $@; } } - # this copies the course.conf file if desired + + # this copies the course.conf file if desired if ($options{copyConfig}) { my $sourceFile = $sourceCE->{courseFiles}{environment}; if (-e $sourceFile) { @@ -490,7 +501,6 @@ sub addCourse { } } } - } } diff --git a/templates/ContentGenerator/CourseAdmin/add_course_form.html.ep b/templates/ContentGenerator/CourseAdmin/add_course_form.html.ep index 3ffc2131d5..4bd8af373b 100644 --- a/templates/ContentGenerator/CourseAdmin/add_course_form.html.ep +++ b/templates/ContentGenerator/CourseAdmin/add_course_form.html.ep @@ -6,12 +6,12 @@ <%= $c->hidden_authen_fields =%> <%= $c->hidden_fields('subDisplay') =%> % -

<%= maketext( +

<%= maketext( 'Specify an ID, title, and institution for the new course. The course ID may contain only letters, ' . 'numbers, hyphens, and underscores, and may have at most [_1] characters.', $ce->{maxCourseIdLength}) %> -

-
+
+
<%= text_field new_courseID => '', @@ -27,7 +27,7 @@ class => 'form-control' =%> <%= label_for add_courseTitle => maketext('Course Title') =%>
-
+
<%= text_field add_courseInstitution => '', id => 'add_courseInstitution', placeholder => '', @@ -41,7 +41,7 @@ <%= maketext( 'To add the WeBWorK administrators to the new course (as administrators) check the box below.') =%>
-
+
-
+
<%= text_field add_initial_userID => '', @@ -103,10 +103,11 @@
- <%= maketext('To copy components from an existing course, select the course and check which components to copy.') =%> + <%= maketext('To copy components from an existing course, ' + . 'select the course and check which components to copy.') =%>
-
- % my @existingCourses = sort { lc($a) cmp lc($b) } listCourses($ce); +
+ % my @existingCourses = sort { lc($a) cmp lc($b) } grep { $_ ne stash('courseID') } listCourses($ce); % unshift(@existingCourses, sort { lc($a) cmp lc($b) } @{ $ce->{modelCoursesForCopy} }); % <%= label_for copy_from_course => maketext('Copy Components From:'), @@ -119,67 +120,65 @@ id => 'copy_from_course', class => 'form-select' =%>
-
-
- Copy These Components: -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
- -
-
+
+ Copy These Components: +
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
<%= hidden_field add_dbLayout => 'sql_single' =%> <%= submit_button maketext('Add Course'), name => 'add_course', class => 'btn btn-primary' =%> <% end =%> From b49b807fcc4a942014cfeaecff654758c927aaa4 Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Wed, 10 Jan 2024 22:05:20 -0800 Subject: [PATCH 6/9] separate course config file copy option --- lib/WeBWorK/ContentGenerator/CourseAdmin.pm | 5 +++-- .../CourseAdmin/add_course_form.html.ep | 22 ++++++++++++++----- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/lib/WeBWorK/ContentGenerator/CourseAdmin.pm b/lib/WeBWorK/ContentGenerator/CourseAdmin.pm index eaf966ac16..f30be1fca9 100644 --- a/lib/WeBWorK/ContentGenerator/CourseAdmin.pm +++ b/lib/WeBWorK/ContentGenerator/CourseAdmin.pm @@ -363,8 +363,9 @@ sub do_add_course ($c) { # Include any optional arguments, including a template course and the course title and course institution. my %optional_arguments; if ($copy_from_course ne '') { - %optional_arguments = map { $_ => 1 } $c->param('copy_component'); - $optional_arguments{copyFrom} = $copy_from_course; + %optional_arguments = map { $_ => 1 } $c->param('copy_component'); + $optional_arguments{copyFrom} = $copy_from_course; + $optional_arguments{copyConfig} = $c->param('copy_config_file'); } if ($add_courseTitle ne '') { $optional_arguments{courseTitle} = $add_courseTitle; diff --git a/templates/ContentGenerator/CourseAdmin/add_course_form.html.ep b/templates/ContentGenerator/CourseAdmin/add_course_form.html.ep index 4bd8af373b..d7e898bdc4 100644 --- a/templates/ContentGenerator/CourseAdmin/add_course_form.html.ep +++ b/templates/ContentGenerator/CourseAdmin/add_course_form.html.ep @@ -142,12 +142,6 @@ <%= maketext('simple configuration file') =%>
-
- -
+
+ +
<%= hidden_field add_dbLayout => 'sql_single' =%> <%= submit_button maketext('Add Course'), name => 'add_course', class => 'btn btn-primary' =%> From f728cb8e6257e58cce17d568c7348fce5340cb84 Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Wed, 7 Feb 2024 13:45:48 -0800 Subject: [PATCH 7/9] remove last externalPrograms cp call --- lib/WeBWorK/Utils/CourseManagement.pm | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/lib/WeBWorK/Utils/CourseManagement.pm b/lib/WeBWorK/Utils/CourseManagement.pm index df8fd12450..4e49ab4890 100644 --- a/lib/WeBWorK/Utils/CourseManagement.pm +++ b/lib/WeBWorK/Utils/CourseManagement.pm @@ -488,17 +488,8 @@ sub addCourse { if ($options{copyConfig}) { my $sourceFile = $sourceCE->{courseFiles}{environment}; if (-e $sourceFile) { - my $destFile = $ce->{courseFiles}{environment}; - my $cp_cmd = - join(" ", ("2>&1", $ce->{externalPrograms}{cp}, shell_quote($sourceFile), shell_quote($destFile))); - my $cp_out = readpipe $cp_cmd; - if ($?) { - my $exit = $? >> 8; - my $signal = $? & 127; - my $core = $? & 128; - warn "Failed to copy course.conf from course '$sourceCourse' " - . "with command '$cp_cmd' (exit=$exit signal=$signal core=$core): $cp_out\n"; - } + eval { Mojo::File->new($sourceFile)->copy_to($ce->{courseFiles}) }; + warn "Failed to copy course.conf from course '$sourceCourse': $@" if $@; } } } From e67a32e5c5bc6cce942380f459b47309ce8b7341 Mon Sep 17 00:00:00 2001 From: Alex Jordan Date: Wed, 7 Feb 2024 15:38:41 -0800 Subject: [PATCH 8/9] fix some errors from a recent merge --- lib/WeBWorK/Utils/CourseManagement.pm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/WeBWorK/Utils/CourseManagement.pm b/lib/WeBWorK/Utils/CourseManagement.pm index 4e49ab4890..8777fdda7e 100644 --- a/lib/WeBWorK/Utils/CourseManagement.pm +++ b/lib/WeBWorK/Utils/CourseManagement.pm @@ -301,7 +301,7 @@ sub addCourse { } # try to create it - eval { Mojo::File($courseDir)->make_path }; + eval { Mojo::File->new($courseDir)->make_path }; warn "Failed to create $courseDirName directory '$courseDir': $@. " . "You will have to create this directory manually." if $@; @@ -479,7 +479,7 @@ sub addCourse { if ($options{copySimpleConfig}) { my $sourceFile = $sourceCE->{courseFiles}{simpleConfig}; if (-e $sourceFile) { - eval { Mojo::File->new($sourceFile)->copy_to($ce->{courseFiles}) }; + eval { Mojo::File->new($sourceFile)->copy_to($ce->{courseDirs}{root}) }; warn "Failed to copy simple.conf from course '$sourceCourse': $@" if $@; } } @@ -488,7 +488,7 @@ sub addCourse { if ($options{copyConfig}) { my $sourceFile = $sourceCE->{courseFiles}{environment}; if (-e $sourceFile) { - eval { Mojo::File->new($sourceFile)->copy_to($ce->{courseFiles}) }; + eval { Mojo::File->new($sourceFile)->copy_to($ce->{courseDirs}{root}) }; warn "Failed to copy course.conf from course '$sourceCourse': $@" if $@; } } From 93c46234818792c12e1f5a933f7608821706ede9 Mon Sep 17 00:00:00 2001 From: Glenn Rice Date: Mon, 12 Feb 2024 05:52:38 -0600 Subject: [PATCH 9/9] Update the `bin/addcourse` script for the new `addCourse` options. This just uses the simplest possible approach and switches the script from using the now removed `templatesFrom` option to using the new `copyFrom` option. If that option is set, then the new `copyTemplatesHtml` option is also set to be true. Thus the existing behavior of the script is preserved. The script could be updated to actually take advantage of the new options instead if one were ambitious, but this is good enough for now. This also fixes all of the long lines in `lib/WeBWorK/Utils/CourseManagement.pm`, and an issue with comments in the `initNonNativeTables` method. --- bin/addcourse | 5 +-- lib/WeBWorK/Utils/CourseManagement.pm | 45 ++++++++++++++------------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/bin/addcourse b/bin/addcourse index 3a545090ed..0eeb844de3 100755 --- a/bin/addcourse +++ b/bin/addcourse @@ -181,8 +181,9 @@ if ($users) { } my %optional_arguments; -if ($templates_from ne "") { - $optional_arguments{templatesFrom} = $templates_from; +if ($templates_from) { + $optional_arguments{copyFrom} = $templates_from; + $optional_arguments{copyTemplatesHtml} = 1; } eval { diff --git a/lib/WeBWorK/Utils/CourseManagement.pm b/lib/WeBWorK/Utils/CourseManagement.pm index 8777fdda7e..3816a58d09 100644 --- a/lib/WeBWorK/Utils/CourseManagement.pm +++ b/lib/WeBWorK/Utils/CourseManagement.pm @@ -285,8 +285,8 @@ sub addCourse { # does the directory already exist? if (-e $courseDir) { - warn - "Can't create $courseDirName directory '$courseDir', since it already exists. Using existing directory.\n"; + warn "Can't create $courseDirName directory '$courseDir', " + . "since it already exists. Using existing directory.\n"; next; } @@ -459,8 +459,8 @@ sub addCourse { my $destDir = $ce->{courseDirs}{templates}; warn "Failed to copy templates from course '$sourceCourse': $! " unless dircopy("$sourceDir", $destDir); } else { - warn - "Failed to copy templates from course '$sourceCourse': templates directory '$sourceDir' does not exist.\n"; + warn "Failed to copy templates from course '$sourceCourse': " + . "templates directory '$sourceDir' does not exist.\n"; } ## copy html ## @@ -749,8 +749,8 @@ sub deleteCourse { or croak "Can't delete the course '$courseID' because the courses directory '$rootParent' is not writeable."; } else { - warn - "Warning: the course root directory '$root' does not exist. Attempting to delete the course database and other course directories...\n"; + warn "Warning: the course root directory '$root' does not exist. " + . "Attempting to delete the course database and other course directories...\n"; } ##### step 1: delete course database (if necessary) ##### @@ -772,8 +772,8 @@ sub deleteCourse { # is it really a directory unless (-d $courseDir) { - warn - "Can't delete $courseDirName directory '$courseDir', since is not a directory. If it is not wanted, you will have to delete it manually.\n"; + warn "Can't delete $courseDirName directory '$courseDir', since is not a directory. " + . "If it is not wanted, you will have to delete it manually.\n"; next; } @@ -782,8 +782,8 @@ sub deleteCourse { pop @courseDirElements; my $courseDirParent = File::Spec->catdir(@courseDirElements); unless (-w $courseDirParent) { - warn - "Can't delete $courseDirName directory '$courseDir', since its parent directory is not writeable. If it is not wanted, you will have to delete it manually.\n"; + warn "Can't delete $courseDirName directory '$courseDir', since its parent directory is not " + . "writeable. If it is not wanted, you will have to delete it manually.\n"; next; } @@ -1024,13 +1024,13 @@ sub unarchiveCourse { warn "failed to unarchive course database from dump file '$old_dump_file: $@\n"; } } else { - warn - "course '$currCourseID' uses dbLayout '$dbLayoutName', which doesn't support restoring database tables. database tables will not be restored.\n"; + warn "course '$currCourseID' uses dbLayout '$dbLayoutName', which doesn't support " + . "restoring database tables. database tables will not be restored.\n"; $no_database = 1; } } else { - warn - "course '$currCourseID' has no database dump in its data directory (checked for $dump_dir and $old_dump_file). database tables will not be restored.\n"; + warn "course '$currCourseID' has no database dump in its data directory " + . "(checked for $dump_dir and $old_dump_file). database tables will not be restored.\n"; $no_database = 1; } @@ -1259,12 +1259,13 @@ sub initNonNativeTables { # Find the names of the non-native database tables foreach my $table (sort keys %$db) { - next unless $db->{$table}{params}{non_native}; # only look at non-native tables - # hack: these two tables are virtual and don't need to be created - # for the admin course or in the database in general - # if they were created in earlier versions for the admin course - # you can use mysql to drop the field version_id manually - # this will get rid of a spurious error + next unless $db->{$table}{params}{non_native}; # only look at non-native tables + + # hack: these two tables are virtual and don't need to be created + # for the admin course or in the database in general + # if they were created in earlier versions for the admin course + # you can use mysql to drop the field version_id manually + # this will get rid of a spurious error next if $table eq 'problem_version' or $table eq 'set_version'; my $database_table_name = @@ -1296,8 +1297,8 @@ sub initNonNativeTables { if (!$database_field_exists) { $fields_ok = 0; $fieldStatus{$field} = [ONLY_IN_A]; - warn - "$field from $database_table_name (aka |$table|) is only in schema, not in database, so adding it ... "; + warn "$field from $database_table_name (aka |$table|) is only in schema, " + . "not in database, so adding it ... "; if ($db->{$table}->can("add_column_field")) { if ($db->{$table}->add_column_field($field_name)) { warn "added column $field_name to table $database_table_name";