- '.&mt($item->{'header'}->[0]->{'col1'}).'
+ '.&mt($item->{'header'}->[0]->{'col1'}).'
'.&mt($item->{'header'}->[0]->{'col2'}).'
';
$rowtotal ++;
@@ -844,7 +896,7 @@ sub print_config_box {
($action eq 'usermodification') || ($action eq 'defaults') || ($action eq 'coursedefaults') ||
($action eq 'selfenrollment') || ($action eq 'usersessions') || ($action eq 'ssl') ||
($action eq 'directorysrch') || ($action eq 'trust') || ($action eq 'helpsettings') ||
- ($action eq 'contacts')) {
+ ($action eq 'contacts') || ($action eq 'privacy') || ($action eq 'wafproxy')) {
$output .= $item->{'print'}->('top',$dom,$settings,\$rowtotal);
} elsif ($action eq 'passwords') {
$output .= $item->{'print'}->('top',$dom,$confname,$settings,\$rowtotal);
@@ -879,7 +931,8 @@ 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 'trust') || ($action eq 'contacts') || ($action eq 'passwords')) {
+ ($action eq 'trust') || ($action eq 'contacts') ||
+ ($action eq 'privacy') || ($action eq 'passwords')) {
if ($action eq 'coursecategories') {
$output .= &print_coursecategories('middle',$dom,$item,$settings,\$rowtotal);
$colspan = ' colspan="2"';
@@ -932,7 +985,7 @@ sub print_config_box {
'."\n";
if ($action eq 'coursecategories') {
$output .= &print_coursecategories('bottom',$dom,$item,$settings,\$rowtotal);
- } elsif (($action eq 'contacts') || ($action eq 'passwords')) {
+ } elsif (($action eq 'contacts') || ($action eq 'privacy') || ($action eq 'passwords')) {
if ($action eq 'passwords') {
$output .= $item->{'print'}->('lower',$dom,$confname,$settings,\$rowtotal);
} else {
@@ -947,7 +1000,7 @@ sub print_config_box {
- '.&mt($item->{'header'}->[3]->{'col1'}).'
+ '.&mt($item->{'header'}->[3]->{'col1'}).'
'.&mt($item->{'header'}->[3]->{'col2'}).' '."\n";
if ($action eq 'passwords') {
$output .= $item->{'print'}->('bottom',$dom,$confname,$settings,\$rowtotal);
@@ -966,7 +1019,7 @@ sub print_config_box {
$rowtotal ++;
} elsif (($action eq 'usermodification') || ($action eq 'coursedefaults') ||
($action eq 'defaults') || ($action eq 'directorysrch') ||
- ($action eq 'helpsettings')) {
+ ($action eq 'helpsettings') || ($action eq 'wafproxy')) {
$output .= $item->{'print'}->('bottom',$dom,$settings,\$rowtotal);
} elsif ($action eq 'scantron') {
$output .= $item->{'print'}->($r,'bottom',$dom,$confname,$settings,\$rowtotal);
@@ -1149,7 +1202,8 @@ sub print_config_box {
$output .= &print_quotas($dom,$settings,\$rowtotal,$action);
} elsif (($action eq 'autoenroll') || ($action eq 'autocreate') ||
($action eq 'serverstatuses') || ($action eq 'loadbalancing') ||
- ($action eq 'ltitools') || ($action eq 'lti')) {
+ ($action eq 'ltitools') || ($action eq 'lti') ||
+ ($action eq 'proctoring')) {
$output .= $item->{'print'}->($dom,$settings,\$rowtotal);
}
}
@@ -2790,6 +2844,102 @@ function toggleLTITools(form,setting,ite
ENDSCRIPT
}
+sub proctoring_javascript {
+ my ($settings) = @_;
+ my (%ordered,$total,%jstext);
+ $total = 0;
+ if (ref($settings) eq 'HASH') {
+ foreach my $item (keys(%{$settings})) {
+ if (ref($settings->{$item}) eq 'HASH') {
+ my $num = $settings->{$item}{'order'};
+ $ordered{$num} = $item;
+ }
+ }
+ $total = scalar(keys(%{$settings}));
+ } else {
+ %ordered = (
+ 0 => 'proctorio',
+ 1 => 'examity',
+ );
+ $total = 2;
+ }
+ my @jsarray = ();
+ foreach my $item (sort {$a <=> $b } (keys(%ordered))) {
+ push(@jsarray,$ordered{$item});
+ }
+ my $jstext = ' var proctors = Array('."'".join("','",@jsarray)."'".');'."\n";
+ return <<"ENDSCRIPT";
+
+
+ENDSCRIPT
+}
+
+
sub lti_javascript {
my ($settings) = @_;
my $togglejs = <i_toggle_js();
@@ -2896,7 +3046,7 @@ function toggleLTI(form,setting,item) {
}
}
}
- } else if ((setting == 'user') || (setting == 'crs') || (setting == 'passback')) {
+ } else if ((setting == 'user') || (setting == 'crs') || (setting == 'passback') || (setting == 'callback')) {
var radioname = '';
var divid = '';
if (setting == 'user') {
@@ -2905,6 +3055,9 @@ function toggleLTI(form,setting,item) {
} else if (setting == 'crs') {
radioname = 'lti_mapcrs_'+item;
divid = 'lti_crsfield_'+item;
+ } else if (setting == 'callback') {
+ radioname = 'lti_callback_'+item;
+ divid = 'lti_callbackfield_'+item;
} else {
radioname = 'lti_passback_'+item;
divid = 'lti_passback_'+item;
@@ -2914,7 +3067,7 @@ function toggleLTI(form,setting,item) {
var setvis = '';
for (var i=0; i '.
- $lt{'crs'.$item}.''.(' ' x2)."\n";
+ $lt{'crs'.$item}.' '."\n";
}
$datatable .= ''.
''.&mt('Custom items sent on launch').' '.
@@ -4806,6 +4959,640 @@ sub ltitools_names {
return %lt;
}
+sub print_proctoring {
+ my ($dom,$settings,$rowtotal) = @_;
+ my $itemcount = 1;
+ my (%ordered,%providernames,%current,%currentdef);
+ my $confname = $dom.'-domainconfig';
+ my $switchserver = &check_switchserver($dom,$confname);
+ if (ref($settings) eq 'HASH') {
+ foreach my $item (keys(%{$settings})) {
+ if (ref($settings->{$item}) eq 'HASH') {
+ my $num = $settings->{$item}{'order'};
+ $ordered{$num} = $item;
+ }
+ }
+ } else {
+ %ordered = (
+ 1 => 'proctorio',
+ 2 => 'examity',
+ );
+ }
+ %providernames = &proctoring_providernames();
+ my $maxnum = scalar(keys(%ordered));
+ my (%requserfields,%optuserfields,%defaults,%extended,%crsconf,@courseroles,@ltiroles);
+ my ($requref,$opturef,$defref,$extref,$crsref,$rolesref,$ltiref) = &proctoring_data();
+ if (ref($requref) eq 'HASH') {
+ %requserfields = %{$requref};
+ }
+ if (ref($opturef) eq 'HASH') {
+ %optuserfields = %{$opturef};
+ }
+ if (ref($defref) eq 'HASH') {
+ %defaults = %{$defref};
+ }
+ if (ref($extref) eq 'HASH') {
+ %extended = %{$extref};
+ }
+ if (ref($crsref) eq 'HASH') {
+ %crsconf = %{$crsref};
+ }
+ if (ref($rolesref) eq 'ARRAY') {
+ @courseroles = @{$rolesref};
+ }
+ if (ref($ltiref) eq 'ARRAY') {
+ @ltiroles = @{$ltiref};
+ }
+ my $datatable;
+ my $css_class;
+ 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 $provider = $ordered{$items[$i]};
+ my $optionsty = 'none';
+ my ($available,$version,$lifetime,$imgsrc,$userincdom,$showroles,
+ %checkedfields,%rolemaps,%inuse,%crsconfig,%current);
+ if (ref($settings) eq 'HASH') {
+ if (ref($settings->{$provider}) eq 'HASH') {
+ %current = %{$settings->{$provider}};
+ if ($current{'available'}) {
+ $optionsty = 'block';
+ $available = 1;
+ }
+ if ($current{'lifetime'} =~ /^\d+$/) {
+ $lifetime = $current{'lifetime'};
+ }
+ if ($current{'version'} =~ /^\d+\.\d+$/) {
+ $version = $current{'version'};
+ }
+ if ($current{'image'} ne '') {
+ $imgsrc = ' ';
+ }
+ if (ref($current{'fields'}) eq 'ARRAY') {
+ map { $checkedfields{$_} = 1; } @{$current{'fields'}};
+ }
+ $userincdom = $current{'incdom'};
+ if (ref($current{'roles'}) eq 'HASH') {
+ %rolemaps = %{$current{'roles'}};
+ $checkedfields{'roles'} = 1;
+ }
+ if (ref($current{'defaults'}) eq 'ARRAY') {
+ foreach my $val (@{$current{'defaults'}}) {
+ if (grep(/^\Q$val\E$/,@{$defaults{$provider}})) {
+ $inuse{$val} = 1;
+ } else {
+ foreach my $poss (keys(%{$extended{$provider}})) {
+ if (ref($extended{$provider}{$poss}) eq 'ARRAY') {
+ if (grep(/^\Q$val\E$/,@{$extended{$provider}{$poss}})) {
+ $inuse{$poss} = $val;
+ last;
+ }
+ }
+ }
+ }
+ }
+ } elsif (ref($current{'defaults'}) eq 'HASH') {
+ foreach my $key (keys(%{$current{'defaults'}})) {
+ my $currval = $current{'defaults'}{$key};
+ if (grep(/^\Q$key\E$/,@{$defaults{$provider}})) {
+ $inuse{$key} = 1;
+ } else {
+ my $match;
+ foreach my $poss (keys(%{$extended{$provider}})) {
+ if (ref($extended{$provider}{$poss}) eq 'ARRAY') {
+ if (grep(/^\Q$key\E$/,@{$extended{$provider}{$poss}})) {
+ $inuse{$poss} = $key;
+ last;
+ }
+ } elsif (ref($extended{$provider}{$poss}) eq 'HASH') {
+ foreach my $inner (sort(keys(%{$extended{$provider}{$poss}}))) {
+ if (ref($extended{$provider}{$poss}{$inner}) eq 'ARRAY') {
+ if (grep(/^\Q$currval\E$/,@{$extended{$provider}{$poss}{$inner}})) {
+ $currentdef{$inner} = $currval;
+ $match = 1;
+ last;
+ }
+ } elsif ($inner eq $key) {
+ $currentdef{$key} = $currval;
+ $match = 1;
+ last;
+ }
+ }
+ }
+ last if ($match);
+ }
+ }
+ }
+ }
+ if (ref($current{'crsconf'}) eq 'ARRAY') {
+ map { $crsconfig{$_} = 1; } @{$current{'crsconf'}};
+ }
+ }
+ }
+ my %lt = &proctoring_titles($provider);
+ my %fieldtitles = &proctoring_fieldtitles($provider);
+ my $onclickavailable = ' onclick="toggleProctoring(this.form,'."'$provider'".');"';
+ my %checkedavailable = (
+ yes => '',
+ no => ' checked="checked"',
+ );
+ if ($available) {
+ $checkedavailable{'yes'} = $checkedavailable{'no'};
+ $checkedavailable{'no'} = '';
+ }
+ my $chgstr = ' onchange="javascript:reorderProctoring(this.form,'."'proctoring_pos_".$provider."'".');"';
+ $datatable .= ''
+ .'';
+ for (my $k=0; $k<$maxnum; $k++) {
+ my $vpos = $k+1;
+ my $selstr;
+ if ($k == $i) {
+ $selstr = ' selected="selected" ';
+ }
+ $datatable .= ''.$vpos.' ';
+ }
+ if ($version eq '') {
+ if ($provider eq 'proctorio') {
+ $version = '1.0';
+ } elsif ($provider eq 'examity') {
+ $version = '1.1';
+ }
+ }
+ if ($lifetime eq '') {
+ $lifetime = '300';
+ }
+ $datatable .=
+ ' '.(' 'x2).''.$providernames{$provider}.' '.
+ ''.$lt{'avai'}.' '.
+ ' '.&mt('Yes').' '."\n".
+ ' '.&mt('No').' '."\n".
+ ' '.
+ ''.
+ ''.$lt{'base'}.' '.
+ ''.$lt{'version'}.':'.
+ ''.$version.' '."\n".
+ (' 'x2).
+ ''.$lt{'sigmethod'}.':'.
+ 'HMAC-SHA1 '.
+ 'HMAC-SHA256 '.
+ (' 'x2).
+ ''.$lt{'lifetime'}.': '."\n".
+ ' '.
+ ''.$lt{'url'}.': '."\n".
+ ' '.
+ ''.$lt{'key'}.': '."\n".
+ (' 'x2).
+ ''.$lt{'secret'}.': '.
+ ' '.$lt{'visible'}.' '."\n";
+ $datatable .= ''.$lt{'icon'}.': ';
+ if ($imgsrc) {
+ $datatable .= $imgsrc.
+ ' '.&mt('Delete?').' '.
+ ' '.&mt('Replace:');
+ }
+ $datatable .= ' ';
+ if ($switchserver) {
+ $datatable .= &mt('Upload to library server: [_1]',$switchserver);
+ } else {
+ $datatable .= ' ';
+ }
+ unless ($imgsrc) {
+ $datatable .= ' ('.&mt('if larger than 21x21 pixels, image will be scaled').')';
+ }
+ $datatable .= ' '."\n";
+ if (ref($requserfields{$provider}) eq 'ARRAY') {
+ if (@{$requserfields{$provider}} > 0) {
+ $datatable .= ''.$lt{'requ'}.' ';
+ foreach my $field (@{$requserfields{$provider}}) {
+ $datatable .= ''.
+ ' '.
+ $lt{$field}.' ';
+ if ($field eq 'user') {
+ my $seluserdom = '';
+ my $unseluserdom = ' selected="selected"';
+ if ($userincdom) {
+ $seluserdom = $unseluserdom;
+ $unseluserdom = '';
+ }
+ $datatable .= ': '.
+ ''.
+ ''.$lt{'username'}.' '.
+ ''.$lt{'uname:dom'}.' '.
+ ' ';
+ } else {
+ $datatable .= ' ';
+ if ($field eq 'roles') {
+ $showroles = 1;
+ }
+ }
+ $datatable .= ' ';
+ }
+ }
+ $datatable .= ' '."\n";
+ }
+ if (ref($optuserfields{$provider}) eq 'ARRAY') {
+ if (@{$optuserfields{$provider}} > 0) {
+ $datatable .= ''.$lt{'optu'}.' ';
+ foreach my $field (@{$optuserfields{$provider}}) {
+ my $checked;
+ if ($checkedfields{$field}) {
+ $checked = ' checked="checked"';
+ }
+ $datatable .= ''.
+ ' '.$lt{$field}.' ';
+ }
+ $datatable .= ' '."\n";
+ }
+ }
+ if (ref($defaults{$provider}) eq 'ARRAY') {
+ if (@{$defaults{$provider}}) {
+ my (%options,@selectboxes);
+ if (ref($extended{$provider}) eq 'HASH') {
+ %options = %{$extended{$provider}};
+ }
+ $datatable .= ''.$lt{'defa'}.' ';
+ my ($rem,$numinrow,$dropdowns);
+ if ($provider eq 'proctorio') {
+ $datatable .= '';
+ if (@selectboxes) {
+ $datatable .= '';
+ $numinrow = 2;
+ for (my $i=0; $i<@selectboxes; $i++) {
+ $rem = $i%($numinrow);
+ if ($rem == 0) {
+ if ($i > 0) {
+ $datatable .= '';
+ }
+ $datatable .= '';
+ }
+ $datatable .= ''.
+ $selectboxes[$i].' ';
+ }
+ if ($numinrow) {
+ $rem = $i%$numinrow;
+ }
+ $colsleft = $numinrow - $rem;
+ if ($colsleft > 1) {
+ $datatable .= '';
+ } else {
+ $datatable .= ' ';
+ }
+ $datatable .= ' '.
+ '
';
+ }
+ }
+ $datatable .= ' ';
+ }
+ if (ref($crsconf{$provider}) eq 'ARRAY') {
+ $datatable .= ''.
+ ''.&mt('Configurable in course').' ';
+ my ($rem,$numinrow);
+ if ($provider eq 'proctorio') {
+ $datatable .= '';
+ $numinrow = 4;
+ }
+ my $i = 0;
+ foreach my $item (@{$crsconf{$provider}}) {
+ my $name;
+ if ($provider eq 'examity') {
+ $name = $lt{'crs'.$item};
+ } elsif ($provider eq 'proctorio') {
+ $name = $fieldtitles{$item};
+ $rem = $i%($numinrow);
+ if ($rem == 0) {
+ if ($i > 0) {
+ $datatable .= '';
+ }
+ $datatable .= '';
+ }
+ $datatable .= ' '.
+ $name.'';
+ if ($provider eq 'examity') {
+ $datatable .= ' ';
+ }
+ $datatable .= "\n";
+ $i++;
+ }
+ if ($provider eq 'proctorio') {
+ if ($numinrow) {
+ $rem = $i%$numinrow;
+ }
+ my $colsleft = $numinrow - $rem;
+ if ($colsleft > 1) {
+ $datatable .= '';
+ } else {
+ $datatable .= ' ';
+ }
+ $datatable .= ' '.
+ '
';
+ }
+ $datatable .= ' ';
+ }
+ if ($showroles) {
+ $datatable .= ''.
+ ''.&mt('Role mapping').' ';
+ foreach my $role (@courseroles) {
+ my ($selected,$selectnone);
+ if (!$rolemaps{$role}) {
+ $selectnone = ' selected="selected"';
+ }
+ $datatable .= ''.
+ &Apache::lonnet::plaintext($role,'Course').' '.
+ ''.
+ ''.&mt('Select').' ';
+ foreach my $ltirole (@ltiroles) {
+ unless ($selectnone) {
+ if ($rolemaps{$role} eq $ltirole) {
+ $selected = ' selected="selected"';
+ } else {
+ $selected = '';
+ }
+ }
+ $datatable .= ''.$ltirole.' ';
+ }
+ $datatable .= ' ';
+ }
+ $datatable .= '
'.
+ ''.
+ ''.&mt('Custom items sent on launch').' '.
+ ' '."\n";
+ }
+ $datatable .= '';
+ }
+ $itemcount ++;
+ }
+ }
+ return $datatable;
+}
+
+sub proctoring_data {
+ my $requserfields = {
+ proctorio => ['user'],
+ examity => ['roles','user'],
+ };
+ my $optuserfields = {
+ proctorio => ['fullname'],
+ examity => ['fullname','firstname','lastname','email'],
+ };
+ my $defaults = {
+ proctorio => ['recordvideo','recordaudio','recordscreen','recordwebtraffic',
+ 'recordroomstart','verifyvideo','verifyaudio','verifydesktop',
+ 'verifyid','verifysignature','fullscreen','clipboard','tabslinks',
+ 'closetabs','onescreen','print','downloads','cache','rightclick',
+ 'reentry','calculator','whiteboard'],
+ examity => ['display'],
+ };
+ my $extended = {
+ proctorio => {
+ verifyid => ['verifyidauto','verifyidlive'],
+ fullscreen => ['fullscreenlenient','fullscreenmoderate','fullscreensever'],
+ tabslinks => ['notabs','linksonly'],
+ reentry => ['noreentry','agentreentry'],
+ calculator => ['calculatorbasic','calculatorsci'],
+ },
+ examity => {
+ display => {
+ target => ['iframe','tab','window'],
+ width => '',
+ height => '',
+ linktext => '',
+ explanation => '',
+ },
+ },
+ };
+ my $crsconf = {
+ proctorio => ['recordvideo','recordaudio','recordscreen','recordwebtraffic',
+ 'recordroomstart','verifyvideo','verifyaudio','verifydesktop',
+ 'verifyid','verifysignature','fullscreen','clipboard','tabslinks',
+ 'closetabs','onescreen','print','downloads','cache','rightclick',
+ 'reentry','calculator','whiteboard'],
+ examity => ['label','title','target','linktext','explanation','append'],
+ };
+ my $courseroles = ['cc','in','ta','ep','st'];
+ my $ltiroles = ['Instructor','ContentDeveloper','TeachingAssistant','Learner'];
+ return ($requserfields,$optuserfields,$defaults,$extended,$crsconf,$courseroles,$ltiroles);
+}
+
+sub proctoring_titles {
+ my ($item) = @_;
+ my (%common_lt,%custom_lt);
+ %common_lt = &Apache::lonlocal::texthash (
+ 'avai' => 'Available?',
+ 'base' => 'Basic Settings',
+ 'requ' => 'User data required to be sent on launch',
+ 'optu' => 'User data optionally sent on launch',
+ 'udsl' => 'User data sent on launch',
+ 'defa' => 'Defaults for items configurable in course',
+ 'sigmethod' => 'Signature Method',
+ 'key' => 'Key',
+ 'lifetime' => 'Nonce lifetime (s)',
+ 'secret' => 'Secret',
+ 'icon' => 'Icon',
+ 'fullname' => 'Full Name',
+ 'visible' => 'Visible input',
+ 'username' => 'username',
+ 'user' => 'User',
+ );
+ if ($item eq 'proctorio') {
+ %custom_lt = &Apache::lonlocal::texthash (
+ 'version' => 'OAuth version',
+ 'url' => 'API URL',
+ 'uname:dom' => 'username-domain',
+ );
+ } elsif ($item eq 'examity') {
+ %custom_lt = &Apache::lonlocal::texthash (
+ 'version' => 'LTI Version',
+ 'url' => 'URL',
+ 'uname:dom' => 'username:domain',
+ 'msgtype' => 'Message Type',
+ 'firstname' => 'First Name',
+ 'lastname' => 'Last Name',
+ 'email' => 'E-mail',
+ 'roles' => 'Role',
+ 'crstarget' => 'Display target',
+ 'crslabel' => 'Course label',
+ 'crstitle' => 'Course title',
+ 'crslinktext' => 'Link Text',
+ 'crsexplanation' => 'Explanation',
+ 'crsappend' => 'Provider URL',
+ );
+ }
+ my %lt = (%common_lt,%custom_lt);
+ return %lt;
+}
+
+sub proctoring_fieldtitles {
+ my ($item) = @_;
+ if ($item eq 'proctorio') {
+ return &Apache::lonlocal::texthash (
+ 'recordvideo' => 'Record video',
+ 'recordaudio' => 'Record audio',
+ 'recordscreen' => 'Record screen',
+ 'recordwebtraffic' => 'Record web traffic',
+ 'recordroomstart' => 'Record room scan',
+ 'verifyvideo' => 'Verify webcam',
+ 'verifyaudio' => 'Verify microphone',
+ 'verifydesktop' => 'Verify desktop recording',
+ 'verifyid' => 'Photo ID verification',
+ 'verifysignature' => 'Require signature',
+ 'fullscreen' => 'Fullscreen',
+ 'clipboard' => 'Disable copy/paste',
+ 'tabslinks' => 'New tabs/windows',
+ 'closetabs' => 'Close other tabs',
+ 'onescreen' => 'Limit to single screen',
+ 'print' => 'Disable Printing',
+ 'downloads' => 'Disable Downloads',
+ 'cache' => 'Empty cache after exam',
+ 'rightclick' => 'Disable right click',
+ 'reentry' => 'Re-entry to exam',
+ 'calculator' => 'Onscreen calculator',
+ 'whiteboard' => 'Onscreen whiteboard',
+ 'verifyidauto' => 'Automated verification',
+ 'verifyidlive' => 'Live agent verification',
+ 'fullscreenlenient' => 'Forced, but can navigate away for up to 30s',
+ 'fullscreenmoderate' => 'Forced, but can navigate away for up to 15s',
+ 'fullscreensever' => 'Forced, navigation away ends exam',
+ 'notabs' => 'Disaallowed',
+ 'linksonly' => 'Allowed from links in exam',
+ 'noreentry' => 'Disallowed',
+ 'agentreentry' => 'Agent required for re-entry',
+ 'calculatorbasic' => 'Basic',
+ 'calculatorsci' => 'Scientific',
+ );
+ } elsif ($item eq 'examity') {
+ return &Apache::lonlocal::texthash (
+ 'target' => 'Display target',
+ 'window' => 'Window',
+ 'tab' => 'Tab',
+ 'iframe' => 'iFrame',
+ 'height' => 'Height (pixels)',
+ 'width' => 'Width (pixels)',
+ 'linktext' => 'Default Link Text',
+ 'explanation' => 'Default Explanation',
+ 'append' => 'Provider URL',
+ );
+ }
+}
+
+sub proctoring_providernames {
+ return (
+ proctorio => 'Proctorio',
+ examity => 'Examity',
+ );
+}
+
sub print_lti {
my ($dom,$settings,$rowtotal) = @_;
my $itemcount = 1;
@@ -4948,7 +5735,7 @@ sub lti_names {
sub lti_options {
my ($num,$current,$itemcount,%lt) = @_;
- my (%checked,%rolemaps,$crssecsrc,$userfield,$cidfield);
+ my (%checked,%rolemaps,$crssecsrc,$userfield,$cidfield,$callback);
$checked{'mapuser'}{'sourcedid'} = ' checked="checked"';
$checked{'mapcrs'}{'course_offering_sourcedid'} = ' checked="checked"';
$checked{'makecrs'}{'N'} = ' checked="checked"';
@@ -4966,6 +5753,7 @@ sub lti_options {
my $crsfieldsty = 'none';
my $crssecfieldsty = 'none';
my $secsrcfieldsty = 'none';
+ my $callbacksty = 'none';
my $passbacksty = 'none';
my $optionsty = 'block';
my $lcauthparm;
@@ -5045,6 +5833,13 @@ sub lti_options {
} else {
$checked{'crssec'}{'N'} = ' checked="checked"';
}
+ if ($current->{'callback'} ne '') {
+ $callback = $current->{'callback'};
+ $checked{'callback'}{'Y'} = ' checked="checked"';
+ $callbacksty = 'inline-block';
+ } else {
+ $checked{'callback'}{'N'} = ' checked="checked"';
+ }
if ($current->{'topmenu'}) {
$checked{'topmenu'}{'Y'} = ' checked="checked"';
} else {
@@ -5070,6 +5865,7 @@ sub lti_options {
} else {
$checked{'makecrs'}{'N'} = ' checked="checked"';
$checked{'crssec'}{'N'} = ' checked="checked"';
+ $checked{'callback'}{'N'} = ' checked="checked"';
$checked{'topmenu'}{'N'} = ' checked="checked"';
$checked{'inlinemenu'}{'Y'} = ' checked="checked"';
$checked{'menuitem'}{'grades'} = ' checked="checked"';
@@ -5098,6 +5894,7 @@ sub lti_options {
my $onclickuser = ' onclick="toggleLTI(this.form,'."'user','$num'".');"';
my $onclickcrs = ' onclick="toggleLTI(this.form,'."'crs','$num'".');"';
my $onclicksec = ' onclick="toggleLTI(this.form,'."'sec','$num'".');"';
+ my $onclickcallback = ' onclick="toggleLTI(this.form,'."'callback','$num'".');"';
my $onclicksecsrc = ' onclick="toggleLTI(this.form,'."'secsrc','$num'".')"';
my $onclicklcauth = ' onclick="toggleLTI(this.form,'."'lcauth','$num'".')"';
my $onclickmenu = ' onclick="toggleLTI(this.form,'."'lcmenu','$num'".');"';
@@ -5111,7 +5908,7 @@ sub lti_options {
$output .= ''.
''.
'
'.
+ 'value="'.$userfield.'" />'.
''.&mt('Mapping course roles').' ';
foreach my $ltirole (@lticourseroles) {
my ($selected,$selectnone);
@@ -5247,7 +6044,17 @@ sub lti_options {
' '.
&mt('Outcomes Service (1.1)').' '.(' 'x2).
' '.
- &mt('Outcomes Extension (1.0)').' '.
+ &mt('Outcomes Extension (1.0)').''.
+ '
'.
+ ''.&mt('Callback on logout').': '.
+ ' '.&mt('No').' '.(' 'x2).
+ ' '.&mt('Yes').'
'.
+ ''.
+ ''.&mt('Parameter').': '.
+ ' '.
+ '
'.
''.&mt('Course defaults (Course Coordinator can override)').' '.
''.$lt{'topmenu'}.': '.
' 'Assigned domain role(s)',
+ author => 'Assigned co-author role(s)',
+ course => 'Assigned course role(s)',
+ community => 'Assigned community role',
+ );
+ $numinrow = 4;
+ ($othertitle,$usertypes,$types) =
+ &Apache::loncommon::sorted_inst_types($dom);
+ }
+ if (($position eq 'top') || ($position eq 'middle')) {
+ my (%by_ip,%by_location,@intdoms,@instdoms);
+ &build_location_hashes(\@intdoms,\%by_ip,\%by_location,\@instdoms);
+ if ($position eq 'top') {
+ my %curr;
+ my @options = ('none','user','domain','auto');
+ my %titles = &Apache::lonlocal::texthash (
+ none => 'Not allowed',
+ user => 'User authorizes',
+ domain => 'DC authorizes',
+ auto => 'Unrestricted',
+ instdom => 'Other domain shares institution/provider',
+ extdom => 'Other domain has different institution/provider',
+ );
+ my %names = &Apache::lonlocal::texthash (
+ domain => 'Domain role',
+ author => 'Co-author role',
+ course => 'Course role',
+ community => 'Community role',
+ );
+ my $primary_id = &Apache::lonnet::domain($dom,'primary');
+ my $intdom = &Apache::lonnet::internet_dom($primary_id);
+ foreach my $domtype ('instdom','extdom') {
+ my (%checked,$skip);
+ $css_class = $itemcount%2?' class="LC_odd_row"':'';
+ $datatable .= ''.$titles{$domtype}.' '.
+ '';
+ if ($domtype eq 'instdom') {
+ unless (@instdoms > 1) {
+ $datatable .= &mt('Nothing to set, as no domains besides [_1] are hosted by [_2]',$dom,$intdom);
+ $skip = 1;
+ }
+ } elsif ($domtype eq 'extdom') {
+ if (keys(%by_location) == 0) {
+ $datatable .= &mt('Nothing to set, as no other hosts besides [_1]',$intdom);
+ $skip = 1;
+ }
+ }
+ unless ($skip) {
+ foreach my $roletype ('domain','author','course','community') {
+ $checked{'auto'} = ' checked="checked"';
+ if (ref($settings) eq 'HASH') {
+ if (ref($settings->{approval}) eq 'HASH') {
+ if (ref($settings->{approval}->{$domtype}) eq 'HASH') {
+ if ($settings->{approval}->{$domtype}->{$roletype}=~ /^(none|user|domain)$/) {
+ $checked{$1} = ' checked="checked"';
+ $checked{'auto'} = '';
+ }
+ }
+ }
+ }
+ $datatable .= ''.$names{$roletype}.' ';
+ foreach my $option (@options) {
+ $datatable .= ''.
+ ' '.$titles{$option}.
+ ' ';
+ }
+ $datatable .= ' ';
+ }
+ }
+ $datatable .= ' ';
+ $itemcount ++;
+ }
+ } elsif ($position eq 'middle') {
+ if ((@instdoms > 1) || (keys(%by_location) > 0)) {
+ if ((ref($types) eq 'ARRAY') && (ref($usertypes) eq 'HASH')) {
+ foreach my $item (@{$types}) {
+ $datatable .= &modifiable_userdata_row('privacy','othdom_'.$item,$settings,
+ $numinrow,$itemcount,'','','','','',
+ '',$usertypes->{$item});
+ $itemcount ++;
+ }
+ }
+ $datatable .= &modifiable_userdata_row('privacy','othdom_default',$settings,
+ $numinrow,$itemcount,'','','','','',
+ '',$othertitle);
+ $itemcount ++;
+ } else {
+ my (@insttypes,%insttitles);
+ if ((ref($types) eq 'ARRAY') && (ref($usertypes) eq 'HASH')) {
+ @insttypes = @{$types};
+ %insttitles = %{$usertypes};
+ }
+ foreach my $item (@insttypes,'default') {
+ my $title;
+ if ($item eq 'default') {
+ $title = $othertitle;
+ } else {
+ $title = $insttitles{$item};
+ }
+ $css_class = $itemcount%2?' class="LC_odd_row"':'';
+ $datatable .= ''.
+ ''.$title.' '.
+ ''.
+ &mt('Nothing to set here, as there are no other domains').
+ ' ';
+ $itemcount ++;
+ }
+ }
+ }
+ } else {
+ my $prefix;
+ if ($position eq 'lower') {
+ $prefix = 'priv';
+ } else {
+ $prefix = 'unpriv';
+ }
+ foreach my $item (@items) {
+ $datatable .= &modifiable_userdata_row('privacy',$prefix.'_'.$item,$settings,
+ $numinrow,$itemcount,'','','','','',
+ '',$names{$item});
+ $itemcount ++;
+ }
+ }
+ if (ref($rowtotal)) {
+ $$rowtotal += $itemcount;
+ }
+ return $datatable;
+}
+
sub print_passwords {
my ($position,$dom,$confname,$settings,$rowtotal) = @_;
my ($datatable,$css_class);
@@ -5822,6 +6766,7 @@ sub print_passwords {
max => 'Maximum password length',
chars => 'Required characters',
expire => 'Password expiration (days)',
+ numsaved => 'Number of previous passwords to save and disallow reuse',
);
if ($position eq 'top') {
my ($othertitle,$usertypes,$types) = &Apache::loncommon::sorted_inst_types($dom);
@@ -5886,7 +6831,7 @@ sub print_passwords {
$datatable .= ''.
' '.$usertypes->{$item}.' '.
- ' ';
+ ' ';
}
}
my $checkedcase;
@@ -5990,7 +6935,7 @@ sub print_passwords {
&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 file'),600,500,
+ my $link = &Apache::loncommon::modal_link($customurl,&mt('custom text'),600,500,
undef,undef,undef,undef,'background-color:#ffffff');
$datatable .= ' '.$link.
' {min}) {
$min = $settings->{min};
@@ -6099,8 +7045,9 @@ sub print_passwords {
if ($settings->{expire}) {
$expire = $settings->{expire};
}
- } else {
- $min = '7';
+ if ($settings->{numsaved}) {
+ $numsaved = $settings->{numsaved};
+ }
}
my %rulenames = &Apache::lonlocal::texthash(
uc => 'At least one upper case letter',
@@ -6111,14 +7058,16 @@ sub print_passwords {
$css_class = $itemcount%2?' class="LC_odd_row"':'';
$datatable .= ''.$titles{'min'}.' '.
''.
- ' '.
- ' '.&mt('(Leave blank for no minimum)').' '.
+ ' '.
+ ' '.&mt('(Enter an integer: 7 or larger)').' '.
' ';
$itemcount ++;
$css_class = $itemcount%2?' class="LC_odd_row"':'';
$datatable .= ''.$titles{'max'}.' '.
''.
- ' '.
+ ' '.
' '.&mt('(Leave blank for no maximum)').' '.
' ';
$itemcount ++;
@@ -6158,37 +7107,214 @@ sub print_passwords {
$css_class = $itemcount%2?' class="LC_odd_row"':'';
$datatable .= ''.$titles{'expire'}.' '.
''.
- ' '.
+ ' '.
' '.&mt('(Leave blank for no expiration)').' '.
' ';
+ $itemcount ++;
+ $css_class = $itemcount%2?' class="LC_odd_row"':'';
+ $datatable .= ''.$titles{'numsaved'}.' '.
+ ''.
+ ' '.
+ ' '.&mt('(Leave blank to not save previous passwords)').' '.
+ ' ';
} else {
- my $checkedon;
- my $checkedoff = ' checked="checked"';
+ 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 ($settings->{crsownerchg}) {
- $checkedon = $checkedoff;
- $checkedoff = '';
+ 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 .= ''.
''.
&mt('Requirements').''.
- ''.&mt("Course 'type' is not a Community").' '.
+ ''.&mt("Course 'type' is not a Community or Placement Test").' '.
''.&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').' '.
' '.
' '.
- ''.
- ' '.&mt('Yes').' '.
- ' '.
- &mt('No').' '.
- ' ';
+ '';
+ 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 .= ''.
+ ' '.$usertypes->{$type}.' '.
+ ' ';
+ }
+ }
+ my $checked;
+ if ($ownerchg{$item}{'default'}) {
+ $checked = ' checked="checked"';
+ }
+ $datatable .= ' '.
+ $othertitle.' ';
+ }
+ $datatable .= ' ';
+ }
+ return $datatable;
+}
+sub print_wafproxy {
+ my ($position,$dom,$settings,$rowtotal) = @_;
+ my $css_class;
+ my $itemcount = 0;
+ my $datatable;
+ my %servers = &Apache::lonnet::internet_dom_servers($dom);
+ my (%othercontrol,%otherdoms,%aliases,%values,$setdom);
+ my %lt = &wafproxy_titles();
+ foreach my $server (sort(keys(%servers))) {
+ my $serverhome = &Apache::lonnet::get_server_homeID($servers{$server});
+ my $serverdom;
+ if ($serverhome ne $server) {
+ $serverdom = &Apache::lonnet::host_domain($serverhome);
+ $othercontrol{$server} = $serverdom;
+ } else {
+ $serverdom = &Apache::lonnet::host_domain($server);
+ if ($serverdom ne $dom) {
+ $othercontrol{$server} = $serverdom;
+ } else {
+ $setdom = 1;
+ if (ref($settings) eq 'HASH') {
+ %{$values{$dom}} = ();
+ if (ref($settings->{'alias'}) eq 'HASH') {
+ $aliases{$dom} = $settings->{'alias'};
+ }
+ foreach my $item ('ipheader','trusted','vpnint','vpnext') {
+ $values{$dom}{$item} = $settings->{$item};
+ }
+ }
+ }
+ }
+ }
+ if (keys(%othercontrol)) {
+ %otherdoms = reverse(%othercontrol);
+ foreach my $domain (keys(%otherdoms)) {
+ %{$values{$domain}} = ();
+ my %config = &Apache::lonnet::get_dom('configuration',['wafproxy'],$domain);
+ if (ref($config{$domain}) eq 'HASH') {
+ if (ref($config{$domain}{'wafproxy'}) eq 'HASH') {
+ $aliases{$domain} = $config{$domain}{'wafproxy'}{'alias'};
+ foreach my $item ('ipheader','trusted','vpnint','vpnext') {
+ $values{$domain}{$item} = $config{$domain}{'wafproxy'}{$item};
+ }
+ }
+ }
+ }
}
+ if ($position eq 'top') {
+ my %servers = &Apache::lonnet::internet_dom_servers($dom);
+ foreach my $server (sort(keys(%servers))) {
+ $itemcount ++;
+ $css_class = $itemcount%2 ? ' class="LC_odd_row"' : '';
+ $datatable .= ''.
+ ''.&mt('Hostname').': '.
+ &Apache::lonnet::hostname($server).' '.
+ '';
+ if ($othercontrol{$server}) {
+ my $current;
+ if (ref($aliases{$othercontrol{$server}}) eq 'HASH') {
+ $current = $aliases{$othercontrol{$server}{$server}};
+ }
+ if ($current) {
+ $datatable .= $current;
+ } else {
+ $datatable .= &mt('None in effect');
+ }
+ $datatable .= '('.
+ &mt('WAF/Reverse Proxy controlled by domain: [_1]',
+ ''.$othercontrol{$server}.' ').' ';
+ } else {
+ my $current;
+ if (ref($aliases{$dom}) eq 'HASH') {
+ if ($aliases{$dom}{$server}) {
+ $current = $aliases{$dom}{$server};
+ }
+ }
+ $datatable .= ' ';
+ }
+ $datatable .= ' ';
+ }
+ } else {
+ if ($setdom) {
+ $itemcount ++;
+ $css_class = $itemcount%2 ? ' class="LC_odd_row"' : '';
+ $datatable .= ''.
+ ''.&mt('Domain: [_1]',''.$dom.' ').' '.
+ &mt('Format for comma separated IP blocks').': '.
+ &mt('A.B.C.D/N or A.B.C.D - E.F.G.H').' '.
+ ' ';
+ }
+ if (keys(%otherdoms)) {
+ foreach my $domain (sort(keys(%otherdoms))) {
+ $itemcount ++;
+ $css_class = $itemcount%2 ? ' class="LC_odd_row"' : '';
+ $datatable .= ''.
+ ''.&mt('Domain: [_1]',$domain).' '.
+ '';
+ foreach my $item ('ipheader','trusted','vpnint','vpnext') {
+ my $showval = &mt('None');
+ if ($values{$domain}{$item}) {
+ $showval = $values{$domain}{$item};
+ }
+ $datatable .= ''.
+ ''.$lt{$item}.': '.$showval.' ';
+ }
+ $datatable .= '
';
+ }
+ }
+ }
+ $$rowtotal += $itemcount;
return $datatable;
}
+sub wafproxy_titles {
+ return &Apache::lonlocal::texthash(
+ vpnint => 'Internal IP Range(s) for VPN sessions',
+ vpnext => 'IP Range for backend WAF connections',
+ trusted => 'Trusted IP range(s)',
+ ipheader => 'Custom request header',
+ );
+}
+
sub print_usersessions {
my ($position,$dom,$settings,$rowtotal) = @_;
my ($css_class,$datatable,$itemcount,%checked,%choices);
@@ -6202,13 +7328,18 @@ sub print_usersessions {
if ($position eq 'top') {
if (keys(%serverhomes) > 1) {
my %spareid = ¤t_offloads_to($dom,$settings,\%servers);
- my $curroffloadnow;
+ my ($curroffloadnow,$curroffloadoth);
if (ref($settings) eq 'HASH') {
if (ref($settings->{'offloadnow'}) eq 'HASH') {
$curroffloadnow = $settings->{'offloadnow'};
}
+ if (ref($settings->{'offloadoth'}) eq 'HASH') {
+ $curroffloadoth = $settings->{'offloadoth'};
+ }
}
- $datatable .= &spares_row($dom,\%servers,\%spareid,\%serverhomes,\%altids,$curroffloadnow,$rowtotal);
+ my $other_insts = scalar(keys(%by_location));
+ $datatable .= &spares_row($dom,\%servers,\%spareid,\%serverhomes,\%altids,
+ $other_insts,$curroffloadnow,$curroffloadoth,$rowtotal);
} else {
$datatable .= ''.
&mt('Nothing to set here, as the cluster to which this domain belongs only contains one server.').
@@ -6652,7 +7783,8 @@ sub current_offloads_to {
}
sub spares_row {
- my ($dom,$servers,$spareid,$serverhomes,$altids,$curroffloadnow,$rowtotal) = @_;
+ my ($dom,$servers,$spareid,$serverhomes,$altids,$other_insts,
+ $curroffloadnow,$curroffloadoth,$rowtotal) = @_;
my $css_class;
my $numinrow = 4;
my $itemcount = 1;
@@ -6672,12 +7804,17 @@ sub spares_row {
}
}
next unless (ref($spareid->{$server}) eq 'HASH');
- my $checkednow;
+ my ($checkednow,$checkedoth);
if (ref($curroffloadnow) eq 'HASH') {
if ($curroffloadnow->{$server}) {
$checkednow = ' checked="checked"';
}
}
+ if (ref($curroffloadoth) eq 'HASH') {
+ if ($curroffloadoth->{$server}) {
+ $checkedoth = ' checked="checked"';
+ }
+ }
$css_class = $itemcount%2 ? ' class="LC_odd_row"' : '';
$datatable .= '
@@ -6686,8 +7823,15 @@ sub spares_row {
,''.$server.' ').' '.
''."\n".
' '.
- ' '.&mt('Switch active users on next access').' '.
+ ' '.&mt('Switch any active user on next access').''.
"\n";
+ if ($other_insts) {
+ $datatable .= ' '.
+ ''."\n".
+ ' '.
+ ' '.&mt('Switch other institutions on next access').' '.
+ "\n";
+ }
my (%current,%canselect);
my @choices =
&possible_newspares($server,$spareid->{$server},$serverhomes,$altids);
@@ -9084,16 +10228,22 @@ ENDSCRIPT
}
sub passwords_javascript {
- 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 %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('$intauthcheck');
+ alert('$intalert{authcheck}');
}
}
if (field.name == 'intauth_cost') {
@@ -9101,7 +10251,60 @@ function warnIntAuth(field) {
if (field.value != '') {
var regexdigit=/^\\d+\$/;
if (!regexdigit.test(field.value)) {
- alert('$intauthcost');
+ 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 {
+ 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 = '';
+ }
}
}
}
@@ -9355,7 +10558,7 @@ sub build_category_rows {
sub modifiable_userdata_row {
my ($context,$item,$settings,$numinrow,$rowcount,$usertypes,$fieldsref,$titlesref,
- $rowid,$customcss,$rowstyle) = @_;
+ $rowid,$customcss,$rowstyle,$itemdesc) = @_;
my ($role,$rolename,$statustype);
$role = $item;
if ($context eq 'cancreate') {
@@ -9378,6 +10581,8 @@ sub modifiable_userdata_row {
}
} elsif ($context eq 'lti') {
$rolename = &mt('Institutional data used (if available)');
+ } elsif ($context eq 'privacy') {
+ $rolename = $itemdesc;
} else {
if ($role eq 'cr') {
$rolename = &mt('Custom role');
@@ -9427,6 +10632,13 @@ sub modifiable_userdata_row {
if (ref($settings) eq 'HASH') {
$hashref = $settings->{'instdata'};
}
+ } elsif ($context eq 'privacy') {
+ my ($key,$inner) = split(/_/,$role);
+ if (ref($settings) eq 'HASH') {
+ if (ref($settings->{$key}) eq 'HASH') {
+ $hashref = $settings->{$key}->{$inner};
+ }
+ }
} elsif (ref($settings->{$context}) eq 'HASH') {
if (ref($settings->{$context}->{$role}) eq 'HASH') {
$hashref = $settings->{'lti_instdata'};
@@ -9465,6 +10677,24 @@ sub modifiable_userdata_row {
unless ($role eq 'emailusername') {
if (exists($checks{$fields[$i]})) {
$check = $checks{$fields[$i]};
+ } elsif ($context eq 'privacy') {
+ if ($role =~ /^priv_(domain|course)$/) {
+ if (ref($settings) ne 'HASH') {
+ $check = ' checked="checked" ';
+ }
+ } elsif ($role =~ /^priv_(author|community)$/) {
+ if (ref($settings) ne 'HASH') {
+ unless ($fields[$i] eq 'id') {
+ $check = ' checked="checked" ';
+ }
+ }
+ } elsif ($role =~ /^(unpriv|othdom)_/) {
+ if (ref($settings) ne 'HASH') {
+ if (($fields[$i] eq 'lastname') || ($fields[$i] eq 'firstname')) {
+ $check = ' checked="checked" ';
+ }
+ }
+ }
} elsif ($context ne 'lti') {
if ($role eq 'st') {
if (ref($settings) ne 'HASH') {
@@ -9493,6 +10723,8 @@ sub modifiable_userdata_row {
} else {
if ($context eq 'lti') {
$prefix = 'lti';
+ } elsif ($context eq 'privacy') {
+ $prefix = 'privacy';
}
$output .= ''.
' dir_config('lonAdmEMail');
+ my ($configuserok,$author_ok,$switchserver) = &config_check($dom,$confname,$servadm);
+ my %providernames = &proctoring_providernames();
+ my $maxnum = scalar(keys(%providernames));
+
+ my (%requserfields,%optuserfields,%defaults,%extended,%crsconf,@courseroles,@ltiroles);
+ my ($requref,$opturef,$defref,$extref,$crsref,$rolesref,$ltiref) = &proctoring_data();
+ if (ref($requref) eq 'HASH') {
+ %requserfields = %{$requref};
+ }
+ if (ref($opturef) eq 'HASH') {
+ %optuserfields = %{$opturef};
+ }
+ if (ref($defref) eq 'HASH') {
+ %defaults = %{$defref};
+ }
+ if (ref($extref) eq 'HASH') {
+ %extended = %{$extref};
+ }
+ if (ref($crsref) eq 'HASH') {
+ %crsconf = %{$crsref};
+ }
+ if (ref($rolesref) eq 'ARRAY') {
+ @courseroles = @{$rolesref};
+ }
+ if (ref($ltiref) eq 'ARRAY') {
+ @ltiroles = @{$ltiref};
+ }
+
+ if (ref($domconfig{$action}) eq 'HASH') {
+ my @todeleteimages = &Apache::loncommon::get_env_multiple('form.proctoring_image_del');
+ if (@todeleteimages) {
+ map { $imgdeletions{$_} = 1; } @todeleteimages;
+ }
+ }
+ my %customadds;
+ my @newcustom = &Apache::loncommon::get_env_multiple('form.proctoring_customadd');
+ if (@newcustom) {
+ map { $customadds{$_} = 1; } @newcustom;
+ }
+ foreach my $provider (sort(keys(%providernames))) {
+ $confhash{$provider} = {};
+ my $pos = $env{'form.proctoring_pos_'.$provider};
+ $pos =~ s/\D+//g;
+ $allpos[$pos] = $provider;
+ my (%current,%currentenc);
+ my $showroles = 0;
+ if (ref($domconfig{$action}) eq 'HASH') {
+ if (ref($domconfig{$action}{$provider}) eq 'HASH') {
+ %current = %{$domconfig{$action}{$provider}};
+ foreach my $item ('key','secret') {
+ $currentenc{$item} = $current{$item};
+ delete($current{$item});
+ }
+ }
+ }
+ if ($env{'form.proctoring_available_'.$provider}) {
+ $confhash{$provider}{'available'} = 1;
+ unless ($current{'available'}) {
+ $changes{$provider} = 1;
+ }
+ } else {
+ %{$confhash{$provider}} = %current;
+ %{$encconfhash{$provider}} = %currentenc;
+ $confhash{$provider}{'available'} = 0;
+ if ($current{'available'}) {
+ $changes{$provider} = 1;
+ }
+ }
+ if ($confhash{$provider}{'available'}) {
+ foreach my $field ('lifetime','version','sigmethod','url','key','secret') {
+ my $possval = $env{'form.proctoring_'.$provider.'_'.$field};
+ if ($field eq 'lifetime') {
+ if ($possval =~ /^\d+$/) {
+ $confhash{$provider}{$field} = $possval;
+ }
+ } elsif ($field eq 'version') {
+ if ($possval =~ /^\d+\.\d+$/) {
+ $confhash{$provider}{$field} = $possval;
+ }
+ } elsif ($field eq 'sigmethod') {
+ if ($possval =~ /^\QHMAC-SHA\E(1|256)$/) {
+ $confhash{$provider}{$field} = $possval;
+ }
+ } elsif ($field eq 'url') {
+ $confhash{$provider}{$field} = $possval;
+ } elsif (($field eq 'key') || ($field eq 'secret')) {
+ $encconfhash{$provider}{$field} = $possval;
+ unless ($currentenc{$field} eq $possval) {
+ $changes{$provider} = 1;
+ }
+ }
+ unless (($field eq 'key') || ($field eq 'secret')) {
+ unless ($current{$field} eq $confhash{$provider}{$field}) {
+ $changes{$provider} = 1;
+ }
+ }
+ }
+ if ($imgdeletions{$provider}) {
+ $changes{$provider} = 1;
+ } elsif ($env{'form.proctoring_image_'.$provider.'.filename'} ne '') {
+ my ($imageurl,$error) =
+ &process_proctoring_image($r,$dom,$confname,'proctoring_image_'.$provider,$provider,
+ $configuserok,$switchserver,$author_ok);
+ if ($imageurl) {
+ $confhash{$provider}{'image'} = $imageurl;
+ $changes{$provider} = 1;
+ }
+ if ($error) {
+ &Apache::lonnet::logthis($error);
+ $errors .= ''.$error.' ';
+ }
+ } elsif (exists($current{'image'})) {
+ $confhash{$provider}{'image'} = $current{'image'};
+ }
+ if (ref($requserfields{$provider}) eq 'ARRAY') {
+ if (@{$requserfields{$provider}} > 0) {
+ if (grep(/^user$/,@{$requserfields{$provider}})) {
+ if ($env{'form.proctoring_userincdom_'.$provider}) {
+ $confhash{$provider}{'incdom'} = 1;
+ }
+ unless ($current{'incdom'} eq $confhash{$provider}{'incdom'}) {
+ $changes{$provider} = 1;
+ }
+ }
+ if (grep(/^roles$/,@{$requserfields{$provider}})) {
+ $showroles = 1;
+ }
+ }
+ }
+ $confhash{$provider}{'fields'} = [];
+ if (ref($optuserfields{$provider}) eq 'ARRAY') {
+ if (@{$optuserfields{$provider}} > 0) {
+ my @optfields = &Apache::loncommon::get_env_multiple('form.proctoring_optional_'.$provider);
+ foreach my $field (@{$optuserfields{$provider}}) {
+ if (grep(/^\Q$field\E$/,@optfields)) {
+ push(@{$confhash{$provider}{'fields'}},$field);
+ }
+ }
+ }
+ if (ref($current{'fields'}) eq 'ARRAY') {
+ unless ($changes{$provider}) {
+ my @new = sort(@{$confhash{$provider}{'fields'}});
+ my @old = sort(@{$current{'fields'}});
+ my @diffs = &Apache::loncommon::compare_arrays(\@new,\@old);
+ if (@diffs) {
+ $changes{$provider} = 1;
+ }
+ }
+ } elsif (@{$confhash{$provider}{'fields'}}) {
+ $changes{$provider} = 1;
+ }
+ }
+ if (ref($defaults{$provider}) eq 'ARRAY') {
+ if (@{$defaults{$provider}} > 0) {
+ my %options;
+ if (ref($extended{$provider}) eq 'HASH') {
+ %options = %{$extended{$provider}};
+ }
+ my @checked = &Apache::loncommon::get_env_multiple('form.proctoring_defaults_'.$provider);
+ foreach my $field (@{$defaults{$provider}}) {
+ if ((exists($options{$field})) && (ref($options{$field}) eq 'ARRAY')) {
+ my $poss = $env{'form.proctoring_defaults_'.$field.'_'.$provider};
+ if (grep(/^\Q$poss\E$/,@{$options{$field}})) {
+ push(@{$confhash{$provider}{'defaults'}},$poss);
+ }
+ } elsif ((exists($options{$field})) && (ref($options{$field}) eq 'HASH')) {
+ foreach my $inner (keys(%{$options{$field}})) {
+ if (ref($options{$field}{$inner}) eq 'ARRAY') {
+ my $poss = $env{'form.proctoring_'.$inner.'_'.$provider};
+ if (grep(/^\Q$poss\E$/,@{$options{$field}{$inner}})) {
+ $confhash{$provider}{'defaults'}{$inner} = $poss;
+ }
+ } else {
+ $confhash{$provider}{'defaults'}{$inner} = $env{'form.proctoring_'.$inner.'_'.$provider};
+ }
+ }
+ } else {
+ if (grep(/^\Q$field\E$/,@checked)) {
+ push(@{$confhash{$provider}{'defaults'}},$field);
+ }
+ }
+ }
+ if (ref($confhash{$provider}{'defaults'}) eq 'ARRAY') {
+ if (ref($current{'defaults'}) eq 'ARRAY') {
+ unless ($changes{$provider}) {
+ my @new = sort(@{$confhash{$provider}{'defaults'}});
+ my @old = sort(@{$current{'defaults'}});
+ my @diffs = &Apache::loncommon::compare_arrays(\@new,\@old);
+ if (@diffs) {
+ $changes{$provider} = 1;
+ }
+ }
+ } elsif (ref($current{'defaults'}) eq 'ARRAY') {
+ if (@{$current{'defaults'}}) {
+ $changes{$provider} = 1;
+ }
+ }
+ } elsif (ref($confhash{$provider}{'defaults'}) eq 'HASH') {
+ if (ref($current{'defaults'}) eq 'HASH') {
+ unless ($changes{$provider}) {
+ foreach my $key (keys(%{$confhash{$provider}{'defaults'}})) {
+ unless ($confhash{$provider}{'defaults'}{$key} eq $current{'defaults'}{$key}) {
+ $changes{$provider} = 1;
+ last;
+ }
+ }
+ }
+ unless ($changes{$provider}) {
+ foreach my $key (keys(%{$current{'defaults'}})) {
+ unless ($current{'defaults'}{$key} eq $confhash{$provider}{'defaults'}{$key}) {
+ $changes{$provider} = 1;
+ last;
+ }
+ }
+ }
+ } elsif (keys(%{$confhash{$provider}{'defaults'}})) {
+ $changes{$provider} = 1;
+ }
+ }
+ }
+ }
+ if (ref($crsconf{$provider}) eq 'ARRAY') {
+ if (@{$crsconf{$provider}} > 0) {
+ $confhash{$provider}{'crsconf'} = [];
+ my @checked = &Apache::loncommon::get_env_multiple('form.proctoring_crsconf_'.$provider);
+ foreach my $crsfield (@{$crsconf{$provider}}) {
+ if (grep(/^\Q$crsfield\E$/,@checked)) {
+ push(@{$confhash{$provider}{'crsconf'}},$crsfield);
+ }
+ }
+ if (ref($current{'crsconf'}) eq 'ARRAY') {
+ unless ($changes{$provider}) {
+ my @new = sort(@{$confhash{$provider}{'crsconf'}});
+ my @old = sort(@{$current{'crsconf'}});
+ my @diffs = &Apache::loncommon::compare_arrays(\@new,\@old);
+ if (@diffs) {
+ $changes{$provider} = 1;
+ }
+ }
+ } elsif (@{$confhash{$provider}{'crsconf'}}) {
+ $changes{$provider} = 1;
+ }
+ }
+ }
+ if ($showroles) {
+ $confhash{$provider}{'roles'} = {};
+ foreach my $role (@courseroles) {
+ my $poss = $env{'form.proctoring_roles_'.$role.'_'.$provider};
+ if (grep(/^\Q$poss\E$/,@ltiroles)) {
+ $confhash{$provider}{'roles'}{$role} = $poss;
+ }
+ }
+ unless ($changes{$provider}) {
+ if (ref($current{'roles'}) eq 'HASH') {
+ foreach my $role (keys(%{$current{'roles'}})) {
+ unless ($current{'roles'}{$role} eq $confhash{$provider}{'roles'}{$role}) {
+ $changes{$provider} = 1;
+ last
+ }
+ }
+ unless ($changes{$provider}) {
+ foreach my $role (keys(%{$confhash{$provider}{'roles'}})) {
+ unless ($confhash{$provider}{'roles'}{$role} eq $current{'roles'}{$role}) {
+ $changes{$provider} = 1;
+ last;
+ }
+ }
+ }
+ } elsif (keys(%{$confhash{$provider}{'roles'}})) {
+ $changes{$provider} = 1;
+ }
+ }
+ }
+ if (ref($current{'custom'}) eq 'HASH') {
+ my @customdels = &Apache::loncommon::get_env_multiple('form.proctoring_customdel_'.$provider);
+ foreach my $key (keys(%{$current{'custom'}})) {
+ if (grep(/^\Q$key\E$/,@customdels)) {
+ $changes{$provider} = 1;
+ } else {
+ $confhash{$provider}{'custom'}{$key} = $env{'form.proctoring_customval_'.$key.'_'.$provider};
+ if ($confhash{$provider}{'custom'}{$key} ne $current{'custom'}{$key}) {
+ $changes{$provider} = 1;
+ }
+ }
+ }
+ }
+ if ($customadds{$provider}) {
+ my $name = $env{'form.proctoring_custom_name_'.$provider};
+ $name =~ s/(`)/'/g;
+ $name =~ s/^\s+//;
+ $name =~ s/\s+$//;
+ my $value = $env{'form.proctoring_custom_value_'.$provider};
+ $value =~ s/(`)/'/g;
+ $value =~ s/^\s+//;
+ $value =~ s/\s+$//;
+ if ($name ne '') {
+ $confhash{$provider}{'custom'}{$name} = $value;
+ $changes{$provider} = 1;
+ }
+ }
+ }
+ }
+ if (@allpos > 0) {
+ my $idx = 0;
+ foreach my $provider (@allpos) {
+ if ($provider ne '') {
+ $confhash{$provider}{'order'} = $idx;
+ unless ($changes{$provider}) {
+ if (ref($domconfig{$action}) eq 'HASH') {
+ if (ref($domconfig{$action}{$provider}) eq 'HASH') {
+ if ($domconfig{$action}{$provider}{'order'} ne $idx) {
+ $changes{$provider} = 1;
+ }
+ }
+ }
+ }
+ $idx ++;
+ }
+ }
+ }
+ my %proc_hash = (
+ $action => { %confhash }
+ );
+ my $putresult = &Apache::lonnet::put_dom('configuration',\%proc_hash,
+ $dom);
+ if ($putresult eq 'ok') {
+ my %proc_enchash = (
+ $action => { %encconfhash }
+ );
+ &Apache::lonnet::put_dom('encconfig',\%proc_enchash,$dom);
+ if (keys(%changes) > 0) {
+ my $cachetime = 24*60*60;
+ my %procall = %confhash;
+ foreach my $provider (keys(%procall)) {
+ if (ref($encconfhash{$provider}) eq 'HASH') {
+ foreach my $key ('key','secret') {
+ $procall{$provider}{$key} = $encconfhash{$provider}{$key};
+ }
+ }
+ }
+ &Apache::lonnet::do_cache_new('proctoring',$dom,\%procall,$cachetime);
+ if (ref($lastactref) eq 'HASH') {
+ $lastactref->{'proctoring'} = 1;
+ }
+ $resulttext = &mt('Configuration for Provider(s) with changes:').'';
+ my %bynum;
+ foreach my $provider (sort(keys(%changes))) {
+ my $position = $confhash{$provider}{'order'};
+ $bynum{$position} = $provider;
+ }
+ foreach my $pos (sort { $a <=> $b } keys(%bynum)) {
+ my $provider = $bynum{$pos};
+ my %lt = &proctoring_titles($provider);
+ my %fieldtitles = &proctoring_fieldtitles($provider);
+ if (!$confhash{$provider}{'available'}) {
+ $resulttext .= ''.&mt('Proctoring integration unavailable for: [_1]',''.$providernames{$provider}.' ').' ';
+ } else {
+ $resulttext .= ''.&mt('Proctoring integration available for: [_1]',''.$providernames{$provider}.' ');
+ if ($confhash{$provider}{'image'}) {
+ $resulttext .= ' '.
+ ' ';
+ }
+ $resulttext .= '';
+ my $position = $pos + 1;
+ $resulttext .= ''.&mt('Order: [_1]',$position).' ';
+ foreach my $key ('version','sigmethod','url','lifetime') {
+ if ($confhash{$provider}{$key} ne '') {
+ $resulttext .= ''.$lt{$key}.': '.$confhash{$provider}{$key}.' ';
+ }
+ }
+ if ($encconfhash{$provider}{'key'} ne '') {
+ $resulttext .= ''.$lt{'key'}.': '.$encconfhash{$provider}{'key'}.' ';
+ }
+ if ($encconfhash{$provider}{'secret'} ne '') {
+ $resulttext .= ''.$lt{'secret'}.': ';
+ my $num = length($encconfhash{$provider}{'secret'});
+ $resulttext .= ('*'x$num).' ';
+ }
+ my (@fields,$showroles);
+ if (ref($requserfields{$provider}) eq 'ARRAY') {
+ push(@fields,@{$requserfields{$provider}});
+ }
+ if (ref($confhash{$provider}{'fields'}) eq 'ARRAY') {
+ push(@fields,@{$confhash{$provider}{'fields'}});
+ } elsif (ref($confhash{$provider}{'fields'}) eq 'HASH') {
+ push(@fields,(keys(%{$confhash{$provider}{'fields'}})));
+ }
+ if (@fields) {
+ if (grep(/^roles$/,@fields)) {
+ $showroles = 1;
+ }
+ $resulttext .= ''.$lt{'udsl'}.': "'.
+ join('", "', map { $lt{$_}; } @fields).'" ';
+ }
+ if (ref($requserfields{$provider}) eq 'ARRAY') {
+ if (grep(/^user$/,@{$requserfields{$provider}})) {
+ if ($confhash{$provider}{'incdom'}) {
+ $resulttext .= ''.&mt('[_1] sent as [_2]',$lt{'user'},$lt{'uname:dom'}).' ';
+ } else {
+ $resulttext .= ''.&mt('[_1] sent as [_2]',$lt{'user'},$lt{'username'}).' ';
+ }
+ }
+ }
+ if (ref($confhash{$provider}{'defaults'}) eq 'ARRAY') {
+ if (@{$confhash{$provider}{'defaults'}} > 0) {
+ $resulttext .= ''.$lt{'defa'};
+ foreach my $field (@{$confhash{$provider}{'defaults'}}) {
+ $resulttext .= ' "'.$fieldtitles{$field}.'",';
+ }
+ $resulttext =~ s/,$//;
+ $resulttext .= ' ';
+ }
+ } elsif (ref($confhash{$provider}{'defaults'}) eq 'HASH') {
+ if (keys(%{$confhash{$provider}{'defaults'}})) {
+ $resulttext .= ''.$lt{'defa'}.': ';
+ foreach my $key (sort(keys(%{$confhash{$provider}{'defaults'}}))) {
+ if ($confhash{$provider}{'defaults'}{$key} ne '') {
+ $resulttext .= ''.$fieldtitles{$key}.' = '.$confhash{$provider}{'defaults'}{$key}.' ';
+ }
+ }
+ $resulttext .= ' ';
+ }
+ }
+ if (ref($crsconf{$provider}) eq 'ARRAY') {
+ if (@{$crsconf{$provider}} > 0) {
+ $resulttext .= ''.&mt('Configurable in course:');
+ my $numconfig = 0;
+ if (ref($confhash{$provider}{'crsconf'}) eq 'ARRAY') {
+ if (@{$confhash{$provider}{'crsconf'}} > 0) {
+ foreach my $field (@{$confhash{$provider}{'crsconf'}}) {
+ $numconfig ++;
+ if ($provider eq 'examity') {
+ $resulttext .= ' "'.$lt{'crs'.$field}.'",';
+ } else {
+ $resulttext .= ' "'.$fieldtitles{$field}.'",';
+ }
+ }
+ $resulttext =~ s/,$//;
+ }
+ }
+ if (!$numconfig) {
+ $resulttext .= ' '.&mt('None');
+ }
+ $resulttext .= ' ';
+ }
+ }
+ if ($showroles) {
+ if (ref($confhash{$provider}{'roles'}) eq 'HASH') {
+ my $rolemaps;
+ foreach my $role (@courseroles) {
+ if ($confhash{$provider}{'roles'}{$role}) {
+ $rolemaps .= (' 'x2).&Apache::lonnet::plaintext($role,'Course').'='.
+ $confhash{$provider}{'roles'}{$role}.',';
+ }
+ }
+ if ($rolemaps) {
+ $rolemaps =~ s/,$//;
+ $resulttext .= ''.&mt('Role mapping:').$rolemaps.' ';
+ }
+ }
+ }
+ if (ref($confhash{$provider}{'custom'}) eq 'HASH') {
+ my $customlist;
+ if (keys(%{$confhash{$provider}{'custom'}})) {
+ foreach my $key (sort(keys(%{$confhash{$provider}{'custom'}}))) {
+ $customlist .= $key.'='.$confhash{$provider}{'custom'}{$key}.', ';
+ }
+ $customlist =~ s/,$//;
+ }
+ if ($customlist) {
+ $resulttext .= ''.&mt('Custom items').': '.$customlist.' ';
+ }
+ }
+ $resulttext .= ' ';
+ }
+ }
+ $resulttext .= ' ';
+ } 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_proctoring_image {
+ my ($r,$dom,$confname,$caller,$provider,$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 Remote Proctoring Provider icon is not permitted to this server: [_1]',
+ $switchserver);
+ } elsif ($author_ok eq 'ok') {
+ my ($result,$imageurl,$madethumb) =
+ &publishlogo($r,'upload',$caller,$dom,$confname,
+ "proctoring/$provider/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 modify_lti {
my ($r,$dom,$action,$lastactref,%domconfig) = @_;
my %domdefaults = &Apache::lonnet::get_domain_defaults($dom,1);
@@ -12458,6 +14220,13 @@ sub modify_lti {
}
}
}
+ if ($env{'form.lti_callback_'.$idx}) {
+ if ($env{'form.lti_callbackparam_'.$idx}) {
+ my $callback = $env{'form.lti_callbackparam_'.$idx};
+ $callback =~ s/^\s+|\s+$//g;
+ $confhash{$itemid}{'callback'} = $callback;
+ }
+ }
foreach my $field ('passback','roster','topmenu','inlinemenu') {
if ($env{'form.lti_'.$field.'_'.$idx}) {
$confhash{$itemid}{$field} = 1;
@@ -12483,7 +14252,7 @@ sub modify_lti {
}
}
unless (($idx eq 'add') || ($changes{$itemid})) {
- foreach my $field ('mapuser','mapcrs','makecrs','section','passback','roster','lcauth','lcauthparm','topmenu','inlinemenu') {
+ foreach my $field ('mapuser','mapcrs','makecrs','section','passback','roster','lcauth','lcauthparm','topmenu','inlinemenu','callback') {
if ($domconfig{$action}{$itemid}{$field} ne $confhash{$itemid}{$field}) {
$changes{$itemid} = 1;
}
@@ -12709,6 +14478,11 @@ sub modify_lti {
} else {
$resulttext .= ''.&mt('No section assignment').' ';
}
+ if ($confhash{$itemid}{'callback'}) {
+ $resulttext .= ''.&mt('Callback setting').': '.$confhash{$itemid}{'callback'}.' ';
+ } else {
+ $resulttext .= ''.&mt('No callback to logout LON-CAPA session when user logs out of Comsumer');
+ }
foreach my $item ('passback','roster','topmenu','inlinemenu') {
$resulttext .= ' '.$lt{$item}.': ';
if ($confhash{$itemid}{$item}) {
@@ -13958,9 +15732,234 @@ sub modify_contacts {
return $resulttext;
}
+sub modify_privacy {
+ my ($dom,%domconfig) = @_;
+ my ($resulttext,%current,%changes);
+ if (ref($domconfig{'privacy'}) eq 'HASH') {
+ %current = %{$domconfig{'privacy'}};
+ }
+ my @fields = ('lastname','firstname','middlename','generation','permanentemail','id');
+ my @items = ('domain','author','course','community');
+ my %names = &Apache::lonlocal::texthash (
+ domain => 'Assigned domain role(s)',
+ author => 'Assigned co-author role(s)',
+ course => 'Assigned course role(s)',
+ community => 'Assigned community role',
+ );
+ my %roles = &Apache::lonlocal::texthash (
+ domain => 'Domain role',
+ author => 'Co-author role',
+ course => 'Course role',
+ community => 'Community role',
+ );
+ my %titles = &Apache::lonlocal::texthash (
+ approval => 'Approval for role in different domain',
+ othdom => 'User information available in other domain',
+ priv => 'Information viewable by privileged user in same domain',
+ unpriv => 'Information viewable by unprivileged user in same domain',
+ instdom => 'Other domain shares institution/provider',
+ extdom => 'Other domain has different institution/provider',
+ none => 'Not allowed',
+ user => 'User authorizes',
+ domain => 'Domain Coordinator authorizes',
+ auto => 'Unrestricted',
+ );
+ my %fieldnames = &Apache::lonlocal::texthash (
+ id => 'Student/Employee ID',
+ permanentemail => 'E-mail address',
+ lastname => 'Last Name',
+ firstname => 'First Name',
+ middlename => 'Middle Name',
+ generation => 'Generation',
+ );
+ my ($othertitle,$usertypes,$types) =
+ &Apache::loncommon::sorted_inst_types($dom);
+ my (%by_ip,%by_location,@intdoms,@instdoms);
+ &build_location_hashes(\@intdoms,\%by_ip,\%by_location,\@instdoms);
+
+ my %privacyhash = (
+ 'approval' => {
+ instdom => {},
+ extdom => {},
+ },
+ 'othdom' => {},
+ 'priv' => {},
+ 'unpriv' => {},
+ );
+ foreach my $item (@items) {
+ if (@instdoms > 1) {
+ if ($env{'form.privacy_approval_instdom'.$item} =~ /^(none|user|domain|auto)$/) {
+ $privacyhash{'approval'}{'instdom'}{$item} = $env{'form.privacy_approval_instdom_'.$item};
+ }
+ if (ref($current{'approval'}) eq 'HASH') {
+ if (ref($current{'approval'}{'instdom'}) eq 'HASH') {
+ unless ($privacyhash{'approval'}{'instdom'}{$item} eq $current{'approval'}{'instdom'}{$item}) {
+ $changes{'approval'} = 1;
+ }
+ }
+ } elsif ($privacyhash{'approval'}{'instdom'}{$item} ne 'auto') {
+ $changes{'approval'} = 1;
+ }
+ }
+ if (keys(%by_location) > 0) {
+ if ($env{'form.privacy_approval_extdom_'.$item} =~ /^(none|user|domain|auto)$/) {
+ $privacyhash{'approval'}{'extdom'}{$item} = $env{'form.privacy_approval_extdom_'.$item};
+ }
+ if (ref($current{'approval'}) eq 'HASH') {
+ if (ref($current{'approval'}{'extdom'}) eq 'HASH') {
+ unless ($privacyhash{'approval'}{'extdom'}{$item} eq $current{'approval'}{'extdom'}{$item}) {
+ $changes{'approval'} = 1;
+ }
+ }
+ } elsif ($privacyhash{'approval'}{'extdom'}{$item} ne 'auto') {
+ $changes{'approval'} = 1;
+ }
+ }
+ foreach my $status ('priv','unpriv') {
+ my @possibles = sort(&Apache::loncommon::get_env_multiple('form.privacy_'.$status.'_'.$item));
+ my @newvalues;
+ foreach my $field (@possibles) {
+ if (grep(/^\Q$field\E$/,@fields)) {
+ $privacyhash{$status}{$item}{$field} = 1;
+ push(@newvalues,$field);
+ }
+ }
+ @newvalues = sort(@newvalues);
+ if (ref($current{$status}) eq 'HASH') {
+ if (ref($current{$status}{$item}) eq 'HASH') {
+ my @currvalues = sort(keys(%{$current{$status}{$item}}));
+ my @diffs = &Apache::loncommon::compare_arrays(\@currvalues,\@newvalues);
+ if (@diffs > 0) {
+ $changes{$status} = 1;
+ }
+ }
+ } else {
+ my @stdfields;
+ foreach my $field (@fields) {
+ if ($field eq 'id') {
+ next if ($status eq 'unpriv');
+ next if (($status eq 'priv') && ($item eq 'community'));
+ }
+ push(@stdfields,$field);
+ }
+ my @diffs = &Apache::loncommon::compare_arrays(\@stdfields,\@newvalues);
+ if (@diffs > 0) {
+ $changes{$status} = 1;
+ }
+ }
+ }
+ }
+ if ((@instdoms > 1) || (keys(%by_location) > 0)) {
+ my @statuses;
+ if (ref($types) eq 'ARRAY') {
+ @statuses = @{$types};
+ }
+ foreach my $type (@statuses,'default') {
+ my @possfields = &Apache::loncommon::get_env_multiple('form.privacy_othdom_'.$type);
+ my @newvalues;
+ foreach my $field (sort(@possfields)) {
+ if (grep(/^\Q$field\E$/,@fields)) {
+ $privacyhash{'othdom'}{$type}{$field} = 1;
+ push(@newvalues,$field);
+ }
+ }
+ @newvalues = sort(@newvalues);
+ if (ref($current{'othdom'}) eq 'HASH') {
+ if (ref($current{'othdom'}{$type}) eq 'HASH') {
+ my @currvalues = sort(keys(%{$current{'othdom'}{$type}}));
+ my @diffs = &Apache::loncommon::compare_arrays(\@currvalues,\@newvalues);
+ if (@diffs > 0) {
+ $changes{'othdom'} = 1;
+ }
+ }
+ } else {
+ my @stdfields = ('lastname','firstname','middlename','generation','permanentemail');
+ my @diffs = &Apache::loncommon::compare_arrays(\@stdfields,\@newvalues);
+ if (@diffs > 0) {
+ $changes{'othdom'} = 1;
+ }
+ }
+ }
+ }
+ my %confighash = (
+ privacy => \%privacyhash,
+ );
+ my $putresult = &Apache::lonnet::put_dom('configuration',\%confighash,$dom);
+ if ($putresult eq 'ok') {
+ if (keys(%changes) > 0) {
+ $resulttext = &mt('Changes made: ').'';
+ foreach my $key ('approval','othdom','priv','unpriv') {
+ if ($changes{$key}) {
+ $resulttext .= ''.$titles{$key}.':';
+ if ($key eq 'approval') {
+ if (keys(%{$privacyhash{$key}{instdom}})) {
+ $resulttext .= ''.$titles{'instdom'}.'';
+ foreach my $item (@items) {
+ $resulttext .= ''.$roles{$item}.': '.$titles{$privacyhash{$key}{instdom}{$item}}.' ';
+ }
+ $resulttext .= ' ';
+ }
+ if (keys(%{$privacyhash{$key}{extdom}})) {
+ $resulttext .= ''.$titles{'extdom'}.'';
+ foreach my $item (@items) {
+ $resulttext .= ''.$roles{$item}.': '.$titles{$privacyhash{$key}{extdom}{$item}}.' ';
+ }
+ $resulttext .= ' ';
+ }
+ } elsif ($key eq 'othdom') {
+ my @statuses;
+ if (ref($types) eq 'ARRAY') {
+ @statuses = @{$types};
+ }
+ if (ref($privacyhash{$key}) eq 'HASH') {
+ foreach my $status (@statuses,'default') {
+ if ($status eq 'default') {
+ $resulttext .= ''.$othertitle.': ';
+ } elsif (ref($usertypes) eq 'HASH') {
+ $resulttext .= ' '.$usertypes->{$status}.': ';
+ } else {
+ next;
+ }
+ if (ref($privacyhash{$key}{$status}) eq 'HASH') {
+ if (keys(%{$privacyhash{$key}{$status}})) {
+ $resulttext .= join(', ', map { $fieldnames{$_}; } (sort(keys(%{$privacyhash{$key}{$status}}))));
+ } else {
+ $resulttext .= &mt('none');
+ }
+ }
+ $resulttext .= ' ';
+ }
+ }
+ } else {
+ foreach my $item (@items) {
+ if (ref($privacyhash{$key}{$item}) eq 'HASH') {
+ $resulttext .= ''.$names{$item}.': ';
+ if (keys(%{$privacyhash{$key}{$item}})) {
+ $resulttext .= join(', ', map { $fieldnames{$_}; } (sort(keys(%{$privacyhash{$key}{$item}}))));
+ } else {
+ $resulttext .= &mt('none');
+ }
+ $resulttext .= ' ';
+ }
+ }
+ }
+ $resulttext .= ' ';
+ }
+ }
+ } else {
+ $resulttext = &mt('No changes made to user information settings');
+ }
+ } else {
+ $resulttext = ''.
+ &mt('An error occurred: [_1]',$putresult).' ';
+ }
+ return $resulttext;
+}
+
sub modify_passwords {
my ($r,$dom,$confname,$lastactref,%domconfig) = @_;
- my ($resulttext,%current,%changes,%newvalues,@oktypes,$errors,$updatedefaults);
+ my ($resulttext,%current,%changes,%newvalues,@oktypes,$errors,
+ $updatedefaults,$updateconf);
my $customfn = 'resetpw.html';
if (ref($domconfig{'passwords'}) eq 'HASH') {
%current = %{$domconfig{'passwords'}};
@@ -13983,6 +15982,7 @@ sub modify_passwords {
max => 'Maximum password length',
chars => 'Required characters',
expire => 'Password expiration (days)',
+ numsaved => 'Number of previous passwords to save',
reset => 'Resetting Forgotten Password',
intauth => 'Encryption of Stored Passwords (Internal Auth)',
rules => 'Rules for LON-CAPA Passwords',
@@ -14012,8 +16012,8 @@ sub modify_passwords {
'intauth_cost' => 10,
'intauth_check' => 0,
'intauth_switch' => 0,
- 'min' => 7,
);
+ $staticdefaults{'min'} = $Apache::lonnet::passwdmin;
foreach my $type (@oktypes) {
$staticdefaults{'resetpostlink'}{$type} = ['email','username'];
}
@@ -14025,7 +16025,7 @@ sub modify_passwords {
if ($current{'resetlink'} ne $linklife) {
$changes{'reset'} = 1;
}
- } elsif (!exists($domconfig{passwords})) {
+ } elsif (!ref($domconfig{passwords}) eq 'HASH') {
if ($staticdefaults{'resetlink'} ne $linklife) {
$changes{'reset'} = 1;
}
@@ -14046,7 +16046,7 @@ sub modify_passwords {
if (@diffs > 0) {
$changes{'reset'} = 1;
}
- } elsif (!exists($domconfig{passwords})) {
+ } elsif (!ref($domconfig{passwords}) eq 'HASH') {
my @diffs = &Apache::loncommon::compare_arrays($staticdefaults{'resetcase'},\@casesens);
if (@diffs > 0) {
$changes{'reset'} = 1;
@@ -14058,7 +16058,7 @@ sub modify_passwords {
if ($current{'resetprelink'} ne $newvalues{'resetprelink'}) {
$changes{'reset'} = 1;
}
- } elsif (!exists($domconfig{passwords})) {
+ } elsif (!ref($domconfig{passwords}) eq 'HASH') {
if ($staticdefaults{'resetprelink'} ne $newvalues{'resetprelink'}) {
$changes{'reset'} = 1;
}
@@ -14085,7 +16085,7 @@ sub modify_passwords {
} else {
$changes{'reset'} = 1;
}
- } elsif (!exists($domconfig{passwords})) {
+ } elsif (!ref($domconfig{passwords}) eq 'HASH') {
my @diffs = &Apache::loncommon::compare_arrays($staticdefaults{'resetpostlink'}{$type},\@postlink);
if (@diffs > 0) {
$changes{'reset'} = 1;
@@ -14107,7 +16107,7 @@ sub modify_passwords {
if (@diffs > 0) {
$changes{'reset'} = 1;
}
- } elsif (!exists($domconfig{passwords})) {
+ } elsif (!ref($domconfig{passwords}) eq 'HASH') {
my @diffs = &Apache::loncommon::compare_arrays($staticdefaults{'resetemail'},\@resetemail);
if (@diffs > 0) {
$changes{'reset'} = 1;
@@ -14190,9 +16190,25 @@ sub modify_passwords {
$updatedefaults = 1;
}
}
- foreach my $rule ('min','max','expire') {
+ foreach my $rule ('min','max','expire','numsaved') {
$env{'form.passwords_'.$rule} =~ s/^\s+|\s+$//g;
- if ($env{'form.passwords_'.$rule} =~ /^(|\d+(|\.\d*))$/) {
+ my $ruleok;
+ if ($rule eq 'expire') {
+ if (($env{'form.passwords_'.$rule} =~ /^\d+(|\.\d*)$/) &&
+ ($env{'form.passwords_'.$rule} ne '0')) {
+ $ruleok = 1;
+ }
+ } elsif ($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}) {
@@ -14202,6 +16218,8 @@ sub modify_passwords {
if ($staticdefaults{$rule} ne $newvalues{$rule}) {
$changes{'rules'} = 1;
}
+ } else {
+ $changes{'rules'} = 1;
}
} elsif (exists($current{$rule})) {
$changes{'rules'} = 1;
@@ -14227,13 +16245,36 @@ sub modify_passwords {
}
}
}
- if ($env{'form.passwords_crsowner'}) {
- $newvalues{'crsownerchg'} = 1;
- unless ($current{'crsownerchg'}) {
- $changes{'crsownerchg'} = 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;
+ }
}
- } elsif ($current{'crsownerchg'}) {
- $changes{'crsownerchg'} = 1;
}
my %confighash = (
@@ -14248,15 +16289,20 @@ sub modify_passwords {
$resulttext = &mt('Changes made: ').'';
foreach my $key ('reset','intauth','rules','crsownerchg') {
if ($changes{$key}) {
+ unless ($key eq 'intauth') {
+ $updateconf = 1;
+ }
$resulttext .= ''.$titles{$key}.':';
if ($key eq 'reset') {
if ($confighash{'passwords'}{'captcha'} eq 'original') {
$resulttext .= ''.&mt('CAPTCHA validation set to use: original CAPTCHA').' ';
} elsif ($confighash{'passwords'}{'captcha'} eq 'recaptcha') {
$resulttext .= ''.&mt('CAPTCHA validation set to use: reCAPTCHA').' '.
- &mt('version: [_1]',$confighash{'passwords'}{'recaptchaversion'}).' '.
- &mt('Public key: [_1]',$confighash{'passwords'}{'recaptchapub'}).''.
- &mt('Private key: [_1]',$confighash{'passwords'}{'recaptchapriv'}).' ';
+ &mt('version: [_1]',$confighash{'passwords'}{'recaptchaversion'}).' ';
+ if (ref($confighash{'passwords'}{'recaptchakeys'}) eq 'HASH') {
+ $resulttext .= &mt('Public key: [_1]',$confighash{'passwords'}{'recaptchakeys'}{'public'}).''.
+ &mt('Private key: [_1]',$confighash{'passwords'}{'recaptchakeys'}{'private'}).' ';
+ }
} else {
$resulttext .= ''.&mt('No CAPTCHA validation').' ';
}
@@ -14335,8 +16381,9 @@ sub modify_passwords {
}
if ($confighash{'passwords'}{'resetcustom'}) {
my $customlink = &Apache::loncommon::modal_link($confighash{'passwords'}{'resetcustom'},
- $titles{custom},600,500);
- $resulttext .= ''.&mt('Preamble to "Forgot Password" form includes [_1]',$customlink).' ';
+ &mt('custom text'),600,500,undef,undef,
+ undef,undef,'background-color:#ffffff');
+ $resulttext .= ''.&mt('Preamble to "Forgot Password" form includes: [_1]',$customlink).' ';
} else {
$resulttext .= ''.&mt('No custom text included in preamble to "Forgot Password" form').' ';
}
@@ -14369,22 +16416,61 @@ sub modify_passwords {
$resulttext .= ''.&mt('[_1] set to "[_2]"',$titles{$key.'_'.$item},$value).' ';
}
} elsif ($key eq 'rules') {
- foreach my $rule ('min','max','expire') {
+ foreach my $rule ('min','max','expire','numsaved') {
if ($confighash{'passwords'}{$rule} eq '') {
- $resulttext .= ''.&mt('[_1] not set.',$titles{$rule});
if ($rule eq 'min') {
- $resulttext .= ' '.&mt('Default of 7 will be used');
+ $resulttext .= ' '.&mt('[_1] not set.',$titles{$rule});
+ ' '.&mt('Default of [_1] will be used',
+ $Apache::lonnet::passwdmin).' ';
+ } else {
+ $resulttext .= ''.&mt('[_1] set to none',$titles{$rule}).' ';
}
- $resulttext .= ' ';
} else {
$resulttext .= ''.&mt('[_1] set to [_2]',$titles{$rule},$confighash{'passwords'}{$rule}).' ';
}
}
+ if (ref($confighash{'passwords'}{'chars'}) eq 'ARRAY') {
+ if (@{$confighash{'passwords'}{'chars'}} > 0) {
+ 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',
+ );
+ my $needed = ''.
+ join(' ',map {$rulenames{$_} } @{$confighash{'passwords'}{'chars'}}).
+ ' ';
+ $resulttext .= ''.&mt('[_1] set to: [_2]',$titles{'chars'},$needed).' ';
+ } else {
+ $resulttext .= ''.&mt('[_1] set to none',$titles{'chars'}).' ';
+ }
+ } else {
+ $resulttext .= ''.&mt('[_1] set to none',$titles{'chars'}).' ';
+ }
} elsif ($key eq 'crsownerchg') {
- if ($confighash{'passwords'}{'crsownerchg'}) {
- $resulttext .= ''.&mt('Course owner may change student passwords.').' ';
+ if (ref($confighash{'passwords'}{'crsownerchg'}) eq 'HASH') {
+ if ((@{$confighash{'passwords'}{'crsownerchg'}{'by'}} == 0) ||
+ (@{$confighash{'passwords'}{'crsownerchg'}{'for'}} == 0)) {
+ $resulttext .= ''.&mt('Course owner may not change student passwords.').' ';
+ } else {
+ my %crsownerstr;
+ foreach my $item ('by','for') {
+ if (ref($confighash{'passwords'}{'crsownerchg'}{$item}) eq 'ARRAY') {
+ foreach my $type (@{$confighash{'passwords'}{'crsownerchg'}{$item}}) {
+ if ($type eq 'default') {
+ $crsownerstr{$item} .= $othertitle.', ';
+ } elsif ($usertypes->{$type} ne '') {
+ $crsownerstr{$item} .= $usertypes->{$type}.', ';
+ }
+ }
+ $crsownerstr{$item} =~ s/\Q, \E$//;
+ }
+ }
+ $resulttext .= ''.&mt('Course owner (with status: [_1]) may change passwords for students (with status: [_2]).',
+ $crsownerstr{'by'},$crsownerstr{'for'}).' ';
+ }
} else {
- $resulttext .= ''.&mt('Course owner may not change student passwords.');
+ $resulttext .= ' '.&mt('Course owner may not change student passwords.').' ';
}
}
$resulttext .= '';
@@ -14394,13 +16480,19 @@ sub modify_passwords {
} else {
$resulttext = &mt('No changes made to password settings');
}
+ my $cachetime = 24*60*60;
if ($updatedefaults) {
- my $cachetime = 24*60*60;
&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).' ';
@@ -15225,7 +17317,7 @@ sub modify_selfcreation {
$chgtext .= &mt('For self-created accounts verified by e-mail address, username is set as follows:').
'';
foreach my $status (@statuses) {
- if ($type eq 'default') {
+ if ($status eq 'default') {
$chgtext .= ''.$othertitle.' -- '.$options{$cancreate{'emailverified'}{$status}}.' ';
} else {
$chgtext .= ''.$usertypes{$status}.' -- '.$options{$cancreate{'emailverified'}{$status}}.' ';
@@ -15494,19 +17586,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;
}
@@ -15528,9 +17626,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 => '',
@@ -15538,8 +17636,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;
}
@@ -15551,6 +17649,8 @@ sub process_captcha {
} elsif (!defined($changes->{'cancreate'})) {
$changes->{'cancreate'} = ['recaptchaversion'];
}
+ } elsif ($container eq 'passwords') {
+ $changes->{'reset'} = 1;
} else {
$changes->{'recaptchaversion'} = 1;
}
@@ -15562,6 +17662,8 @@ sub process_captcha {
} elsif (!defined($changes->{'cancreate'})) {
$changes->{'cancreate'} = ['recaptchakeys'];
}
+ } elsif ($container eq 'passwords') {
+ $changes->{'reset'} = 1;
} else {
$changes->{'recaptchakeys'} = 1;
}
@@ -16357,6 +18459,10 @@ sub modify_coursecategories {
}
$resulttext .= ' ';
}
+ &Apache::lonnet::do_cache_new('cats',$dom,$cathash,3600);
+ if (ref($lastactref) eq 'HASH') {
+ $lastactref->{'cats'} = 1;
+ }
}
$resulttext .= '';
if ($changes{'unauth'} || $changes{'auth'}) {
@@ -17471,6 +19577,215 @@ sub modify_selfenrollment {
return $resulttext;
}
+sub modify_wafproxy {
+ my ($dom,$action,$lastactref,%domconfig) = @_;
+ my %servers = &Apache::lonnet::internet_dom_servers($dom);
+ my (%othercontrol,%canset,%values,%curralias,%currvalue,@warnings,%wafproxy,
+ %changes,%expirecache);
+ foreach my $server (sort(keys(%servers))) {
+ my $serverhome = &Apache::lonnet::get_server_homeID($servers{$server});
+ if ($serverhome eq $server) {
+ my $serverdom = &Apache::lonnet::host_domain($server);
+ if ($serverdom eq $dom) {
+ $canset{$server} = 1;
+ if (ref($domconfig{'wafproxy'}) eq 'HASH') {
+ %{$values{$dom}} = ();
+ if (ref($domconfig{'wafproxy'}{'alias'}) eq 'HASH') {
+ %curralias = %{$domconfig{'wafproxy'}{'alias'}};
+ }
+ foreach my $item ('ipheader','trusted','exempt') {
+ $currvalue{$item} = $domconfig{'wafproxy'}{$item};
+ }
+ }
+ }
+ }
+ }
+ my $output;
+ if (keys(%canset)) {
+ %{$wafproxy{'alias'}} = ();
+ foreach my $key (sort(keys(%canset))) {
+ $wafproxy{'alias'}{$key} = $env{'form.wafproxy_alias_'.$key};
+ $wafproxy{'alias'}{$key} =~ s/^\s+|\s+$//g;
+ if ($wafproxy{'alias'}{$key} ne $curralias{$key}) {
+ $changes{'alias'} = 1;
+ }
+ if ($wafproxy{'alias'}{$key} eq '') {
+ if ($curralias{$key}) {
+ $expirecache{$key} = 1;
+ }
+ delete($wafproxy{'alias'}{$key});
+ }
+ }
+ unless (keys(%{$wafproxy{'alias'}})) {
+ delete($wafproxy{'alias'});
+ }
+ # Localization for values in %warn occus in &mt() calls separately.
+ my %warn = (
+ trusted => 'trusted IP range(s)',
+ exempt => 'exempt IP range(s)',
+ );
+ foreach my $item ('ipheader','trusted','exempt') {
+ my $possible = $env{'form.wafproxy_'.$item};
+ $possible =~ s/^\s+|\s+$//g;
+ if ($possible ne '') {
+ if ($item eq 'ipheader') {
+ $wafproxy{$item} = $possible;
+ } else {
+ my (@ok,$count);
+ $possible =~ s/[\r\n]+/\s/g;
+ $possible =~ s/\s*-\s*/-/g;
+ $possible =~ s/\s+/,/g;
+ $count = 0;
+ if ($possible) {
+ foreach my $poss (split(/\,/,$possible)) {
+ $count ++;
+ if (&validate_ip_pattern($poss)) {
+ push(@ok,$poss);
+ }
+ }
+ if (@ok) {
+ $wafproxy{$item} = join(',',@ok);
+ }
+ my $diff = $count - scalar(@ok);
+ if ($diff) {
+ push(@warnings,''.
+ &mt('[quant,_1,IP] invalid and excluded from saved value for [_2]',
+ $diff,$warn{$item}).
+ ' ');
+ }
+ if ($wafproxy{$item} ne $currvalue{$item}) {
+ $changes{$item} = 1;
+ }
+ }
+ }
+ } else {
+ if ($currvalue{$item}) {
+ $changes{$item} = 1;
+ }
+ }
+ }
+ }
+ if (keys(%changes)) {
+ my %defaultshash = (
+ wafproxy => \%wafproxy,
+ );
+ my $putresult = &Apache::lonnet::put_dom('configuration',\%defaultshash,
+ $dom);
+ if ($putresult eq 'ok') {
+ my $cachetime = 24*60*60;
+ my (%domdefaults,$updatedomdefs);
+ foreach my $item ('ipheader','trusted','exempt') {
+ if ($changes{$item}) {
+ unless ($updatedomdefs) {
+ %domdefaults = &Apache::lonnet::get_domain_defaults($dom);
+ $updatedomdefs = 1;
+ }
+ if ($wafproxy{$item}) {
+ $domdefaults{'waf_'.$item} = $wafproxy{$item};
+ } elsif (exists($domdefaults{'waf_'.$item})) {
+ delete($domdefaults{'waf_'.$item});
+ }
+ }
+ }
+ if ($updatedomdefs) {
+ &Apache::lonnet::do_cache_new('domdefaults',$dom,\%domdefaults,$cachetime);
+ if (ref($lastactref) eq 'HASH') {
+ $lastactref->{'domdefaults'} = 1;
+ }
+ }
+ if ((exists($wafproxy{'alias'})) || (keys(%expirecache))) {
+ my %updates = %expirecache;
+ foreach my $key (keys(%expirecache)) {
+ &Apache::lonnet::devalidate_cache_new('proxyalias',$key);
+ }
+ if (ref($wafproxy{'alias'}) eq 'HASH') {
+ my $cachetime = 24*60*60;
+ foreach my $key (keys(%{$wafproxy{'alias'}})) {
+ $updates{$key} = 1;
+ &Apache::lonnet::do_cache_new('proxyalias',$key,$wafproxy{'alias'}{$key},
+ $cachetime);
+ }
+ }
+ if (ref($lastactref) eq 'HASH') {
+ $lastactref->{'proxyalias'} = \%updates;
+ }
+ }
+ $output = &mt('Changes were made to Web Application Firewall/Reverse Proxy').'';
+ foreach my $item ('alias','ipheader','trusted','exempt') {
+ if ($changes{$item}) {
+ if ($item eq 'alias') {
+ my $numaliased = 0;
+ if (ref($wafproxy{'alias'}) eq 'HASH') {
+ my $shown;
+ if (keys(%{$wafproxy{'alias'}})) {
+ foreach my $server (sort(keys(%{$wafproxy{'alias'}}))) {
+ $shown .= ''.&mt('[_1] aliased by [_2]',
+ &Apache::lonnet::hostname($server),
+ $wafproxy{'alias'}{$server}).' ';
+ $numaliased ++;
+ }
+ if ($numaliased) {
+ $output .= ''.&mt('Aliases for hostnames set to: [_1]',
+ '').' ';
+ }
+ }
+ }
+ unless ($numaliased) {
+ $output .= ''.&mt('Aliases deleted for hostnames').' ';
+ }
+ } else {
+ if ($item eq 'ipheader') {
+ if ($wafproxy{$item}) {
+ $output .= ''.&mt('Custom request header set to [_1]',
+ $wafproxy{$item}).' ';
+ } else {
+ $output .= ''.&mt('Custom request header deleted').' ';
+ }
+ } elsif ($item eq 'trusted') {
+ if ($wafproxy{$item}) {
+ $output .= ''.&mt('Trusted IP range(s) set to [_1]',
+ $wafproxy{$item}).' ';
+ } else {
+ $output .= ''.&mt('Trusted IP range(s) deleted').' ';
+ }
+ } elsif ($item eq 'exempt') {
+ if ($wafproxy{$item}) {
+ $output .= ''.&mt('Exempt IP range(s) set to [_1]',
+ $wafproxy{$item}).' ';
+ } else {
+ $output .= ''.&mt('Exempt IP range(s) deleted').' ';
+ }
+ }
+ }
+ }
+ }
+ } else {
+ $output = ''.
+ &mt('An error occurred: [_1]',$putresult).' ';
+ }
+ } elsif (keys(%canset)) {
+ $output = &mt('No changes made to Web Application Firewall/Reverse Proxy settings');
+ }
+ if (@warnings) {
+ $output .= ' '.&mt('Warnings:').''.
+ join("\n",@warnings).' ';
+ }
+ return $output;
+}
+
+sub validate_ip_pattern {
+ my ($pattern) = @_;
+ if ($pattern =~ /^([^-]+)\-([^-]+)$/) {
+ my ($start,$end) = ($1,$2);
+ if ((&Net::CIDR::cidrvalidate($start)) && (&Net::CIDR::cidrvalidate($end))) {
+ return 1;
+ }
+ } elsif (&Net::CIDR::cidrvalidate($pattern)) {
+ return 1;
+ }
+ return
+}
+
sub modify_usersessions {
my ($dom,$lastactref,%domconfig) = @_;
my @hostingtypes = ('version','excludedomain','includedomain');
@@ -17638,6 +19953,7 @@ sub modify_usersessions {
}
}
$defaultshash{'usersessions'}{'offloadnow'} = {};
+ $defaultshash{'usersessions'}{'offloadoth'} = {};
my @offloadnow = &Apache::loncommon::get_env_multiple('form.offloadnow');
my @okoffload;
if (@offloadnow) {
@@ -17654,6 +19970,22 @@ sub modify_usersessions {
}
}
}
+ my @offloadoth = &Apache::loncommon::get_env_multiple('form.offloadoth');
+ my @okoffloadoth;
+ if (@offloadoth) {
+ foreach my $server (@offloadoth) {
+ if (&Apache::lonnet::hostname($server) ne '') {
+ unless (grep(/^\Q$server\E$/,@okoffloadoth)) {
+ push(@okoffloadoth,$server);
+ }
+ }
+ }
+ if (@okoffloadoth) {
+ foreach my $lonhost (@okoffloadoth) {
+ $defaultshash{'usersessions'}{'offloadoth'}{$lonhost} = 1;
+ }
+ }
+ }
if (ref($domconfig{'usersessions'}) eq 'HASH') {
if (ref($domconfig{'usersessions'}{'spares'}) eq 'HASH') {
if (ref($changes{'spares'}) eq 'HASH') {
@@ -17664,26 +19996,38 @@ sub modify_usersessions {
} else {
$savespares = 1;
}
- if (ref($domconfig{'usersessions'}{'offloadnow'}) eq 'HASH') {
- foreach my $lonhost (keys(%{$domconfig{'usersessions'}{'offloadnow'}})) {
- unless ($defaultshash{'usersessions'}{'offloadnow'}{$lonhost}) {
- $changes{'offloadnow'} = 1;
- last;
- }
- }
- unless ($changes{'offloadnow'}) {
- foreach my $lonhost (keys(%{$defaultshash{'usersessions'}{'offloadnow'}})) {
- unless ($domconfig{'usersessions'}{'offloadnow'}{$lonhost}) {
- $changes{'offloadnow'} = 1;
+ foreach my $offload ('offloadnow','offloadoth') {
+ if (ref($domconfig{'usersessions'}{$offload}) eq 'HASH') {
+ foreach my $lonhost (keys(%{$domconfig{'usersessions'}{$offload}})) {
+ unless ($defaultshash{'usersessions'}{$offload}{$lonhost}) {
+ $changes{$offload} = 1;
last;
}
}
- }
- } elsif (@okoffload) {
+ unless ($changes{$offload}) {
+ foreach my $lonhost (keys(%{$defaultshash{'usersessions'}{$offload}})) {
+ unless ($domconfig{'usersessions'}{$offload}{$lonhost}) {
+ $changes{$offload} = 1;
+ last;
+ }
+ }
+ }
+ } else {
+ if (($offload eq 'offloadnow') && (@okoffload)) {
+ $changes{'offloadnow'} = 1;
+ }
+ if (($offload eq 'offloadoth') && (@okoffloadoth)) {
+ $changes{'offloadoth'} = 1;
+ }
+ }
+ }
+ } else {
+ if (@okoffload) {
$changes{'offloadnow'} = 1;
}
- } elsif (@okoffload) {
- $changes{'offloadnow'} = 1;
+ if (@okoffloadoth) {
+ $changes{'offloadoth'} = 1;
+ }
}
my $nochgmsg = &mt('No changes made to settings for user session hosting/offloading.');
if ((keys(%changes) > 0) || ($savespares)) {
@@ -17700,6 +20044,9 @@ sub modify_usersessions {
if (ref($defaultshash{'usersessions'}{'offloadnow'}) eq 'HASH') {
$domdefaults{'offloadnow'} = $defaultshash{'usersessions'}{'offloadnow'};
}
+ if (ref($defaultshash{'usersessions'}{'offloadoth'}) eq 'HASH') {
+ $domdefaults{'offloadoth'} = $defaultshash{'usersessions'}{'offloadoth'};
+ }
}
my $cachetime = 24*60*60;
&Apache::lonnet::do_cache_new('domdefaults',$dom,\%domdefaults,$cachetime);
@@ -17779,16 +20126,31 @@ sub modify_usersessions {
if ($changes{'offloadnow'}) {
if (ref($defaultshash{'usersessions'}{'offloadnow'}) eq 'HASH') {
if (keys(%{$defaultshash{'usersessions'}{'offloadnow'}}) > 0) {
- $resulttext .= ''.&mt('Switch active users on next access, for server(s):').'';
+ $resulttext .= ''.&mt('Switch any active user on next access, for server(s):').'';
foreach my $lonhost (sort(keys(%{$defaultshash{'usersessions'}{'offloadnow'}}))) {
$resulttext .= ''.$lonhost.' ';
}
$resulttext .= ' ';
} else {
- $resulttext .= ' '.&mt('No servers now set to switch active users on next access.');
+ $resulttext .= ' '.&mt('No servers now set to switch any active user on next access.');
+ }
+ } else {
+ $resulttext .= ' '.&mt('No servers now set to switch any active user on next access.').' ';
+ }
+ }
+ if ($changes{'offloadoth'}) {
+ if (ref($defaultshash{'usersessions'}{'offloadoth'}) eq 'HASH') {
+ if (keys(%{$defaultshash{'usersessions'}{'offloadoth'}}) > 0) {
+ $resulttext .= ''.&mt('Switch other institutions on next access, for server(s):').'';
+ foreach my $lonhost (sort(keys(%{$defaultshash{'usersessions'}{'offloadoth'}}))) {
+ $resulttext .= ''.$lonhost.' ';
+ }
+ $resulttext .= ' ';
+ } else {
+ $resulttext .= ' '.&mt('No servers now set to switch other institutions on next access.');
}
} else {
- $resulttext .= ' '.&mt('No servers now set to switch active users on next access.').' ';
+ $resulttext .= ''.&mt('No servers now set to switch other institutions on next access.').' ';
}
}
$resulttext .= ' ';
@@ -18360,24 +20722,24 @@ sub modify_loadbalancing {
$resulttext .= ' '.&mt('Load Balancer: [_1] -- cookie use enabled',
$balancer).' ';
}
- if (keys(%toupdate)) {
- my %thismachine;
- my $updatedhere;
- my $cachetime = 60*60*24;
- map { $thismachine{$_} = 1; } &Apache::lonnet::current_machine_ids();
- foreach my $lonhost (keys(%toupdate)) {
- if ($thismachine{$lonhost}) {
- unless ($updatedhere) {
- &Apache::lonnet::do_cache_new('loadbalancing',$dom,
- $defaultshash{'loadbalancing'},
- $cachetime);
- $updatedhere = 1;
- }
- } else {
- my $cachekey = &escape('loadbalancing').':'.&escape($dom);
- &Apache::lonnet::remote_devalidate_cache($lonhost,[$cachekey]);
- }
+ }
+ }
+ if (keys(%toupdate)) {
+ my %thismachine;
+ my $updatedhere;
+ my $cachetime = 60*60*24;
+ map { $thismachine{$_} = 1; } &Apache::lonnet::current_machine_ids();
+ foreach my $lonhost (keys(%toupdate)) {
+ if ($thismachine{$lonhost}) {
+ unless ($updatedhere) {
+ &Apache::lonnet::do_cache_new('loadbalancing',$dom,
+ $defaultshash{'loadbalancing'},
+ $cachetime);
+ $updatedhere = 1;
}
+ } else {
+ my $cachekey = &escape('loadbalancing').':'.&escape($dom);
+ &Apache::lonnet::remote_devalidate_cache($lonhost,[$cachekey]);
}
}
}
@@ -18874,6 +21236,7 @@ function balancerChange(balnum,baltotal,
END
}
+
sub new_spares_js {
my @sparestypes = ('primary','default');
my $types = join("','",@sparestypes);
@@ -18947,7 +21310,7 @@ function updateNewSpares(formname,lonhos
function checkNewSpares(lonhost,type) {
var newSpare = document.getElementById('newspare_'+type+'_'+lonhost);
var chosen = newSpare.options[newSpare.selectedIndex].value;
- if (chosen != '') {
+ if (chosen != '') {
var othertype;
var othernewSpare;
if (type == 'primary') {
@@ -19081,7 +21444,7 @@ function toggleDisplay(domForm,caller) {
var dispval = 'block';
var selfcreateRegExp = /^cancreate_emailverified/;
if (caller == 'emailoptions') {
- optionsElement = domForm.cancreate_email;
+ optionsElement = domForm.cancreate_email;
}
if (caller == 'studentsubmission') {
optionsElement = domForm.postsubmit;
@@ -19136,14 +21499,23 @@ 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','ltitools','usersessions','directorysrch');
+ my @posscached = ('domainconfig','domdefaults','ltitools','usersessions',
+ 'directorysrch','passwdconf','cats','proxyalias');
if (keys(%servers)) {
foreach my $server (keys(%servers)) {
next if ($thismachine{$server});
my @cached;
foreach my $name (@posscached) {
if ($cachekeys->{$name}) {
- push(@cached,&escape($name).':'.&escape($dom));
+ if ($name eq 'proxyalias') {
+ if (ref($cachekeys->{$name}) eq 'HASH') {
+ foreach my $key (keys(%{$cachekeys->{$name}})) {
+ push(@cached,&escape($name).':'.&escape($key));
+ }
+ }
+ } else {
+ push(@cached,&escape($name).':'.&escape($dom));
+ }
}
}
if (@cached) {