| … | |
… | |
| 274 | # $inputType contains either an input box or a popup_menu for changing a given db field |
274 | # $inputType contains either an input box or a popup_menu for changing a given db field |
| 275 | my $inputType = ""; |
275 | my $inputType = ""; |
| 276 | if ($edit) { |
276 | if ($edit) { |
| 277 | $inputType = CGI::input({ |
277 | $inputType = CGI::input({ |
| 278 | name => "$recordType.$recordID.$field", |
278 | name => "$recordType.$recordID.$field", |
| 279 | value => $forUsers ? $userValue : $globalValue, |
279 | value => $r->param("$recordType.$recordID.$field") || ($forUsers ? $userValue : $globalValue), |
| 280 | size => $properties{size} || 5, |
280 | size => $properties{size} || 5, |
| 281 | }); |
281 | }); |
| 282 | } elsif ($choose) { |
282 | } elsif ($choose) { |
| 283 | # Note that in popup menus, you're almost guaranteed to have the choices hashed to labels in %properties |
283 | # Note that in popup menus, you're almost guaranteed to have the choices hashed to labels in %properties |
| 284 | # but $userValue and and $globalValue are the values in the hash not the keys |
284 | # but $userValue and and $globalValue are the values in the hash not the keys |
| 285 | # so we have to use the actual db record field values to select our default here. |
285 | # so we have to use the actual db record field values to select our default here. |
| 286 | $inputType = CGI::popup_menu({ |
286 | $inputType = CGI::popup_menu({ |
| 287 | name => "$recordType.$recordID.$field", |
287 | name => "$recordType.$recordID.$field", |
| 288 | values => $properties{choices}, |
288 | values => $properties{choices}, |
| 289 | labels => \%labels, |
289 | labels => \%labels, |
| 290 | default => $forUsers ? $userRecord->$field : $globalRecord->$field, |
290 | default => $r->param("$recordType.$recordID.$field") || ($forUsers ? $userRecord->$field : $globalRecord->$field), |
| 291 | }); |
291 | }); |
| 292 | } |
292 | } |
| 293 | |
293 | |
| 294 | return (($forUsers && $edit && $check) ? CGI::checkbox({ |
294 | return (($forUsers && $edit && $check) ? CGI::checkbox({ |
| 295 | type => "checkbox", |
295 | type => "checkbox", |
| 296 | name => "$recordType.$recordID.$field.override", |
296 | name => "$recordType.$recordID.$field.override", |
| 297 | label => "", |
297 | label => "", |
| 298 | value => $field, |
298 | value => $field, |
| 299 | checked => ($userValue ne "" ? 1 : 0), |
299 | checked => $r->param("$recordType.$recordID.$field.override") || ($userValue ne "" ? 1 : 0), |
| 300 | }) : "", |
300 | }) : "", |
| 301 | $properties{name}, |
301 | $properties{name}, |
| 302 | $inputType, |
302 | $inputType, |
| 303 | $forUsers ? " $globalValue" : "", |
303 | $forUsers ? " $globalValue" : "", |
| 304 | ); |
304 | ); |
| … | |
… | |
| 640 | $db->putUserProblem($record) if $changed; |
640 | $db->putUserProblem($record) if $changed; |
| 641 | } |
641 | } |
| 642 | } |
642 | } |
| 643 | } |
643 | } |
| 644 | } |
644 | } |
| 645 | |
645 | |
| 646 | # Delete all problems marked for deletion |
646 | # Delete all problems marked for deletion |
| 647 | foreach my $problemID ($r->param('deleteProblem')) { |
647 | foreach my $problemID ($r->param('deleteProblem')) { |
| 648 | $db->deleteGlobalProblem($setID, $problemID); |
648 | $db->deleteGlobalProblem($setID, $problemID); |
| 649 | } |
649 | } |
| 650 | |
650 | |
| 651 | # "Deleting" a header means setting it to "" so that the default header is used instead. |
651 | # Sets the specified header to "" so that the default file will get used. |
| 652 | foreach my $header ($r->param('deleteHeader')) { |
652 | foreach my $header ($r->param('defaultHeader')) { |
| 653 | $setRecord->$header(""); |
653 | $setRecord->$header(""); |
| 654 | } |
654 | } |
| 655 | |
655 | } elsif (defined $r->param('undo_changes')) { |
|
|
656 | |
|
|
657 | # reset all the parameters dealing with set/problem/header information |
|
|
658 | # if the current naming scheme is changed/broken, this could reek havoc |
|
|
659 | # on all kinds of things |
|
|
660 | foreach my $param ($r->param) { |
|
|
661 | $r->param($param, "") if $param =~ /^(set|problem|header)\./; |
|
|
662 | } |
|
|
663 | } |
|
|
664 | |
| 656 | # Leftover code from when there were up/down buttons |
665 | # Leftover code from when there were up/down buttons |
| 657 | |
666 | |
| 658 | # } else { |
667 | # } else { |
| 659 | # # Look for up and down buttons |
668 | # # Look for up and down buttons |
| 660 | # my $index = 2; |
669 | # my $index = 2; |
| … | |
… | |
| 670 | # if (defined $r->param("move.down.$index.x")) { |
679 | # if (defined $r->param("move.down.$index.x")) { |
| 671 | # moveme($index, $db, $setID, @problemList); |
680 | # moveme($index, $db, $setID, @problemList); |
| 672 | # } |
681 | # } |
| 673 | # $index++; |
682 | # $index++; |
| 674 | # } |
683 | # } |
| 675 | } |
684 | # } |
|
|
685 | |
| 676 | |
686 | |
| 677 | |
687 | |
| 678 | # handle renumbering of problems if necessary |
688 | # handle renumbering of problems if necessary |
| 679 | print CGI::a({name=>"problems"}); |
689 | print CGI::a({name=>"problems"}); |
| 680 | |
690 | |
| … | |
… | |
| 704 | sub changed ($$) { |
714 | sub changed ($$) { |
| 705 | my ($first, $second) = @_; |
715 | my ($first, $second) = @_; |
| 706 | |
716 | |
| 707 | return "def/undef" if defined $first and not defined $second; |
717 | return "def/undef" if defined $first and not defined $second; |
| 708 | return "undef/def" if not defined $first and defined $second; |
718 | return "undef/def" if not defined $first and defined $second; |
| 709 | return 0 if not defined $first and not defined $second; |
719 | return "" if not defined $first and not defined $second; |
| 710 | return "ne" if $first ne $second; |
720 | return "ne" if $first ne $second; |
| 711 | return 0; # if they're equal, there's no change |
721 | return ""; # if they're equal, there's no change |
| 712 | } |
722 | } |
| 713 | |
723 | |
| 714 | # helper method that determines if a given |
724 | # helper method that determines for how many users at a time a field can be changed |
| 715 | # none means it can't be changed for anyone |
725 | # none means it can't be changed for anyone |
| 716 | # any means it can be changed for anyone |
726 | # any means it can be changed for anyone |
| 717 | # one means it can ONLY be changed for one at a time. (eg problem_seed) |
727 | # one means it can ONLY be changed for one at a time. (eg problem_seed) |
| 718 | # all means it can ONLY be changed for all at a time. (eg set_header) |
728 | # all means it can ONLY be changed for all at a time. (eg set_header) |
| 719 | sub canChange ($$) { |
729 | sub canChange ($$) { |
| … | |
… | |
| 727 | return 0 if $howManyCan eq "none"; |
737 | return 0 if $howManyCan eq "none"; |
| 728 | return 1 if $howManyCan eq "any"; |
738 | return 1 if $howManyCan eq "any"; |
| 729 | return 1 if $howManyCan eq "one" && $forOneUser; |
739 | return 1 if $howManyCan eq "one" && $forOneUser; |
| 730 | return 1 if $howManyCan eq "all" && !$forUsers; |
740 | return 1 if $howManyCan eq "all" && !$forUsers; |
| 731 | return 0; # FIXME: maybe it should default to 1? |
741 | return 0; # FIXME: maybe it should default to 1? |
|
|
742 | } |
|
|
743 | |
|
|
744 | # helper method that determines if a file is valid and returns a pretty error message |
|
|
745 | sub checkFile ($) { |
|
|
746 | my ($self, $file) = @_; |
|
|
747 | |
|
|
748 | my $r = $self->r; |
|
|
749 | my $ce = $r->ce; |
|
|
750 | |
|
|
751 | return "No source file specified" unless $file; |
|
|
752 | $file = $ce->{courseDirs}->{templates} . '/' . $file unless $file =~ m|^/|; |
|
|
753 | |
|
|
754 | my $text = "This source file "; |
|
|
755 | my $fileError; |
|
|
756 | return "" if -e $file && -f $file && -r $file; |
|
|
757 | return $text . "is not readable!" if -e $file && -f $file; |
|
|
758 | return $text . "is a directory!" if -d $file; |
|
|
759 | return $text . "does not exist!" unless -e $file; |
|
|
760 | return $text . "is not a plain file!"; |
| 732 | } |
761 | } |
| 733 | |
762 | |
| 734 | # Creates two separate tables, first of the headers, and the of the problems in a given set |
763 | # Creates two separate tables, first of the headers, and the of the problems in a given set |
| 735 | # If one or more users are specified in the "editForUser" param, only the data for those users |
764 | # If one or more users are specified in the "editForUser" param, only the data for those users |
| 736 | # becomes editable, not all the data |
765 | # becomes editable, not all the data |
| … | |
… | |
| 843 | } else { |
872 | } else { |
| 844 | print CGI::p(CGI::b("Any changes made below will be reflected in the set for ALL students.")); |
873 | print CGI::p(CGI::b("Any changes made below will be reflected in the set for ALL students.")); |
| 845 | } |
874 | } |
| 846 | |
875 | |
| 847 | print CGI::start_form({method=>"POST", action=>$setDetailURL}); |
876 | print CGI::start_form({method=>"POST", action=>$setDetailURL}); |
|
|
877 | print $self->hiddenEditForUserFields(@editForUser); |
|
|
878 | print $self->hidden_authen_fields; |
| 848 | print CGI::input({type=>"submit", name=>"submit_changes", value=>"Save Changes"}); |
879 | print CGI::input({type=>"submit", name=>"submit_changes", value=>"Save Changes"}); |
| 849 | |
880 | print CGI::input({type=>"submit", name=>"undo_changes", value => "Reset Form"}); |
|
|
881 | |
| 850 | # spacing |
882 | # spacing |
| 851 | print CGI::p(); |
883 | print CGI::p(); |
| 852 | |
884 | |
| 853 | ##################################################################### |
885 | ##################################################################### |
| 854 | # Display general set information |
886 | # Display general set information |
| … | |
… | |
| 870 | |
902 | |
| 871 | ##################################################################### |
903 | ##################################################################### |
| 872 | # Display header information |
904 | # Display header information |
| 873 | ##################################################################### |
905 | ##################################################################### |
| 874 | my @headers = @{ HEADER_ORDER() }; |
906 | my @headers = @{ HEADER_ORDER() }; |
| 875 | my %headerModules = (set_header => 'problem_list', 'hardcopy_header' => 'hardcopy_preselect_set'); |
907 | my %headerModules = (set_header => 'problem_list', hardcopy_header => 'hardcopy_preselect_set'); |
|
|
908 | my %headerDefaults = (set_header => $ce->{webworkFiles}->{screenSnippets}->{setHeader}, hardcopy_header => $ce->{webworkFiles}->{hardcopySnippets}->{setHeader}); |
| 876 | my @headerFiles = map { $setRecord->{$_} } @headers; |
909 | my @headerFiles = map { $setRecord->{$_} } @headers; |
| 877 | if (scalar @headers and not $forUsers) { |
910 | if (scalar @headers and not $forUsers) { |
| 878 | |
911 | |
| 879 | print CGI::start_table({border=>1, cellpadding=>4}); |
912 | print CGI::start_table({border=>1, cellpadding=>4}); |
| 880 | print CGI::Tr({}, CGI::th({}, [ |
913 | print CGI::Tr({}, CGI::th({}, [ |
| … | |
… | |
| 885 | CGI::input({type => "submit", name => "refresh", value => "Refresh"}), |
918 | CGI::input({type => "submit", name => "refresh", value => "Refresh"}), |
| 886 | ])); |
919 | ])); |
| 887 | |
920 | |
| 888 | my %header_html; |
921 | my %header_html; |
| 889 | |
922 | |
|
|
923 | my %error; |
| 890 | foreach my $header (@headers) { |
924 | foreach my $header (@headers) { |
|
|
925 | my $headerFile = $r->param("set.$setID.$header") || $setRecord->{$header} || $headerDefaults{$header}; |
|
|
926 | |
|
|
927 | $error{$header} = $self->checkFile($headerFile); |
|
|
928 | unless ($error{$header}) { |
| 891 | my @temp = renderProblems( r=> $r, |
929 | my @temp = renderProblems( r=> $r, |
| 892 | user => $db->getUser($userToShow), |
930 | user => $db->getUser($userToShow), |
| 893 | displayMode=> $default_header_mode, |
931 | displayMode=> $default_header_mode, |
| 894 | problem_number=> 0, |
932 | problem_number=> 0, |
| 895 | this_set => $db->getMergedSet($userToShow, $setID), |
933 | this_set => $db->getMergedSet($userToShow, $setID), |
| 896 | problem_list => [$setRecord->{$header}], |
934 | problem_list => [$headerFile], |
| 897 | ); |
935 | ); |
| 898 | $header_html{$header} = $temp[0]; |
936 | $header_html{$header} = $temp[0]; |
|
|
937 | } |
| 899 | } |
938 | } |
| 900 | |
939 | |
| 901 | foreach my $header (@headers) { |
940 | foreach my $header (@headers) { |
| 902 | |
941 | |
| 903 | my $editHeaderPage = $urlpath->new(type => 'instructor_problem_editor_withset_withproblem', args => { courseID => $courseID, setID => $setID, problemID => 0 }); |
942 | my $editHeaderPage = $urlpath->new(type => 'instructor_problem_editor_withset_withproblem', args => { courseID => $courseID, setID => $setID, problemID => 0 }); |
| … | |
… | |
| 909 | print CGI::Tr({}, CGI::td({}, [ |
948 | print CGI::Tr({}, CGI::td({}, [ |
| 910 | CGI::start_table({border => 0, cellpadding => 0}) . |
949 | CGI::start_table({border => 0, cellpadding => 0}) . |
| 911 | CGI::Tr({}, CGI::td({}, $properties{$header}->{name})) . |
950 | CGI::Tr({}, CGI::td({}, $properties{$header}->{name})) . |
| 912 | CGI::Tr({}, CGI::td({}, CGI::a({href => $editHeaderLink}, "Edit it"))) . |
951 | CGI::Tr({}, CGI::td({}, CGI::a({href => $editHeaderLink}, "Edit it"))) . |
| 913 | CGI::Tr({}, CGI::td({}, CGI::a({href => $viewHeaderLink}, "View it"))) . |
952 | CGI::Tr({}, CGI::td({}, CGI::a({href => $viewHeaderLink}, "View it"))) . |
| 914 | CGI::Tr({}, CGI::td({}, CGI::checkbox({name => "defaultHeader", value => $header, label => "Use Default"}))) . |
953 | # CGI::Tr({}, CGI::td({}, CGI::checkbox({name => "defaultHeader", value => $header, label => "Use Default"}))) . |
| 915 | CGI::end_table(), |
954 | CGI::end_table(), |
| 916 | # "", |
955 | # "", |
| 917 | # CGI::input({ name => "set.$setID.$header", value => $setRecord->{$header}, size => 50}) . |
956 | # CGI::input({ name => "set.$setID.$header", value => $setRecord->{$header}, size => 50}) . |
| 918 | # join ("\n", $self->FieldHTML($userToShow, $setID, $problemID, "source_file")) . |
957 | # join ("\n", $self->FieldHTML($userToShow, $setID, $problemID, "source_file")) . |
| 919 | # CGI::br() . CGI::div({class=> "RenderSolo"}, $problem_html[0]->{body_text}), |
958 | # CGI::br() . CGI::div({class=> "RenderSolo"}, $problem_html[0]->{body_text}), |
| 920 | |
959 | |
| 921 | comboBox({ |
960 | comboBox({ |
| 922 | name => "set.$setID.$header", |
961 | name => "set.$setID.$header", |
| 923 | request => $r, |
962 | request => $r, |
| 924 | default => $setRecord->{$header}, |
963 | default => $r->param("set.$setID.$header") || $setRecord->{$header}, |
| 925 | multiple => 0, |
964 | multiple => 0, |
| 926 | values => ["", @headerFileList], |
965 | values => ["", @headerFileList], |
| 927 | labels => { "" => "Use Default Header File" }, |
966 | labels => { "" => "Use Default Header File" }, |
| 928 | }) . |
967 | }) . |
|
|
968 | ($error{$header} ? |
|
|
969 | CGI::div({class=>"ResultsWithError", style=>"font-weight: bold"}, $error{$header}) |
| 929 | CGI::div({class=> "RenderSolo"}, $header_html{$header}->{body_text}), |
970 | : CGI::div({class=> "RenderSolo"}, $header_html{$header}->{body_text}) |
|
|
971 | ), |
| 930 | ])); |
972 | ])); |
| 931 | } |
973 | } |
| 932 | |
974 | |
| 933 | print CGI::end_table(); |
975 | print CGI::end_table(); |
| 934 | } else { |
976 | } else { |
| … | |
… | |
| 955 | "Display Mode: " . |
997 | "Display Mode: " . |
| 956 | CGI::popup_menu(-name => "problem.displaymode", -values => \@active_modes, -default => $default_problem_mode) . ' '. |
998 | CGI::popup_menu(-name => "problem.displaymode", -values => \@active_modes, -default => $default_problem_mode) . ' '. |
| 957 | CGI::input({type => "submit", name => "refresh", value => "Refresh"}), |
999 | CGI::input({type => "submit", name => "refresh", value => "Refresh"}), |
| 958 | ])); |
1000 | ])); |
| 959 | |
1001 | |
|
|
1002 | my %shownYet; |
|
|
1003 | my $repeatFile; |
| 960 | foreach my $problemID (@problemIDList) { |
1004 | foreach my $problemID (@problemIDList) { |
| 961 | |
1005 | |
| 962 | my $problemRecord; |
1006 | my $problemRecord; |
| 963 | if ($forOneUser) { |
1007 | if ($forOneUser) { |
| 964 | $problemRecord = $db->getMergedProblem($editForUser[0], $setID, $problemID); |
1008 | $problemRecord = $db->getMergedProblem($editForUser[0], $setID, $problemID); |
| … | |
… | |
| 974 | my $viewProblemLink = $self->systemLink($viewProblemPage, params => { effectiveUser => ($forOneUser ? $editForUser[0] : $userID)}); |
1018 | my $viewProblemLink = $self->systemLink($viewProblemPage, params => { effectiveUser => ($forOneUser ? $editForUser[0] : $userID)}); |
| 975 | |
1019 | |
| 976 | my @fields = @{ PROBLEM_FIELDS() }; |
1020 | my @fields = @{ PROBLEM_FIELDS() }; |
| 977 | push @fields, @{ USER_PROBLEM_FIELDS() } if $forOneUser; |
1021 | push @fields, @{ USER_PROBLEM_FIELDS() } if $forOneUser; |
| 978 | |
1022 | |
|
|
1023 | my $problemFile = $r->param("problem.$problemID.source_file") || $problemRecord->source_file; |
|
|
1024 | |
|
|
1025 | # warn of repeat problems |
|
|
1026 | if (defined $shownYet{$problemFile}) { |
|
|
1027 | $repeatFile = "This problem uses the same source file as number " . $shownYet{$problemFile} . "."; |
|
|
1028 | } else { |
|
|
1029 | $shownYet{$problemFile} = $problemID; |
|
|
1030 | } |
|
|
1031 | |
|
|
1032 | my $error = $self->checkFile($problemFile); |
|
|
1033 | my @problem_html; |
|
|
1034 | unless ($error) { |
| 979 | my @problem_html = renderProblems( r=> $r, |
1035 | @problem_html = renderProblems( r=> $r, |
| 980 | user => $db->getUser($userToShow), |
1036 | user => $db->getUser($userToShow), |
| 981 | displayMode=> $default_problem_mode, |
1037 | displayMode=> $default_problem_mode, |
| 982 | problem_number=> $problemID, |
1038 | problem_number=> $problemID, |
| 983 | this_set => $db->getMergedSet($userToShow, $setID), |
1039 | this_set => $db->getMergedSet($userToShow, $setID), |
| 984 | problem_seed => $forOneUser ? $problemRecord->problem_seed : 0, |
1040 | problem_seed => $forOneUser ? $problemRecord->problem_seed : 0, |
| 985 | problem_list => [$problemRecord->source_file], |
1041 | problem_list => [$problemRecord->source_file], |
| 986 | ); |
1042 | ); |
|
|
1043 | } |
| 987 | |
1044 | |
| 988 | print CGI::Tr({}, CGI::td({}, [ |
1045 | print CGI::Tr({}, CGI::td({}, [ |
| 989 | CGI::start_table({border => 0, cellpadding => 1}) . |
1046 | CGI::start_table({border => 0, cellpadding => 1}) . |
| 990 | CGI::Tr({}, CGI::td({}, problem_number_popup($problemID, $maxProblemNumber))) . |
1047 | CGI::Tr({}, CGI::td({}, problem_number_popup($problemID, $maxProblemNumber))) . |
| 991 | CGI::Tr({}, CGI::td({}, CGI::a({href => $editProblemLink}, "Edit it"))) . |
1048 | CGI::Tr({}, CGI::td({}, CGI::a({href => $editProblemLink}, "Edit it"))) . |
| … | |
… | |
| 1002 | # multiple => 0, |
1059 | # multiple => 0, |
| 1003 | # values => \@problemFileList, |
1060 | # values => \@problemFileList, |
| 1004 | # }) . |
1061 | # }) . |
| 1005 | |
1062 | |
| 1006 | join ("\n", $self->FieldHTML($userToShow, $setID, $problemID, "source_file")) . |
1063 | join ("\n", $self->FieldHTML($userToShow, $setID, $problemID, "source_file")) . |
|
|
1064 | CGI::br() . |
|
|
1065 | ($error ? |
|
|
1066 | CGI::div({class=>"ResultsWithError", style=>"font-weight: bold"}, $error) |
| 1007 | CGI::br() . CGI::div({class=> "RenderSolo"}, $problem_html[0]->{body_text}), |
1067 | : CGI::div({class=> "RenderSolo"}, $problem_html[0]->{body_text}) |
|
|
1068 | ) . |
|
|
1069 | ($repeatFile ? CGI::div({class=>"ResultsWithError", style=>"font-weight: bold"}, $repeatFile) : ''), |
| 1008 | ])); |
1070 | ])); |
| 1009 | } |
1071 | } |
| 1010 | |
1072 | |
| 1011 | print CGI::end_table(); |
1073 | print CGI::end_table(); |
| 1012 | print $self->hiddenEditForUserFields(@editForUser); |
|
|
| 1013 | print $self->hidden_authen_fields; |
|
|
| 1014 | print CGI::checkbox({ |
1074 | print CGI::checkbox({ |
| 1015 | label=> "Force problems to be numbered consecutively from one", |
1075 | label=> "Force problems to be numbered consecutively from one", |
| 1016 | name=>"force_renumber", value=>"1"}), |
1076 | name=>"force_renumber", value=>"1"}), |
| 1017 | |
1077 | |
| 1018 | CGI::br(); |
1078 | CGI::br(); |