diff --git a/conf/defaults.config b/conf/defaults.config index 4c769c02c2..79c2726fc2 100644 --- a/conf/defaults.config +++ b/conf/defaults.config @@ -277,6 +277,9 @@ $webworkDirs{bin} = "$webworkDirs{root}/bin"; # Location of configuration files. $webworkDirs{conf} = "$webworkDirs{root}/conf"; +# Location of add-on configuration files. +$webworkDirs{addOnConf} = "$webworkDirs{conf}/addon"; + # Location of assets (tex, pg, themes) $webworkDirs{assets} = "$webworkDirs{root}/assets"; diff --git a/htdocs/js/CourseAdmin/restrict_select.js b/htdocs/js/CourseAdmin/restrict_select.js new file mode 100644 index 0000000000..8e5550ee34 --- /dev/null +++ b/htdocs/js/CourseAdmin/restrict_select.js @@ -0,0 +1,34 @@ +(() => { + const addOnConfSelect = document.getElementById('add_on_conf'); + const addOnConfOptgroups = [...addOnConfSelect.querySelectorAll('optgroup')]; + + // Track previously selected options to identify the newly clicked option + let previousSelection = []; + + addOnConfSelect.addEventListener('change', (event) => { + const currentSelection = Array.from(addOnConfSelect.selectedOptions); + + // Find the option the user just clicked/selected + const newlySelected = currentSelection.find((option) => !previousSelection.includes(option)); + + if (newlySelected) { + // Find the parent optgroup + const parent = newlySelected.closest('optgroup'); + + // Loop through all options in the other groups and unselect them as appropriate + addOnConfOptgroups.forEach((group) => { + Array.from(group.children).forEach((option) => { + if ( + option !== newlySelected && + (parent.dataset.single || (!parent.dataset.single && group.dataset.single)) + ) { + option.selected = false; + } + }); + }); + } + + // Update tracking variable for the next change event + previousSelection = Array.from(addOnConfSelect.selectedOptions); + }); +})(); diff --git a/lib/WeBWorK/ContentGenerator/CourseAdmin.pm b/lib/WeBWorK/ContentGenerator/CourseAdmin.pm index ff67697c4b..9aaa1dd3a0 100644 --- a/lib/WeBWorK/ContentGenerator/CourseAdmin.pm +++ b/lib/WeBWorK/ContentGenerator/CourseAdmin.pm @@ -377,9 +377,12 @@ 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; + $optional_arguments{copyConfig} = + $c->param('copy_config_file') || ($c->param('add_on_conf') && $c->param('add_on_conf') eq '*'); + $optional_arguments{addOnConf} = + $c->param('add_on_conf') && $c->param('add_on_conf') ne '*' ? [ $c->param('add_on_conf') ] : []; } if ($add_courseTitle ne '') { $optional_arguments{courseTitle} = $add_courseTitle; diff --git a/lib/WeBWorK/Utils/CourseManagement.pm b/lib/WeBWorK/Utils/CourseManagement.pm index 3d002d86d6..ce81e0b4eb 100644 --- a/lib/WeBWorK/Utils/CourseManagement.pm +++ b/lib/WeBWorK/Utils/CourseManagement.pm @@ -424,7 +424,12 @@ sub addCourse { my $courseEnvFile = $ce->{courseFiles}{environment}; open my $fh, ">:utf8", $courseEnvFile or die "failed to open $courseEnvFile for writing.\n"; - writeCourseConf($fh); + my $addOnConf = $options{addOnConf} // []; + my $relConfFolder = File::Spec->abs2rel($ce->{webworkDirs}{addOnConf}, $ce->{webworkDirs}{root}); + for (@$addOnConf) { + $_ = File::Spec->catfile($relConfFolder, $_); + } + writeCourseConf($fh, $addOnConf); close $fh; } @@ -1172,24 +1177,36 @@ sub protectQString { return $string; } -=item writeCourseConf($fh) +=item writeCourseConf($fh, $addOnConf) Writes an essentially empty course.conf file to $fh, a file handle. System administrators can use this file to override global settings for a course. +$addOnConf should be an array reference of config files to tack on at the end. =back =cut sub writeCourseConf { - my ($fh) = @_; + my ($fh, $addOnConf) = @_; - print $fh <<'EOF'; + my $content = <<'EOF'; #!perl # This file is used to override the global WeBWorK course environment for this course. EOF + + if ($addOnConf ne '') { + for my $conf (@$addOnConf) { + $content .= <<"EOF"; + +include('$conf'); +EOF + } + } + + print $fh $content; } sub get_SeedCE diff --git a/templates/ContentGenerator/CourseAdmin/add_course_form.html.ep b/templates/ContentGenerator/CourseAdmin/add_course_form.html.ep index d6e1ee1413..cdadb9175d 100644 --- a/templates/ContentGenerator/CourseAdmin/add_course_form.html.ep +++ b/templates/ContentGenerator/CourseAdmin/add_course_form.html.ep @@ -1,5 +1,9 @@ % use WeBWorK::Utils::CourseManagement qw(listCourses); % +% content_for js => begin + <%= javascript getAssetURL($ce, 'js/CourseAdmin/restrict_select.js'), defer => undef =%> +% end +% % # Create an array of permission values for the permission selects. % my $permissionLevels = []; % for my $role (sort { $ce->{userRoles}{$a} <=> $ce->{userRoles}{$b} } keys %{ $ce->{userRoles} }) { @@ -217,26 +221,64 @@ <%= maketext('course institution (will override "Institution" input above)') =%> -
+
<%= 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.') =%>
++ <%= maketext('If there are .conf files in the [_1] folder, you may select a number of these to include at the ' + . 'end of the course.conf file. This only applies when not copying a course.conf file from another course.', + $ce->{webworkDirs}{addOnConf}) =%> +