Parent Directory
|
Revision Log
Revision 2926 -
(view)
(download)
(as text)
Original Path: trunk/webwork2/lib/WeBWorK/ContentGenerator/Instructor/Index.pm
| 1 : | malsyned | 831 | ################################################################################ |
| 2 : | sh002i | 1663 | # WeBWorK Online Homework Delivery System |
| 3 : | # Copyright © 2000-2003 The WeBWorK Project, http://openwebwork.sf.net/ | ||
| 4 : | sh002i | 2926 | # $CVSHeader: webwork2/lib/WeBWorK/ContentGenerator/Instructor/Index.pm,v 1.45 2004/10/09 03:07:23 gage Exp $ |
| 5 : | sh002i | 1663 | # |
| 6 : | # This program is free software; you can redistribute it and/or modify it under | ||
| 7 : | # the terms of either: (a) the GNU General Public License as published by the | ||
| 8 : | # Free Software Foundation; either version 2, or (at your option) any later | ||
| 9 : | # version, or (b) the "Artistic License" which comes with this package. | ||
| 10 : | # | ||
| 11 : | # This program is distributed in the hope that it will be useful, but WITHOUT | ||
| 12 : | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | ||
| 13 : | sh002i | 2926 | # FOR A PARTICULAR PURPOSE. See either the GNU General Public License or the |
| 14 : | sh002i | 1663 | # Artistic License for more details. |
| 15 : | malsyned | 831 | ################################################################################ |
| 16 : | |||
| 17 : | package WeBWorK::ContentGenerator::Instructor::Index; | ||
| 18 : | use base qw(WeBWorK::ContentGenerator::Instructor); | ||
| 19 : | |||
| 20 : | =head1 NAME | ||
| 21 : | |||
| 22 : | sh002i | 1920 | WeBWorK::ContentGenerator::Instructor::Index - Menu interface to the Instructor |
| 23 : | pages | ||
| 24 : | malsyned | 831 | |
| 25 : | =cut | ||
| 26 : | |||
| 27 : | use strict; | ||
| 28 : | use warnings; | ||
| 29 : | sh002i | 1920 | use Apache::Constants qw(:response); |
| 30 : | malsyned | 831 | use CGI qw(); |
| 31 : | sh002i | 1920 | use WeBWorK::HTML::ScrollingRecordList qw/scrollingRecordList/; |
| 32 : | mschmitt | 2187 | #use WeBWorK::Utils::FilterRecords qw/getFiltersForClass/; |
| 33 : | sh002i | 1920 | |
| 34 : | use constant E_NO_USERS => "Please do not select any users."; | ||
| 35 : | use constant E_NO_SETS => "Please do not select any sets."; | ||
| 36 : | use constant E_MAX_ONE_USER => "Please select at most one user."; | ||
| 37 : | use constant E_MAX_ONE_SET => "Please select at most one set."; | ||
| 38 : | use constant E_ONE_USER => "Please select exactly one user."; | ||
| 39 : | use constant E_ONE_SET => "Please select exactly one set."; | ||
| 40 : | use constant E_MIN_ONE_USER => "Please select at least one user."; | ||
| 41 : | use constant E_MIN_ONE_SET => "Please select at least one set."; | ||
| 42 : | sh002i | 2926 | use constant E_SET_NAME => "Please specify a homework set name."; |
| 43 : | use constant E_BAD_NAME => "Please use only letter, digits, -, _ and . in your set name."; | ||
| 44 : | sh002i | 1920 | |
| 45 : | gage | 1579 | sub pre_header_initialize { |
| 46 : | sh002i | 1841 | my ($self) = @_; |
| 47 : | sh002i | 1920 | my $r = $self->r; |
| 48 : | my $ce = $r->ce; | ||
| 49 : | my $db = $r->db; | ||
| 50 : | my $authz = $r->authz; | ||
| 51 : | my $urlpath = $r->urlpath; | ||
| 52 : | toenail | 2304 | |
| 53 : | sh002i | 1920 | my $courseID = $urlpath->arg("courseID"); |
| 54 : | my $userID = $r->param("user"); | ||
| 55 : | my $eUserID = $r->param("effectiveUser"); | ||
| 56 : | sh002i | 2926 | $self->{courseName} = $courseID; |
| 57 : | toenail | 2304 | # Check permissions |
| 58 : | return unless ($authz->hasPermissions($userID, "access_instructor_tools")); | ||
| 59 : | sh002i | 1920 | |
| 60 : | my @selectedUserIDs = $r->param("selected_users"); | ||
| 61 : | my @selectedSetIDs = $r->param("selected_sets"); | ||
| 62 : | |||
| 63 : | my $nusers = @selectedUserIDs; | ||
| 64 : | my $nsets = @selectedSetIDs; | ||
| 65 : | |||
| 66 : | my $firstUserID = $nusers ? $selectedUserIDs[0] : ""; | ||
| 67 : | my $firstSetID = $nsets ? $selectedSetIDs[0] : ""; | ||
| 68 : | |||
| 69 : | # these will be used to construct a new URL | ||
| 70 : | my $module; | ||
| 71 : | my %args = ( courseID => $courseID ); | ||
| 72 : | my %params; | ||
| 73 : | |||
| 74 : | my $pfx = "WeBWorK::ContentGenerator"; | ||
| 75 : | my $ipfx = "WeBWorK::ContentGenerator::Instructor"; | ||
| 76 : | |||
| 77 : | my @error; | ||
| 78 : | |||
| 79 : | # depending on which button was pushed, fill values in for URL construction | ||
| 80 : | |||
| 81 : | defined param $r "sets_assigned_to_user" and do { | ||
| 82 : | if ($nusers == 1) { | ||
| 83 : | $module = "${ipfx}::SetsAssignedToUser"; | ||
| 84 : | $args{userID} = $firstUserID; | ||
| 85 : | sh002i | 2926 | $params{fromTools} = 1; |
| 86 : | sh002i | 1920 | } else { |
| 87 : | push @error, E_ONE_USER; | ||
| 88 : | } | ||
| 89 : | gage | 1579 | }; |
| 90 : | sh002i | 1920 | |
| 91 : | defined param $r "users_assigned_to_set" and do { | ||
| 92 : | if ($nsets == 1) { | ||
| 93 : | $module = "${ipfx}::UsersAssignedToSet"; | ||
| 94 : | $args{setID} = $firstSetID; | ||
| 95 : | sh002i | 2926 | $params{fromTools} = 1; |
| 96 : | sh002i | 1920 | } else { |
| 97 : | push @error, E_ONE_SET; | ||
| 98 : | } | ||
| 99 : | gage | 1579 | }; |
| 100 : | sh002i | 1920 | |
| 101 : | defined param $r "edit_users" and do { | ||
| 102 : | if ($nusers >= 1) { | ||
| 103 : | $module = "${ipfx}::UserList"; | ||
| 104 : | $params{visible_users} = \@selectedUserIDs; | ||
| 105 : | $params{editMode} = 1; | ||
| 106 : | } else { | ||
| 107 : | push @error, E_MIN_ONE_USER; | ||
| 108 : | } | ||
| 109 : | gage | 1579 | }; |
| 110 : | |||
| 111 : | sh002i | 1920 | defined param $r "edit_sets" and do { |
| 112 : | if ($nsets == 1) { | ||
| 113 : | toenail | 2806 | $module = "${ipfx}::ProblemSetDetail"; |
| 114 : | sh002i | 1920 | $args{setID} = $firstSetID; |
| 115 : | } else { | ||
| 116 : | push @error, E_ONE_SET; | ||
| 117 : | |||
| 118 : | } | ||
| 119 : | }; | ||
| 120 : | gage | 1579 | |
| 121 : | sh002i | 2926 | defined param $r "prob_lib" and do { |
| 122 : | if ($nsets == 1) { | ||
| 123 : | $module = "${ipfx}::SetMaker"; | ||
| 124 : | $params{local_sets} = $firstSetID; | ||
| 125 : | } elsif ($nsets == 0) { | ||
| 126 : | $module = "${ipfx}::SetMaker"; | ||
| 127 : | } else { | ||
| 128 : | push @error, E_ONE_SET; | ||
| 129 : | |||
| 130 : | } | ||
| 131 : | }; | ||
| 132 : | |||
| 133 : | sh002i | 1920 | defined param $r "user_stats" and do { |
| 134 : | if ($nusers == 1) { | ||
| 135 : | $module = "${ipfx}::Stats"; | ||
| 136 : | $args{statType} = "student"; # FIXME: fix URLPath -- i shouldn't have to type this! | ||
| 137 : | $args{userID} = $firstUserID; | ||
| 138 : | } else { | ||
| 139 : | push @error, E_ONE_USER; | ||
| 140 : | } | ||
| 141 : | }; | ||
| 142 : | |||
| 143 : | defined param $r "set_stats" and do { | ||
| 144 : | if ($nsets == 1) { | ||
| 145 : | $module = "${ipfx}::Stats"; | ||
| 146 : | $args{statType} = "set"; # FIXME: fix URLPath -- i shouldn't have to type this! | ||
| 147 : | $args{setID} = $firstSetID; | ||
| 148 : | } else { | ||
| 149 : | push @error, E_ONE_SET; | ||
| 150 : | } | ||
| 151 : | }; | ||
| 152 : | |||
| 153 : | sh002i | 2926 | defined param $r "user_progress" and do { |
| 154 : | if ($nusers == 1) { | ||
| 155 : | $module = "${ipfx}::StudentProgress"; | ||
| 156 : | $args{statType} = "student"; # FIXME: fix URLPath -- i shouldn't have to type this! | ||
| 157 : | $args{userID} = $firstUserID; | ||
| 158 : | } else { | ||
| 159 : | push @error, E_ONE_USER; | ||
| 160 : | } | ||
| 161 : | }; | ||
| 162 : | |||
| 163 : | defined param $r "set_progress" and do { | ||
| 164 : | if ($nsets == 1) { | ||
| 165 : | $module = "${ipfx}::StudentProgress"; | ||
| 166 : | $args{statType} = "set"; # FIXME: fix URLPath -- i shouldn't have to type this! | ||
| 167 : | $args{setID} = $firstSetID; | ||
| 168 : | } else { | ||
| 169 : | push @error, E_ONE_SET; | ||
| 170 : | } | ||
| 171 : | }; | ||
| 172 : | |||
| 173 : | sh002i | 1920 | defined param $r "user_options" and do { |
| 174 : | if ($nusers == 1) { | ||
| 175 : | $module = "${pfx}::Options"; | ||
| 176 : | $params{effectiveUser} = $firstUserID; | ||
| 177 : | } else { | ||
| 178 : | push @error, E_ONE_USER; | ||
| 179 : | } | ||
| 180 : | }; | ||
| 181 : | |||
| 182 : | defined param $r "score_sets" and do { | ||
| 183 : | if ($nsets >= 1) { | ||
| 184 : | $module = "${ipfx}::Scoring"; | ||
| 185 : | $params{selectedSet} = \@selectedSetIDs; | ||
| 186 : | $params{scoreSelected} = 1; | ||
| 187 : | } else { | ||
| 188 : | push @error, E_MIN_ONE_SET; | ||
| 189 : | } | ||
| 190 : | }; | ||
| 191 : | |||
| 192 : | sh002i | 2926 | defined param $r "assign_users" and do { |
| 193 : | if ($nusers >= 1 and $nsets >= 1) { | ||
| 194 : | $module = "${ipfx}::Assigner"; | ||
| 195 : | $params{selected_users} = \@selectedUserIDs; | ||
| 196 : | $params{selected_sets} = \@selectedSetIDs; | ||
| 197 : | $params{assign} = "Assign selected sets to selected users"; | ||
| 198 : | } else { | ||
| 199 : | push @error, E_MIN_ONE_USER unless $nusers >= 1; | ||
| 200 : | push @error, E_MIN_ONE_SET unless $nsets >= 1; | ||
| 201 : | } | ||
| 202 : | }; | ||
| 203 : | |||
| 204 : | sh002i | 1920 | defined param $r "act_as_user" and do { |
| 205 : | if ($nusers == 1 and $nsets <= 1) { | ||
| 206 : | if ($nsets) { | ||
| 207 : | $module = "${pfx}::ProblemSet"; | ||
| 208 : | $args{setID} = $firstSetID; | ||
| 209 : | } else { | ||
| 210 : | $module = "${pfx}::ProblemSets"; | ||
| 211 : | } | ||
| 212 : | $params{effectiveUser} = $firstUserID; | ||
| 213 : | } else { | ||
| 214 : | push @error, E_ONE_USER unless $nusers == 1; | ||
| 215 : | push @error, E_MAX_ONE_SET unless $nsets <= 1; | ||
| 216 : | } | ||
| 217 : | }; | ||
| 218 : | |||
| 219 : | toenail | 2806 | defined param $r "edit_set_for_users" and do { |
| 220 : | toenail | 2811 | if ($nusers >= 1 and $nsets == 1) { |
| 221 : | toenail | 2806 | $module = "${ipfx}::ProblemSetDetail"; |
| 222 : | sh002i | 2341 | $args{setID} = $firstSetID; |
| 223 : | toenail | 2811 | $params{editForUser} = \@selectedUserIDs; |
| 224 : | sh002i | 2341 | } else { |
| 225 : | toenail | 2806 | push @error, E_MIN_ONE_USER unless $nusers >= 1; |
| 226 : | sh002i | 2341 | push @error, E_ONE_SET unless $nsets == 1; |
| 227 : | |||
| 228 : | } | ||
| 229 : | }; | ||
| 230 : | |||
| 231 : | sh002i | 2926 | defined param $r "create_set" and do { |
| 232 : | my $setname = $r->param("new_set_name"); | ||
| 233 : | if ($setname && $setname ne 'Name for new set here') { | ||
| 234 : | if ($setname =~ /^[\w.-]*$/) { | ||
| 235 : | $module = "${ipfx}::SetMaker"; | ||
| 236 : | $params{new_local_set} = "Create a New Set in this Course"; | ||
| 237 : | $params{new_set_name} = $setname; | ||
| 238 : | $params{selfassign} = 1; | ||
| 239 : | } else { | ||
| 240 : | push @error, E_BAD_NAME; | ||
| 241 : | } | ||
| 242 : | } else { | ||
| 243 : | push @error, E_SET_NAME; | ||
| 244 : | } | ||
| 245 : | }; | ||
| 246 : | |||
| 247 : | gage | 2340 | defined param $r "add_users" and do { |
| 248 : | $module = "${ipfx}::AddUsers"; | ||
| 249 : | sh002i | 2926 | }; |
| 250 : | gage | 2340 | |
| 251 : | sh002i | 2926 | defined param $r "email_users" and do { |
| 252 : | $module = "${ipfx}::SendMail"; | ||
| 253 : | sh002i | 1920 | }; |
| 254 : | toenail | 2304 | |
| 255 : | sh002i | 2926 | defined param $r "transfer_files" and do { |
| 256 : | $module = "${ipfx}::FileManager"; | ||
| 257 : | }; | ||
| 258 : | |||
| 259 : | toenail | 2304 | push @error, "You are not allowed to act as a student." |
| 260 : | if (defined param $r "act_as_user" and not $authz->hasPermissions($userID, "become_student")); | ||
| 261 : | push @error, "You are not allowed to modify problem sets." | ||
| 262 : | if ((defined param $r "edit_sets" or defined param $r "edit_set_for_user") and not $authz->hasPermissions($userID, "modify_problem_sets")); | ||
| 263 : | push @error, "You are not allowed to assign problem sets." | ||
| 264 : | if ((defined param $r "sets_assigned_to_user" or defined param $r "users_assigned_to_set") and not $authz->hasPermissions($userID, "assign_problem_sets")); | ||
| 265 : | push @error, "You are not allowed to modify student data." | ||
| 266 : | gage | 2340 | if ((defined param $r "edit_users" or defined param $r "user_options" or defined param $r "user_options") and not $authz->hasPermissions($userID, "modify_student_data")); |
| 267 : | toenail | 2304 | push @error, "You are not allowed to score sets." |
| 268 : | if (defined param $r "score_sets" and not $authz->hasPermissions($userID, "score_sets")); | ||
| 269 : | sh002i | 1920 | |
| 270 : | # handle errors, redirect to target page | ||
| 271 : | if (@error) { | ||
| 272 : | sh002i | 2926 | $self->addbadmessage(CGI::p(join(CGI::br(),@error))); |
| 273 : | toenail | 2080 | |
| 274 : | sh002i | 1920 | } elsif ($module) { |
| 275 : | my $page = $urlpath->newFromModule($module, %args); | ||
| 276 : | my $url = $self->systemLink($page, params => \%params); | ||
| 277 : | $self->reply_with_redirect($url); | ||
| 278 : | } | ||
| 279 : | gage | 1295 | } |
| 280 : | |||
| 281 : | malsyned | 831 | sub body { |
| 282 : | sh002i | 1920 | my ($self) = @_; |
| 283 : | my $r = $self->r; | ||
| 284 : | my $db = $r->db; | ||
| 285 : | mschmitt | 2187 | my $ce = $r->ce; |
| 286 : | sh002i | 1920 | my $authz = $r->authz; |
| 287 : | gage | 2339 | my $courseName = $self->{courseName}; |
| 288 : | gage | 2876 | my $user = $r->param("user"); |
| 289 : | sh002i | 1681 | |
| 290 : | toenail | 2304 | return CGI::div({class=>"ResultsWithError"}, "You are not authorized to access the Instructor tools.") |
| 291 : | gage | 2876 | unless $authz->hasPermissions($user, "access_instructor_tools"); |
| 292 : | sh002i | 1681 | |
| 293 : | sh002i | 1920 | print CGI::p("Use the interface below to quickly access commonly-used |
| 294 : | gage | 2314 | instructor tools, or select a tool from the list to the left.", CGI::br(), |
| 295 : | "Select user(s) and/or set(s) below and click the action button | ||
| 296 : | sh002i | 1920 | of your choice."); |
| 297 : | gage | 1766 | |
| 298 : | sh002i | 1920 | my @userIDs = $db->listUsers; |
| 299 : | my @Users = $db->getUsers(@userIDs); | ||
| 300 : | mschmitt | 2187 | |
| 301 : | ## Mark's Edits for filtering | ||
| 302 : | my @myUsers; | ||
| 303 : | mschmitt | 2195 | |
| 304 : | my (@viewable_sections,@viewable_recitations); | ||
| 305 : | |||
| 306 : | if (defined @{$ce->{viewable_sections}->{$user}}) | ||
| 307 : | {@viewable_sections = @{$ce->{viewable_sections}->{$user}};} | ||
| 308 : | if (defined @{$ce->{viewable_recitations}->{$user}}) | ||
| 309 : | {@viewable_recitations = @{$ce->{viewable_recitations}->{$user}};} | ||
| 310 : | mschmitt | 2194 | |
| 311 : | mschmitt | 2195 | if (@viewable_sections or @viewable_recitations){ |
| 312 : | mschmitt | 2194 | foreach my $student (@Users){ |
| 313 : | my $keep = 0; | ||
| 314 : | foreach my $sec (@viewable_sections){ | ||
| 315 : | if ($student->section() eq $sec){$keep = 1;} | ||
| 316 : | } | ||
| 317 : | foreach my $rec (@viewable_recitations){ | ||
| 318 : | if ($student->recitation() eq $rec){$keep = 1;} | ||
| 319 : | } | ||
| 320 : | if ($keep) {push @myUsers, $student;} | ||
| 321 : | mschmitt | 2187 | } |
| 322 : | mschmitt | 2194 | @Users = @myUsers; |
| 323 : | mschmitt | 2187 | } |
| 324 : | ## End Mark's Edits | ||
| 325 : | |||
| 326 : | sh002i | 1920 | my @globalSetIDs = $db->listGlobalSets; |
| 327 : | my @GlobalSets = $db->getGlobalSets(@globalSetIDs); | ||
| 328 : | |||
| 329 : | my @selected_users = $r->param("selected_users"); | ||
| 330 : | my @selected_sets = $r->param("selected_sets"); | ||
| 331 : | |||
| 332 : | my $scrolling_user_list = scrollingRecordList({ | ||
| 333 : | name => "selected_users", | ||
| 334 : | request => $r, | ||
| 335 : | default_sort => "lnfn", | ||
| 336 : | default_format => "lnfn_uid", | ||
| 337 : | mschmitt | 2187 | default_filters => ["all"], |
| 338 : | sh002i | 1920 | size => 10, |
| 339 : | multiple => 1, | ||
| 340 : | }, @Users); | ||
| 341 : | |||
| 342 : | my $scrolling_set_list = scrollingRecordList({ | ||
| 343 : | name => "selected_sets", | ||
| 344 : | request => $r, | ||
| 345 : | default_sort => "set_id", | ||
| 346 : | default_format => "set_id", | ||
| 347 : | mschmitt | 2187 | default_filters => ["all"], |
| 348 : | sh002i | 1920 | size => 10, |
| 349 : | multiple => 1, | ||
| 350 : | }, @GlobalSets); | ||
| 351 : | |||
| 352 : | print CGI::start_form({method=>"get", action=>$r->uri()}); | ||
| 353 : | print $self->hidden_authen_fields(); | ||
| 354 : | |||
| 355 : | print CGI::table({class=>"FormLayout"}, | ||
| 356 : | CGI::Tr( | ||
| 357 : | CGI::th("Users"), | ||
| 358 : | CGI::th("Sets"), | ||
| 359 : | gage | 1579 | ), |
| 360 : | sh002i | 1920 | CGI::Tr( |
| 361 : | CGI::td({style=>"width:50%"}, $scrolling_user_list), | ||
| 362 : | CGI::td({style=>"width:50%"}, $scrolling_set_list), | ||
| 363 : | gage | 1579 | ), |
| 364 : | sh002i | 1920 | CGI::Tr({class=>"ButtonRow"}, [ |
| 365 : | gage | 1766 | CGI::td([ |
| 366 : | gage | 2017 | CGI::submit("sets_assigned_to_user", "View/edit")." all sets for one <b>user</b>", |
| 367 : | CGI::submit("users_assigned_to_set", "View/edit")." all users for one <b>set</b>", | ||
| 368 : | gage | 1766 | ]), |
| 369 : | sh002i | 1920 | CGI::td([ |
| 370 : | gage | 2017 | CGI::submit("edit_users", "Edit"). " selected <b>users</b>", |
| 371 : | sh002i | 2926 | CGI::submit("edit_sets", "Edit"). " one <b>set</b>" . " ". |
| 372 : | "or ".CGI::submit("prob_lib","add problems")." to one <b>set</b>", | ||
| 373 : | sh002i | 1920 | ]), |
| 374 : | CGI::td([ | ||
| 375 : | sh002i | 2926 | CGI::submit("user_stats", "Statistics")." or ". |
| 376 : | CGI::submit("user_progress", "progress")." for one <b>user</b>", | ||
| 377 : | CGI::submit("set_stats", "Statistics")." or ". | ||
| 378 : | CGI::submit("set_progress", "progress")." for one <b>set</b>", | ||
| 379 : | sh002i | 1920 | ]), |
| 380 : | CGI::td([ | ||
| 381 : | gage | 2017 | CGI::submit("user_options", "Change password")." for one <b>user</b>", |
| 382 : | CGI::submit("score_sets", "Score"). " selected <b>sets</b>", | ||
| 383 : | sh002i | 1920 | ]), |
| 384 : | sh002i | 2926 | CGI::td([ |
| 385 : | CGI::submit("add_users", "Add")." new users", | ||
| 386 : | CGI::submit("create_set", "Create"). " new set: ". | ||
| 387 : | CGI::textfield(-name=>"new_set_name", | ||
| 388 : | -default=>"Name for new set here", | ||
| 389 : | -override=>1, -size=>20), | ||
| 390 : | ]), | ||
| 391 : | gage | 2017 | ]), |
| 392 : | sh002i | 2926 | CGI::Tr({class=>"ButtonRowCenter"}, |
| 393 : | CGI::td({-colspan=>2, align=>"center"}, | ||
| 394 : | CGI::table({-border=>0, align=>"center"}, | ||
| 395 : | CGI::Tr({-align=>"left"}, [ | ||
| 396 : | CGI::td({-height=>2}), | ||
| 397 : | CGI::td(CGI::submit("assign_users", "Assign")." selected <b>users</b> to selected <b>sets</b>"), | ||
| 398 : | CGI::td(CGI::submit("act_as_user", "Act as")." one <b>user</b> (on one <b>set</b>)"), | ||
| 399 : | CGI::td(CGI::submit("edit_set_for_user", "Edit"). " one <b>set</b> for one <b>user</b>"), | ||
| 400 : | CGI::td({-height=>4}), | ||
| 401 : | CGI::td(CGI::submit("email_users", "Email"). " your students"), | ||
| 402 : | CGI::td(CGI::submit("transfer_files", "Transfer"). " course files"), | ||
| 403 : | ]) | ||
| 404 : | ) | ||
| 405 : | ) | ||
| 406 : | ), | ||
| 407 : | gage | 1766 | ); |
| 408 : | gage | 1486 | |
| 409 : | sh002i | 1920 | print CGI::end_form(); |
| 410 : | gage | 1579 | |
| 411 : | sh002i | 1920 | return ""; |
| 412 : | gage | 1579 | } |
| 413 : | |||
| 414 : | malsyned | 831 | 1; |
| 415 : | |||
| 416 : | __END__ | ||
| 417 : | |||
| 418 : | =head1 AUTHOR | ||
| 419 : | |||
| 420 : | sh002i | 1920 | Written by Dennis Lambe Jr., malsyned (at) math.rochester.edu. |
| 421 : | malsyned | 831 | |
| 422 : | =cut |
| aubreyja at gmail dot com | ViewVC Help |
| Powered by ViewVC 1.0.9 |