--- loncom/interface/domainprefs.pm 2019/08/17 19:24:29 1.160.6.97 +++ loncom/interface/domainprefs.pm 2020/02/12 23:06:16 1.160.6.102.2.3 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Handler to set domain-wide configuration settings # -# $Id: domainprefs.pm,v 1.160.6.97 2019/08/17 19:24:29 raeburn Exp $ +# $Id: domainprefs.pm,v 1.160.6.102.2.3 2020/02/12 23:06:16 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -216,13 +216,27 @@ sub handler { 'contacts','defaults','scantron','coursecategories', 'serverstatuses','requestcourses','helpsettings', 'coursedefaults','usersessions','loadbalancing', - 'requestauthor','selfenrollment','inststatus'],$dom); - my @prefs_order = ('rolecolors','login','defaults','quotas','autoenroll', + 'requestauthor','selfenrollment','inststatus', + 'passwords','ltitools'],$dom); + if (ref($domconfig{'ltitools'}) eq 'HASH') { + my %encconfig = + &Apache::lonnet::get_dom('encconfig',['ltitools'],$dom); + if (ref($encconfig{'ltitools'}) eq 'HASH') { + foreach my $id (keys(%{$domconfig{'ltitools'}})) { + if (ref($domconfig{'ltitools'}{$id}) eq 'HASH') { + foreach my $item ('key','secret') { + $domconfig{'ltitools'}{$id}{$item} = $encconfig{'ltitools'}{$id}{$item}; + } + } + } + } + } + my @prefs_order = ('rolecolors','login','defaults','passwords','quotas','autoenroll', 'autoupdate','autocreate','directorysrch','contacts', 'usercreation','selfcreation','usermodification','scantron', 'requestcourses','requestauthor','coursecategories', 'serverstatuses','helpsettings','coursedefaults', - 'selfenrollment','usersessions'); + 'ltitools','selfenrollment','usersessions'); my %existing; if (ref($domconfig{'loadbalancing'}) eq 'HASH') { %existing = %{$domconfig{'loadbalancing'}}; @@ -262,13 +276,25 @@ sub handler { help => 'Domain_Configuration_LangTZAuth', header => [{col1 => 'Setting', col2 => 'Value'}, - {col1 => 'Internal Authentication', - col2 => 'Value'}, {col1 => 'Institutional user types', col2 => 'Name displayed'}], print => \&print_defaults, modify => \&modify_defaults, }, + 'passwords' => + { text => 'Passwords (Internal authentication)', + help => 'Domain_Configuration_Passwords', + header => [{col1 => 'Resetting Forgotten Password', + col2 => 'Settings'}, + {col1 => 'Encryption of Stored Passwords (Internal Auth)', + col2 => 'Settings'}, + {col1 => 'Rules for LON-CAPA Passwords', + col2 => 'Settings'}, + {col1 => 'Course Owner Changing Student Passwords', + col2 => 'Settings'}], + print => \&print_passwords, + modify => \&modify_passwords, + }, 'quotas' => { text => 'Blogs, personal web pages, webDAV/quotas, portfolios', help => 'Domain_Configuration_Quotas', @@ -476,6 +502,14 @@ sub handler { print => \&print_loadbalancing, modify => \&modify_loadbalancing, }, + 'ltitools' => + {text => 'External Tools (LTI)', + help => 'Domain_Configuration_LTI_Tools', + header => [{col1 => 'Setting', + col2 => 'Value',}], + print => \&print_ltitools, + modify => \&modify_ltitools, + }, ); if (keys(%servers) > 1) { $prefs{'login'} = { text => 'Log-in page options', @@ -658,6 +692,10 @@ sub process_changes { $output = &modify_usersessions($dom,$lastactref,%domconfig); } elsif ($action eq 'loadbalancing') { $output = &modify_loadbalancing($dom,%domconfig); + } elsif ($action eq 'passwords') { + $output = &modify_passwords($r,$dom,$confname,$lastactref,%domconfig); + } elsif ($action eq 'ltitools') { + $output = &modify_ltitools($r,$dom,$action,$lastactref,%domconfig); } return $output; } @@ -670,6 +708,8 @@ sub print_config_box { $output = &coursecategories_javascript($settings); } elsif ($action eq 'defaults') { $output = &defaults_javascript($settings); + } elsif ($action eq 'passwords') { + $output = &passwords_javascript(); } elsif ($action eq 'helpsettings') { my (%privs,%levelscurrent); my %full=(); @@ -702,6 +742,7 @@ sub print_config_box { if ($numheaders > 1) { my $colspan = ''; my $rightcolspan = ''; + my $leftnobr = ''; if (($action eq 'rolecolors') || ($action eq 'defaults') || ($action eq 'directorysrch') || (($action eq 'login') && ($numheaders < 4))) { @@ -710,12 +751,15 @@ sub print_config_box { if ($action eq 'usersessions') { $rightcolspan = ' colspan="3"'; } + if ($action eq 'passwords') { + $leftnobr = ' LC_nobreak'; + } $output .= ' - + '; $rowtotal ++; @@ -724,6 +768,8 @@ sub print_config_box { ($action eq 'selfenrollment') || ($action eq 'usersessions') || ($action eq 'directorysrch') || ($action eq 'helpsettings') || ($action eq 'contacts')) { $output .= $item->{'print'}->('top',$dom,$settings,\$rowtotal); + } elsif ($action eq 'passwords') { + $output .= $item->{'print'}->('top',$dom,$confname,$settings,\$rowtotal); } elsif ($action eq 'coursecategories') { $output .= $item->{'print'}->('top',$dom,$item,$settings,\$rowtotal); } elsif ($action eq 'scantron') { @@ -755,10 +801,12 @@ sub print_config_box { if (($action eq 'autoupdate') || ($action eq 'usercreation') || ($action eq 'selfcreation') || ($action eq 'selfenrollment') || ($action eq 'usersessions') || ($action eq 'coursecategories') || - ($action eq 'contacts') || ($action eq 'defaults')) { + ($action eq 'contacts') || ($action eq 'passwords')) { if ($action eq 'coursecategories') { $output .= &print_coursecategories('middle',$dom,$item,$settings,\$rowtotal); $colspan = ' colspan="2"'; + } elsif ($action eq 'passwords') { + $output .= $item->{'print'}->('middle',$dom,$confname,$settings,\$rowtotal); } else { $output .= $item->{'print'}->('middle',$dom,$settings,\$rowtotal); } @@ -770,11 +818,28 @@ sub print_config_box { + + + + + '; } else { $output .= $item->{'print'}->('bottom',$dom,$settings,\$rowtotal); } @@ -941,7 +1006,8 @@ sub print_config_box { if ($action eq 'quotas') { $output .= &print_quotas($dom,$settings,\$rowtotal,$action); } elsif (($action eq 'autoenroll') || ($action eq 'autocreate') || - ($action eq 'serverstatuses') || ($action eq 'loadbalancing')) { + ($action eq 'serverstatuses') || ($action eq 'loadbalancing') || + ($action eq 'ltitools')) { $output .= $item->{'print'}->($dom,$settings,\$rowtotal); } } @@ -2457,6 +2523,74 @@ $jstext{'templates'}; ENDSCRIPT } +sub ltitools_javascript { + my ($settings) = @_; + return unless(ref($settings) eq 'HASH'); + my (%ordered,$total,%jstext); + $total = 0; + foreach my $item (keys(%{$settings})) { + if (ref($settings->{$item}) eq 'HASH') { + my $num = $settings->{$item}{'order'}; + $ordered{$num} = $item; + } + } + $total = scalar(keys(%{$settings})); + my @jsarray = (); + foreach my $item (sort {$a <=> $b } (keys(%ordered))) { + push(@jsarray,$ordered{$item}); + } + my $jstext = ' var ltitools = Array('."'".join("','",@jsarray)."'".');'."\n"; + return <<"ENDSCRIPT"; + + +ENDSCRIPT +} + sub print_autoenroll { my ($dom,$settings,$rowtotal) = @_; my $autorun = &Apache::lonnet::auto_run(undef,$dom), @@ -2931,7 +3065,54 @@ sub print_contacts { $to{$item}.'" />'; $rownum ++; } - } else { + } elsif ($position eq 'bottom') { + $css_class = $rownum%2?' class="LC_odd_row"':''; + $datatable .= ''. + ''."\n"; + $rownum ++; + } + unless ($position eq 'top') { foreach my $type (@mailings) { $css_class = $rownum%2?' class="LC_odd_row"':''; $datatable .= ''. @@ -2991,56 +3172,138 @@ sub print_contacts { \%choices,$rownum); $datatable .= $reports; } elsif ($position eq 'bottom') { - $css_class = $rownum%2?' class="LC_odd_row"':''; - $datatable .= ''. - ''."\n"; - $rownum ++; } $$rowtotal += $rownum; return $datatable; } +sub overridden_helpdesk { + my ($checked,$otheremails,$bccemails,$includeloc,$includestr,$type,$rowid, + $typetitle,$css_class,$rowstyle,$contacts,$short_titles) = @_; + my $class = 'LC_left_item'; + if ($css_class) { + $css_class = ' class="'.$css_class.'"'; + } + if ($rowid) { + $rowid = ' id="'.$rowid.'"'; + } + if ($rowstyle) { + $rowstyle = ' style="'.$rowstyle.'"'; + } + my ($output,$description); + $description = &mt('Helpdesk requests from: [_1] in this domain (overrides default)',"$typetitle"); + $output = ''. + "\n". + ''."\n"; + return $output; +} + sub contacts_javascript { return <<"ENDSCRIPT"; @@ -3058,6 +3321,36 @@ function screenshotSize(field) { return; } +function toggleHelpdeskRow(form,checkbox,target,prefix,docount) { + if (form.elements[checkbox].length != undefined) { + var count = 0; + if (docount) { + for (var i=0; i @@ -3505,6 +3798,373 @@ sub radiobutton_prefs { return ($datatable,$itemcount); } +sub print_ltitools { + my ($dom,$settings,$rowtotal) = @_; + my $rownum = 0; + my $css_class; + my $itemcount = 1; + my $maxnum = 0; + my %ordered; + if (ref($settings) eq 'HASH') { + foreach my $item (keys(%{$settings})) { + if (ref($settings->{$item}) eq 'HASH') { + my $num = $settings->{$item}{'order'}; + $ordered{$num} = $item; + } + } + } + my $confname = $dom.'-domainconfig'; + my $switchserver = &check_switchserver($dom,$confname); + my $maxnum = scalar(keys(%ordered)); + my $datatable = <itools_javascript($settings); + my %lt = <itools_names(); + my @courseroles = ('cc','in','ta','ep','st'); + my @ltiroles = qw(Instructor ContentDeveloper TeachingAssistant Learner); + my @fields = ('fullname','firstname','lastname','email','user','roles'); + if (keys(%ordered)) { + my @items = sort { $a <=> $b } keys(%ordered); + for (my $i=0; $i<@items; $i++) { + $css_class = $itemcount%2?' class="LC_odd_row"':''; + my $item = $ordered{$items[$i]}; + my ($title,$key,$secret,$url,$imgsrc,$version); + if (ref($settings->{$item}) eq 'HASH') { + $title = $settings->{$item}->{'title'}; + $url = $settings->{$item}->{'url'}; + $key = $settings->{$item}->{'key'}; + $secret = $settings->{$item}->{'secret'}; + my $image = $settings->{$item}->{'image'}; + if ($image ne '') { + $imgsrc = ''.&mt('Tool Provider icon').''; + } + } + my $chgstr = ' onchange="javascript:reorderLTI(this.form,'."'ltitools_".$item."'".');"'; + $datatable .= ''. + ''."\n"; + $itemcount ++; + } + } + $css_class = $itemcount%2?' class="LC_odd_row"':''; + my $chgstr = ' onchange="javascript:reorderLTI(this.form,'."'ltitools_add_pos'".');"'; + $datatable .= ''."\n". + ''."\n". + ''."\n". + ''."\n"; + $itemcount ++; + return $datatable; +} + +sub ltitools_names { + my %lt = &Apache::lonlocal::texthash( + 'title' => 'Title', + 'version' => 'Version', + 'msgtype' => 'Message Type', + 'url' => 'URL', + 'key' => 'Key', + 'secret' => 'Secret', + 'icon' => 'Icon', + 'user' => 'Username:domain', + 'fullname' => 'Full Name', + 'firstname' => 'First Name', + 'lastname' => 'Last Name', + 'email' => 'E-mail', + 'roles' => 'Role', + 'window' => 'Window', + 'tab' => 'Tab', + 'iframe' => 'iFrame', + 'height' => 'Height', + 'width' => 'Width', + 'linktext' => 'Default Link Text', + 'explanation' => 'Default Explanation', + 'passback' => 'Tool can return grades:', + 'roster' => 'Tool can retrieve roster:', + 'crstarget' => 'Display target', + 'crslabel' => 'Course label', + 'crstitle' => 'Course title', + 'crslinktext' => 'Link Text', + 'crsexplanation' => 'Explanation', + ); + + return %lt; +} + sub print_coursedefaults { my ($position,$dom,$settings,$rowtotal) = @_; my ($css_class,$datatable,%checkedon,%checkedoff,%defaultchecked,@toggles); @@ -4017,6 +4677,428 @@ sub print_validation_rows { return $datatable; } +sub print_passwords { + my ($position,$dom,$confname,$settings,$rowtotal) = @_; + my ($datatable,$css_class); + my $itemcount = 0; + my %titles = &Apache::lonlocal::texthash ( + captcha => '"Forgot Password" CAPTCHA validation', + link => 'Reset link expiration (hours)', + case => 'Case-sensitive usernames/e-mail', + prelink => 'Information required (form 1)', + postlink => 'Information required (form 2)', + emailsrc => 'LON-CAPA e-mail address type(s)', + customtext => 'Domain specific text (HTML)', + intauth_cost => 'Encryption cost for bcrypt (positive integer)', + intauth_check => 'Check bcrypt cost if authenticated', + intauth_switch => 'Existing crypt-based switched to bcrypt on authentication', + permanent => 'Permanent e-mail address', + critical => 'Critical notification address', + notify => 'Notification address', + min => 'Minimum password length', + max => 'Maximum password length', + chars => 'Required characters', + numsaved => 'Number of previous passwords to save and disallow reuse', + ); + if ($position eq 'top') { + my ($othertitle,$usertypes,$types) = &Apache::loncommon::sorted_inst_types($dom); + my $shownlinklife = 2; + my $prelink = 'both'; + my (%casesens,%postlink,%emailsrc,$nostdtext,$customurl); + if (ref($settings) eq 'HASH') { + if ($settings->{resetlink} =~ /^\d+(|\.\d*)$/) { + $shownlinklife = $settings->{resetlink}; + } + if (ref($settings->{resetcase}) eq 'ARRAY') { + map { $casesens{$_} = 1; } (@{$settings->{resetcase}}); + } + if ($settings->{resetprelink} =~ /^(both|either)$/) { + $prelink = $settings->{resetprelink}; + } + if (ref($settings->{resetpostlink}) eq 'HASH') { + %postlink = %{$settings->{resetpostlink}}; + } + if (ref($settings->{resetemail}) eq 'ARRAY') { + map { $emailsrc{$_} = 1; } (@{$settings->{resetemail}}); + } + if ($settings->{resetremove}) { + $nostdtext = 1; + } + if ($settings->{resetcustom}) { + $customurl = $settings->{resetcustom}; + } + } else { + if (ref($types) eq 'ARRAY') { + foreach my $item (@{$types}) { + $casesens{$item} = 1; + $postlink{$item} = ['username','email']; + } + } + $casesens{'default'} = 1; + $postlink{'default'} = ['username','email']; + $prelink = 'both'; + %emailsrc = ( + permanent => 1, + critical => 1, + notify => 1, + ); + } + $datatable = &captcha_choice('passwords',$settings,$$rowtotal); + $itemcount ++; + $css_class = $itemcount%2?' class="LC_odd_row"':''; + $datatable .= ''. + ''; + $itemcount ++; + $css_class = $itemcount%2?' class="LC_odd_row"':''; + $datatable .= ''. + ''; + $itemcount ++; + $css_class = $itemcount%2?' class="LC_odd_row"':''; + my %checkedpre = ( + both => ' checked="checked"', + either => '', + ); + if ($prelink eq 'either') { + $checkedpre{either} = ' checked="checked"'; + $checkedpre{both} = ''; + } + $datatable .= ''. + ''; + $itemcount ++; + $css_class = $itemcount%2?' class="LC_odd_row"':''; + $datatable .= ''. + ''; + $itemcount ++; + $css_class = $itemcount%2?' class="LC_odd_row"':''; + $datatable .= ''. + ''; + $itemcount ++; + $css_class = $itemcount%2?' class="LC_odd_row"':''; + my $switchserver = &check_switchserver($dom,$confname); + my ($showstd,$noshowstd); + if ($nostdtext) { + $noshowstd = ' checked="checked"'; + } else { + $showstd = ' checked="checked"'; + } + $datatable .= ''. + ''; + } elsif ($position eq 'middle') { + my %domconf = &Apache::lonnet::get_dom('configuration',['defaults'],$dom); + my @items = ('intauth_cost','intauth_check','intauth_switch'); + my %defaults; + if (ref($domconf{'defaults'}) eq 'HASH') { + %defaults = %{$domconf{'defaults'}}; + if ($defaults{'intauth_cost'} !~ /^\d+$/) { + $defaults{'intauth_cost'} = 10; + } + if ($defaults{'intauth_check'} !~ /^(0|1|2)$/) { + $defaults{'intauth_check'} = 0; + } + if ($defaults{'intauth_switch'} !~ /^(0|1|2)$/) { + $defaults{'intauth_switch'} = 0; + } + } else { + %defaults = ( + 'intauth_cost' => 10, + 'intauth_check' => 0, + 'intauth_switch' => 0, + ); + } + foreach my $item (@items) { + if ($itemcount%2) { + $css_class = ''; + } else { + $css_class = ' class="LC_odd_row" '; + } + $datatable .= ''. + ''; + $itemcount ++; + } + } elsif ($position eq 'lower') { + my ($min,$max,%chars,$numsaved); + $min = $Apache::lonnet::passwdmin; + if (ref($settings) eq 'HASH') { + if ($settings->{min}) { + $min = $settings->{min}; + } + if ($settings->{max}) { + $max = $settings->{max}; + } + if (ref($settings->{chars}) eq 'ARRAY') { + map { $chars{$_} = 1; } (@{$settings->{chars}}); + } + if ($settings->{numsaved}) { + $numsaved = $settings->{numsaved}; + } + } + my %rulenames = &Apache::lonlocal::texthash( + uc => 'At least one upper case letter', + lc => 'At least one lower case letter', + num => 'At least one number', + spec => 'At least one non-alphanumeric', + ); + $css_class = $itemcount%2?' class="LC_odd_row"':''; + $datatable .= ''. + ''; + $itemcount ++; + $css_class = $itemcount%2?' class="LC_odd_row"':''; + $datatable .= ''. + ''; + $itemcount ++; + $css_class = $itemcount%2?' class="LC_odd_row"':''; + $datatable .= ''; + my $numinrow = 2; + my @possrules = ('uc','lc','num','spec'); + $datatable .= ''; + $itemcount ++; + $css_class = $itemcount%2?' class="LC_odd_row"':''; + $datatable .= ''. + ''; + } else { + my ($othertitle,$usertypes,$types) = &Apache::loncommon::sorted_inst_types($dom); + my %ownerchg = ( + by => {}, + for => {}, + ); + my %ownertitles = &Apache::lonlocal::texthash ( + by => 'Course owner status(es) allowed', + for => 'Student status(es) allowed', + ); + if (ref($settings) eq 'HASH') { + if (ref($settings->{crsownerchg}) eq 'HASH') { + if (ref($settings->{crsownerchg}{'by'}) eq 'ARRAY') { + map { $ownerchg{by}{$_} = 1; } (@{$settings->{crsownerchg}{'by'}}); + } + if (ref($settings->{crsownerchg}{'for'}) eq 'ARRAY') { + map { $ownerchg{for}{$_} = 1; } (@{$settings->{crsownerchg}{'for'}}); + } + } + } + $css_class = $itemcount%2?' class="LC_odd_row"':''; + $datatable .= ''. + ''. + ''; + } + return $datatable; +} + sub print_usersessions { my ($position,$dom,$settings,$rowtotal) = @_; my ($css_class,$datatable,%checked,%choices); @@ -4834,8 +5916,8 @@ sub contact_titles { 'adminemail' => 'Default Server Admin E-mail address', 'errormail' => 'Error reports to be e-mailed to', 'packagesmail' => 'Package update alerts to be e-mailed to', - 'helpdeskmail' => "Helpdesk requests for this domain's users", - 'otherdomsmail' => 'Helpdesk requests for other (unconfigured) domains', + 'helpdeskmail' => "Helpdesk requests from all users in this domain", + 'otherdomsmail' => 'Helpdesk requests from users in other (unconfigured) domains', 'lonstatusmail' => 'E-mail from nightly status check (warnings/errors)', 'requestsmail' => 'E-mail from course requests requiring approval', 'updatesmail' => 'E-mail from nightly check of LON-CAPA module integrity/updates', @@ -5575,10 +6657,14 @@ sub captcha_choice { $vertext,$currver); my %lt = &captcha_phrases(); $keyentry = 'hidden'; + my $colspan=2; if ($context eq 'cancreate') { $rowname = &mt('CAPTCHA validation'); } elsif ($context eq 'login') { $rowname = &mt('"Contact helpdesk" CAPTCHA validation'); + } elsif ($context eq 'passwords') { + $rowname = &mt('"Forgot Password" CAPTCHA validation'); + $colspan=1; } if (ref($settings) eq 'HASH') { if ($settings->{'captcha'}) { @@ -5618,7 +6704,7 @@ sub captcha_choice { $css_class .= ' style="'.$rowstyle.'"'; } my $output = ''. - ''; + $output .= '
'.&mt($item->{'header'}->[0]->{'col1'}).''.&mt($item->{'header'}->[0]->{'col1'}).' '.&mt($item->{'header'}->[0]->{'col2'}).'
- + '."\n"; if ($action eq 'coursecategories') { $output .= &print_coursecategories('bottom',$dom,$item,$settings,\$rowtotal); + } elsif ($action eq 'passwords') { + $output .= $item->{'print'}->('lower',$dom,$confname,$settings,\$rowtotal).' + +
'.&mt($item->{'header'}->[2]->{'col1'}).''.&mt($item->{'header'}->[2]->{'col1'}).' '.&mt($item->{'header'}->[2]->{'col2'}).'
+
+ + + + '."\n". + $item->{'print'}->('bottom',$dom,$confname,$settings,\$rowtotal).' +
'.&mt($item->{'header'}->[3]->{'col1'}).''.&mt($item->{'header'}->[3]->{'col2'}).'
+
'.&mt('Extra helpdesk form fields:').'
'. + &mt('(e-mail, subject, and description always shown)'). + '
'; + if ((ref($fields) eq 'ARRAY') && (ref($fieldtitles) eq 'HASH') && + (ref($fieldoptions) eq 'HASH') && (ref($possoptions) eq 'HASH')) { + $datatable .= ''; + foreach my $field (@{$fields}) { + $datatable .= ''. + ''; + } + $datatable .= '
'.&mt('Field').''.&mt('Status').'
'.$fieldtitles->{$field}; + if (($field eq 'screenshot') || ($field eq 'cc')) { + $datatable .= ' '.&mt('(logged-in users)'); + } + $datatable .=''; + my $clickaction; + if ($field eq 'screenshot') { + $clickaction = ' onclick="screenshotSize(this);"'; + } + if (ref($possoptions->{$field}) eq 'ARRAY') { + foreach my $option (@{$possoptions->{$field}}) { + my $checked; + if ($currfield{$field} eq $option) { + $checked = ' checked="checked"'; + } + $datatable .= ''.(' 'x2); + } + } + if ($field eq 'screenshot') { + my $display; + if ($currfield{$field} eq 'no') { + $display = ' style="display:none"'; + } + $datatable .= '
'.&mt('Maximum size for upload (MB)').''. + ''; + } + $datatable .= '
'; + } + $datatable .= '
'.&mt('Extra helpdesk form fields:').'
'. - &mt('(e-mail, subject, and description always shown)'). - '
'; - if ((ref($fields) eq 'ARRAY') && (ref($fieldtitles) eq 'HASH') && - (ref($fieldoptions) eq 'HASH') && (ref($possoptions) eq 'HASH')) { - $datatable .= ''; - foreach my $field (@{$fields}) { - $datatable .= ''. - ''; } - $datatable .= '
'.&mt('Field').''.&mt('Status').'
'.$fieldtitles->{$field}; - if (($field eq 'screenshot') || ($field eq 'cc')) { - $datatable .= ' '.&mt('(logged-in users)'); - } - $datatable .=''; - my $clickaction; - if ($field eq 'screenshot') { - $clickaction = ' onclick="screenshotSize(this);"'; - } - if (ref($possoptions->{$field}) eq 'ARRAY') { - foreach my $option (@{$possoptions->{$field}}) { - my $checked; - if ($currfield{$field} eq $option) { - $checked = ' checked="checked"'; + my ($othertitle,$usertypes,$types) = &Apache::loncommon::sorted_inst_types($dom); + my (@posstypes,%usertypeshash); + if (ref($types) eq 'ARRAY') { + @posstypes = @{$types}; + } + if (@posstypes) { + if (ref($usertypes) eq 'HASH') { + %usertypeshash = %{$usertypes}; + } + my @overridden; + my $numinrow = 4; + if (ref($settings) eq 'HASH') { + if (ref($settings->{'overrides'}) eq 'HASH') { + foreach my $key (sort(keys(%{$settings->{'overrides'}}))) { + if (ref($settings->{'overrides'}{$key}) eq 'HASH') { + push(@overridden,$key); + foreach my $item (@contacts) { + if ($settings->{'overrides'}{$key}{$item}) { + $checked{'override_'.$key}{$item} = ' checked="checked" '; + } + } + $otheremails{'override_'.$key} = $settings->{'overrides'}{$key}{'others'}; + $bccemails{'override_'.$key} = $settings->{'overrides'}{$key}{'bcc'}; + $includeloc{'override_'.$key} = ''; + $includestr{'override_'.$key} = ''; + if ($settings->{'overrides'}{$key}{'include'} ne '') { + ($includeloc{'override_'.$key},$includestr{'override_'.$key}) = + split(/:/,$settings->{'overrides'}{$key}{'include'},2); + $includestr{'override_'.$key} = &unescape($includestr{'override_'.$key}); + } } - $datatable .= ''.(' 'x2); } } - if ($field eq 'screenshot') { - my $display; - if ($currfield{$field} eq 'no') { - $display = ' style="display:none"'; - } - $datatable .= '
'.&mt('Maximum size for upload (MB)').''. - ''; + } + my $customclass = 'LC_helpdesk_override'; + my $optionsprefix = 'LC_options_helpdesk_'; + + my $onclicktypes = "toggleHelpdeskRow(this.form,'overrides','$customclass','$optionsprefix');"; + + $datatable .= &insttypes_row($settings,$types,$usertypes,$dom, + $numinrow,$othertitle,'overrides', + \$rownum,$onclicktypes,$customclass); + $rownum ++; + $usertypeshash{'default'} = $othertitle; + foreach my $status (@posstypes) { + my $css_class; + if ($rownum%2) { + $css_class = 'LC_odd_row '; + } + $css_class .= $customclass; + my $rowid = $optionsprefix.$status; + my $hidden = 1; + my $currstyle = 'display:none'; + if (grep(/^\Q$status\E$/,@overridden)) { + $currstyle = 'display:table-row'; + $hidden = 0; + } + my $key = 'override_'.$status; + $datatable .= &overridden_helpdesk($checked{$key},$otheremails{$key},$bccemails{$key}, + $includeloc{$key},$includestr{$key},$status,$rowid, + $usertypeshash{$status},$css_class,$currstyle, + \@contacts,$short_titles); + unless ($hidden) { + $rownum ++; } - $datatable .= '
'; } - $datatable .= '
$description'. + '
'.&mt('E-mail recipient(s)').''. + ''; + if (ref($contacts) eq 'ARRAY') { + foreach my $item (@{$contacts}) { + my $check; + if (ref($checked) eq 'HASH') { + $check = $checked->{$item}; + } + my $title; + if (ref($short_titles) eq 'HASH') { + $title = $short_titles->{$item}; + } + $output .= ' '; + } + } + $output .= '
'.&mt('Others').':  '. + ''; + my %locchecked; + foreach my $loc ('s','b') { + if ($includeloc eq $loc) { + $locchecked{$loc} = ' checked="checked"'; + last; + } + } + $output .= '
'.&mt('Bcc:').(' 'x6). + '
'. + '
'.&mt('Optional added text').''. + &mt('Text automatically added to e-mail:').' '. + '
'. + ''.&mt('Location:').' '. + ''. + (' 'x2). + ''. + '
'. + '
' + .''.(' 'x2). + ''. + '
'.&mt('Required settings').''. + ''.$lt{'title'}.': '. + (' 'x2). + ''.$lt{'version'}.': '. + (' 'x2). + ''.$lt{'msgtype'}.': '. + '

'. + ''.$lt{'url'}.':'. + (' 'x2). + ''.$lt{'key'}. + ' '. + (' 'x2). + ''.$lt{'secret'}.':'. + ''. + ''. + ''. + '
'. + '
'.&mt('Optional settings').''. + ''.&mt('Display target:'); + my %currdisp; + if (ref($settings->{$item}->{'display'}) eq 'HASH') { + if ($settings->{$item}->{'display'}->{'target'} eq 'window') { + $currdisp{'window'} = ' checked="checked"'; + } elsif ($settings->{$item}->{'display'}->{'target'} eq 'tab') { + $currdisp{'tab'} = ' checked="checked"'; + } else { + $currdisp{'iframe'} = ' checked="checked"'; + } + if ($settings->{$item}->{'display'}->{'width'} =~ /^(\d+)$/) { + $currdisp{'width'} = $1; + } + if ($settings->{$item}->{'display'}->{'height'} =~ /^(\d+)$/) { + $currdisp{'height'} = $1; + } + $currdisp{'linktext'} = $settings->{$item}->{'display'}->{'linktext'}; + $currdisp{'explanation'} = $settings->{$item}->{'display'}->{'explanation'}; + } else { + $currdisp{'iframe'} = ' checked="checked"'; + } + foreach my $disp ('iframe','tab','window') { + $datatable .= ''.(' 'x2); + } + $datatable .= (' 'x4); + foreach my $dimen ('width','height') { + $datatable .= ''. + (' 'x2); + } + $datatable .= '
'. + '
'.$lt{'linktext'}.'
'. + '
'. + '
'.$lt{'explanation'}.'
'. + '

'; + $datatable .= '
'; + foreach my $extra ('passback','roster') { + my $checkedon = ''; + my $checkedoff = ' checked="checked"'; + if ($settings->{$item}->{$extra}) { + $checkedon = $checkedoff; + $checkedoff = ''; + } + $datatable .= $lt{$extra}.' '. + ''.(' 'x2). + ''.(' 'x4); + } + $datatable .= '

'.$lt{'icon'}.': '; + if ($imgsrc) { + $datatable .= $imgsrc. + ' '. + ' '.&mt('Replace:').' '; + } else { + $datatable .= '('.&mt('if larger than 21x21 pixels, image will be scaled').') '; + } + if ($switchserver) { + $datatable .= &mt('Upload to library server: [_1]',$switchserver); + } else { + $datatable .= ''; + } + $datatable .= '
'; + my (%checkedfields,%rolemaps); + if (ref($settings->{$item}) eq 'HASH') { + if (ref($settings->{$item}->{'fields'}) eq 'HASH') { + %checkedfields = %{$settings->{$item}->{'fields'}}; + } + if (ref($settings->{$item}->{'roles'}) eq 'HASH') { + %rolemaps = %{$settings->{$item}->{'roles'}}; + $checkedfields{'roles'} = 1; + } + } + $datatable .= '
'.&mt('User data sent on launch').''. + ''; + foreach my $field (@fields) { + my $checked; + if ($checkedfields{$field}) { + $checked = ' checked="checked"'; + } + $datatable .= ''.(' ' x2); + } + $datatable .= '
'. + '
'.&mt('Role mapping').''; + foreach my $role (@courseroles) { + my ($selected,$selectnone); + if (!$rolemaps{$role}) { + $selectnone = ' selected="selected"'; + } + $datatable .= ''; + } + $datatable .= '
'. + &Apache::lonnet::plaintext($role,'Course').'
'. + '
'; + my %courseconfig; + if (ref($settings->{$item}) eq 'HASH') { + if (ref($settings->{$item}->{'crsconf'}) eq 'HASH') { + %courseconfig = %{$settings->{$item}->{'crsconf'}}; + } + } + $datatable .= '
'.&mt('Configurable in course').''; + foreach my $item ('label','title','target','linktext','explanation') { + my $checked; + if ($courseconfig{$item}) { + $checked = ' checked="checked"'; + } + $datatable .= ''.(' ' x2)."\n"; + } + $datatable .= '
'. + '
'.&mt('Custom items sent on launch').''. + ''; + if (ref($settings->{$item}->{'custom'}) eq 'HASH') { + my %custom = %{$settings->{$item}->{'custom'}}; + if (keys(%custom) > 0) { + foreach my $key (sort(keys(%custom))) { + $datatable .= ''. + ''; + } + } + } + $datatable .= ''; + $datatable .= '
'.&mt('Action').''.&mt('Name').''.&mt('Value').'
'. + ''.$key.'
'. + ''. + '
'."\n". + ''."\n". + ' '."\n". + ''.&mt('Add').''. + '
'.&mt('Required settings').''. + ''.$lt{'title'}.': '."\n". + (' 'x2). + ''.$lt{'version'}.': '."\n". + (' 'x2). + ''.$lt{'msgtype'}.': '. + '
'. + ''.$lt{'url'}.': '."\n". + (' 'x2). + ''.$lt{'key'}.': '."\n". + (' 'x2). + ''.$lt{'secret'}.':'. + ' '."\n". + '
'. + '
'.&mt('Optional settings').''. + ''.&mt('Display target:'); + my %defaultdisp; + $defaultdisp{'iframe'} = ' checked="checked"'; + foreach my $disp ('iframe','tab','window') { + $datatable .= ''.(' 'x2); + } + $datatable .= (' 'x4); + foreach my $dimen ('width','height') { + $datatable .= ''. + (' 'x2); + } + $datatable .= '
'. + '
'.$lt{'linktext'}.'
'. + '
'. + '
'.$lt{'explanation'}.'
'. + ''. + '

'; + foreach my $extra ('passback','roster') { + $datatable .= $lt{$extra}.' '. + ''.(' 'x2). + ''.(' 'x4); + } + $datatable .= '

'.$lt{'icon'}.': '. + '('.&mt('if larger than 21x21 pixels, image will be scaled').') '; + if ($switchserver) { + $datatable .= &mt('Upload to library server: [_1]',$switchserver); + } else { + $datatable .= ''; + } + $datatable .= '
'. + '
'.&mt('User data sent on launch').''. + ''; + foreach my $field (@fields) { + $datatable .= ''.(' ' x2); + } + $datatable .= '
'. + '
'.&mt('Role mapping').''; + foreach my $role (@courseroles) { + my ($checked,$checkednone); + $datatable .= ''; + } + $datatable .= '
'. + &Apache::lonnet::plaintext($role,'Course').'
'. + '
'. + '
'.&mt('Configurable in course').''; + foreach my $item ('label','title','target','linktext','explanation') { + $datatable .= ''.(' ' x2)."\n"; + } + $datatable .= '
'. + '
'.&mt('Custom items sent on launch').''. + ''. + ''. + '
'.&mt('Action').''.&mt('Name').''.&mt('Value').'
'. + ''. + '
'.$titles{'link'}.''. + '
'.$titles{'case'}.''; + if ((ref($types) eq 'ARRAY') && (ref($usertypes) eq 'HASH')) { + foreach my $item (@{$types}) { + my $checkedcase; + if ($casesens{$item}) { + $checkedcase = ' checked="checked"'; + } + $datatable .= ''. + '   '; + } + } + my $checkedcase; + if ($casesens{'default'}) { + $checkedcase = ' checked="checked"'; + } + $datatable .= ''.$titles{'prelink'}.''. + '   '. + '
'.$titles{'postlink'}.''; + my %postlinked; + if ((ref($types) eq 'ARRAY') && (ref($usertypes) eq 'HASH')) { + foreach my $item (@{$types}) { + undef(%postlinked); + $datatable .= '
'. + ''.$usertypes->{$item}.''; + if (ref($postlink{$item}) eq 'ARRAY') { + map { $postlinked{$_} = 1; } (@{$postlink{$item}}); + } + foreach my $field ('email','username') { + my $checked; + if ($postlinked{$field}) { + $checked = ' checked="checked"'; + } + $datatable .= ''. + '   '; + } + $datatable .= '
'; + } + } + if (ref($postlink{'default'}) eq 'ARRAY') { + map { $postlinked{$_} = 1; } (@{$postlink{'default'}}); + } + $datatable .= '
'. + ''.$othertitle.''; + foreach my $field ('email','username') { + my $checked; + if ($postlinked{$field}) { + $checked = ' checked="checked"'; + } + $datatable .= ''. + '   '; + } + $datatable .= '
'.$titles{'emailsrc'}.''; + foreach my $type ('permanent','critical','notify') { + my $checkedemail; + if ($emailsrc{$type}) { + $checkedemail = ' checked="checked"'; + } + $datatable .= ''. + '   '; + } + $datatable .= '
'.$titles{'customtext'}.''. + &mt('Retain standard text:'). + ''.' '. + '
'. + ''. + &mt('(If you use the same account ... reset a password from this page.)').'

'. + &mt('Include custom text:'); + if ($customurl) { + my $link = &Apache::loncommon::modal_link($customurl,&mt('custom text'),600,500, + undef,undef,undef,undef,'background-color:#ffffff'); + $datatable .= ' '.$link. + ''. + '  '.&mt('Replace:').''; + } + if ($switchserver) { + $datatable .= ' '.&mt('Upload to library server: [_1]',$switchserver).''; + } else { + $datatable .=' '. + ''; + } + $datatable .= '
'.$titles{$item}. + ''; + if ($item eq 'intauth_switch') { + my @options = (0,1,2); + my %optiondesc = &Apache::lonlocal::texthash ( + 0 => 'No', + 1 => 'Yes', + 2 => 'Yes, and copy existing passwd file to passwd.bak file', + ); + $datatable .= ''; + foreach my $option (@options) { + my $checked = ' '; + if ($defaults{$item} eq $option) { + $checked = ' checked="checked"'; + } + $datatable .= ''; + } + $datatable .= '
'. + '
'; + } elsif ($item eq 'intauth_check') { + my @options = (0,1,2); + my %optiondesc = &Apache::lonlocal::texthash ( + 0 => 'No', + 1 => 'Yes, allow login then update passwd file using default cost (if higher)', + 2 => 'Yes, disallow login if stored cost is less than domain default', + ); + $datatable .= ''; + foreach my $option (@options) { + my $checked = ' '; + my $onclick; + if ($defaults{$item} eq $option) { + $checked = ' checked="checked"'; + } + if ($option == 2) { + $onclick = ' onclick="javascript:warnIntAuth(this);"'; + } + $datatable .= ''; + } + $datatable .= '
'. + '
'; + } else { + $datatable .= ''; + } + $datatable .= '
'.$titles{'min'}.''. + ''. + ' '.&mt('(Enter an integer: 7 or larger)').''. + '
'.$titles{'max'}.''. + ''. + ' '.&mt('(Leave blank for no maximum)').''. + '
'.$titles{'chars'}.'
'. + ''.&mt('(Leave unchecked if not required)'). + '
'; + for (my $i=0; $i<@possrules; $i++) { + my ($rem,$checked); + if ($chars{$possrules[$i]}) { + $checked = ' checked="checked"'; + } + $rem = $i%($numinrow); + if ($rem == 0) { + if ($i > 0) { + $datatable .= ''; + } + $datatable .= ''; + } + $datatable .= ''; + } + my $rem = @possrules%($numinrow); + my $colsleft = $numinrow - $rem; + if ($colsleft > 1 ) { + $datatable .= ''; + } elsif ($colsleft == 1) { + $datatable .= ''; + } + $datatable .='
'. + '  
'.$titles{'numsaved'}.''. + ''. + ' '.&mt('(Leave blank to not save previous passwords)').''. + '
'. + &mt('Requirements').'
    '. + '
  • '.&mt("Course 'type' is not a Community").'
  • '. + '
  • '.&mt('User is Course Coordinator and also course owner').'
  • '. + '
  • '.&mt("Student's only active roles are student role(s) in course(s) owned by this user").'
  • '. + '
  • '.&mt('User, course, and student share same domain').'
  • '. + '
'. + '
'; + foreach my $item ('by','for') { + $datatable .= '
'. + ''.$ownertitles{$item}.''; + if ((ref($types) eq 'ARRAY') && (ref($usertypes) eq 'HASH')) { + foreach my $type (@{$types}) { + my $checked; + if ($ownerchg{$item}{$type}) { + $checked = ' checked="checked"'; + } + $datatable .= ''. + '   '; + } + } + my $checked; + if ($ownerchg{$item}{'default'}) { + $checked = ' checked="checked"'; + } + $datatable .= '
'; + } + $datatable .= '
'.$rowname.''."\n". + ''.$rowname.''."\n". ''; - $rownum ++; - } } else { my %defaults; if (ref($settings) eq 'HASH') { @@ -6690,35 +7697,7 @@ sub serverstatus_pages { sub defaults_javascript { my ($settings) = @_; - my $intauthcheck = &mt('Warning: disallowing login for an authenticated user if the stored cost is less than the default will require a password reset by/for the user.'); - my $intauthcost = &mt('Warning: bcrypt encryption cost for internal authentication must be an integer.'); - &js_escape(\$intauthcheck); - &js_escape(\$intauthcost); - my $intauthjs = <<"ENDSCRIPT"; - -function warnIntAuth(field) { - if (field.name == 'intauth_check') { - if (field.value == '2') { - alert('$intauthcheck'); - } - } - if (field.name == 'intauth_cost') { - field.value.replace(/\s/g,''); - if (field.value != '') { - var regexdigit=/^\\d+\$/; - if (!regexdigit.test(field.value)) { - alert('$intauthcost'); - } - } - } - return; -} - -ENDSCRIPT - - if (ref($settings) ne 'HASH') { - return &Apache::lonhtmlcommon::scripttag($intauthjs); - } + return unless (ref($settings) eq 'HASH'); if ((ref($settings->{'inststatusorder'}) eq 'ARRAY') && (ref($settings->{'inststatustypes'}) eq 'HASH')) { my $maxnum = scalar(@{$settings->{'inststatusorder'}}); if ($maxnum eq '') { @@ -6772,15 +7751,99 @@ $jstext return; } -$intauthjs - // ]]> ENDSCRIPT + } +} + +sub passwords_javascript { + my %intalert = &Apache::lonlocal::texthash ( + authcheck => 'Warning: disallowing login for an authenticated user if the stored cost is less than the default will require a password reset by/for the user.', + authcost => 'Warning: bcrypt encryption cost for internal authentication must be an integer.', + passmin => 'Warning: minimum password length must be a positive integer greater than 6.', + passmax => 'Warning: maximum password length must be a positive integer (or blank).', + passexp => 'Warning: days before password expiration must be a positive integer (or blank).', + passnum => 'Warning: number of previous passwords to save must be a positive integer (or blank).', + ); + &js_escape(\%intalert); + my $defmin = $Apache::lonnet::passwdmin; + my $intauthjs = <<"ENDSCRIPT"; + +function warnIntAuth(field) { + if (field.name == 'intauth_check') { + if (field.value == '2') { + alert('$intalert{authcheck}'); + } + } + if (field.name == 'intauth_cost') { + field.value.replace(/\s/g,''); + if (field.value != '') { + var regexdigit=/^\\d+\$/; + if (!regexdigit.test(field.value)) { + alert('$intalert{authcost}'); + } + } + } + return; +} + +function warnIntPass(field) { + field.value.replace(/^\s+/,''); + field.value.replace(/\s+\$/,''); + var regexdigit=/^\\d+\$/; + if (field.name == 'passwords_min') { + if (field.value == '') { + alert('$intalert{passmin}'); + field.value = '$defmin'; + } else { + if (!regexdigit.test(field.value)) { + alert('$intalert{passmin}'); + field.value = '$defmin'; + } + var minval = parseInt(field.value,10); + if (minval < $defmin) { + alert('$intalert{passmin}'); + field.value = '$defmin'; + } + } } else { - return &Apache::lonhtmlcommon::scripttag($intauthjs); + if (field.value == '0') { + field.value = ''; + } + if (field.value != '') { + if (field.name == 'passwords_expire') { + var regexpposnum=/^\\d+(|\\.\\d*)\$/; + if (!regexpposnum.test(field.value)) { + alert('$intalert{passexp}'); + field.value = ''; + } else { + var expval = parseFloat(field.value); + if (expval == 0) { + alert('$intalert{passexp}'); + field.value = ''; + } + } + } else { + if (!regexdigit.test(field.value)) { + if (field.name == 'passwords_max') { + alert('$intalert{passmax}'); + } else { + if (field.name == 'passwords_numsaved') { + alert('$intalert{passnum}'); + } + } + field.value = ''; + } + } + } } + return; +} + +ENDSCRIPT + return &Apache::lonhtmlcommon::scripttag($intauthjs); } sub coursecategories_javascript { @@ -6925,7 +7988,7 @@ sub initialize_categories { .'' .'' .' ' - .&mt('Add category').'>'.&mt('Name:') + .&mt('Add category').''; return $datatable; @@ -7121,7 +8184,7 @@ sub modifiable_userdata_row { my $check = ' '; unless ($role eq 'emailusername') { if (exists($checks{$fields[$i]})) { - $check = $checks{$fields[$i]} + $check = $checks{$fields[$i]}; } else { if ($role eq 'st') { if (ref($settings) ne 'HASH') { @@ -7177,6 +8240,7 @@ sub insttypes_row { statustocreate => 'Institutional affiliation(s) able to create own account (login/SSO)', lockablenames => 'User preference to lock name', selfassign => 'Self-reportable affiliations', + overrides => "Override domain's helpdesk settings based on requester's affiliation", ); my $showdom; if ($context eq 'cansearch') { @@ -7223,6 +8287,10 @@ sub insttypes_row { if (grep(/^\Q$types->[$i]\E$/,@{$settings->{$context}})) { $check = ' checked="checked" '; } + } elsif (ref($settings->{$context}) eq 'HASH') { + if (ref($settings->{$context}->{$types->[$i]}) eq 'HASH') { + $check = ' checked="checked" '; + } } elsif ($context eq 'statustocreate') { $check = ' checked="checked" '; } @@ -7237,29 +8305,38 @@ sub insttypes_row { $rem = @{$types}%($numinrow); } my $colsleft = $numinrow - $rem; - if ($rem == 0) { - $output .= ''; - } - if ($colsleft > 1) { - $output .= ''; + } + if ($colsleft > 1) { + $output .= ''. - '
'."\n"; foreach my $option ('original','recaptcha','notused') { $output .= ''.$titles->{$item}. - ''; - if ($item eq 'intauth_switch') { - my @options = (0,1,2); - my %optiondesc = &Apache::lonlocal::texthash ( - 0 => 'No', - 1 => 'Yes', - 2 => 'Yes, and copy existing passwd file to passwd.bak file', - ); - $datatable .= ''; - foreach my $option (@options) { - my $checked = ' '; - if ($defaults{$item} eq $option) { - $checked = ' checked="checked"'; - } - $datatable .= ''; - } - $datatable .= '
'. - '
'; - } elsif ($item eq 'intauth_check') { - my @options = (0,1,2); - my %optiondesc = &Apache::lonlocal::texthash ( - 0 => 'No', - 1 => 'Yes, allow login then update passwd file using default cost (if higher)', - 2 => 'Yes, disallow login if stored cost is less than domain default', - ); - $datatable .= ''; - foreach my $option (@options) { - my $checked = ' '; - my $onclick; - if ($defaults{$item} eq $option) { - $checked = ' checked="checked"'; - } - if ($option == 2) { - $onclick = ' onclick="javascript:warnIntAuth(this);"'; - } - $datatable .= ''; - } - $datatable .= '
'. - '
'; - } else { - $datatable .= ''; - } - $datatable .= '
'.&mt('Name:') .' ' .'
'; + if ($context eq 'overrides') { + if ($colsleft > 1) { + $output .= ''; + } else { + $output .= ''; + } + $output .= ' '; } else { - $output .= ''; - } - my $defcheck = ' '; - if (ref($settings) eq 'HASH') { - if (ref($settings->{$context}) eq 'ARRAY') { - if (grep(/^default$/,@{$settings->{$context}})) { + if ($rem == 0) { + $output .= '
'; + } else { + $output .= ''; + } + my $defcheck = ' '; + if (ref($settings) eq 'HASH') { + if (ref($settings->{$context}) eq 'ARRAY') { + if (grep(/^default$/,@{$settings->{$context}})) { + $defcheck = ' checked="checked" '; + } + } elsif ($context eq 'statustocreate') { $defcheck = ' checked="checked" '; } - } elsif ($context eq 'statustocreate') { - $defcheck = ' checked="checked" '; } + $output .= ''; } - $output .= '
'; return $output; } @@ -8219,7 +9296,7 @@ sub check_configuser { my ($configuserok,%currroles); if ($uhome eq 'no_host') { srand( time() ^ ($$ + ($$ << 15)) ); # Seed rand. - my $configpass = &LONCAPA::Enrollment::create_password(); + my $configpass = &LONCAPA::Enrollment::create_password($dom); $configuserok = &Apache::lonnet::modifyuser($dom,$confname,'','internal', $configpass,'','','','','',undef,$servadm); @@ -9266,6 +10343,590 @@ sub process_textbook_image { return ($url,$error); } +sub modify_ltitools { + my ($r,$dom,$action,$lastactref,%domconfig) = @_; + my %domdefaults = &Apache::lonnet::get_domain_defaults($dom,1); + my ($newid,@allpos,%changes,%confhash,%encconfig,$errors,$resulttext); + my $confname = $dom.'-domainconfig'; + my $servadm = $r->dir_config('lonAdmEMail'); + my ($configuserok,$author_ok,$switchserver) = &config_check($dom,$confname,$servadm); + my (%posslti,%possfield); + my @courseroles = ('cc','in','ta','ep','st'); + my @ltiroles = qw(Instructor ContentDeveloper TeachingAssistant Learner); + map { $posslti{$_} = 1; } @ltiroles; + my @allfields = ('fullname','firstname','lastname','email','user','roles'); + map { $possfield{$_} = 1; } @allfields; + my %lt = <itools_names(); + if ($env{'form.ltitools_add'}) { + my $title = $env{'form.ltitools_add_title'}; + $title =~ s/(`)/'/g; + ($newid,my $error) = &get_ltitools_id($dom,$title); + if ($newid) { + my $position = $env{'form.ltitools_add_pos'}; + $position =~ s/\D+//g; + if ($position ne '') { + $allpos[$position] = $newid; + } + $changes{$newid} = 1; + foreach my $item ('title','url','key','secret') { + $env{'form.ltitools_add_'.$item} =~ s/(`)/'/g; + if ($env{'form.ltitools_add_'.$item}) { + if (($item eq 'key') || ($item eq 'secret')) { + $encconfig{$newid}{$item} = $env{'form.ltitools_add_'.$item}; + } else { + $confhash{$newid}{$item} = $env{'form.ltitools_add_'.$item}; + } + } + } + if ($env{'form.ltitools_add_version'} eq 'LTI-1p0') { + $confhash{$newid}{'version'} = $env{'form.ltitools_add_version'}; + } + if ($env{'form.ltitools_add_msgtype'} eq 'basic-lti-launch-request') { + $confhash{$newid}{'msgtype'} = $env{'form.ltitools_add_msgtype'}; + } + foreach my $item ('width','height','linktext','explanation') { + $env{'form.ltitools_add_'.$item} =~ s/^\s+//; + $env{'form.ltitools_add_'.$item} =~ s/\s+$//; + if (($item eq 'width') || ($item eq 'height')) { + if ($env{'form.ltitools_add_'.$item} =~ /^\d+$/) { + $confhash{$newid}{'display'}{$item} = $env{'form.ltitools_add_'.$item}; + } + } else { + if ($env{'form.ltitools_add_'.$item} ne '') { + $confhash{$newid}{'display'}{$item} = $env{'form.ltitools_add_'.$item}; + } + } + } + if ($env{'form.ltitools_add_target'} eq 'window') { + $confhash{$newid}{'display'}{'target'} = $env{'form.ltitools_add_target'}; + } elsif ($env{'form.ltitools_add_target'} eq 'tab') { + $confhash{$newid}{'display'}{'target'} = $env{'form.ltitools_add_target'}; + } else { + $confhash{$newid}{'display'}{'target'} = 'iframe'; + } + foreach my $item ('passback','roster') { + if ($env{'form.ltitools_add_'.$item}) { + $confhash{$newid}{$item} = 1; + } + } + if ($env{'form.ltitools_add_image.filename'} ne '') { + my ($imageurl,$error) = + &process_ltitools_image($r,$dom,$confname,'ltitools_add_image',$newid, + $configuserok,$switchserver,$author_ok); + if ($imageurl) { + $confhash{$newid}{'image'} = $imageurl; + } + if ($error) { + &Apache::lonnet::logthis($error); + $errors .= '
  • '.$error.'
  • '; + } + } + my @fields = &Apache::loncommon::get_env_multiple('form.ltitools_add_fields'); + foreach my $field (@fields) { + if ($possfield{$field}) { + if ($field eq 'roles') { + foreach my $role (@courseroles) { + my $choice = $env{'form.ltitools_add_roles_'.$role}; + if (($choice ne '') && ($posslti{$choice})) { + $confhash{$newid}{'roles'}{$role} = $choice; + if ($role eq 'cc') { + $confhash{$newid}{'roles'}{'co'} = $choice; + } + } + } + } else { + $confhash{$newid}{'fields'}{$field} = 1; + } + } + } + my @courseconfig = &Apache::loncommon::get_env_multiple('form.ltitools_courseconfig'); + foreach my $item (@courseconfig) { + $confhash{$newid}{'crsconf'}{$item} = 1; + } + if ($env{'form.ltitools_add_custom'}) { + my $name = $env{'form.ltitools_add_custom_name'}; + my $value = $env{'form.ltitools_add_custom_value'}; + $value =~ s/(`)/'/g; + $name =~ s/(`)/'/g; + $confhash{$newid}{'custom'}{$name} = $value; + } + } else { + my $error = &mt('Failed to acquire unique ID for new external tool'); + $errors .= '
  • '.$error.'
  • '; + } + } + if (ref($domconfig{$action}) eq 'HASH') { + my %deletions; + my @todelete = &Apache::loncommon::get_env_multiple('form.ltitools_del'); + if (@todelete) { + map { $deletions{$_} = 1; } @todelete; + } + my %customadds; + my @newcustom = &Apache::loncommon::get_env_multiple('form.ltitools_customadd'); + if (@newcustom) { + map { $customadds{$_} = 1; } @newcustom; + } + my %imgdeletions; + my @todeleteimages = &Apache::loncommon::get_env_multiple('form.ltitools_image_del'); + if (@todeleteimages) { + map { $imgdeletions{$_} = 1; } @todeleteimages; + } + my $maxnum = $env{'form.ltitools_maxnum'}; + for (my $i=0; $i<=$maxnum; $i++) { + my $itemid = $env{'form.ltitools_id_'.$i}; + $itemid =~ s/\D+//g; + if (ref($domconfig{$action}{$itemid}) eq 'HASH') { + if ($deletions{$itemid}) { + if ($domconfig{$action}{$itemid}{'image'}) { + #FIXME need to obsolete item in RES space + } + $changes{$itemid} = $domconfig{$action}{$itemid}{'title'}; + next; + } else { + my $newpos = $env{'form.ltitools_'.$itemid}; + $newpos =~ s/\D+//g; + foreach my $item ('title','url') { + $confhash{$itemid}{$item} = $env{'form.ltitools_'.$item.'_'.$i}; + if ($domconfig{$action}{$itemid}{$item} ne $confhash{$itemid}{$item}) { + $changes{$itemid} = 1; + } + } + foreach my $item ('key','secret') { + $encconfig{$itemid}{$item} = $env{'form.ltitools_'.$item.'_'.$i}; + if ($domconfig{$action}{$itemid}{$item} ne $encconfig{$itemid}{$item}) { + $changes{$itemid} = 1; + } + } + if ($env{'form.ltitools_version_'.$i} eq 'LTI-1p0') { + $confhash{$itemid}{'version'} = $env{'form.ltitools_version_'.$i}; + } + if ($env{'form.ltitools_msgtype_'.$i} eq 'basic-lti-launch-request') { + $confhash{$itemid}{'msgtype'} = $env{'form.ltitools_msgtype_'.$i}; + } + foreach my $size ('width','height') { + $env{'form.ltitools_'.$size.'_'.$i} =~ s/^\s+//; + $env{'form.ltitools_'.$size.'_'.$i} =~ s/\s+$//; + if ($env{'form.ltitools_'.$size.'_'.$i} =~ /^\d+$/) { + $confhash{$itemid}{'display'}{$size} = $env{'form.ltitools_'.$size.'_'.$i}; + if (ref($domconfig{$action}{$itemid}{'display'}) eq 'HASH') { + if ($domconfig{$action}{$itemid}{'display'}{$size} ne $confhash{$itemid}{'display'}{$size}) { + $changes{$itemid} = 1; + } + } else { + $changes{$itemid} = 1; + } + } elsif (ref($domconfig{$action}{$itemid}{'display'}) eq 'HASH') { + if ($domconfig{$action}{$itemid}{'display'}{$size} ne '') { + $changes{$itemid} = 1; + } + } + } + foreach my $item ('linktext','explanation') { + $env{'form.ltitools_'.$item.'_'.$i} =~ s/^\s+//; + $env{'form.ltitools_'.$item.'_'.$i} =~ s/\s+$//; + if ($env{'form.ltitools_'.$item.'_'.$i} ne '') { + $confhash{$itemid}{'display'}{$item} = $env{'form.ltitools_'.$item.'_'.$i}; + if (ref($domconfig{$action}{$itemid}{'display'}) eq 'HASH') { + if ($domconfig{$action}{$itemid}{'display'}{$item} ne $confhash{$itemid}{'display'}{$item}) { + $changes{$itemid} = 1; + } + } else { + $changes{$itemid} = 1; + } + } elsif (ref($domconfig{$action}{$itemid}{'display'}) eq 'HASH') { + if ($domconfig{$action}{$itemid}{'display'}{$item} ne '') { + $changes{$itemid} = 1; + } + } + } + if ($env{'form.ltitools_target_'.$i} eq 'window') { + $confhash{$itemid}{'display'}{'target'} = $env{'form.ltitools_target_'.$i}; + } elsif ($env{'form.ltitools_target_'.$i} eq 'tab') { + $confhash{$itemid}{'display'}{'target'} = $env{'form.ltitools_target_'.$i}; + } else { + $confhash{$itemid}{'display'}{'target'} = 'iframe'; + } + if (ref($domconfig{$action}{$itemid}{'display'}) eq 'HASH') { + if ($domconfig{$action}{$itemid}{'display'}{'target'} ne $confhash{$itemid}{'display'}{'target'}) { + $changes{$itemid} = 1; + } + } else { + $changes{$itemid} = 1; + } + foreach my $extra ('passback','roster') { + if ($env{'form.ltitools_'.$extra.'_'.$i}) { + $confhash{$itemid}{$extra} = 1; + } + if ($domconfig{$action}{$itemid}{$extra} ne $confhash{$itemid}{$extra}) { + $changes{$itemid} = 1; + } + } + my @courseconfig = &Apache::loncommon::get_env_multiple('form.ltitools_courseconfig_'.$i); + foreach my $item ('label','title','target','linktext','explanation') { + if (grep(/^\Q$item\E$/,@courseconfig)) { + $confhash{$itemid}{'crsconf'}{$item} = 1; + if (ref($domconfig{$action}{$itemid}{'crsconf'}) eq 'HASH') { + if ($domconfig{$action}{$itemid}{'crsconf'}{$item} ne $confhash{$itemid}{'crsconf'}{$item}) { + $changes{$itemid} = 1; + } + } else { + $changes{$itemid} = 1; + } + } + } + my @fields = &Apache::loncommon::get_env_multiple('form.ltitools_fields_'.$i); + foreach my $field (@fields) { + if ($possfield{$field}) { + if ($field eq 'roles') { + foreach my $role (@courseroles) { + my $choice = $env{'form.ltitools_roles_'.$role.'_'.$i}; + if (($choice ne '') && ($posslti{$choice})) { + $confhash{$itemid}{'roles'}{$role} = $choice; + if ($role eq 'cc') { + $confhash{$itemid}{'roles'}{'co'} = $choice; + } + } + if (ref($domconfig{$action}{$itemid}{'roles'}) eq 'HASH') { + if ($domconfig{$action}{$itemid}{'roles'}{$role} ne $confhash{$itemid}{'roles'}{$role}) { + $changes{$itemid} = 1; + } + } elsif ($confhash{$itemid}{'roles'}{$role}) { + $changes{$itemid} = 1; + } + } + } else { + $confhash{$itemid}{'fields'}{$field} = 1; + if (ref($domconfig{$action}{$itemid}{'fields'}) eq 'HASH') { + if ($domconfig{$action}{$itemid}{'fields'}{$field} ne $confhash{$itemid}{'fields'}{$field}) { + $changes{$itemid} = 1; + } + } else { + $changes{$itemid} = 1; + } + } + } + } + $allpos[$newpos] = $itemid; + } + if ($imgdeletions{$itemid}) { + $changes{$itemid} = 1; + #FIXME need to obsolete item in RES space + } elsif ($env{'form.ltitools_image_'.$i.'.filename'}) { + my ($imgurl,$error) = &process_ltitools_image($r,$dom,$confname,'ltitools_image_'.$i, + $itemid,$configuserok,$switchserver, + $author_ok); + if ($imgurl) { + $confhash{$itemid}{'image'} = $imgurl; + $changes{$itemid} = 1; + } + if ($error) { + &Apache::lonnet::logthis($error); + $errors .= '
  • '.$error.'
  • '; + } + } elsif ($domconfig{$action}{$itemid}{'image'}) { + $confhash{$itemid}{'image'} = + $domconfig{$action}{$itemid}{'image'}; + } + if ($customadds{$i}) { + my $name = $env{'form.ltitools_custom_name_'.$i}; + $name =~ s/(`)/'/g; + $name =~ s/^\s+//; + $name =~ s/\s+$//; + my $value = $env{'form.ltitools_custom_value_'.$i}; + $value =~ s/(`)/'/g; + $value =~ s/^\s+//; + $value =~ s/\s+$//; + if ($name ne '') { + $confhash{$itemid}{'custom'}{$name} = $value; + $changes{$itemid} = 1; + } + } + my %customdels; + my @customdeletions = &Apache::loncommon::get_env_multiple('form.ltitools_customdel_'.$i); + if (@customdeletions) { + $changes{$itemid} = 1; + } + map { $customdels{$_} = 1; } @customdeletions; + if (ref($domconfig{$action}{$itemid}{'custom'}) eq 'HASH') { + foreach my $key (keys(%{$domconfig{$action}{$itemid}{'custom'}})) { + unless ($customdels{$key}) { + if ($env{'form.ltitools_customval_'.$key.'_'.$i} ne '') { + $confhash{$itemid}{'custom'}{$key} = $env{'form.ltitools_customval_'.$key.'_'.$i}; + } + if ($domconfig{$action}{$itemid}{'custom'}{$key} ne $env{'form.ltitools_customval_'.$key.'_'.$i}) { + $changes{$itemid} = 1; + } + } + } + } + unless ($changes{$itemid}) { + foreach my $key (keys(%{$domconfig{$action}{$itemid}})) { + if (ref($domconfig{$action}{$itemid}{$key}) eq 'HASH') { + if (ref($confhash{$itemid}{$key}) eq 'HASH') { + foreach my $innerkey (keys(%{$domconfig{$action}{$itemid}{$key}})) { + unless (exists($confhash{$itemid}{$key}{$innerkey})) { + $changes{$itemid} = 1; + last; + } + } + } elsif (keys(%{$domconfig{$action}{$itemid}{$key}}) > 0) { + $changes{$itemid} = 1; + } + } + last if ($changes{$itemid}); + } + } + } + } + } + if (@allpos > 0) { + my $idx = 0; + foreach my $itemid (@allpos) { + if ($itemid ne '') { + $confhash{$itemid}{'order'} = $idx; + if (ref($domconfig{$action}) eq 'HASH') { + if (ref($domconfig{$action}{$itemid}) eq 'HASH') { + if ($domconfig{$action}{$itemid}{'order'} ne $idx) { + $changes{$itemid} = 1; + } + } + } + $idx ++; + } + } + } + my %ltitoolshash = ( + $action => { %confhash } + ); + my $putresult = &Apache::lonnet::put_dom('configuration',\%ltitoolshash, + $dom); + if ($putresult eq 'ok') { + my %ltienchash = ( + $action => { %encconfig } + ); + &Apache::lonnet::put_dom('encconfig',\%ltienchash,$dom); + if (keys(%changes) > 0) { + my $cachetime = 24*60*60; + my %ltiall = %confhash; + foreach my $id (keys(%ltiall)) { + if (ref($encconfig{$id}) eq 'HASH') { + foreach my $item ('key','secret') { + $ltiall{$id}{$item} = $encconfig{$id}{$item}; + } + } + } + &Apache::lonnet::do_cache_new('ltitools',$dom,\%ltiall,$cachetime); + if (ref($lastactref) eq 'HASH') { + $lastactref->{'ltitools'} = 1; + } + $resulttext = &mt('Changes made:').''; + } else { + $resulttext = &mt('No changes made.'); + } + } else { + $errors .= '
  • '.&mt('Failed to save changes').'
  • '; + } + if ($errors) { + $resulttext .= &mt('The following errors occurred: ').''; + } + return $resulttext; +} + +sub process_ltitools_image { + my ($r,$dom,$confname,$caller,$itemid,$configuserok,$switchserver,$author_ok) = @_; + my $filename = $env{'form.'.$caller.'.filename'}; + my ($error,$url); + my ($width,$height) = (21,21); + if ($configuserok eq 'ok') { + if ($switchserver) { + $error = &mt('Upload of Tool Provider (LTI) icon is not permitted to this server: [_1]', + $switchserver); + } elsif ($author_ok eq 'ok') { + my ($result,$imageurl,$madethumb) = + &publishlogo($r,'upload',$caller,$dom,$confname, + "ltitools/$itemid/icon",$width,$height); + if ($result eq 'ok') { + if ($madethumb) { + my ($path,$imagefile) = ($imageurl =~ m{^(.+)/([^/]+)$}); + my $imagethumb = "$path/tn-".$imagefile; + $url = $imagethumb; + } else { + $url = $imageurl; + } + } else { + $error = &mt("Upload of [_1] failed because an error occurred publishing the file in RES space. Error was: [_2].",$filename,$result); + } + } else { + $error = &mt("Upload of [_1] failed because an author role could not be assigned to a Domain Configuration user ([_2]) in domain: [_3]. Error was: [_4].",$filename,$confname,$dom,$author_ok); + } + } else { + $error = &mt("Upload of [_1] failed because a Domain Configuration user ([_2]) could not be created in domain: [_3]. Error was: [_4].",$filename,$confname,$dom,$configuserok); + } + return ($url,$error); +} + +sub get_ltitools_id { + my ($cdom,$title) = @_; + # get lock on ltitools db + my $lockhash = { + lock => $env{'user.name'}. + ':'.$env{'user.domain'}, + }; + my $tries = 0; + my $gotlock = &Apache::lonnet::newput_dom('ltitools',$lockhash,$cdom); + my ($id,$error); + + while (($gotlock ne 'ok') && ($tries<10)) { + $tries ++; + sleep (0.1); + $gotlock = &Apache::lonnet::newput_dom('ltitools',$lockhash,$cdom); + } + if ($gotlock eq 'ok') { + my %currids = &Apache::lonnet::dump_dom('ltitools',$cdom); + if ($currids{'lock'}) { + delete($currids{'lock'}); + if (keys(%currids)) { + my @curr = sort { $a <=> $b } keys(%currids); + if ($curr[-1] =~ /^\d+$/) { + $id = 1 + $curr[-1]; + } + } else { + $id = 1; + } + if ($id) { + unless (&Apache::lonnet::newput_dom('ltitools',{ $id => $title },$cdom) eq 'ok') { + $error = 'nostore'; + } + } else { + $error = 'nonumber'; + } + } + my $dellockoutcome = &Apache::lonnet::del_dom('ltitools',['lock'],$cdom); + } else { + $error = 'nolock'; + } + return ($id,$error); +} + sub modify_autoenroll { my ($dom,$lastactref,%domconfig) = @_; my ($resulttext,%changes); @@ -9891,17 +11552,57 @@ sub modify_contacts { my $value = $env{'form.helpform_'.$field}; $value =~ s/^\s+|\s+$//g; if (grep(/^\Q$value\E$/,@{$possoptions->{$field}})) { - $contacts_hash{contacts}{'helpform'}{$field} = $value; + $contacts_hash{'contacts'}{'helpform'}{$field} = $value; if ($field eq 'screenshot') { $env{'form.helpform_maxsize'} =~ s/^\s+|\s+$//g; if ($env{'form.helpform_maxsize'} =~ /^\d+\.?\d*$/) { - $contacts_hash{contacts}{'helpform'}{'maxsize'} = $env{'form.helpform_maxsize'}; + $contacts_hash{'contacts'}{'helpform'}{'maxsize'} = $env{'form.helpform_maxsize'}; } } } } } } + my ($othertitle,$usertypes,$types) = &Apache::loncommon::sorted_inst_types($dom); + my (@statuses,%usertypeshash,@overrides); + if ((ref($types) eq 'ARRAY') && (@{$types} > 0)) { + @statuses = @{$types}; + if (ref($usertypes) eq 'HASH') { + %usertypeshash = %{$usertypes}; + } + } + if (@statuses) { + my @possoverrides = &Apache::loncommon::get_env_multiple('form.overrides'); + foreach my $type (@possoverrides) { + if (($type ne '') && (grep(/^\Q$type\E$/,@statuses))) { + push(@overrides,$type); + } + } + if (@overrides) { + foreach my $type (@overrides) { + my @standard = &Apache::loncommon::get_env_multiple('form.override_'.$type); + foreach my $item (@contacts) { + if (grep(/^\Q$item\E$/,@standard)) { + $contacts_hash{'contacts'}{'overrides'}{$type}{$item} = 1; + $newsetting{'override_'.$type}{$item} = 1; + } else { + $contacts_hash{'contacts'}{'overrides'}{$type}{$item} = 0; + $newsetting{'override_'.$type}{$item} = 0; + } + } + $contacts_hash{'contacts'}{'overrides'}{$type}{'others'} = $env{'form.override_'.$type.'_others'}; + $contacts_hash{'contacts'}{'overrides'}{$type}{'bcc'} = $env{'form.override_'.$type.'_bcc'}; + $newsetting{'override_'.$type}{'others'} = $env{'form.override_'.$type.'_others'}; + $newsetting{'override_'.$type}{'bcc'} = $env{'form.override_'.$type.'_bcc'}; + if (($env{'form.override_'.$type.'_includestr'} ne '') && ($env{'form.override_'.$type.'_includeloc'} =~ /^s|b$/)) { + $includestr{$type} = $env{'form.override_'.$type.'_includestr'}; + $includeloc{$type} = $env{'form.override_'.$type.'_includeloc'}; + $contacts_hash{'contacts'}{'overrides'}{$type}{'include'} = $includeloc{$type}.':'.&escape($includestr{$type}); + $newsetting{'override_'.$type}{'include'} = $contacts_hash{'contacts'}{'overrides'}{$type}{'include'}; + } + } + } + } if (keys(%currsetting) > 0) { foreach my $item (@contacts) { if ($to{$item} ne $currsetting{$item}) { @@ -9956,6 +11657,33 @@ sub modify_contacts { } } } + if (@statuses) { + if (ref($currsetting{'overrides'}) eq 'HASH') { + foreach my $key (keys(%{$currsetting{'overrides'}})) { + if (ref($currsetting{'overrides'}{$key}) eq 'HASH') { + if (ref($newsetting{'override_'.$key}) eq 'HASH') { + foreach my $item (@contacts,'bcc','others','include') { + if ($currsetting{'overrides'}{$key}{$item} ne $newsetting{'override_'.$key}{$item}) { + push(@{$changes{'overrides'}},$key); + last; + } + } + } else { + push(@{$changes{'overrides'}},$key); + } + } + } + foreach my $key (@overrides) { + unless (exists($currsetting{'overrides'}{$key})) { + push(@{$changes{'overrides'}},$key); + } + } + } else { + foreach my $key (@overrides) { + push(@{$changes{'overrides'}},$key); + } + } + } } else { my %default; $default{'supportemail'} = $Apache::lonnet::perlvar{'lonSupportEMail'}; @@ -10071,6 +11799,60 @@ sub modify_contacts { $resulttext .= ''; } } + if (ref($changes{'overrides'}) eq 'ARRAY') { + my @deletions; + foreach my $type (@{$changes{'overrides'}}) { + if ($usertypeshash{$type}) { + if (grep(/^\Q$type\E/,@overrides)) { + $resulttext .= '
  • '.&mt("Overrides based on requester's affiliation set for [_1]", + $usertypeshash{$type}).'
  • '; + } else { + push(@deletions,$usertypeshash{$type}); + } + } + } + if (@deletions) { + $resulttext .= '
  • '.&mt("Overrides based on requester's affiliation discontinued for: [_1]", + join(', ',@deletions)).'
  • '; + } + } my @offon = ('off','on'); if ($changes{'reporterrors'}) { $resulttext .= '
  • '. @@ -10126,7 +11908,6 @@ sub modify_contacts { &mt('Max size for file uploaded to help form by logged-in user set to [_1] MB.', $contacts_hash{'contacts'}{'helpform'}{'maxsize'}). '
  • '; - } } $resulttext .= ''; @@ -10140,6 +11921,549 @@ sub modify_contacts { return $resulttext; } +sub modify_passwords { + my ($r,$dom,$confname,$lastactref,%domconfig) = @_; + my ($resulttext,%current,%changes,%newvalues,@oktypes,$errors, + $updatedefaults,$updateconf); + my $customfn = 'resetpw.html'; + if (ref($domconfig{'passwords'}) eq 'HASH') { + %current = %{$domconfig{'passwords'}}; + } + my %domdefaults = &Apache::lonnet::get_domain_defaults($dom,1); + my ($othertitle,$usertypes,$types) = &Apache::loncommon::sorted_inst_types($dom); + if (ref($types) eq 'ARRAY') { + @oktypes = @{$types}; + } + push(@oktypes,'default'); + + my %titles = &Apache::lonlocal::texthash ( + intauth_cost => 'Encryption cost for bcrypt (positive integer)', + intauth_check => 'Check bcrypt cost if authenticated', + intauth_switch => 'Existing crypt-based switched to bcrypt on authentication', + permanent => 'Permanent e-mail address', + critical => 'Critical notification address', + notify => 'Notification address', + min => 'Minimum password length', + max => 'Maximum password length', + chars => 'Required characters', + numsaved => 'Number of previous passwords to save', + reset => 'Resetting Forgotten Password', + intauth => 'Encryption of Stored Passwords (Internal Auth)', + rules => 'Rules for LON-CAPA Passwords', + crsownerchg => 'Course Owner Changing Student Passwords', + username => 'Username', + email => 'E-mail address', + ); + +# +# Retrieve current domain configuration for internal authentication from $domconfig{'defaults'}. +# + my (%curr_defaults,%save_defaults); + if (ref($domconfig{'defaults'}) eq 'HASH') { + foreach my $key (keys(%{$domconfig{'defaults'}})) { + if ($key =~ /^intauth_(cost|check|switch)$/) { + $curr_defaults{$key} = $domconfig{'defaults'}{$key}; + } else { + $save_defaults{$key} = $domconfig{'defaults'}{$key}; + } + } + } + my %staticdefaults = ( + 'resetlink' => 2, + 'resetcase' => \@oktypes, + 'resetprelink' => 'both', + 'resetemail' => ['critical','notify','permanent'], + 'intauth_cost' => 10, + 'intauth_check' => 0, + 'intauth_switch' => 0, + ); + $staticdefaults{'min'} = $Apache::lonnet::passwdmin; + foreach my $type (@oktypes) { + $staticdefaults{'resetpostlink'}{$type} = ['email','username']; + } + my $linklife = $env{'form.passwords_link'}; + $linklife =~ s/^\s+|\s+$//g; + if (($linklife =~ /^\d+(|\.\d*)$/) && ($linklife > 0)) { + $newvalues{'resetlink'} = $linklife; + if ($current{'resetlink'}) { + if ($current{'resetlink'} ne $linklife) { + $changes{'reset'} = 1; + } + } elsif (!ref($domconfig{passwords}) eq 'HASH') { + if ($staticdefaults{'resetlink'} ne $linklife) { + $changes{'reset'} = 1; + } + } + } elsif ($current{'resetlink'}) { + $changes{'reset'} = 1; + } + my @casesens; + my @posscase = &Apache::loncommon::get_env_multiple('form.passwords_case_sensitive'); + foreach my $case (sort(@posscase)) { + if (grep(/^\Q$case\E$/,@oktypes)) { + push(@casesens,$case); + } + } + $newvalues{'resetcase'} = \@casesens; + if (ref($current{'resetcase'}) eq 'ARRAY') { + my @diffs = &Apache::loncommon::compare_arrays($current{'resetcase'},\@casesens); + if (@diffs > 0) { + $changes{'reset'} = 1; + } + } elsif (!ref($domconfig{passwords}) eq 'HASH') { + my @diffs = &Apache::loncommon::compare_arrays($staticdefaults{'resetcase'},\@casesens); + if (@diffs > 0) { + $changes{'reset'} = 1; + } + } + if ($env{'form.passwords_prelink'} =~ /^(both|either)$/) { + $newvalues{'resetprelink'} = $env{'form.passwords_prelink'}; + if (exists($current{'resetprelink'})) { + if ($current{'resetprelink'} ne $newvalues{'resetprelink'}) { + $changes{'reset'} = 1; + } + } elsif (!ref($domconfig{passwords}) eq 'HASH') { + if ($staticdefaults{'resetprelink'} ne $newvalues{'resetprelink'}) { + $changes{'reset'} = 1; + } + } + } elsif ($current{'resetprelink'}) { + $changes{'reset'} = 1; + } + foreach my $type (@oktypes) { + my @possplink = &Apache::loncommon::get_env_multiple('form.passwords_postlink_'.$type); + my @postlink; + foreach my $item (sort(@possplink)) { + if ($item =~ /^(email|username)$/) { + push(@postlink,$item); + } + } + $newvalues{'resetpostlink'}{$type} = \@postlink; + unless ($changes{'reset'}) { + if (ref($current{'resetpostlink'}) eq 'HASH') { + if (ref($current{'resetpostlink'}{$type}) eq 'ARRAY') { + my @diffs = &Apache::loncommon::compare_arrays($current{'resetpostlink'}{$type},\@postlink); + if (@diffs > 0) { + $changes{'reset'} = 1; + } + } else { + $changes{'reset'} = 1; + } + } elsif (!ref($domconfig{passwords}) eq 'HASH') { + my @diffs = &Apache::loncommon::compare_arrays($staticdefaults{'resetpostlink'}{$type},\@postlink); + if (@diffs > 0) { + $changes{'reset'} = 1; + } + } + } + } + my @possemailsrc = &Apache::loncommon::get_env_multiple('form.passwords_emailsrc'); + my @resetemail; + foreach my $item (sort(@possemailsrc)) { + if ($item =~ /^(permanent|critical|notify)$/) { + push(@resetemail,$item); + } + } + $newvalues{'resetemail'} = \@resetemail; + unless ($changes{'reset'}) { + if (ref($current{'resetemail'}) eq 'ARRAY') { + my @diffs = &Apache::loncommon::compare_arrays($current{'resetemail'},\@resetemail); + if (@diffs > 0) { + $changes{'reset'} = 1; + } + } elsif (!ref($domconfig{passwords}) eq 'HASH') { + my @diffs = &Apache::loncommon::compare_arrays($staticdefaults{'resetemail'},\@resetemail); + if (@diffs > 0) { + $changes{'reset'} = 1; + } + } + } + if ($env{'form.passwords_stdtext'} == 0) { + $newvalues{'resetremove'} = 1; + unless ($current{'resetremove'}) { + $changes{'reset'} = 1; + } + } elsif ($current{'resetremove'}) { + $changes{'reset'} = 1; + } + if ($env{'form.passwords_customfile.filename'} ne '') { + my $servadm = $r->dir_config('lonAdmEMail'); + my $servadm = $r->dir_config('lonAdmEMail'); + my ($configuserok,$author_ok,$switchserver) = + &config_check($dom,$confname,$servadm); + my $error; + if ($configuserok eq 'ok') { + if ($switchserver) { + $error = &mt("Upload of file containing domain-specific text is not permitted to this server: [_1]",$switchserver); + } else { + if ($author_ok eq 'ok') { + my ($result,$customurl) = + &publishlogo($r,'upload','passwords_customfile',$dom, + $confname,'customtext/resetpw','','',$customfn); + if ($result eq 'ok') { + $newvalues{'resetcustom'} = $customurl; + $changes{'reset'} = 1; + } else { + $error = &mt("Upload of [_1] failed because an error occurred publishing the file in RES space. Error was: [_2].",$customfn,$result); + } + } else { + $error = &mt("Upload of [_1] failed because an author role could not be assigned to a Domain Configuration user ([_2]) in domain: [_3]. Error was: [_4].",$customfn,$confname,$dom,$author_ok); + } + } + } else { + $error = &mt("Upload of [_1] failed because a Domain Configuration user ([_2]) could not be created in domain: [_3]. Error was: [_4].",$customfn,$confname,$dom,$configuserok); + } + if ($error) { + &Apache::lonnet::logthis($error); + $errors .= '
  • '.$error.'
  • '; + } + } elsif ($current{'resetcustom'}) { + if ($env{'form.passwords_custom_del'}) { + $changes{'reset'} = 1; + } else { + $newvalues{'resetcustom'} = $current{'resetcustom'}; + } + } + $env{'form.intauth_cost'} =~ s/^\s+|\s+$//g; + if (($env{'form.intauth_cost'} ne '') && ($env{'form.intauth_cost'} =~ /^\d+$/)) { + $save_defaults{'intauth_cost'} = $env{'form.intauth_cost'}; + if ($save_defaults{'intauth_cost'} ne $curr_defaults{'intauth_cost'}) { + $changes{'intauth'} = 1; + } + } else { + $save_defaults{'intauth_cost'} = $curr_defaults{'intauth_cost'}; + } + if ($env{'form.intauth_check'} =~ /^(0|1|2)$/) { + $save_defaults{'intauth_check'} = $env{'form.intauth_check'}; + if ($save_defaults{'intauth_check'} ne $curr_defaults{'intauth_check'}) { + $changes{'intauth'} = 1; + } + } else { + $save_defaults{'intauth_check'} = $curr_defaults{'intauth_check'}; + } + if ($env{'form.intauth_switch'} =~ /^(0|1|2)$/) { + $save_defaults{'intauth_switch'} = $env{'form.intauth_switch'}; + if ($save_defaults{'intauth_switch'} ne $curr_defaults{'intauth_switch'}) { + $changes{'intauth'} = 1; + } + } else { + $save_defaults{'intauth_check'} = $curr_defaults{'intauth_check'}; + } + foreach my $item ('cost','check','switch') { + if ($save_defaults{'intauth_'.$item} ne $domdefaults{'intauth_'.$item}) { + $domdefaults{'intauth_'.$item} = $save_defaults{'intauth_'.$item}; + $updatedefaults = 1; + } + } + foreach my $rule ('min','max','numsaved') { + $env{'form.passwords_'.$rule} =~ s/^\s+|\s+$//g; + my $ruleok; + if ($rule eq 'min') { + if ($env{'form.passwords_'.$rule} =~ /^\d+$/) { + if ($env{'form.passwords_'.$rule} >= $Apache::lonnet::passwdmin) { + $ruleok = 1; + } + } + } elsif (($env{'form.passwords_'.$rule} =~ /^\d+$/) && + ($env{'form.passwords_'.$rule} ne '0')) { + $ruleok = 1; + } + if ($ruleok) { + $newvalues{$rule} = $env{'form.passwords_'.$rule}; + if (exists($current{$rule})) { + if ($newvalues{$rule} ne $current{$rule}) { + $changes{'rules'} = 1; + } + } elsif ($rule eq 'min') { + if ($staticdefaults{$rule} ne $newvalues{$rule}) { + $changes{'rules'} = 1; + } + } else { + $changes{'rules'} = 1; + } + } elsif (exists($current{$rule})) { + $changes{'rules'} = 1; + } + } + my @posschars = &Apache::loncommon::get_env_multiple('form.passwords_chars'); + my @chars; + foreach my $item (sort(@posschars)) { + if ($item =~ /^(uc|lc|num|spec)$/) { + push(@chars,$item); + } + } + $newvalues{'chars'} = \@chars; + unless ($changes{'rules'}) { + if (ref($current{'chars'}) eq 'ARRAY') { + my @diffs = &Apache::loncommon::compare_arrays($current{'chars'},\@chars); + if (@diffs > 0) { + $changes{'rules'} = 1; + } + } else { + if (@chars > 0) { + $changes{'rules'} = 1; + } + } + } + my %crsownerchg = ( + by => [], + for => [], + ); + foreach my $item ('by','for') { + my @posstypes = &Apache::loncommon::get_env_multiple('form.passwords_crsowner_'.$item); + foreach my $type (sort(@posstypes)) { + if (grep(/^\Q$type\E$/,@oktypes)) { + push(@{$crsownerchg{$item}},$type); + } + } + } + $newvalues{'crsownerchg'} = \%crsownerchg; + if (ref($current{'crsownerchg'}) eq 'HASH') { + foreach my $item ('by','for') { + if (ref($current{'crsownerchg'}{$item}) eq 'ARRAY') { + my @diffs = &Apache::loncommon::compare_arrays($current{'crsownerchg'}{$item},$crsownerchg{$item}); + if (@diffs > 0) { + $changes{'crsownerchg'} = 1; + last; + } + } + } + } elsif (!(ref($domconfig{passwords}) eq 'HASH')) { + foreach my $item ('by','for') { + if (@{$crsownerchg{$item}} > 0) { + $changes{'crsownerchg'} = 1; + last; + } + } + } + + my %confighash = ( + defaults => \%save_defaults, + passwords => \%newvalues, + ); + &process_captcha('passwords',\%changes,$confighash{'passwords'},$domconfig{'passwords'}); + + my $putresult = &Apache::lonnet::put_dom('configuration',\%confighash,$dom); + if ($putresult eq 'ok') { + if (keys(%changes) > 0) { + $resulttext = &mt('Changes made: ').''; + } else { + $resulttext = &mt('No changes made to password settings'); + } + my $cachetime = 24*60*60; + if ($updatedefaults) { + &Apache::lonnet::do_cache_new('domdefaults',$dom,\%domdefaults,$cachetime); + if (ref($lastactref) eq 'HASH') { + $lastactref->{'domdefaults'} = 1; + } + } + if ($updateconf) { + &Apache::lonnet::do_cache_new('passwdconf',$dom,$confighash{'passwords'},$cachetime); + if (ref($lastactref) eq 'HASH') { + $lastactref->{'passwdconf'} = 1; + } + } + } else { + $resulttext = ''. + &mt('An error occurred: [_1]',$putresult).''; + } + if ($errors) { + $resulttext .= '

    '.&mt('The following errors occurred: ').'

    '; + } + return $resulttext; +} + sub modify_usercreation { my ($dom,%domconfig) = @_; my ($resulttext,%curr_usercreation,%changes,%authallowed,%cancreate,%save_usercreate); @@ -11223,19 +13547,25 @@ sub modify_selfcreation { } sub process_captcha { - my ($container,$changes,$newsettings,$current) = @_; - return unless ((ref($changes) eq 'HASH') && (ref($newsettings) eq 'HASH') || (ref($current) eq 'HASH')); + my ($container,$changes,$newsettings,$currsettings) = @_; + return unless ((ref($changes) eq 'HASH') && (ref($newsettings) eq 'HASH')); $newsettings->{'captcha'} = $env{'form.'.$container.'_captcha'}; unless ($newsettings->{'captcha'} eq 'recaptcha' || $newsettings->{'captcha'} eq 'notused') { $newsettings->{'captcha'} = 'original'; } - if ($current->{'captcha'} ne $newsettings->{'captcha'}) { + my %current; + if (ref($currsettings) eq 'HASH') { + %current = %{$currsettings}; + } + if ($current{'captcha'} ne $newsettings->{'captcha'}) { if ($container eq 'cancreate') { if (ref($changes->{'cancreate'}) eq 'ARRAY') { push(@{$changes->{'cancreate'}},'captcha'); } elsif (!defined($changes->{'cancreate'})) { $changes->{'cancreate'} = ['captcha']; } + } elsif ($container eq 'passwords') { + $changes->{'reset'} = 1; } else { $changes->{'captcha'} = 1; } @@ -11257,9 +13587,9 @@ sub process_captcha { } $newsettings->{'recaptchaversion'} = $newversion; } - if (ref($current->{'recaptchakeys'}) eq 'HASH') { - $currpub = $current->{'recaptchakeys'}{'public'}; - $currpriv = $current->{'recaptchakeys'}{'private'}; + if (ref($current{'recaptchakeys'}) eq 'HASH') { + $currpub = $current{'recaptchakeys'}{'public'}; + $currpriv = $current{'recaptchakeys'}{'private'}; unless ($newsettings->{'captcha'} eq 'recaptcha') { $newsettings->{'recaptchakeys'} = { public => '', @@ -11267,8 +13597,8 @@ sub process_captcha { } } } - if ($current->{'captcha'} eq 'recaptcha') { - $currversion = $current->{'recaptchaversion'}; + if ($current{'captcha'} eq 'recaptcha') { + $currversion = $current{'recaptchaversion'}; if ($currversion ne '2') { $currversion = 1; } @@ -11280,6 +13610,8 @@ sub process_captcha { } elsif (!defined($changes->{'cancreate'})) { $changes->{'cancreate'} = ['recaptchaversion']; } + } elsif ($container eq 'passwords') { + $changes->{'reset'} = 1; } else { $changes->{'recaptchaversion'} = 1; } @@ -11291,6 +13623,8 @@ sub process_captcha { } elsif (!defined($changes->{'cancreate'})) { $changes->{'cancreate'} = ['recaptchakeys']; } + } elsif ($container eq 'passwords') { + $changes->{'reset'} = 1; } else { $changes->{'recaptchakeys'} = 1; } @@ -11406,7 +13740,7 @@ sub modify_defaults { my ($resulttext,$mailmsgtxt,%newvalues,%changes,@errors); my %domdefaults = &Apache::lonnet::get_domain_defaults($dom,1); my @items = ('auth_def','auth_arg_def','lang_def','timezone_def','datelocale_def', - 'portal_def','intauth_cost','intauth_check','intauth_switch'); + 'portal_def'); my @authtypes = ('internal','krb4','krb5','localauth'); foreach my $item (@items) { $newvalues{$item} = $env{'form.'.$item}; @@ -11448,24 +13782,6 @@ sub modify_defaults { push(@errors,$item); } } - } elsif ($item eq 'intauth_cost') { - if ($newvalues{$item} ne '') { - if ($newvalues{$item} =~ /\D/) { - push(@errors,$item); - } - } - } elsif ($item eq 'intauth_check') { - if ($newvalues{$item} ne '') { - unless ($newvalues{$item} =~ /^(0|1|2)$/) { - push(@errors,$item); - } - } - } elsif ($item eq 'intauth_switch') { - if ($newvalues{$item} ne '') { - unless ($newvalues{$item} =~ /^(0|1|2)$/) { - push(@errors,$item); - } - } } if (grep(/^\Q$item\E$/,@errors)) { $newvalues{$item} = $domdefaults{$item}; @@ -11474,6 +13790,18 @@ sub modify_defaults { } $domdefaults{$item} = $newvalues{$item}; } + my %staticdefaults = ( + 'intauth_cost' => 10, + 'intauth_check' => 0, + 'intauth_switch' => 0, + ); + foreach my $item ('intauth_cost','intauth_check','intauth_switch') { + if (exists($domdefaults{$item})) { + $newvalues{$item} = $domdefaults{$item}; + } else { + $newvalues{$item} = $staticdefaults{$item}; + } + } my %defaults_hash = ( defaults => \%newvalues, ); @@ -11603,28 +13931,6 @@ sub modify_defaults { localauth => 'loc', ); $value = $authnames{$shortauth{$value}}; - } elsif ($item eq 'intauth_switch') { - my %optiondesc = &Apache::lonlocal::texthash ( - 0 => 'No', - 1 => 'Yes', - 2 => 'Yes, and copy existing passwd file to passwd.bak file', - ); - if ($value =~ /^(0|1|2)$/) { - $value = $optiondesc{$value}; - } else { - $value = &mt('none -- defaults to No'); - } - } elsif ($item eq 'intauth_check') { - my %optiondesc = &Apache::lonlocal::texthash ( - 0 => 'No', - 1 => 'Yes, allow login then update passwd file using default cost (if higher)', - 2 => 'Yes, disallow login if stored cost is less than domain default', - ); - if ($value =~ /^(0|1|2)$/) { - $value = $optiondesc{$value}; - } else { - $value = &mt('none -- defaults to No'); - } } $resulttext .= '
  • '.&mt('[_1] set to "[_2]"',$title->{$item},$value).'
  • '; $mailmsgtext .= "$title->{$item} set to $value\n"; @@ -14533,7 +16839,8 @@ sub devalidate_remote_domconfs { my %servers = &Apache::lonnet::internet_dom_servers($dom); my %thismachine; map { $thismachine{$_} = 1; } &Apache::lonnet::current_machine_ids(); - my @posscached = ('domainconfig','domdefaults','usersessions','directorysrch','cats'); + my @posscached = ('domainconfig','domdefaults','usersessions', + 'ltitools','directorysrch','passwdconf','cats'); if (keys(%servers)) { foreach my $server (keys(%servers)) { next if ($thismachine{$server});