--- loncom/enrollment/localenroll.pm 2014/01/03 18:39:55 1.45 +++ loncom/enrollment/localenroll.pm 2022/02/27 01:43:14 1.64 @@ -1,6 +1,6 @@ # functions to glue school database system into Lon-CAPA for # automated enrollment -# $Id: localenroll.pm,v 1.45 2014/01/03 18:39:55 raeburn Exp $ +# $Id: localenroll.pm,v 1.64 2022/02/27 01:43:14 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -39,8 +39,6 @@ described at http://www.lon-capa.org. =head1 NOTABLE SUBROUTINES -=over - =cut package localenroll; @@ -48,6 +46,8 @@ package localenroll; use strict; =pod + +=over =item run() set this to return 1 if you want the auto enrollment to run @@ -144,7 +144,7 @@ sub run() { Manager, or via the 'Upload a class list','Enroll a single student' or 'Modify student data' utilities in the Enrollment Manager, by checking the 'make these dates the default for future enrollment' checkbox. If no default - dates have been set, then the tudent role will be active immediately, and will + dates have been set, then the student role will be active immediately, and will remain active until the role is explicitly expired using ENRL -> Drop students. If dates are to included in the XML file, they should be in the format YYYY:MM:DD:HH:MM:SS (: separators required). @@ -152,13 +152,16 @@ sub run() { The tag need only be used if the credits earned by the students will be different from the default for the course. The course default is set when the course is created and can be modifed by a Domain Coordinator via "View or - modify a course or community" on the DC's Main Menu screen. + modify a course or community" on the DC's Main Menu screen. A value for should be the institutional status used for students, - and should be one of the types defined in inst_usertypes(). If no status - types are defined for the domain this tag can be omitted. If Autoupdate.pl - is enabled in your domain, updates to the institutional status set here - will be updated by Autoupdate.pl, should changes occur. + and should be one of the types defined in the "Institutional user types" + section in the domain config screen for: + "Default authentication/language/timezone/portal/types" + + If no status types are defined for the domain this tag can be omitted. + If Autoupdate.pl is enabled in your domain, updates to the institutional + status set here will be updated by Autoupdate.pl, should changes occur. If there were 10 students in fs03nop590001, 5 students in fs03nop59o601, 8 students in fs03nop590602, and 2 students in fs03ost580002, @@ -204,7 +207,7 @@ sub fetch_enrollment { ("001","601","602") would be returned If the array returned contains at least one element, then - the interface offerred to the course coordinator, lists + the interface offered to the course coordinator, lists official sections and provides a checkbox to use to select enrollment in the LON-CAPA course from each official section. @@ -255,9 +258,11 @@ sub get_sections { username:domain (c) the LON-CAPA domain that contains the course - new_course also takes a fourth (optional) argument - + new_course also takes optional fourth and fifth arguments - (d) the course co-owners, as a comma-separated list of username:domain for - any co-owners. + any co-owners. + (e) database handle (might be set when new_course() is called by check_section + routine within localenroll.pm). =cut @@ -337,6 +342,32 @@ sub validate_instcode { =pod +=item validate_crosslist_access() + +This is called for an official course to check whether a course +with the institutional code can have access to enrollment data +from a cross-listed institutional section code, given a co-owner. + +validate_crosslist_access() takes four arguments - +(a) the course's LON-CAPA domain +(b) the institional course code assigned to the course +(c) the institutional course section code for the crosslisting +(d) the co-owner to check for affiliation with the crosslisting + (username:domain). + +A combination of (a), (b), (c) and (d) with access to enrollment +data, as per institutional policies, is confirmed by returning 'valid'. + +=cut + +sub validate_crosslist_access { + my ($dom,$instcode,$inst_xlist,$coowner) = @_; + my $outcome = ''; + return $outcome; +} + +=pod + =item validate_crsreq() This is used to check whether a course request should be processed @@ -344,9 +375,10 @@ automatically, or held in a queue pendin the institution. Course requests will trigger this check if the process type has been set -to 'validate' for the course type (official, unofficial or community) and -the requestor's affiliation. Whether "validate" is an available option -in the Domain Configuration menu is controlled by auto_courserequest_checks(). +to 'validate' for the course type (official, unofficial, textbook, +placement or community) and the requestor's affiliation. Whether +"validate" is an available option in the Domain Configuration menu +is controlled by auto_courserequest_checks(). One scenario is where the request is for an official course, in which case a check could be made that the requestor is listed as instructor of record for the course in the institution's course schedule/database. @@ -355,34 +387,44 @@ Other scenarios are possible, and the ro to whatever rules a domain wishes to implement to run validations against given the data passed in to the routine. -validate_crsreq takes six arguments - +validate_crsreq takes seven arguments - (a) the LON-CAPA domain that will contain the course. (b) the username:domain for the course owner. - (c) the course type (official, unofficial or community) + (c) the course type (official, unofficial,textbook, placement or community) (d) a comma-separated list of institutional affiliations of the course owner. (e) the institutional code (in the MSU case this is a concatenation of - semester code, department code, and course number, e.g., fs03nop590. + semester code, department code, and course number, e.g., fs03nop590). (f) a comma-separated list of institutional sections included in the course request (only applicable to official courses). + (g) an optional reference to a hash of custom form data. + The custom form data will come from crsreq_updates(), with one + additional item: $custominfo->{'_LC_clonefrom'}, provided internally + (the courseID of the LON-CAPA course being cloned). A valid courserequest is confirmed by returning 'process'. -The following can be returned: process, rejected, pending, approval or error (with error condition - no :), followed by a : and then an optional message. +The following can be returned: process, rejected, pending, approval or +error (with error condition - no :), followed by a : and then an optional message. (a) process - the requestor is the recorded instructor - create the course + (b) rejected - the requestor should never be requesting this course, reject the request permanently + (c) pending - the requestor is not the recorded instructor, but could become so after administrative action at the institution. Put the - request in a queue and check localenroll:validate_instcode() - periodically until the status changes to "valid". + request in a queue and, if an official course, check + localenroll:validate_instcode() periodically until the status changes to + "valid". + (d) approval - the request will be held pending review by a Domain Coordinator. + (e) error (followed by the error condition). =cut sub validate_crsreq { - my ($dom,$owner,$crstype,$inststatuslist,$instcode,$instseclist) = @_; + my ($dom,$owner,$crstype,$inststatuslist,$instcode,$instseclist,$custominfo) = @_; my $outcome = 'approval'; return $outcome; } @@ -395,8 +437,9 @@ This is used to determine whether the "v possible choices for course request processing in the Domain Configuration menu for Course Requests. Ultimately it is called by domainprefs.pm (via: lonnet -> lond -> localenroll.pm) The domain configuration menu includes -a table where columns are course type (official, unofficial or community) -and rows are institutional affiliations (e.g., Faculty, Staff, Student etc.). +a table where columns are course type (official, unofficial, textbook, +placement or community) and rows are institutional affiliations +(e.g., Faculty, Staff, Student etc.). crsreq_checks() takes three arguments: $dom, $reqtypes, $validations. $dom - the domain for which validation options are needed. @@ -437,9 +480,45 @@ sub crsreq_checks { return 'ok'; } +=pod + +=item crsreq_updates() + +This is used to customize the LON-CAPA course request process. +There are two hash references: $incoming, and $outgoing; $incoming can +contain additional information collected from the requester, whereas $outgoing +can contain custom items to send back to lonrequestcourse.pm, which creates the +HTML displayed to the user during a course request. + +Different key-value pairs may be returned to lonrequestcourse.pm in the $outgoing +hashref depending on the current action. The available actions are: +review, prevalidate, process, created and queued. + +One scenario would be to return HTML markup in: $outgoing->{'reviewweb'}, +i.e., where the action is 'review', to prompt the user to provide additional +information as part of the course request, at the request review stage, +(i.e,, the page which contains the button used to submit a completed course request). + +The HTML could contain form elements (e.g., radio buttons etc.). The value(s) +selected by the requester in those form elements will be available in the incoming +hashref, for a subsequent action, if the corresponding keys have been included +in $outgoing->{'formitems'}, i.e., $outgoing will be hash of a hash. If a +particular form item will the single valued, the value set for the key in the +inner hash in $outgoing should be 1, otherwise, if it will be multi-valued, +the value should be multiple. + +The $outgoing hashref can contain a 'formitems' key for both the prevalidate +and process actions, as calls to localenroll::crsreq_update() can originate +in lonrequestcourse::process_request() for both of those actions. + +The retrieved form values are passed to localenroll::validate_crsreq() as the +optional seventh arg (a hashref) -- $custominfo. + +=cut + sub crsreq_updates { my ($cdom,$cnum,$crstype,$action,$ownername,$ownerdomain,$fullname,$title, - $code,$incoming,$outgoing) = @_; + $code,$accessstart,$accessend,$incoming,$outgoing) = @_; unless (ref($outgoing) eq 'HASH') { return 'fail'; } @@ -449,20 +528,39 @@ sub crsreq_updates { } if ($action eq 'review') { $outgoing->{'reviewweb'} = ''; + } elsif ($action eq 'prevalidate') { + $outgoing->{'formitems'} = {}; # key=>value, where key is form element name + # and value is multiple, if there + # are multiple form elements with + # the same name. } elsif ($action eq 'process') { $outgoing->{'formitems'} = {}; # key=>value, where key is form element name # and value is multiple, if there # are multiple form elements with - # the same name. + # the same name. } elsif ($action eq 'created') { $outgoing->{'createdweb'} = ''; $outgoing->{'createdmsg'} = [{ mt => '', args => [], }]; + $outgoing->{'createdactions'} = { + environment => {}, + }; + # environment can contain key=>value for + # items to set in the course environment. + # These would be items which are NOT included + # in the items set via options in the course + # request form. Currently self-enrollment + # settings are the only ones allowed, i.e., + # internal.selfenroll_types internal.selfenroll_registered + # internal.selfenroll_section internal.selfenroll_start_access + # internal.selfenroll_end_access internal.selfenroll_limit + # internal.selfenroll_cap internal.selfenroll_approval + # internal.selfenroll_notifylist } elsif ($action eq 'queued') { - $outgoing->{'queuedmsg'} = [{ - mt => '', + $outgoing->{'queuedmsg'} = [{ + mt => '', args => [], }]; $outgoing->{'queuedweb'} = ''; @@ -472,6 +570,189 @@ sub crsreq_updates { =pod +=item export_grades() + +This routine can be customized to push grade information to some other gradebook, +LCMS, or administrative system external to LON-CAPA. + +export_grades() takes five arguments - +(a) the LON-CAPA course ID +(b) the LON-CAPA course domain +(c) a hash reference containing the following: + scope => scope of the grades (e.g., course, map or resource). + instcode => institutional course code (if an official course) + crstype => course type -- Course, Community or Placement + context => calling context, e.g., "completion" when a student completes a placement test. +(d) a perl data structure (hash of a hash) containing the grade data. + in the outer hash, the keys are student's username:domain + in the inner hash, keys are: + id => student/employee ID + lastname => student's last name + firstname => student's first name + email => student's "permannent" e-mail address + section => student's LON-CAPA course section + total => total points earned + bytitle => reference to a hash (keys are question titles, values are points + bysymb => reference to a hash (keys are symbs, i.e., unique resource identifiers). +(e) reference to a hash which will contain information to return. + keys will be the student's username:domain. Value of 1 to show grades pushed + successfully. + +=cut + +sub export_grades { + my ($cnum,$cdom,$hashref,$dataref,$outgoing) = @_; + my %info; + if (ref($hashref) eq 'HASH') { + %info = %{$hashref}; + } + if ((ref($dataref) eq 'HASH') && (ref($outgoing) eq 'HASH')) { + foreach my $key (keys(%{$dataref})) { + $outgoing->{$key} = 1; + } + return 'ok'; + } else { + return 'error'; + } +} + +=pod + +=item check_instclasses() + + This is used to supply information about which instituional course sections + and cross-listings are available to supply enrollment data, given the current + list of owner and co-owners. The data are used to populate the column titled: + "Auto-enrollment of registered students" when showing full detailed for a course + in the course catalog. + + This subroutine takes four arguments - + + (a) $owners - comma-separated list of username:domain for course owner + and co-owners. + (b) $dom - domain of course. + (c) $classes - reference to hash of institutional course sections and + crosslistings for which access to enrollment data is being checked. + (d) $validated - reference to hash which will be populated with all + keys from incoming $classes hashref, for which one or more of the + owner/co-owners has rights to access enrollment data. For each + key included in $validated hashref, corresponding value will be set to 1. + + The subroutine returns 'ok' if there is no processing error. + +=cut + + +sub check_instclasses { + my ($owners,$dom,$classes,$validated) = @_; + if ((ref($classes) eq 'HASH') && (ref($validated) eq 'HASH')) { + foreach my $class (keys(%{$classes})){ + if (&check_section($class,$owners,$dom) eq 'ok') { + $validated->{$class} = 1; + } + } + } + return 'ok'; +} + +=pod + +=item instsec_reformat() + + Inputs: $dom, $action, $instsecref + + $dom is the course's domain + $action is either: clutter or declutter + $instsecref is a reference to a hash, in which each key is + course num:course code, and each value is either an array of + institutional sections, or (in the case of crosslisted courses) + an array of institutional course sections. + + Returns: ok + + Side effects: will modify the items in the array as determined by + code implemented for the domain. Modification will differ depending + on whether the action is clutter or declutter. + + The idea is that "clutter" will modify the name of the section such + that a concatenation of institutional code then (modified) section + will result in a string that other customized routines in localenroll.pm + can separate without ambiguity into instituional code then (real) + institutional section using a regular expression. + + Conversely, "declutter" will modify the name of an already modified + item such that display of the concatenated string (e.g., for a + crosslisting in the course catalog) does not include the "added" + characters used to eliminate ambiguity. + + Examples (MSU): + + Starting in Fall 2021 at MSU, institution section numbers are no + longer guaranteed to be three digit numbers (including leading zeroes). + + So, for example the course code: fs21phy183b might have sections: + 001, 002, LEC1, LEC2, and be crosslisted with fs21phy233b (with + sections: 730, LEC3, LEC4). + + The sections: LEC1, and LEC2 should be changed to _LEC1, and _LEC2 + before creating the inner keys in the %affiliates hash of a hash, + passed to fetch_enrollment() in Enrollment.pm. They will however + be stored in the course's environment as LEC1 and LEC2. + + For the crosslistings, LEC3 and LEC4 should be changed to + _LEC3 and _LEC4 before storing in the course's environment.db file. + + In both cases when it comes time to extract the various components + of an institutional section code (i.e., the concatenated string) in + fetch_enrollment(), for example, the regexp used at MSU would be: + + if ($class =~ m/^([suf]s)(\d{2})(\w{2,4})(\d{3,4}[A-Za-z]?)(\d{3}|_[A-Za-z0-9]{1,5})$/) { + my ($sem,$yr,$subj,$crse,$sec) = ($1,$2,$3,$4,$5); + + The three digit sections would match the \d{3} and the other sections + (LEC1, LEC2 etc.) would match the _[A-Za-z0-9]{1,5}. + + The customization in &instsec_reformat() would be: + + if ($action eq 'clutter') { + unless ($item =~ /^\d{3}$/) { + $item = '_'.$item; + } + } elsif ($action eq 'declutter') { + if ($item =~ /^([suf]s\d{2}\w{2,4}\d{3,4}[A-Za-z]?)(\d{3}|_[A-Za-z0-9]{1,5})$/) { + my ($instcode,$instsec) = ($1,$2); + $instsec =~ s/^_//; + $item = $instcode.$instsec; + } elsif ($item =~ /^_[A-Za-z0-9]{1,5}$/) { + $item =~ s/^_//; + } + } + +=cut + +sub instsec_reformat { + my ($dom,$action,$instsecref) = @_; + if ((ref($instsecref) eq 'HASH') && + (($action eq 'clutter') || ($action eq 'declutter'))) { + foreach my $key (keys(%{$instsecref})) { + if (ref($instsecref->{$key}) eq 'ARRAY') { + foreach my $sec (@{$instsecref->{$key}}) { + if ($action eq 'clutter') { + # modify the section, as needed. + next; + } elsif ($action eq 'declutter') { + # modify the section, as needed. + next; + } + } + } + } + } + return 'ok'; +} + +=pod + =item create_password() This is called when the authentication method set for the automated @@ -791,6 +1072,13 @@ sub instcode_defaults { (d) $lc_users - reference to hash containing LON-CAPA usernames in in domain $dom, as keys. Needed if institutional data source only allows query by username. + (e) $counts - reference to hash (optional), for use when called + from Autoupdate.pl which can contain counts for + user-specified items retrieved in allusers_info() + or in custom subroutines which it calls. Key in + hashref, and count value will be printed to + autoupdate.log by Autoupdate.pl. + returns 1 parameter - 'ok' if no processing error, or other value if an error occurred. side effects - populates the $instusers and $instids refs to hashes. @@ -801,7 +1089,7 @@ sub instcode_defaults { =cut sub allusers_info { - my ($dom,$instusers,$instids,$lc_users) = @_; + my ($dom,$instusers,$instids,$lc_users,$counts) = @_; my $outcome = 'ok'; return $outcome; } @@ -837,8 +1125,8 @@ sub allusers_info { institutional types to check. (g) $srchby - optional if $uname or $id defined, otherwise required. Allowed values include: 1. lastfirst, 2. last, 3. uname - corresponding to searches by 1. lastname,firstname; - 2. lastname; 3. username + 4. email, corresponding to searches by 1. lastname,firstname; + 2. lastname; 3. username; 4. e-mail address (h) $srchterm - optional if $uname or $id defined, otherwise required String to search for. (i) $srchtype - optional. Allowed values: contains, begins (defaults @@ -869,8 +1157,64 @@ sub get_userinfo { =pod +=item get_multusersinfo() + + (a) $dom - domain + (b) $type - username or id + (c) $unamenames - reference to hash containing usernames of users + (d) $instusers - reference to hash which will contain info for user + as key = value; keys will be one or all of: + lastname,firstname,middlename,generation,id,inststatus - + institutional status (e.g., faculty,staff,student) + Values are all scalars except inststatus, + which is an array. + (e) $instids - reference to hash which will contain ID numbers - + keys will be unique IDs (student or faculty/staff ID) + values will be either: scalar (username) or an array + if a single ID matches multiple usernames. + + returns 1 parameter - 'ok' if no processing error, or other value + if an error occurred. + + side effects - populates the $instusers and $instids refs to hashes. + with information for specified username, or specified + id, if fifth argument provided, from all available, or + specified (e.g., faculty only) institutional datafeeds, + if sixth argument provided. + + WARNING: You need to set $outcome to 'ok' once you have customized + this routine to communicate with an instititional + directory data source, otherwise retrieval of institutional + user information will always be reported as being unavailable + in domain $dom. + +=cut + +sub get_multusersinfo { + my ($dom,$type,$usernames,$instusers,$instids) = @_; + my $outcome = 'unavailable'; + return $outcome; +} + +=pod + =item inst_usertypes() + Starting with LON-CAPA 2.11.0 use of this subroutine + is deprecated. The domain configuration web GUI + accessible to Domain Coordinators will be used to + manage institutional types. If you have previously + customized this routine, then values set there will + be used when displaying the "Institutional user types" + section in the domain config screen for: + "Default authentication/language/timezone/portal/types". + + Once you have visited that screen and saved the settings, + configuration thereafter will be via the web GUI of + values stored in the domain's configuration.db file on + the primary library server in the domain, and values in + inst_usertypes() will no longer be consulted. + Incoming data: three arguments (a) $dom - domain (b) $usertypes - reference to hash which will contain @@ -906,8 +1250,8 @@ sub inst_usertypes { keys of top level hash are short names (e.g., netid, noncredit) for each key, value is a hash - desc => long name for rule - rule => description of rule + name => long name for rule + desc => description of rule authtype => (krb5,krb4,int, or loc) authentication type for rule authparm => authentication parameter for rule @@ -941,8 +1285,8 @@ sub username_rules { keys of top level hash are short names (e.g., netid, noncredit) for each key, value is a hash - desc => long name for rule - rule => description of rule + name => long name for rule + desc => description of rule (c) $rulesorder - reference to array containing rule names in order to be displayed @@ -968,8 +1312,8 @@ sub id_rules { keys of top level hash are short names (e.g., netid) for each key, value is a hash - desc => long name for rule - rule => description of rule + name => long name for rule + desc => description of rule (c) $rulesorder - reference to array containing rule names in order to be displayed @@ -987,6 +1331,46 @@ sub selfcreate_rules { =pod +=item unamemap_rules() + + Incoming data: three arguments + (a) $dom - domain + (b) $ruleshash - reference to hash containing rules + (a hash of a hash) + keys of top level hash are short names + (e.g., netid) + for each key, value is a hash + name => long name for rule + desc => description of rule + + For example: + + %{$ruleshash} = ( + emailaddress => { + name => 'Email address to UserID', + desc => 'Extract userID from userID@example.tld', + }, + ); + would enable display of a checkbox for: 'Email address to UserID' in the + "Available conversions" item in the "Mapping for missing usernames via standard log-in" + panel available to a Domain Coordinator via: + Main Menu > Set domain configuration > Display ("Default authentication/language/timezone/portal/types" checked) + + (c) $rulesorder - reference to array containing rule names + in order to be displayed + + returns 'ok' if no processing error. + +=cut + +sub unamemap_rules { + my ($dom,$ruleshash,$rulesorder) = @_; + my $outcome; + return $outcome; +} + +=pod + =item username_check() Incoming data: four arguments @@ -994,7 +1378,7 @@ sub selfcreate_rules { (b) $uname - username to compare against rules (scalar) (c) $to_check (reference to array of rule names to check) (d) $resultshash (reference to hash of results) - hash of results for rule checked + hash of results for rules checked - keys are rule names - values are: 1 or 0 (for matched or unmatched) @@ -1018,7 +1402,7 @@ sub username_check { (b) $id - ID to compare against rules (scalar) (c) $to_check (reference to array of rule names to check) (d) $resultshash (reference to hash of results) - hash of results for rule checked + hash of results for rules checked - keys are rule names - values are: 1 or 0 (for matched or unmatched) @@ -1042,7 +1426,7 @@ sub id_check { (b) $selfcreatename - e-mail proposed as username (compare against rules - scalar) (c) $to_check (reference to array of rule names to check) (d) $resultshash (reference to hash of results) - hash of results for rule checked + hash of results for rules checked - keys are rule names - values are: 1 or 0 (for matched or unmatched) @@ -1056,6 +1440,44 @@ sub selfcreate_check { my $outcome; return $outcome; } + +=pod + +=item unamemap_check() + + Incoming data: four arguments + (a) $dom - domain (scalar) + (b) $uname - username entered on log-in page (compare against rules - scalar) + (c) $to_check (reference to array of rule names to check) + (d) $resultshash (reference to hash of results) + hash of results for rules checked + - keys are rule names + - values are derived username from substitution operation + applied to $uname. + + For example, in the msu domain the rule "msuemail" will replace an MSU + email address submitted as a username, with the part before the @msu.edu, + (known as the MSUNetID), which is what is used in LON-CAPA as a username. + + if ($dom eq 'msu') { + foreach my $item (@{$to_check}) { + if ($item eq 'msuemail') { + if ($uname =~ /^(\w{2,8})\@msu\.edu$/) { + $resultshash->{$item} = $1; + } + } + } + } + + returns 'ok' if no processing error. + +=cut + +sub unamemap_check { + my ($dom,$uname,$to_check,$resultshash) = @_; + my $outcome; + return $outcome; +} =pod