--- loncom/interface/loncommon.pm 2015/06/23 02:42:34 1.1223 +++ loncom/interface/loncommon.pm 2016/10/10 03:02:47 1.1255 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # a pile of common routines # -# $Id: loncommon.pm,v 1.1223 2015/06/23 02:42:34 musolffc Exp $ +# $Id: loncommon.pm,v 1.1255 2016/10/10 03:02:47 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -72,11 +72,13 @@ use Apache::lonuserstate(); use Apache::courseclassifier(); use LONCAPA qw(:DEFAULT :match); use DateTime::TimeZone; -use DateTime::Locale::Catalog; +use DateTime::Locale; use Encode(); use Text::Aspell; use Authen::Captcha; use Captcha::reCAPTCHA; +use JSON::DWIW; +use LWP::UserAgent; use Crypt::DES; use DynaLoader; # for Crypt::DES version use MIME::Lite; @@ -588,7 +590,10 @@ sub coursebrowser_javascript { if (formname == 'ccrs') { var ownername = document.forms[formid].ccuname.value; var ownerdom = document.forms[formid].ccdomain.options[document.forms[formid].ccdomain.selectedIndex].value; - url += '&cloner='+ownername+':'+ownerdom+'&crscode='+document.forms[formid].crscode.value; + url += '&cloner='+ownername+':'+ownerdom; + if (type == 'Course') { + url += '&crscode='+document.forms[formid].crscode.value; + } } if (formname == 'requestcrs') { url += '&crsdom=$domainfilter&crscode=$instcode'; @@ -876,6 +881,8 @@ sub selectcourse_link { my $linktext = &mt('Select Course'); if ($selecttype eq 'Community') { $linktext = &mt('Select Community'); + } elsif ($selecttype eq 'Placement') { + $linktext = &mt('Select Placement Test'); } elsif ($selecttype eq 'Course/Community') { $linktext = &mt('Select Course/Community'); $type = ''; @@ -967,15 +974,16 @@ sub select_datelocale { } $output .= '> '; } + my @languages = &Apache::lonlocal::preferred_languages(); my (@possibles,%locale_names); - my @locales = DateTime::Locale::Catalog::Locales; - foreach my $locale (@locales) { - if (ref($locale) eq 'HASH') { - my $id = $locale->{'id'}; - if ($id ne '') { - my $en_terr = $locale->{'en_territory'}; - my $native_terr = $locale->{'native_territory'}; - my @languages = &Apache::lonlocal::preferred_languages(); + my @locales = DateTime::Locale->ids(); + foreach my $id (@locales) { + if ($id ne '') { + my ($en_terr,$native_terr); + my $loc = DateTime::Locale->load($id); + if (ref($loc)) { + $en_terr = $loc->name(); + $native_terr = $loc->native_name(); if (grep(/^en$/,@languages) || !@languages) { if ($en_terr ne '') { $locale_names{$id} = '('.$en_terr.')'; @@ -990,8 +998,8 @@ sub select_datelocale { } } $locale_names{$id} = Encode::encode('UTF-8',$locale_names{$id}); - push (@possibles,$id); - } + push(@possibles,$id); + } } } foreach my $item (sort(@possibles)) { @@ -1086,6 +1094,9 @@ linked_select_forms takes the following =item * $onchangesecond, additional javascript call to execute for an onchange event for the second \n"; + $result .= "'. + ''."\n". + '
'. + $lt{'fnam'}.'
'."\n"; + } + return ($possdirs,$output); +} + =pod =head1 Excel and CSV file utility routines @@ -2258,12 +2548,15 @@ sub multiple_select_form { =pod -=item * &select_form($defdom,$name,$hashref,$onchange) +=item * &select_form($defdom,$name,$hashref,$onchange,$readonly) Returns a string containing a \n"; + my $disabled; + if ($readonly) { + $disabled = ' disabled="disabled"'; + } + my $selectform = "'; my @srchins = ('crs','dom','alc','instd'); @@ -9759,61 +10223,165 @@ END_BLOCK &Apache::lonhtmlcommon::row_closure(1). &Apache::lonhtmlcommon::end_pick_box(). '
'; - return $output; + return ($output,1); } sub user_rule_check { my ($usershash,$checks,$alerts,$rulematch,$inst_results,$curr_rules,$got_rules) = @_; - my $response; + my ($response,%inst_response); if (ref($usershash) eq 'HASH') { - foreach my $user (keys(%{$usershash})) { - my ($uname,$udom) = split(/:/,$user); - next if ($udom eq '' || $uname eq ''); - my ($id,$newuser); - if (ref($usershash->{$user}) eq 'HASH') { - $newuser = $usershash->{$user}->{'newuser'}; - $id = $usershash->{$user}->{'id'}; - } - my $inst_response; + if (keys(%{$usershash}) > 1) { + my (%by_username,%by_id,%userdoms); + my $checkid; if (ref($checks) eq 'HASH') { - if (defined($checks->{'username'})) { - ($inst_response,%{$inst_results->{$user}}) = - &Apache::lonnet::get_instuser($udom,$uname); - } elsif (defined($checks->{'id'})) { - ($inst_response,%{$inst_results->{$user}}) = - &Apache::lonnet::get_instuser($udom,undef,$id); + if ((!defined($checks->{'username'})) && (defined($checks->{'id'}))) { + $checkid = 1; + } + } + foreach my $user (keys(%{$usershash})) { + my ($uname,$udom) = split(/:/,$user); + if ($checkid) { + if (ref($usershash->{$user}) eq 'HASH') { + if ($usershash->{$user}->{'id'} ne '') { + $by_id{$udom}{$usershash->{$user}->{'id'}} = $uname; + $userdoms{$udom} = 1; + if (ref($inst_results) eq 'HASH') { + $inst_results->{$uname.':'.$udom} = {}; + } + } + } + } else { + $by_username{$udom}{$uname} = 1; + $userdoms{$udom} = 1; + if (ref($inst_results) eq 'HASH') { + $inst_results->{$uname.':'.$udom} = {}; + } + } + } + foreach my $udom (keys(%userdoms)) { + if (!$got_rules->{$udom}) { + my %domconfig = &Apache::lonnet::get_dom('configuration', + ['usercreation'],$udom); + if (ref($domconfig{'usercreation'}) eq 'HASH') { + foreach my $item ('username','id') { + if (ref($domconfig{'usercreation'}{$item.'_rule'}) eq 'ARRAY') { + $$curr_rules{$udom}{$item} = + $domconfig{'usercreation'}{$item.'_rule'}; + } + } + } + $got_rules->{$udom} = 1; + } + } + if ($checkid) { + foreach my $udom (keys(%by_id)) { + my ($outcome,$results) = &Apache::lonnet::get_multiple_instusers($udom,$by_id{$udom},'id'); + if ($outcome eq 'ok') { + foreach my $id (keys(%{$by_id{$udom}})) { + my $uname = $by_id{$udom}{$id}; + $inst_response{$uname.':'.$udom} = $outcome; + } + if (ref($results) eq 'HASH') { + foreach my $uname (keys(%{$results})) { + if (exists($inst_response{$uname.':'.$udom})) { + $inst_response{$uname.':'.$udom} = $outcome; + $inst_results->{$uname.':'.$udom} = $results->{$uname}; + } + } + } + } } } else { - ($inst_response,%{$inst_results->{$user}}) = - &Apache::lonnet::get_instuser($udom,$uname); - return; + foreach my $udom (keys(%by_username)) { + my ($outcome,$results) = &Apache::lonnet::get_multiple_instusers($udom,$by_username{$udom}); + if ($outcome eq 'ok') { + foreach my $uname (keys(%{$by_username{$udom}})) { + $inst_response{$uname.':'.$udom} = $outcome; + } + if (ref($results) eq 'HASH') { + foreach my $uname (keys(%{$results})) { + $inst_results->{$uname.':'.$udom} = $results->{$uname}; + } + } + } + } } - if (!$got_rules->{$udom}) { - my %domconfig = &Apache::lonnet::get_dom('configuration', - ['usercreation'],$udom); - if (ref($domconfig{'usercreation'}) eq 'HASH') { - foreach my $item ('username','id') { - if (ref($domconfig{'usercreation'}{$item.'_rule'}) eq 'ARRAY') { - $$curr_rules{$udom}{$item} = - $domconfig{'usercreation'}{$item.'_rule'}; + } elsif (keys(%{$usershash}) == 1) { + my $user = (keys(%{$usershash}))[0]; + my ($uname,$udom) = split(/:/,$user); + if (($udom ne '') && ($uname ne '')) { + if (ref($usershash->{$user}) eq 'HASH') { + if (ref($checks) eq 'HASH') { + if (defined($checks->{'username'})) { + ($inst_response{$user},%{$inst_results->{$user}}) = + &Apache::lonnet::get_instuser($udom,$uname); + } elsif (defined($checks->{'id'})) { + if ($usershash->{$user}->{'id'} ne '') { + ($inst_response{$user},%{$inst_results->{$user}}) = + &Apache::lonnet::get_instuser($udom,undef, + $usershash->{$user}->{'id'}); + } else { + ($inst_response{$user},%{$inst_results->{$user}}) = + &Apache::lonnet::get_instuser($udom,$uname); + } + } + } else { + ($inst_response{$user},%{$inst_results->{$user}}) = + &Apache::lonnet::get_instuser($udom,$uname); + return; + } + if (!$got_rules->{$udom}) { + my %domconfig = &Apache::lonnet::get_dom('configuration', + ['usercreation'],$udom); + if (ref($domconfig{'usercreation'}) eq 'HASH') { + foreach my $item ('username','id') { + if (ref($domconfig{'usercreation'}{$item.'_rule'}) eq 'ARRAY') { + $$curr_rules{$udom}{$item} = + $domconfig{'usercreation'}{$item.'_rule'}; + } + } } + $got_rules->{$udom} = 1; } } - $got_rules->{$udom} = 1; + } else { + return; + } + } else { + return; + } + foreach my $user (keys(%{$usershash})) { + my ($uname,$udom) = split(/:/,$user); + next if (($udom eq '') || ($uname eq '')); + my $id; + if (ref($inst_results) eq 'HASH') { + if (ref($inst_results->{$user}) eq 'HASH') { + $id = $inst_results->{$user}->{'id'}; + } + } + if ($id eq '') { + if (ref($usershash->{$user})) { + $id = $usershash->{$user}->{'id'}; + } } foreach my $item (keys(%{$checks})) { if (ref($$curr_rules{$udom}) eq 'HASH') { if (ref($$curr_rules{$udom}{$item}) eq 'ARRAY') { if (@{$$curr_rules{$udom}{$item}} > 0) { - my %rule_check = &Apache::lonnet::inst_rulecheck($udom,$uname,$id,$item,$$curr_rules{$udom}{$item}); + my %rule_check = &Apache::lonnet::inst_rulecheck($udom,$uname,$id,$item, + $$curr_rules{$udom}{$item}); foreach my $rule (@{$$curr_rules{$udom}{$item}}) { if ($rule_check{$rule}) { $$rulematch{$user}{$item} = $rule; - if ($inst_response eq 'ok') { + if ($inst_response{$user} eq 'ok') { if (ref($inst_results) eq 'HASH') { if (ref($inst_results->{$user}) eq 'HASH') { if (keys(%{$inst_results->{$user}}) == 0) { $$alerts{$item}{$udom}{$uname} = 1; + } elsif ($item eq 'id') { + if ($inst_results->{$user}->{'id'} eq '') { + $$alerts{$item}{$udom}{$uname} = 1; + } } } } @@ -10081,7 +10649,9 @@ reservable_now - ref to hash of student_ Keys in inner hash are: (a) symb: either blank or symb to which slot use is restricted. - (b) endreserve: end date of reservation period. + (b) endreserve: end date of reservation period. + (c) uniqueperiod: start,end dates when slot is to be uniquely + selected. sorted_future - ref to array of student_schedulable slots reservable in the future, ordered by start date of reservation period. @@ -10091,7 +10661,9 @@ future_reservable - ref to hash of stude Keys in inner hash are: (a) symb: either blank or symb to which slot use is restricted. - (b) startreserve: start date of reservation period. + (b) startreserve: start date of reservation period. + (c) uniqueperiod: start,end dates when slot is to be uniquely + selected. =back @@ -10099,13 +10671,35 @@ future_reservable - ref to hash of stude sub get_future_slots { my ($cnum,$cdom,$now,$symb) = @_; + my $map; + if ($symb) { + ($map) = &Apache::lonnet::decode_symb($symb); + } my (%reservable_now,%future_reservable,@sorted_reservable,@sorted_future); my %slots = &Apache::lonnet::get_course_slots($cnum,$cdom); foreach my $slot (keys(%slots)) { next unless($slots{$slot}->{'type'} eq 'schedulable_student'); if ($symb) { - next if (($slots{$slot}->{'symb'} ne '') && - ($slots{$slot}->{'symb'} ne $symb)); + if ($slots{$slot}->{'symb'} ne '') { + my $canuse; + my %oksymbs; + my @slotsymbs = split(/\s*,\s*/,$slots{$slot}->{'symb'}); + map { $oksymbs{$_} = 1; } @slotsymbs; + if ($oksymbs{$symb}) { + $canuse = 1; + } else { + foreach my $item (@slotsymbs) { + if ($item =~ /\.(page|sequence)$/) { + (undef,undef,my $sloturl) = &Apache::lonnet::decode_symb($item); + if (($map ne '') && ($map eq $sloturl)) { + $canuse = 1; + last; + } + } + } + } + next unless ($canuse); + } } if (($slots{$slot}->{'starttime'} > $now) && ($slots{$slot}->{'endtime'} > $now)) { @@ -10145,6 +10739,10 @@ sub get_future_slots { my $startreserve = $slots{$slot}->{'startreserve'}; my $endreserve = $slots{$slot}->{'endreserve'}; my $symb = $slots{$slot}->{'symb'}; + my $uniqueperiod; + if (ref($slots{$slot}->{'uniqueperiod'}) eq 'ARRAY') { + $uniqueperiod = join(',',@{$slots{$slot}->{'uniqueperiod'}}); + } if (($startreserve < $now) && (!$endreserve || $endreserve > $now)) { my $lastres = $endreserve; @@ -10153,13 +10751,15 @@ sub get_future_slots { } $reservable_now{$slot} = { symb => $symb, - endreserve => $lastres + endreserve => $lastres, + uniqueperiod => $uniqueperiod, }; } elsif (($startreserve > $now) && (!$endreserve || $endreserve > $startreserve)) { $future_reservable{$slot} = { symb => $symb, - startreserve => $startreserve + startreserve => $startreserve, + uniqueperiod => $uniqueperiod, }; } } @@ -10317,7 +10917,23 @@ sub get_env_multiple { return(@values); } +# Looks at given dependencies, and returns something depending on the context. +# For coursedocs paste, returns (undef, $counter, $numpathchg, \%existing). +# For syllabus rewrites, returns (undef, $counter, $numpathchg, \%existing, \%mapping). +# For all other contexts, returns ($output, $counter, $numpathchg). +# $output: string with the HTML output. Can contain missing dependencies with an upload form, existing dependencies, and dependencies no longer in use. +# $counter: integer with the number of existing dependencies when no HTML output is returned, and the number of missing dependencies when an HTML output is returned. +# $numpathchg: integer with the number of cleaned up dependency paths. +# \%existing: hash reference clean path -> 1 only for existing dependencies. +# \%mapping: hash reference clean path -> original path for all dependencies. +# @param {string} actionurl - The path to the handler, indicative of the context. +# @param {string} state - Can contain HTML with hidden inputs that will be added to the output form. +# @param {hash reference} allfiles - List of file info from lonnet::extract_embedded_items +# @param {hash reference} codebase - undef, not modified by lonnet::extract_embedded_items ? +# @param {hash reference} args - More parameters ! Possible keys: error_on_invalid_names (boolean), ignore_remote_references (boolean), current_path (string), docs_url (string), docs_title (string), context (string) +# @return {Array} - array depending on the context (not a reference) sub ask_for_embedded_content { + # NOTE: documentation was added afterwards, it could be wrong my ($actionurl,$state,$allfiles,$codebase,$args)=@_; my (%subdependencies,%dependencies,%mapping,%existing,%newfiles,%pathchanges, %currsubfile,%unused,$rem); @@ -10333,6 +10949,9 @@ sub ask_for_embedded_content { my $heading = &mt('Upload embedded files'); my $buttontext = &mt('Upload'); + # fills these variables based on the context: + # $navmap, $cdom, $cnum, $udom, $uname, $url, $toplevel, $getpropath, + # $path, $fileloc, $title, $rem, $filename if ($env{'request.course.id'}) { if ($actionurl eq '/adm/dependencies') { $navmap = Apache::lonnavmaps::navmap->new(); @@ -10417,6 +11036,16 @@ sub ask_for_embedded_content { $fileloc = &Apache::lonnet::filelocation('',$toplevel).'/'; $fileloc =~ s{^/}{}; } + + # parses the dependency paths to get some info + # fills $newfiles, $mapping, $subdependencies, $dependencies + # $newfiles: hash URL -> 1 for new files or external URLs + # (will be completed later) + # $mapping: + # for external URLs: external URL -> external URL + # for relative paths: clean path -> original path + # $subdependencies: hash clean path -> clean file name -> 1 for relative paths in subdirectories + # $dependencies: hash clean or not file name -> 1 for relative paths not in subdirectories foreach my $file (keys(%{$allfiles})) { my $embed_file; if (($path eq "/uploaded/$cdom/$cnum/portfolio/syllabus") && ($file =~ m{^\Q$path/\E(.+)$})) { @@ -10459,6 +11088,19 @@ sub ask_for_embedded_content { } } } + + # looks for all existing files in dependency subdirectories (from $subdependencies filled above) + # and lists + # fills $currsubfile, $pathchanges, $existing, $numexisting, $newfiles, $unused + # $currsubfile: hash clean path -> file name -> 1 for all existing files in the path + # $pathchanges: hash clean path -> 1 if the file in subdirectory exists and + # the path had to be cleaned up + # $existing: hash clean path -> 1 if the file exists + # $numexisting: number of keys in $existing + # $newfiles: updated with clean path -> 1 for files in subdirectories that do not exist + # $unused: only for /adm/dependencies, hash clean path -> 1 for existing files in + # dependency subdirectories that are + # not listed as dependencies, with some exceptions using $rem my $dirptr = 16384; foreach my $path (keys(%subdependencies)) { $currsubfile{$path} = {}; @@ -10534,6 +11176,9 @@ sub ask_for_embedded_content { } } } + + # fills $currfile, hash file name -> 1 or [$size,$mtime] + # for files in $url or $fileloc (target directory) in some contexts my %currfile; if (($actionurl eq '/adm/portfolio') || ($actionurl eq '/adm/coursegrp_portfolio')) { @@ -10572,6 +11217,8 @@ sub ask_for_embedded_content { } } } + # updates $pathchanges, $existing, $numexisting, $newfiles and $unused for files that + # are not in subdirectories, using $currfile foreach my $file (keys(%dependencies)) { if (exists($currfile{$file})) { unless ($mapping{$file} eq $file) { @@ -10600,6 +11247,8 @@ sub ask_for_embedded_content { $unused{$file} = 1; } } + + # returns some results for coursedocs paste and syllabus rewrites ($output is undef) if (($actionurl eq '/adm/coursedocs') && (ref($args) eq 'HASH') && ($args->{'context'} eq 'paste')) { $counter = scalar(keys(%existing)); @@ -10611,6 +11260,12 @@ sub ask_for_embedded_content { $numpathchg = scalar(keys(%pathchanges)); return ($output,$counter,$numpathchg,\%existing,\%mapping); } + + # returns HTML otherwise, with dependency results and to ask for more uploads + + # $upload_output: missing dependencies (with upload form) + # $modify_output: uploaded dependencies (in use) + # $delete_output: files no longer in use (unused files are not listed for londocs, bug?) foreach my $embed_file (sort {lc($a) cmp lc($b)} keys(%newfiles)) { if ($actionurl eq '/adm/dependencies') { next if ($embed_file =~ m{^\w+://}); @@ -13853,6 +14508,8 @@ sub build_recipient_list { =pod +=over 4 + =item * &mime_email() Sends an email with a possible attachment @@ -14034,6 +14691,8 @@ sub extract_categories { $trailstr = &mt('Official courses (with institutional codes)'); } elsif ($name eq 'communities') { $trailstr = &mt('Communities'); + } elsif ($name eq 'placement') { + $trailstr = &mt('Placement Tests'); } else { $trailstr = $name; } @@ -14172,8 +14831,10 @@ sub assign_categories_table { next if ($parent eq 'instcode'); if ($type eq 'Community') { next unless ($parent eq 'communities'); + } elsif ($type eq 'Placement') { + next unless ($parent eq 'placement'); } else { - next if ($parent eq 'communities'); + next if (($parent eq 'communities') || ($parent eq 'placement')); } my $css_class = $itemcount%2?' class="LC_odd_row"':''; my $item = &escape($parent).'::0'; @@ -14186,6 +14847,8 @@ sub assign_categories_table { my $parent_title = $parent; if ($parent eq 'communities') { $parent_title = &mt('Communities'); + } elsif ($parent eq 'placement') { + $parent_title = &mt('Placement Tests'); } $table .= ''. ''.$clonemsg.''; } @@ -14855,6 +15525,9 @@ sub construct_course { if ($args->{'setcontent'}) { $cenv{'question.email'}=$args->{'ccuname'}.':'.$args->{'ccdomain'}; } + if ($args->{'setcomment'}) { + $cenv{'comment.email'}=$args->{'ccuname'}.':'.$args->{'ccdomain'}; + } } if ($args->{'reshome'}) { $cenv{'reshome'}=$args->{'reshome'}.'/'; @@ -14961,6 +15634,30 @@ sub construct_course { $outcome .= ($fatal?$errtext:'write ok').$linefeed; } +# +# Set params for Placement Tests +# + if ($args->{'crstype'} eq 'Placement') { + my %storecontent; + my $prefix=$$crsudom.'_'.$$crsunum.'.0.'; + my %defaults = ( + buttonshide => { value => 'yes', + type => 'string_yesno',}, + type => { value => 'randomizetry', + type => 'string_questiontype',}, + maxtries => { value => 1, + type => 'int_pos',}, + problemstatus => { value => 'no', + type => 'string_problemstatus',}, + ); + foreach my $key (keys(%defaults)) { + $storecontent{$prefix.$key} = $defaults{$key}{'value'}; + $storecontent{$prefix.$key.'.type'} = $defaults{$key}{'type'}; + } + &Apache::lonnet::cput + ('resourcedata',\%storecontent,$$crsudom,$$crsunum); + } + return (1,$outcome); } @@ -15021,8 +15718,7 @@ sub generate_code { ############################################################ ############################################################ -#SD -# only Community and Course, or anything else? +# Community, Course and Placement Test sub course_type { my ($cid) = @_; if (!defined($cid)) { @@ -15040,17 +15736,19 @@ sub group_term { my %names = ( 'Course' => 'group', 'Community' => 'group', + 'Placement' => 'group', ); return $names{$crstype}; } sub course_types { - my @types = ('official','unofficial','community','textbook'); + my @types = ('official','unofficial','community','textbook','placement'); my %typename = ( official => 'Official course', unofficial => 'Unofficial course', community => 'Community', textbook => 'Textbook course', + placement => 'Placement test', ); return (\@types,\%typename); } @@ -15265,7 +15963,7 @@ sub init_user_environment { undef,\%userenv,\%domdef,\%is_adv); } - foreach my $crstype ('official','unofficial','community','textbook') { + foreach my $crstype ('official','unofficial','community','textbook','placement') { $userenv{'canrequest.'.$crstype} = &Apache::lonnet::usertools_access($username,$domain,$crstype, 'reload','requestcourses', @@ -15513,15 +16211,19 @@ sub build_filters { $createdfilterform = &timebased_select_form('createdfilter',$filter); } + my $prefix = $crstype; + if ($crstype eq 'Placement') { + $prefix = 'Placement Test' + } my %lt = &Apache::lonlocal::texthash( - 'cac' => "$crstype Activity", - 'ccr' => "$crstype Created", - 'cde' => "$crstype Title", - 'cdo' => "$crstype Domain", + 'cac' => "$prefix Activity", + 'ccr' => "$prefix Created", + 'cde' => "$prefix Title", + 'cdo' => "$prefix Domain", 'ins' => 'Institutional Code', 'inc' => 'Institutional Categorization', - 'cow' => "$crstype Owner/Co-owner", - 'cop' => "$crstype Personnel Includes", + 'cow' => "$prefix Owner/Co-owner", + 'cop' => "$prefix Personnel Includes", 'cog' => 'Type', ); @@ -15529,6 +16231,8 @@ sub build_filters { my $typeval = 'Course'; if ($crstype eq 'Community') { $typeval = 'Community'; + } elsif ($crstype eq 'Placement') { + $typeval = 'Placement'; } $typeselectform = ''; } else { @@ -15537,9 +16241,15 @@ sub build_filters { $typeselectform .= ' onchange="'.$onchange.'"'; } $typeselectform .= '>'."\n"; - foreach my $posstype ('Course','Community') { + foreach my $posstype ('Course','Community','Placement') { + my $shown; + if ($posstype eq 'Placement') { + $shown = &mt('Placement Test'); + } else { + $shown = &mt($posstype); + } $typeselectform.='\n"; + ($posstype eq $crstype ? ' selected="selected" ' : ''). ">".$shown."\n"; } $typeselectform.=""; } @@ -16115,7 +16825,7 @@ sub update_content_constraints { my ($reqdmajor,$reqdminor) = split(/\./,$curr_reqd_hash{'internal.releaserequired'}); my %checkresponsetypes; foreach my $key (keys(%Apache::lonnet::needsrelease)) { - my ($item,$name,$value,$valmatch) = split(/:/,$key); + my ($item,$name,$value) = split(/:/,$key); if ($item eq 'resourcetag') { if ($name eq 'responsetype') { $checkresponsetypes{$value} = $Apache::lonnet::needsrelease{$key} @@ -16290,29 +17000,30 @@ sub symb_to_docspath { sub captcha_display { my ($context,$lonhost) = @_; my ($output,$error); - my ($captcha,$pubkey,$privkey) = &get_captcha_config($context,$lonhost); + my ($captcha,$pubkey,$privkey,$version) = + &get_captcha_config($context,$lonhost); if ($captcha eq 'original') { $output = &create_captcha(); unless ($output) { $error = 'captcha'; } } elsif ($captcha eq 'recaptcha') { - $output = &create_recaptcha($pubkey); + $output = &create_recaptcha($pubkey,$version); unless ($output) { $error = 'recaptcha'; } } - return ($output,$error,$captcha); + return ($output,$error,$captcha,$version); } sub captcha_response { my ($context,$lonhost) = @_; my ($captcha_chk,$captcha_error); - my ($captcha,$pubkey,$privkey) = &get_captcha_config($context,$lonhost); + my ($captcha,$pubkey,$privkey,$version) = &get_captcha_config($context,$lonhost); if ($captcha eq 'original') { ($captcha_chk,$captcha_error) = &check_captcha(); } elsif ($captcha eq 'recaptcha') { - $captcha_chk = &check_recaptcha($privkey); + $captcha_chk = &check_recaptcha($privkey,$version); } else { $captcha_chk = 1; } @@ -16321,7 +17032,7 @@ sub captcha_response { sub get_captcha_config { my ($context,$lonhost) = @_; - my ($captcha,$pubkey,$privkey,$hashtocheck); + my ($captcha,$pubkey,$privkey,$version,$hashtocheck); my $hostname = &Apache::lonnet::hostname($lonhost); my $serverhomeID = &Apache::lonnet::get_server_homeID($hostname); my $serverhomedom = &Apache::lonnet::host_domain($serverhomeID); @@ -16337,6 +17048,10 @@ sub get_captcha_config { } if ($privkey && $pubkey) { $captcha = 'recaptcha'; + $version = $hashtocheck->{'recaptchaversion'}; + if ($version ne '2') { + $version = 1; + } } else { $captcha = 'original'; } @@ -16354,6 +17069,10 @@ sub get_captcha_config { $privkey = $domconfhash{$serverhomedom.'.login.recaptchakeys_private'}; if ($privkey && $pubkey) { $captcha = 'recaptcha'; + $version = $domconfhash{$serverhomedom.'.login.recaptchaversion'}; + if ($version ne '2') { + $version = 1; + } } else { $captcha = 'original'; } @@ -16361,7 +17080,7 @@ sub get_captcha_config { $captcha = 'original'; } } - return ($captcha,$pubkey,$privkey); + return ($captcha,$pubkey,$privkey,$version); } sub create_captcha { @@ -16420,38 +17139,61 @@ sub check_captcha { } sub create_recaptcha { - my ($pubkey) = @_; - my $use_ssl; - if ($ENV{'SERVER_PORT'} == 443) { - $use_ssl = 1; - } - my $captcha = Captcha::reCAPTCHA->new; - return $captcha->get_options_setter({theme => 'white'})."\n". - $captcha->get_html($pubkey,undef,$use_ssl). - &mt('If the text is hard to read, [_1] will replace them.', - 'reCAPTCHA refresh'). - '

'; + my ($pubkey,$version) = @_; + if ($version >= 2) { + return '
'; + } else { + my $use_ssl; + if ($ENV{'SERVER_PORT'} == 443) { + $use_ssl = 1; + } + my $captcha = Captcha::reCAPTCHA->new; + return $captcha->get_options_setter({theme => 'white'})."\n". + $captcha->get_html($pubkey,undef,$use_ssl). + &mt('If the text is hard to read, [_1] will replace them.', + 'reCAPTCHA refresh'). + '

'; + } } sub check_recaptcha { - my ($privkey) = @_; + my ($privkey,$version) = @_; my $captcha_chk; - my $captcha = Captcha::reCAPTCHA->new; - my $captcha_result = - $captcha->check_answer( - $privkey, - $ENV{'REMOTE_ADDR'}, - $env{'form.recaptcha_challenge_field'}, - $env{'form.recaptcha_response_field'}, - ); - if ($captcha_result->{is_valid}) { - $captcha_chk = 1; + if ($version >= 2) { + my $ua = LWP::UserAgent->new; + $ua->timeout(10); + my %info = ( + secret => $privkey, + response => $env{'form.g-recaptcha-response'}, + remoteip => $ENV{'REMOTE_ADDR'}, + ); + my $response = $ua->post('https://www.google.com/recaptcha/api/siteverify',\%info); + if ($response->is_success) { + my $data = JSON::DWIW->from_json($response->decoded_content); + if (ref($data) eq 'HASH') { + if ($data->{'success'}) { + $captcha_chk = 1; + } + } + } + } else { + my $captcha = Captcha::reCAPTCHA->new; + my $captcha_result = + $captcha->check_answer( + $privkey, + $ENV{'REMOTE_ADDR'}, + $env{'form.recaptcha_challenge_field'}, + $env{'form.recaptcha_response_field'}, + ); + if ($captcha_result->{is_valid}) { + $captcha_chk = 1; + } } return $captcha_chk; } sub emailusername_info { - my @fields = ('firstname','lastname','institution','web','location','officialemail'); + my @fields = ('firstname','lastname','institution','web','location','officialemail','id'); my %titles = &Apache::lonlocal::texthash ( lastname => 'Last Name', firstname => 'First Name', @@ -16459,6 +17201,7 @@ sub emailusername_info { location => "School's city, state/province, country", web => "School's web address", officialemail => 'E-mail address at institution (if different)', + id => 'Student/Employee ID', ); return (\@fields,\%titles); } @@ -16539,11 +17282,19 @@ sub des_decrypt { } else { $cypher=new DES $keybin; } - my $plaintext= - $cypher->decrypt(unpack("a8",pack("H16",substr($cyphertext,0,16)))); - $plaintext.= - $cypher->decrypt(unpack("a8",pack("H16",substr($cyphertext,16,16)))); - $plaintext=substr($plaintext,1,ord(substr($plaintext,0,1)) ); + my $plaintext=''; + my $cypherlength = length($cyphertext); + my $numchunks = int($cypherlength/32); + for (my $j=0; $j<$numchunks; $j++) { + my $start = $j*32; + my $cypherblock = substr($cyphertext,$start,32); + my $chunk = + $cypher->decrypt(unpack("a8",pack("H16",substr($cypherblock,0,16)))); + $chunk .= + $cypher->decrypt(unpack("a8",pack("H16",substr($cypherblock,16,16)))); + $chunk=substr($chunk,1,ord(substr($chunk,0,1)) ); + $plaintext .= $chunk; + } return $plaintext; }