[prev in list] [next in list] [prev in thread] [next in thread] 

List:       lon-capa-cvs
Subject:    [LON-CAPA-cvs] cvs: loncom /interface domainprefs.pm lonconfigsettings.pm
From:       raeburn <raeburn () source ! lon-capa ! org>
Date:       2020-09-17 0:35:04
Message-ID: cvsraeburn1600302904 () cvsserver
[Download RAW message or body]

This is a MIME encoded message


raeburn		Thu Sep 17 00:35:04 2020 EDT

  Modified files:              
    /loncom/interface	domainprefs.pm lonconfigsettings.pm 
  Log:
  - Bug 6935
  
  
["raeburn-20200917003504.txt" (text/plain)]

Index: loncom/interface/domainprefs.pm
diff -u loncom/interface/domainprefs.pm:1.371 loncom/interface/domainprefs.pm:1.372
--- loncom/interface/domainprefs.pm:1.371	Sun Mar 15 23:04:15 2020
+++ loncom/interface/domainprefs.pm	Thu Sep 17 00:35:04 2020
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Handler to set domain-wide configuration settings
 #
-# $Id: domainprefs.pm,v 1.371 2020/03/15 23:04:15 raeburn Exp $
+# $Id: domainprefs.pm,v 1.372 2020/09/17 00:35:04 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -219,9 +219,10 @@
                 'serverstatuses','requestcourses','helpsettings',
                 'coursedefaults','usersessions','loadbalancing',
                 'requestauthor','selfenrollment','inststatus',
-                'ltitools','ssl','trust','lti','privacy','passwords'],$dom);
+                'ltitools','ssl','trust','lti','privacy','passwords',
+                'proctoring'],$dom);
     my %encconfig =
-        &Apache::lonnet::get_dom('encconfig',['ltitools','lti'],$dom);
+        &Apache::lonnet::get_dom('encconfig',['ltitools','lti','proctoring'],$dom);
     if (ref($domconfig{'ltitools'}) eq 'HASH') {
         if (ref($encconfig{'ltitools'}) eq 'HASH') {
             foreach my $id (keys(%{$domconfig{'ltitools'}})) {
@@ -246,12 +247,25 @@
             }
         }
     }
+    if (ref($domconfig{'proctoring'}) eq 'HASH') {
+        if (ref($encconfig{'proctoring'}) eq 'HASH') {
+            foreach my $provider (keys(%{$domconfig{'proctoring'}})) {
+                if ((ref($domconfig{'proctoring'}{$provider}) eq 'HASH') &&
+                    (ref($encconfig{'proctoring'}{$provider}) eq 'HASH')) {
+                    foreach my $item ('key','secret') {
+                        $domconfig{'proctoring'}{$provider}{$item} = \
$encconfig{'proctoring'}{$provider}{$item}; +                    }
+                }
+            }
+        }
+    }
     my @prefs_order = \
                ('rolecolors','login','defaults','passwords','quotas','autoenroll',
                        \
                'autoupdate','autocreate','directorysrch','contacts','privacy',
                        'usercreation','selfcreation','usermodification','scantron',
                        'requestcourses','requestauthor','coursecategories',
                        'serverstatuses','helpsettings','coursedefaults',
-                       \
'ltitools','selfenrollment','usersessions','ssl','trust','lti'); +                    \
'ltitools','proctoring','selfenrollment','usersessions','ssl', +                      \
'trust','lti');  my %existing;
     if (ref($domconfig{'loadbalancing'}) eq 'HASH') {
         %existing = %{$domconfig{'loadbalancing'}};
@@ -541,6 +555,14 @@
                   print => \&print_ltitools,
                   modify => \&modify_ltitools,
                  },
+        'proctoring' =>
+                 {text => 'Remote Proctoring Integration',
+                  help => 'Domain_Configuration_Proctoring',
+                  header => [{col1 => 'Name',
+                              col2 => 'Configuration'}],
+                  print => \&print_proctoring,
+                  modify => \&modify_proctoring,
+                 },
         'ssl' =>
                  {text  => 'LON-CAPA Network (SSL)',
                   help  => 'Domain_Configuration_Network_SSL',
@@ -771,6 +793,8 @@
         $output = &modify_loadbalancing($dom,%domconfig);
     } elsif ($action eq 'ltitools') {
         $output = &modify_ltitools($r,$dom,$action,$lastactref,%domconfig);
+    } elsif ($action eq 'proctoring') {
+        $output = &modify_proctoring($r,$dom,$action,$lastactref,%domconfig);
     } elsif ($action eq 'ssl') {
         $output = &modify_ssl($dom,$lastactref,%domconfig);
     } elsif ($action eq 'trust') {
@@ -815,6 +839,8 @@
         $output .= &ltitools_javascript($settings);
     } elsif ($action eq 'lti') {
         $output .= &lti_javascript($settings);
+    } elsif ($action eq 'proctoring') {
+        $output .= &proctoring_javascript($settings);
     }
     $output .=
          '<table class="LC_nested_outer">
@@ -1162,7 +1188,8 @@
             $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);
         }
     }
@@ -2803,6 +2830,102 @@
 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";
+<script type="text/javascript">
+// <![CDATA[
+function reorderProctoring(form,item) {
+    var changedVal;
+$jstext
+    var maxh = $total;
+    var current = new Array;
+    var changedVal = \
form.elements[item].options[form.elements[item].selectedIndex].value; +    for (var \
i=0; i<proctors.length; i++) { +        var elementName = \
'proctoring_pos_'+proctors[i]; +        if (elementName != item) {
+            if (form.elements[elementName]) {
+                var currVal = \
form.elements[elementName].options[form.elements[elementName].selectedIndex].value; + \
current[currVal] = elementName; +            }
+        }
+    }
+    var oldVal;
+    for (var j=0; j<maxh; j++) {
+        if (current[j] == undefined) {
+            oldVal = j;
+        }
+    }
+    if (oldVal < changedVal) {
+        for (var k=oldVal+1; k<=changedVal ; k++) {
+           var elementName = current[k];
+           form.elements[elementName].selectedIndex = \
form.elements[elementName].selectedIndex - 1; +        }
+    } else {
+        for (var k=changedVal; k<oldVal; k++) {
+            var elementName = current[k];
+            form.elements[elementName].selectedIndex = \
form.elements[elementName].selectedIndex + 1; +        }
+    }
+    return;
+}
+
+function toggleProctoring(form,item) {
+    var fieldsets = document.getElementsByClassName('proctoring_'+item);
+    if (fieldsets.length) {
+        var radioname = 'proctoring_available_'+item;
+        var num = form.elements[radioname].length;
+        if (num) {
+            var setvis = '';
+            for (var i=0; i<num; i++) {
+                if (form.elements[radioname][i].checked) {
+                    if (form.elements[radioname][i].value == '1') {
+                       setvis = 1;
+                       break;
+                    }
+                }
+            }
+            for (var j=0; j<fieldsets.length; j++) {
+                if (setvis) {
+                    fieldsets[j].style.display = 'block';
+                } else {
+                    fieldsets[j].style.display = 'none';
+                }
+            }
+        }
+    }
+    return;
+}
+
+// ]]>
+</script>
+
+ENDSCRIPT
+}
+
+
 sub lti_javascript {
     my ($settings) = @_;
     my $togglejs = &lti_toggle_js();
@@ -4620,7 +4743,7 @@
                 }
                 $datatable .= '<label>'.
                        '<input type="checkbox" name="ltitools_courseconfig_'.$i.'" \
                value="'.$item.'"'.$checked.' />'.
-                       $lt{'crs'.$item}.'</label>'.('&nbsp;' x2)."\n";
+                       $lt{'crs'.$item}.'</label>&nbsp; '."\n";
             }
             $datatable .= '</span></fieldset>'.
                           '<fieldset><legend>'.&mt('Custom items sent on \
launch').'</legend>'. @@ -4822,6 +4945,640 @@
     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 = '<img src="'.$current{'image'}.'" \
alt="'.&mt('Proctoring service icon').'" />'; +                    }
+                    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 .= '<tr '.$css_class.'><td><span class="LC_nobreak">'
+                         .'<select name="proctoring_pos_'.$provider.'"'.$chgstr.'>';
+            for (my $k=0; $k<$maxnum; $k++) {
+                my $vpos = $k+1;
+                my $selstr;
+                if ($k == $i) {
+                    $selstr = ' selected="selected" ';
+                }
+                $datatable .= '<option \
value="'.$k.'"'.$selstr.'>'.$vpos.'</option>'; +            }
+            if ($version eq '') {
+                if ($provider eq 'proctorio') {
+                    $version = '1.0';
+                } elsif ($provider eq 'examity') {
+                    $version = '1.1';
+                }
+            }
+            if ($lifetime eq '') {
+                $lifetime = '300';
+            }
+            $datatable .=
+                '</select>'.('&nbsp;'x2).'<b>'.$providernames{$provider}.'</b></span><br \
/>'. +                '<span class="LC_nobreak">'.$lt{'avai'}.'&nbsp;'.
+                '<label><input type="radio" \
name="proctoring_available_'.$provider.'" \
value="1"'.$onclickavailable.$checkedavailable{yes}.' \
/>'.&mt('Yes').'</label>&nbsp;'."\n". +                '<label><input type="radio" \
name="proctoring_available_'.$provider.'" \
value="0"'.$onclickavailable.$checkedavailable{no}.' \
/>'.&mt('No').'</label></span>'."\n". +                '</td>'.
+                '<td colspan="2">'.
+                '<fieldset class="proctoring_'.$provider.'" \
style="display:'.$optionsty.'"><legend>'.$lt{'base'}.'</legend>'. +                \
'<span class="LC_nobreak">'.$lt{'version'}.':<select \
name="proctoring_'.$provider.'_version">'. +                '<option \
value="'.$version.'" selected="selected">'.$version.'</option></select></span> \
'."\n". +                ('&nbsp;'x2).
+                '<span class="LC_nobreak">'.$lt{'sigmethod'}.':<select \
name="proctoring_'.$provider.'_sigmethod">'. +                '<option \
value="HMAC-SHA1" selected="selected">HMAC-SHA1</option>'. +                '<option \
value="HMAC-SHA256">HMAC-SHA256</option></select></span>'. +                \
('&nbsp;'x2). +                '<span class="LC_nobreak">'.$lt{'lifetime'}.':<input \
type="text" size="5" name="proctoring_'.$provider.'_lifetime" value="'.$lifetime.'" \
/></span> '."\n". +                '<br />'.
+                '<span class="LC_nobreak">'.$lt{'url'}.':<input type="text" \
size="40" name="proctoring_'.$provider.'_url" value="'.$current{'url'}.'" /></span> \
'."\n". +                '<br />'.
+                '<span class="LC_nobreak">'.$lt{'key'}.':<input type="text" \
size="25" name="proctoring_'.$provider.'_key" value="'.$current{'key'}.'" /></span> \
'."\n". +                ('&nbsp;'x2).
+                '<span class="LC_nobreak">'.$lt{'secret'}.':<input type="password" \
size="20" name="proctoring_'.$provider.'_secret" value="'.$current{'secret'}.'" />'. \
+                '<label><input type="checkbox" name="visible" onclick="if \
(this.checked) { this.form.proctoring_'.$provider.'_secret.type='."'text'".' } else { \
this.form.proctoring_'.$provider.'_secret.type='."'password'".' }" \
/>'.$lt{'visible'}.'</label></span><br />'."\n"; +            $datatable .= '<span \
class="LC_nobreak">'.$lt{'icon'}.':&nbsp;'; +            if ($imgsrc) {
+                $datatable .= $imgsrc.
+                              '<label><input type="checkbox" \
name="proctoring_image_del"'. +                              ' value="'.$provider.'" \
/>'.&mt('Delete?').'</label></span> '. +                              '<span \
class="LC_nobreak">&nbsp;'.&mt('Replace:'); +            }
+            $datatable .= '&nbsp;';
+            if ($switchserver) {
+                $datatable .= &mt('Upload to library server: [_1]',$switchserver);
+            } else {
+                $datatable .= '<input type="file" \
name="proctoring_image_'.$provider.'" value="" />'; +            }
+            unless ($imgsrc) {
+                $datatable .= '<br />('.&mt('if larger than 21x21 pixels, image will \
be scaled').')'; +            }
+            $datatable .= '</fieldset>'."\n";
+            if (ref($requserfields{$provider}) eq 'ARRAY') {
+                if (@{$requserfields{$provider}} > 0) {
+                    $datatable .= '<fieldset class="proctoring_'.$provider.'" \
style="display:'.$optionsty.'"><legend>'.$lt{'requ'}.'</legend>'; +                   \
foreach my $field (@{$requserfields{$provider}}) { +                        \
$datatable .= '<span class="LC_nobreak">'. +                                      \
'<label><input type="checkbox" name="proctoring_reqd_'.$provider.'" \
value="'.$field.'" checked="checked" disabled="disabled" />'. +                       \
$lt{$field}.'</label>'; +                        if ($field eq 'user') {
+                            my $seluserdom = '';
+                            my $unseluserdom = ' selected="selected"';
+                            if ($userincdom) {
+                                $seluserdom = $unseluserdom;
+                                $unseluserdom = '';
+                            }
+                            $datatable .= ':&nbsp;'.
+                                '<select \
name="proctoring_userincdom_'.$provider.'">'. +                                \
'<option value="0"'.$unseluserdom.'>'.$lt{'username'}.'</option>'. +                  \
'<option value="1"'.$seluserdom.'>'.$lt{'uname:dom'}.'</option>'. +                   \
'</select>&nbsp;'; +                        } else {
+                            $datatable .= '&nbsp;';
+                            if ($field eq 'roles') {
+                                $showroles = 1; 
+                            } 
+                        }
+                        $datatable .= '</span> ';
+                    }
+                }
+                $datatable .= '</fieldset>'."\n";
+            }
+            if (ref($optuserfields{$provider}) eq 'ARRAY') {
+                if (@{$optuserfields{$provider}} > 0) {
+                    $datatable .= '<fieldset class="proctoring_'.$provider.'" \
style="display:'.$optionsty.'"><legend>'.$lt{'optu'}.'</legend>';  +                  \
foreach my $field (@{$optuserfields{$provider}}) { +                        my \
$checked; +                        if ($checkedfields{$field}) {
+                            $checked = ' checked="checked"';
+                        }
+                        $datatable .= '<span class="LC_nobreak">'.
+                                      '<label><input type="checkbox" \
name="proctoring_optional_'.$provider.'" value="'.$field.'"'.$checked.' \
/>'.$lt{$field}.'</label></span>&nbsp; '; +                    }
+                    $datatable .= '</fieldset>'."\n";
+                }
+            }
+            if (ref($defaults{$provider}) eq 'ARRAY') {
+                if (@{$defaults{$provider}}) {
+                    my (%options,@selectboxes);
+                    if (ref($extended{$provider}) eq 'HASH') {
+                        %options = %{$extended{$provider}};
+                    }
+                    $datatable .= '<fieldset class="proctoring_'.$provider.'" \
style="display:'.$optionsty.'"><legend>'.$lt{'defa'}.'</legend>'; +                   \
my ($rem,$numinrow,$dropdowns); +                    if ($provider eq 'proctorio') { 
+                        $datatable .= '<table>';
+                        $numinrow = 4;
+                    }
+                    my $i = 0;
+                    foreach my $field (@{$defaults{$provider}}) {
+                        my $checked;
+                        if ($inuse{$field}) {
+                            $checked = ' checked="checked"';
+                        }
+                        if ($provider eq 'examity') {
+                            if ($field eq 'display') {
+                                $datatable .= '<span \
class="LC_nobreak">'.&mt('Display target:'); +                                foreach \
my $option ('iframe','tab','window') { +                                    my \
$checkdisp; +                                    if ($currentdef{'target'} eq \
$option) { +                                        $checkdisp = ' \
checked="checked"'; +                                    }
+                                    $datatable .= '<label><input type="radio" \
name="proctoring_target_'.$provider.'" value="'.$option.'"'.$checkdisp.' />'. +       \
$fieldtitles{$option}.'</label>'.('&nbsp;'x2); +                                }
+                                $datatable .= ('&nbsp;'x4);
+                                foreach my $dimen ('width','height') {
+                                    $datatable .= \
'<label>'.$fieldtitles{$dimen}.'&nbsp;'. +                                            \
'<input type="text" name="proctoring_'.$dimen.'_'.$provider.'" size="5" '. +          \
'value="'.$currentdef{$dimen}.'" /></label>'. +                                       \
('&nbsp;'x2); +                                }
+                                $datatable .= '</span><br />'.
+                                              '<div \
class="LC_left_float">'.$fieldtitles{'linktext'}.'<br />'. +                          \
'<input type="text" name="proctoring_linktext_'.$provider.'" '. +                     \
'size="25" value="'.$currentdef{'linktext'}.'" /></div>'. +                           \
'<div class="LC_left_float">'.$fieldtitles{'explanation'}.'<br />'. +                 \
'<textarea name="proctoring_explanation_'.$provider.'" rows="5" cols="40">'. +        \
$currentdef{'explanation'}. +                                              \
'</textarea></div><div style=""></div><br />'; +                            }
+                        } else {
+                            if ((exists($options{$field})) && (ref($options{$field}) \
eq 'ARRAY')) { +                                my ($output,$selnone);
+                                unless ($checked) {
+                                    $selnone = ' selected="selected"';
+                                }
+                                $output .= '<span \
class="LC_nobreak">'.$fieldtitles{$field}.': '. +                                     \
'<select name="proctoring_defaults_'.$field.'_'.$provider.'">'. +                     \
'<option value=""'.$selnone.'>'.&mt('Not in use').'</option>';  +                     \
foreach my $option (@{$options{$field}}) { +                                    my \
$sel;  +                                    if ($inuse{$field} eq $option) {
+                                        $sel = ' selected="selected"';
+                                    }
+                                    $output .= '<option \
value="'.$option.'"'.$sel.'>'.$fieldtitles{$option}.'</option>'; +                    \
} +                                $output .= '</select></span>';
+                                push(@selectboxes,$output);
+                            } else {
+                                $rem = $i%($numinrow);
+                                if ($rem == 0) {
+                                    if ($i > 0) {
+                                        $datatable .= '</tr>';
+                                    }
+                                    $datatable .= '<tr>';
+                                }
+                                $datatable .= '<td class="LC_left_item">'.
+                                              '<span class="LC_nobreak">'.
+                                              '<label><input type="checkbox" \
name="proctoring_defaults_'.$provider.'" value="'.$field.'"'.$checked.' />'. +        \
$fieldtitles{$field}.'</label></span></td>'; +                                $i++;
+                            }
+                        }
+                    }
+                    if ($provider eq 'proctorio') {
+                        if ($numinrow) {
+                            $rem = $i%$numinrow;
+                        }
+                        my $colsleft = $numinrow - $rem;
+                        if ($colsleft > 1) {
+                            $datatable .= '<td colspan="'.$colsleft.'" \
class="LC_left_item">'; +                        } else {
+                            $datatable .= '<td class="LC_left_item">';
+                        }
+                        $datatable .= '&nbsp;'.
+                                      '</td></tr></table>';
+                        if (@selectboxes) {
+                            $datatable .= '<hr /><table>';
+                            $numinrow = 2;
+                            for (my $i=0; $i<@selectboxes; $i++) {
+                                $rem = $i%($numinrow);
+                                if ($rem == 0) {
+                                    if ($i > 0) {
+                                        $datatable .= '</tr>';
+                                    }
+                                    $datatable .= '<tr>';
+                                }
+                                $datatable .= '<td class="LC_left_item">'.
+                                              $selectboxes[$i].'</td>';
+                            }
+                            if ($numinrow) {
+                                $rem = $i%$numinrow;
+                            }
+                            $colsleft = $numinrow - $rem;
+                            if ($colsleft > 1) {
+                                $datatable .= '<td colspan="'.$colsleft.'" \
class="LC_left_item">'; +                            } else {
+                                $datatable .= '<td class="LC_left_item">';
+                            }
+                            $datatable .= '&nbsp;'.
+                                          '</td></tr></table>';
+                        }
+                    }
+                    $datatable .= '</fieldset>';
+                }
+                if (ref($crsconf{$provider}) eq 'ARRAY') {
+                    $datatable .= '<fieldset class="proctoring_'.$provider.'" \
style="display:'.$optionsty.'">'. +                                  \
'<legend>'.&mt('Configurable in course').'</legend>'; +                    my \
($rem,$numinrow); +                    if ($provider eq 'proctorio') {
+                        $datatable .= '<table>';
+                        $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 .= '</tr>';
+                                }
+                                $datatable .= '<tr>';
+                            }
+                            $datatable .= '<td class="LC_left_item>';
+                        }
+                        my $checked;
+                        if ($crsconfig{$item}) {
+                            $checked = ' checked="checked"';
+                        }
+                        $datatable .= '<span class="LC_nobreak"><label>'.
+                                      '<input type="checkbox" \
name="proctoring_crsconf_'.$provider.'" value="'.$item.'"'.$checked.' />'. +          \
$name.'</label></span>'; +                        if ($provider eq 'examity') {
+                            $datatable .= '&nbsp; ';
+                        }
+                        $datatable .= "\n";
+                        $i++;
+                    }
+                    if ($provider eq 'proctorio') {
+                        if ($numinrow) {
+                            $rem = $i%$numinrow;
+                        }
+                        my $colsleft = $numinrow - $rem;
+                        if ($colsleft > 1) {
+                            $datatable .= '<td colspan="'.$colsleft.'" \
class="LC_left_item">'; +                        } else {
+                            $datatable .= '<td class="LC_left_item">';
+                        }
+                        $datatable .= '&nbsp;'.
+                                      '</td></tr></table>';
+                    }
+                    $datatable .= '</fieldset>';
+                }
+                if ($showroles) {
+                    $datatable .= '<fieldset class="proctoring_'.$provider.'" \
style="display:'.$optionsty.'">'. +                                  \
'<legend>'.&mt('Role mapping').'</legend><table><tr>'; +                    foreach \
my $role (@courseroles) { +                        my ($selected,$selectnone);
+                        if (!$rolemaps{$role}) {
+                            $selectnone = ' selected="selected"';
+                        }
+                        $datatable .= '<td style="text-align: center">'.
+                                      \
&Apache::lonnet::plaintext($role,'Course').'<br />'. +                                \
'<select name="proctoring_roles_'.$role.'_'.$provider.'">'. +                         \
'<option value=""'.$selectnone.'>'.&mt('Select').'</option>'; +                       \
foreach my $ltirole (@ltiroles) { +                            unless ($selectnone) {
+                                if ($rolemaps{$role} eq $ltirole) {
+                                    $selected = ' selected="selected"';
+                                } else {
+                                    $selected = '';
+                                }
+                            }
+                            $datatable .= '<option \
value="'.$ltirole.'"'.$selected.'>'.$ltirole.'</option>'; +                        }
+                        $datatable .= '</select></td>';
+                    }
+                    $datatable .= '</tr></table></fieldset>'.
+                                  '<fieldset class="proctoring_'.$provider.'" \
style="display:'.$optionsty.'">'. +                                  \
'<legend>'.&mt('Custom items sent on launch').'</legend>'. +                          \
'<table><tr><th>'.&mt('Action').'</th><th>'.&mt('Name').'</th><th>'.&mt('Value').'</th></tr>'.
 +                                  '<tr><td></td><td>lms</td>'.
+                                  '<td><input type="text" \
name="proctoring_customval_lms_'.$provider.'"'. +                                  ' \
value="Loncapa" disabled="disabled"/></td></tr>'; +                    if \
((ref($settings) eq 'HASH') && (ref($settings->{$provider}) eq 'HASH') && +           \
(ref($settings->{$provider}->{'custom'}) eq 'HASH')) { +                        my \
%custom = %{$settings->{$provider}->{'custom'}}; +                        if \
(keys(%custom) > 0) { +                            foreach my $key \
(sort(keys(%custom))) { +                                next if ($key eq 'lms');
+                                $datatable .= '<tr><td><span class="LC_nobreak">'.
+                                              '<label><input type="checkbox" \
name="proctoring_customdel_'.$provider.'" value="'. +                                 \
$key.'" />'.&mt('Delete').'</label></span></td><td>'.$key.'</td>'. +                  \
'<td><input type="text" name="proctoring_customval_'.$key.'_'.$provider.'"'. +        \
' value="'.$custom{$key}.'" /></td></tr>'; +                            }
+                        }
+                    }
+                    $datatable .= '<tr><td><span class="LC_nobreak">'.
+                                  '<label><input type="checkbox" \
name="proctoring_customadd" value="'.$provider.'" />'. +                              \
&mt('Add more').'</label></span></td><td><input type="text" \
name="proctoring_custom_name_'.$provider.'" />'. +                                  \
'</td><td><input type="text" name="proctoring_custom_value_'.$provider.'" \
/></td></tr>'. +                                  \
'</table></fieldset></td></tr>'."\n"; +                }
+                $datatable .= '</td></tr>';
+            }
+            $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;
@@ -5137,7 +5894,7 @@
     $output .= '</span></div>'.
                '<div class="LC_floatleft" style="display:'.$userfieldsty.';" \
id="lti_userfield_'.$num.'">'.  '<input type="text" name="lti_customuser_'.$num.'" '.
-               'value="'.$userfield.'" /></div></fieldset>'. 
+               'value="'.$userfield.'" /></div></fieldset>'.
                '<fieldset class="ltioption_'.$num.'" \
style="display:'.$optionsty.'"><legend>'.&mt('Mapping course \
roles').'</legend><table><tr>';  foreach my $ltirole (@lticourseroles) {
         my ($selected,$selectnone);
@@ -12427,7 +13184,7 @@
                         }
                     }
                     if (!$numconfig) {
-                        $resulttext .= &mt('None');
+                        $resulttext .= '&nbsp;'.&mt('None');
                     }
                     $resulttext .= '</li>';
                     foreach my $item ('passback','roster') {
@@ -12606,6 +13363,536 @@
     return ($id,$error);
 }
 
+sub modify_proctoring {
+    my ($r,$dom,$action,$lastactref,%domconfig) = @_;
+    my %domdefaults = &Apache::lonnet::get_domain_defaults($dom,1);
+    my (@allpos,%changes,%confhash,%encconfhash,$errors,$resulttext,%imgdeletions);
+    my $confname = $dom.'-domainconfig';
+    my $servadm = $r->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 .= '<li><span class="LC_error">'.$error.'</span></li>';
+                }
+            } 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:').'<ul>';
+            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 .= '<li>'.&mt('Proctoring integration unavailable \
for: [_1]','<b>'.$providernames{$provider}.'</b>').'</li>'; +                } else {
+                    $resulttext .= '<li>'.&mt('Proctoring integration available for: \
[_1]','<b>'.$providernames{$provider}.'</b>'); +                    if \
($confhash{$provider}{'image'}) { +                        $resulttext .= '&nbsp;'.
+                                       '<img \
src="'.$confhash{$provider}{'image'}.'"'. +                                       ' \
alt="'.&mt('Proctoring icon').'" />'; +                    }
+                    $resulttext .= '<ul>';
+                    my $position = $pos + 1;
+                    $resulttext .= '<li>'.&mt('Order: [_1]',$position).'</li>';
+                    foreach my $key ('version','sigmethod','url','lifetime') {
+                        if ($confhash{$provider}{$key} ne '') {
+                            $resulttext .= \
'<li>'.$lt{$key}.':&nbsp;'.$confhash{$provider}{$key}.'</li>'; +                      \
} +                    }
+                    if ($encconfhash{$provider}{'key'} ne '') {
+                        $resulttext .= \
'<li>'.$lt{'key'}.':&nbsp;'.$encconfhash{$provider}{'key'}.'</li>'; +                 \
} +                    if ($encconfhash{$provider}{'secret'} ne '') {
+                        $resulttext .= '<li>'.$lt{'secret'}.':&nbsp;';
+                        my $num = length($encconfhash{$provider}{'secret'});
+                        $resulttext .= ('*'x$num).'</li>';
+                    }
+                    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 .= '<li>'.$lt{'udsl'}.':&nbsp;"'.
+                                       join('", "', map { $lt{$_}; } \
@fields).'"</li>'; +                    }
+                    if (ref($requserfields{$provider}) eq 'ARRAY') {
+                        if (grep(/^user$/,@{$requserfields{$provider}})) {
+                            if ($confhash{$provider}{'incdom'}) {
+                                $resulttext .= '<li>'.&mt('[_1] sent as \
[_2]',$lt{'user'},$lt{'uname:dom'}).'</li>'; +                            } else { 
+                                $resulttext .= '<li>'.&mt('[_1] sent as \
[_2]',$lt{'user'},$lt{'username'}).'</li>'; +                            }
+                        }
+                    }
+                    if (ref($confhash{$provider}{'defaults'}) eq 'ARRAY') {
+                        if (@{$confhash{$provider}{'defaults'}} > 0) {
+                            $resulttext .= '<li>'.$lt{'defa'};
+                            foreach my $field (@{$confhash{$provider}{'defaults'}}) \
{ +                                $resulttext .= ' "'.$fieldtitles{$field}.'",';
+                            }
+                            $resulttext =~ s/,$//;
+                            $resulttext .= '</li>';
+                        }
+                    } elsif (ref($confhash{$provider}{'defaults'}) eq 'HASH') {
+                        if (keys(%{$confhash{$provider}{'defaults'}})) {
+                            $resulttext .= '<li>'.$lt{'defa'}.':&nbsp;<ul>';
+                            foreach my $key \
(sort(keys(%{$confhash{$provider}{'defaults'}}))) { +                                \
if ($confhash{$provider}{'defaults'}{$key} ne '') { +                                 \
$resulttext .= '<li>'.$fieldtitles{$key}.' = \
'.$confhash{$provider}{'defaults'}{$key}.'</li>'; +                                }
+                            }
+                            $resulttext .= '</ul></li>';
+                        }
+                    }
+                    if (ref($crsconf{$provider}) eq 'ARRAY') {
+                        if (@{$crsconf{$provider}} > 0) {
+                            $resulttext .= '<li>'.&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 .= '&nbsp;'.&mt('None');
+                            }
+                            $resulttext .= '</li>';
+                        }
+                    }
+                    if ($showroles) {
+                        if (ref($confhash{$provider}{'roles'}) eq 'HASH') {
+                            my $rolemaps;
+                            foreach my $role (@courseroles) {
+                                if ($confhash{$provider}{'roles'}{$role}) {
+                                    $rolemaps .= \
('&nbsp;'x2).&Apache::lonnet::plaintext($role,'Course').'='. +                        \
$confhash{$provider}{'roles'}{$role}.','; +                                }
+                            }
+                            if ($rolemaps) {
+                                $rolemaps =~ s/,$//;
+                                $resulttext .= '<li>'.&mt('Role \
mapping:').$rolemaps.'</li>'; +                            }
+                        }
+                    }
+                    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 .= '<li>'.&mt('Custom items').': \
'.$customlist.'</li>'; +                        }
+                    } 
+                    $resulttext .= '</ul></li>';
+                }
+            }
+            $resulttext .= '</ul>';
+        } else {
+            $resulttext = &mt('No changes made.');
+        }
+    } else {
+        $errors .= '<li><span class="LC_error">'.&mt('Failed to save \
changes').'</span></li>'; +    }
+    if ($errors) {
+        $resulttext .= &mt('The following errors occurred: ').'<ul>'.
+                       $errors.'</ul>';
+    }
+    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);
@@ -19589,6 +20876,7 @@
 END
 }
 
+
 sub new_spares_js {
     my @sparestypes = ('primary','default');
     my $types = join("','",@sparestypes);
@@ -19662,7 +20950,7 @@
 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') {
@@ -19796,7 +21084,7 @@
         var dispval = 'block';
         var selfcreateRegExp = /^cancreate_emailverified/;
         if (caller == 'emailoptions') {
-            optionsElement = domForm.cancreate_email; 
+            optionsElement = domForm.cancreate_email;
         }
         if (caller == 'studentsubmission') {
             optionsElement = domForm.postsubmit;
Index: loncom/interface/lonconfigsettings.pm
diff -u loncom/interface/lonconfigsettings.pm:1.46 \
                loncom/interface/lonconfigsettings.pm:1.47
--- loncom/interface/lonconfigsettings.pm:1.46	Thu Jul 18 18:28:52 2019
+++ loncom/interface/lonconfigsettings.pm	Thu Sep 17 00:35:04 2020
@@ -1,7 +1,7 @@
 # The LearningOnline Network with CAPA
 # Handler to set domain-wide configuration settings
 #
-# $Id: lonconfigsettings.pm,v 1.46 2019/07/18 18:28:52 raeburn Exp $
+# $Id: lonconfigsettings.pm,v 1.47 2020/09/17 00:35:04 raeburn Exp $
 #
 # Copyright Michigan State University Board of Trustees
 #
@@ -253,6 +253,10 @@
                     }
                 }
             }
+            if (grep(/^proctoring$/,@actions)) {
+                $onload .= "toggleProctoring(document.display,'proctorio');".
+                           "toggleProctoring(document.display,'examity');";
+            }
             if (grep(/^scantron$/,@actions)) {
                 $onload .= "toggleScantron('document.display');";
             }



_______________________________________________
LON-CAPA-cvs mailing list
LON-CAPA-cvs@mail.lon-capa.org
http://mail.lon-capa.org/mailman/listinfo/lon-capa-cvs


[prev in list] [next in list] [prev in thread] [next in thread] 

Configure | About | News | Add a list | Sponsored by KoreLogic