--- loncom/interface/domainprefs.pm 2017/11/30 02:17:50 1.319 +++ loncom/interface/domainprefs.pm 2017/12/06 23:41:19 1.321 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Handler to set domain-wide configuration settings # -# $Id: domainprefs.pm,v 1.319 2017/11/30 02:17:50 raeburn Exp $ +# $Id: domainprefs.pm,v 1.321 2017/12/06 23:41:19 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -219,13 +219,14 @@ sub handler { 'serverstatuses','requestcourses','helpsettings', 'coursedefaults','usersessions','loadbalancing', 'requestauthor','selfenrollment','inststatus', - 'ltitools','ssl','trust'],$dom); + 'ltitools','ssl','trust','lti'],$dom); + my %encconfig = + &Apache::lonnet::get_dom('encconfig',['ltitools','lti'],$dom); if (ref($domconfig{'ltitools'}) eq 'HASH') { - my %encconfig = - &Apache::lonnet::get_dom('encconfig',['ltitools'],$dom); if (ref($encconfig{'ltitools'}) eq 'HASH') { foreach my $id (keys(%{$domconfig{'ltitools'}})) { - if (ref($domconfig{'ltitools'}{$id}) eq 'HASH') { + if ((ref($domconfig{'ltitools'}{$id}) eq 'HASH') && + (ref($encconfig{'ltitools'}{$id}) eq 'HASH')) { foreach my $item ('key','secret') { $domconfig{'ltitools'}{$id}{$item} = $encconfig{'ltitools'}{$id}{$item}; } @@ -233,12 +234,24 @@ sub handler { } } } + if (ref($domconfig{'lti'}) eq 'HASH') { + if (ref($encconfig{'lti'}) eq 'HASH') { + foreach my $id (keys(%{$domconfig{'lti'}})) { + if ((ref($domconfig{'lti'}{$id}) eq 'HASH') && + (ref($encconfig{'lti'}{$id}) eq 'HASH')) { + foreach my $item ('key','secret') { + $domconfig{'lti'}{$id}{$item} = $encconfig{'lti'}{$id}{$item}; + } + } + } + } + } my @prefs_order = ('rolecolors','login','defaults','quotas','autoenroll', 'autoupdate','autocreate','directorysrch','contacts', 'usercreation','selfcreation','usermodification','scantron', 'requestcourses','requestauthor','coursecategories', 'serverstatuses','helpsettings','coursedefaults', - 'ltitools','selfenrollment','usersessions','ssl','trust'); + 'ltitools','selfenrollment','usersessions','ssl','trust','lti'); my %existing; if (ref($domconfig{'loadbalancing'}) eq 'HASH') { %existing = %{$domconfig{'loadbalancing'}}; @@ -545,6 +558,14 @@ sub handler { print => \&print_trust, modify => \&modify_trust, }, + 'lti' => + {text => 'LTI Provider', + help => 'Domain_Configuration_LTI_Provider', + header => [{col1 => 'Setting', + col2 => 'Value',}], + print => \&print_lti, + modify => \&modify_lti, + }, ); if (keys(%servers) > 1) { $prefs{'login'} = { text => 'Log-in page options', @@ -730,6 +751,8 @@ sub process_changes { $output = &modify_ssl($dom,$lastactref,%domconfig); } elsif ($action eq 'trust') { $output = &modify_trust($dom,$lastactref,%domconfig); + } elsif ($action eq 'lti') { + $output = &modify_lti($r,$dom,$action,$lastactref,%domconfig); } return $output; } @@ -1065,7 +1088,7 @@ 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 'ltitools') || ($action eq 'lti')) { $output .= $item->{'print'}->($dom,$settings,\$rowtotal); } elsif ($action eq 'scantron') { $output .= &print_scantronformat($r,$dom,$confname,$settings,\$rowtotal); @@ -2697,6 +2720,165 @@ function toggleLTITools(form,setting,ite ENDSCRIPT } +sub lti_javascript { + my ($settings) = @_; + my $togglejs = <i_toggle_js(); + unless (ref($settings) eq 'HASH') { + return $togglejs; + } + my (%ordered,$total,%jstext); + $total = 0; + foreach my $item (keys(%{$settings})) { + if (ref($settings->{$item}) eq 'HASH') { + my $num = $settings->{$item}{'order'}; + $ordered{$num} = $item; + } + } + $total = scalar(keys(%{$settings})); + my @jsarray = (); + foreach my $item (sort {$a <=> $b } (keys(%ordered))) { + push(@jsarray,$ordered{$item}); + } + my $jstext = ' var lti = Array('."'".join("','",@jsarray)."'".');'."\n"; + return <<"ENDSCRIPT"; +<script type="text/javascript"> +// <![CDATA[ +function reorderLTI(form,item) { + var changedVal; +$jstext + var newpos = 'lti_pos_add'; + var maxh = 1 + $total; + var current = new Array; + var newitemVal = form.elements[newpos].options[form.elements[newpos].selectedIndex].value; + if (item == newpos) { + changedVal = newitemVal; + } else { + changedVal = form.elements[item].options[form.elements[item].selectedIndex].value; + current[newitemVal] = newpos; + } + for (var i=0; i<lti.length; i++) { + var elementName = 'lti_pos_'+lti[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; +} +// ]]> +</script> + +$togglejs + +ENDSCRIPT +} + +sub lti_toggle_js { + return <<"ENDSCRIPT"; +<script type="text/javascript"> +// <![CDATA[ + +function toggleLTI(form,setting,item) { + if ((setting == 'user') || (setting == 'crs')) { + var radioname = ''; + var divid = ''; + if (setting == 'user') { + radioname = 'lti_mapuser_'+item; + divid = 'lti_userfield_'+item; + } else { + radioname = 'lti_mapcrs_'+item; + divid = 'lti_crsfield_'+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 == 'other') { + if (document.getElementById(divid)) { + document.getElementById(divid).style.display = 'inline-block'; + } + setvis = 1; + break; + } + } + } + if (!setvis) { + if (document.getElementById(divid)) { + document.getElementById(divid).style.display = 'none'; + } + } + } + } else if ((setting == 'sec') || (setting == 'secsrc')) { + var numsec = form.elements['lti_crssec_'+item].length; + if (numsec) { + var setvis = ''; + for (var i=0; i<numsec; i++) { + if (form.elements['lti_crssec_'+item][i].checked) { + if (form.elements['lti_crssec_'+item][i].value == '1') { + if (document.getElementById('lti_crssecfield_'+item)) { + document.getElementById('lti_crssecfield_'+item).style.display = 'inline-block'; + setvis = 1; + var numsrcsec = form.elements['lti_crssecsrc_'+item].length; + if (numsrcsec) { + var setsrcvis = ''; + for (var j=0; j<numsrcsec; j++) { + if (form.elements['lti_crssecsrc_'+item][j].checked) { + if (form.elements['lti_crssecsrc_'+item][j].value == 'other') { + if (document.getElementById('lti_secsrcfield_'+item)) { + document.getElementById('lti_secsrcfield_'+item).style.display = 'inline-block'; + setsrcvis = 1; + } + } + } + } + if (!setsrcvis) { + if (document.getElementById('lti_secsrcfield_'+item)) { + document.getElementById('lti_secsrcfield_'+item).style.display = 'none'; + } + } + } + } + } + } + } + if (!setvis) { + if (document.getElementById('lti_crssecfield_'+item)) { + document.getElementById('lti_crssecfield_'+item).style.display = 'none'; + } + if (document.getElementById('lti_secsrcfield_'+item)) { + document.getElementById('lti_secsrcfield_'+item).style.display = 'none'; + } + } + } + } + return; +} +// ]]> +</script> + +ENDSCRIPT +} + sub print_autoenroll { my ($dom,$settings,$rowtotal) = @_; my $autorun = &Apache::lonnet::auto_run(undef,$dom), @@ -4298,6 +4480,327 @@ sub ltitools_names { return %lt; } +sub print_lti { + my ($dom,$settings,$rowtotal) = @_; + my $itemcount = 1; + my $maxnum = 0; + my $css_class; + my %ordered; + if (ref($settings) eq 'HASH') { + foreach my $item (keys(%{$settings})) { + if (ref($settings->{$item}) eq 'HASH') { + my $num = $settings->{$item}{'order'}; + $ordered{$num} = $item; + } + } + } + my $maxnum = scalar(keys(%ordered)); + my $datatable = <i_javascript($settings); + my %lt = <i_names(); + if (keys(%ordered)) { + my @items = sort { $a <=> $b } keys(%ordered); + for (my $i=0; $i<@items; $i++) { + $css_class = $itemcount%2?' class="LC_odd_row"':''; + my $item = $ordered{$items[$i]}; + my ($key,$secret,$lifetime,$consumer,$current); + if (ref($settings->{$item}) eq 'HASH') { + $key = $settings->{$item}->{'key'}; + $secret = $settings->{$item}->{'secret'}; + $lifetime = $settings->{$item}->{'lifetime'}; + $consumer = $settings->{$item}->{'consumer'}; + $current = $settings->{$item}; + } + my $chgstr = ' onchange="javascript:reorderLTI(this.form,'."'lti_pos_".$item."'".');"'; + $datatable .= '<tr '.$css_class.'><td><span class="LC_nobreak">' + .'<select name="lti_pos_'.$item.'"'.$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>'; + } + $datatable .= '</select>'.(' 'x2). + '<label><input type="checkbox" name="lti_del" value="'.$item.'" />'. + &mt('Delete?').'</label></span></td>'. + '<td colspan="2">'. + '<fieldset><legend>'.&mt('Required settings').'</legend>'. + '<span class="LC_nobreak">'.$lt{'consumer'}. + ':<input type="text" size="20" name="lti_consumer_'.$i.'" value="'.$consumer.'" /></span> '. + (' 'x2). + '<span class="LC_nobreak">'.$lt{'version'}.':<select name="lti_version_'.$i.'">'. + '<option value="LTI-1p0" selected="selected">1.1</option></select></span> '. + (' 'x2). + '<span class="LC_nobreak">'.$lt{'lifetime'}.':<input type="text" name="lti_lifetime_'.$i.'"'. + 'value="'.$lifetime.'" size="5" /></span>'. + '<br /><br />'. + '<span class="LC_nobreak">'.$lt{'key'}. + ':<input type="text" size="25" name="lti_key_'.$i.'" value="'.$key.'" /></span> '. + (' 'x2). + '<span class="LC_nobreak">'.$lt{'secret'}.':'. + '<input type="password" size="20" name="lti_secret_'.$i.'" value="'.$secret.'" />'. + '<label><input type="checkbox" name="visible" onclick="if (this.checked) { this.form.lti_secret_'.$i.'.type='."'text'".' } else { this.form.lti_secret_'.$i.'.type='."'password'".' }" />'.&mt('Visible input').'</label>'. + '<input type="hidden" name="lti_id_'.$i.'" value="'.$item.'" /></span>'. + '</fieldset>'.<i_options($i,$current,%lt).'</td></tr>'; + $itemcount ++; + } + } + $css_class = $itemcount%2?' class="LC_odd_row"':''; + my $chgstr = ' onchange="javascript:reorderLTI(this.form,'."'lti_pos_add'".');"'; + $datatable .= '<tr '.$css_class.'><td><span class="LC_nobreak">'."\n". + '<input type="hidden" name="lti_maxnum" value="'.$maxnum.'" />'."\n". + '<select name="lti_pos_add"'.$chgstr.'>'; + for (my $k=0; $k<$maxnum+1; $k++) { + my $vpos = $k+1; + my $selstr; + if ($k == $maxnum) { + $selstr = ' selected="selected" '; + } + $datatable .= '<option value="'.$k.'"'.$selstr.'>'.$vpos.'</option>'; + } + $datatable .= '</select> '."\n". + '<input type="checkbox" name="lti_add" value="1" />'.&mt('Add').'</td>'."\n". + '<td colspan="2">'. + '<fieldset><legend>'.&mt('Required settings').'</legend>'. + '<span class="LC_nobreak">'.$lt{'consumer'}. + ':<input type="text" size="20" name="lti_consumer_add" value="" /></span> '."\n". + (' 'x2). + '<span class="LC_nobreak">'.$lt{'version'}.':<select name="lti_version_add">'. + '<option value="LTI-1p0" selected="selected">1.1</option></select></span> '."\n". + (' 'x2). + '<span class="LC_nobreak">'.$lt{'lifetime'}.':<input type="text" size="5" name="lti_lifetime_add" value="" /></span> '."\n". + '<br /><br />'. + '<span class="LC_nobreak">'.$lt{'key'}.':<input type="text" size="25" name="lti_key_add" value="" /></span> '."\n". + (' 'x2). + '<span class="LC_nobreak">'.$lt{'secret'}.':<input type="password" size="20" name="lti_secret_add" value="" />'. + '<label><input type="checkbox" name="visible" onclick="if (this.checked) { this.form.lti_secret_add.type='."'text'".' } else { this.form.lti_secret_add.type='."'password'".' }" />'.&mt('Visible input').'</label></span> '."\n". + '</fieldset>'.<i_options('add',undef,%lt). + '</td>'."\n". + '</tr>'."\n"; + $$rowtotal ++; + return $datatable;; +} + +sub lti_names { + my %lt = &Apache::lonlocal::texthash( + 'version' => 'LTI Version', + 'url' => 'URL', + 'key' => 'Key', + 'lifetime' => 'Nonce lifetime (seconds)', + 'consumer' => 'LTI Consumer', + 'secret' => 'Secret', + 'email' => 'Email address', + 'sourcedid' => 'User ID', + 'other' => 'Other', + 'passback' => 'Can return grades to Consumer:', + 'roster' => 'Can retrieve roster from Consumer:', + ); + return %lt; +} + +sub lti_options { + my ($num,$current,%lt) = @_; + my (%checked,%rolemaps,$crssecsrc,$userfield,$cidfield); + $checked{'mapuser'}{'sourcedid'} = ' checked="checked"'; + $checked{'mapcrs'}{'course_offering_sourcedid'} = ' checked="checked"'; + $checked{'makecrs'}{'N'} = ' checked="checked"'; + $checked{'mapcrstype'} = {}; + $checked{'makeuser'} = {}; + $checked{'selfenroll'} = {}; + $checked{'crssec'} = {}; + $checked{'crssecsrc'} = {}; + + my $userfieldsty = 'none'; + my $crsfieldsty = 'none'; + my $crssecfieldsty = 'none'; + my $secsrcfieldsty = 'none'; + + if (ref($current) eq 'HASH') { + if (($current->{'mapuser'} ne '') && ($current->{'mapuser'} ne 'lis_person_sourcedid')) { + $checked{'mapuser'}{'sourcedid'} = ''; + if ($current->{'mapuser'} eq 'lis_person_contact_email_primary') { + $checked{'mapuser'}{'email'} = ' checked="checked"'; + } else { + $checked{'mapuser'}{'other'} = ' checked="checked"'; + $userfield = $current->{'mapuser'}; + $userfieldsty = 'inline-block'; + } + } + if (($current->{'mapcrs'} ne '') && ($current->{'mapcrs'} ne 'course_offering_sourcedid')) { + $checked{'mapcrs'}{'course_offering_sourcedid'} = ''; + if ($current->{'mapcrs'} eq 'context_id') { + $checked{'mapcrs'}{'context_id'} = ' checked="checked"'; + } else { + $checked{'mapcrs'}{'other'} = ' checked="checked"'; + $cidfield = $current->{'mapcrs'}; + $crsfieldsty = 'inline-block'; + } + } + if (ref($current->{'mapcrstype'}) eq 'ARRAY') { + foreach my $type (@{$current->{'mapcrstype'}}) { + $checked{'mapcrstype'}{$type} = ' checked="checked"'; + } + } + if ($current->{'makecrs'}) { + $checked{'makecrs'}{'Y'} = ' checked="checked"'; + } + if (ref($current->{'makeuser'}) eq 'ARRAY') { + foreach my $role (@{$current->{'makeuser'}}) { + $checked{'makeuser'}{$role} = ' checked="checked"'; + } + } + if (ref($current->{'selfenroll'}) eq 'ARRAY') { + foreach my $role (@{$current->{'selfenroll'}}) { + $checked{'selfenroll'}{$role} = ' checked="checked"'; + } + } + if (ref($current->{'maproles'}) eq 'HASH') { + %rolemaps = %{$current->{'maproles'}}; + } + if ($current->{'section'} ne '') { + $checked{'crssec'}{'Y'} = ' checked="checked"'; + $crssecfieldsty = 'inline-block'; + if ($current->{'section'} eq 'course_section_sourcedid') { + $checked{'crssecsrc'}{'sourcedid'} = ' checked="checked"'; + } else { + $checked{'crssecsrc'}{'other'} = ' checked="checked"'; + $crssecsrc = $current->{'section'}; + $secsrcfieldsty = 'inline-block'; + } + } else { + $checked{'crssec'}{'N'} = ' checked="checked"'; + } + } else { + $checked{'makecrs'}{'N'} = ' checked="checked"'; + $checked{'crssec'}{'N'} = ' checked="checked"'; + } + my @coursetypes = ('official','unofficial','community','textbook','placement'); + my %coursetypetitles = &Apache::lonlocal::texthash ( + official => 'Official', + unofficial => 'Unofficial', + community => 'Community', + textbook => 'Textbook', + placement => 'Placement Test', + ); + my @ltiroles = qw(Learner Instructor ContentDeveloper TeachingAssistant Mentor Member Manager Administrator); + my @lticourseroles = qw(Learner Instructor TeachingAssistant Mentor); + my @courseroles = ('cc','in','ta','ep','st'); + 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 $onclicksecsrc = ' onclick="toggleLTI(this.form,'."'secsrc','$num'".')"'; + my $output = '<fieldset><legend>'.&mt('Mapping users').'</legend>'. + '<div class="LC_floatleft"><span class="LC_nobreak">'.&mt('LON-CAPA username').': '; + foreach my $option ('sourcedid','email','other') { + $output .= '<label><input type="radio" name="lti_mapuser_'.$num.'" value="'.$option.'"'. + $checked{'mapuser'}{$option}.$onclickuser.' />'.$lt{$option}.'</label>'. + ($option eq 'other' ? '' : (' 'x2) ); + } + $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>'. + '<fieldset><legend>'.&mt('Mapping course roles').'</legend><table><tr>'; + foreach my $ltirole (@lticourseroles) { + my ($selected,$selectnone); + if ($rolemaps{$ltirole} eq '') { + $selectnone = ' selected="selected"'; + } + $output .= '<td style="text-align: center">'.$ltirole.'<br />'. + '<select name="lti_maprole_'.$ltirole.'_'.$num.'">'. + '<option value=""'.$selectnone.'>'.&mt('Select').'</option>'; + foreach my $role (@courseroles) { + unless ($selectnone) { + if ($rolemaps{$ltirole} eq $role) { + $selected = ' selected="selected"'; + } else { + $selected = ''; + } + } + $output .= '<option value="'.$role.'"'.$selected.'>'. + &Apache::lonnet::plaintext($role,'Course'). + '</option>'; + } + $output .= '</select></td>'; + } + $output .= '</tr></table></fieldset>'. + '<fieldset><legend>'.&mt('Roles which may create user accounts').'</legend>'; + foreach my $ltirole (@ltiroles) { + $output .= '<span class="LC_nobreak"><label><input type="checkbox" name="lti_makeuser_'.$num.'" value="'.$ltirole.'"'. + $checked{'makeuser'}{$ltirole}.' />'.$ltirole.'</label> </span> '; + } + $output .= '</fieldset>'. + '<fieldset><legend>'.&mt('Mapping courses').'</legend>'. + '<div class="LC_floatleft"><span class="LC_nobreak">'. + &mt('Unique course identifier').': '; + foreach my $option ('course_offering_sourcedid','context_id','other') { + $output .= '<label><input type="radio" name="lti_mapcrs_'.$num.'" value="'.$option.'"'. + $checked{'mapcrs'}{$option}.$onclickcrs.' />'.$option.'</label>'. + ($option eq 'other' ? '' : (' 'x2) ); + } + $output .= '</div><div class="LC_floatleft" style="display:'.$crsfieldsty.';" id="lti_crsfield_'.$num.'".>'. + '<input type="text" name="lti_mapcrsfield_'.$num.'" value="'.$cidfield.'" />'. + '</div><div style="padding:0;clear:both;margin:0;border:0"></div>'. + '<span class="LC_nobreak">'.&mt('LON-CAPA course type(s)').': '; + foreach my $type (@coursetypes) { + $output .= '<label><input type="checkbox" name="lti_mapcrstype_'.$num.'" value="'.$type.'"'. + $checked{'mapcrstype'}{$type}.' />'.$coursetypetitles{$type}.'</label>'. + (' 'x2); + } + $output .= '</span></fieldset>'. + '<fieldset><legend>'.&mt('Creating courses').'</legend>'. + '<span class="LC_nobreak">'.&mt('Course created (if absent) on Instructor access').': '. + '<label><input type="radio" name="lti_makecrs_'.$num.'" value="0"'. + $checked{'makecrs'}{'N'}.' />'.&mt('No').'</label>'.(' 'x2). + '<label><input type="radio" name="lti_makecrs_'.$num.'" value="1"'. + $checked{'makecrs'}{'Y'}.' />'.&mt('Yes').'</label></span>'. + '</fieldset>'. + '<fieldset><legend>'.&mt('Roles which may self-enroll').'</legend>'; + foreach my $lticrsrole (@lticourseroles) { + $output .= '<span class="LC_nobreak"><label><input type="checkbox" name="lti_selfenroll_'.$num.'" value="'.$lticrsrole.'"'. + $checked{'selfenroll'}{$lticrsrole}.' />'.$lticrsrole.'</label> </span> '; + } + $output .= '</fieldset>'. + '<fieldset><legend>'.&mt('Course options').'</legend>'. + '<div class="LC_floatleft"><span class="LC_nobreak">'.&mt('Assign users to sections').': '. + '<label><input type="radio" name="lti_crssec_'.$num.'" value="0"'. + $checked{'crssec'}{'N'}.$onclicksec.' />'.&mt('No').'</label>'.(' 'x2). + '<label><input type="radio" name="lti_crssec_'.$num.'" value="1"'. + $checked{'crssec'}{'Y'}.$onclicksec.' />'.&mt('Yes').'</label><span></div>'. + '<div class="LC_floatleft" style="display:'.$crssecfieldsty.';" id="lti_crssecfield_'.$num.'">'. + '<span class="LC_nobreak">'.&mt('From').':<label>'. + '<input type="radio" name="lti_crssecsrc_'.$num.'" value="course_section_sourcedid"'. + $checked{'crssecsrc'}{'sourcedid'}.$onclicksecsrc.' />'. + &mt('Standard field').'</label>'.(' 'x2). + '<label><input type="radio" name="lti_crssecsrc_'.$num.'" value="other"'. + $checked{'crssecsrc'}{'other'}.$onclicksecsrc.' />'.&mt('Other'). + '</label></div><div class="LC_floatleft" style="display:'.$secsrcfieldsty.';" id="lti_secsrcfield_'.$num.'">'. + '<input type="text" name="lti_customsection_'.$num.'" value="'.$crssecsrc.'" />'. + '</div><div style="padding:0;clear:both;margin:0;border:0"></div>'; + foreach my $extra ('passback','roster') { + my $checkedon = ''; + my $checkedoff = ' checked="checked"'; + if (ref($current) eq 'HASH') { + if (($current->{$extra})) { + $checkedon = $checkedoff; + $checkedoff = ''; + } + } + $output .= $lt{$extra}.' '. + '<label><input type="radio" name="lti_'.$extra.'_'.$num.'" value="0"'.$checkedoff.' />'. + &mt('No').'</label>'.(' 'x2). + '<label><input type="radio" name="lti_'.$extra.'_'.$num.'" value="1"'.$checkedon.' />'. + &mt('Yes').'</label><br />'; + } + $output .= '</span></fieldset>'; +# '<fieldset><legend>'.&mt('Assigning author roles').'</legend>'; +# +# $output .= '</fieldset>'. +# '<fieldset><legend>'.&mt('Assigning domain roles').'</legend>'; + return $output; +} + sub print_coursedefaults { my ($position,$dom,$settings,$rowtotal) = @_; my ($css_class,$datatable,%checkedon,%checkedoff,%defaultchecked,@toggles); @@ -10723,6 +11226,422 @@ sub get_ltitools_id { } else { $error = 'nolock'; } + return ($id,$error); +} + +sub modify_lti { + my ($r,$dom,$action,$lastactref,%domconfig) = @_; + my %domdefaults = &Apache::lonnet::get_domain_defaults($dom,1); + my ($newid,@allpos,%changes,%confhash,%encconfig,$errors,$resulttext); + my (%posslti,%posslticrs,%posscrstype); + my @courseroles = ('cc','in','ta','ep','st'); + my @ltiroles = qw(Learner Instructor ContentDeveloper TeachingAssistant Mentor Member Manager Administrator); + my @lticourseroles = qw(Instructor TeachingAssistant Mentor Learner); + my @coursetypes = ('official','unofficial','community','textbook','placement'); + my %coursetypetitles = &Apache::lonlocal::texthash ( + official => 'Official', + unofficial => 'Unofficial', + community => 'Community', + textbook => 'Textbook', + placement => 'Placement Test', + ); + my %lt = <i_names(); + map { $posslti{$_} = 1; } @ltiroles; + map { $posslticrs{$_} = 1; } @lticourseroles; + map { $posscrstype{$_} = 1; } @coursetypes; + + my (@items,%deletions,%itemids); + if ($env{'form.lti_add'}) { + my $consumer = $env{'form.lti_consumer_add'}; + $consumer =~ s/(`)/'/g; + ($newid,my $error) = &get_lti_id($dom,$consumer); + if ($newid) { + $itemids{'add'} = $newid; + push(@items,'add'); + $changes{$newid} = 1; + } else { + my $error = &mt('Failed to acquire unique ID for new LTI configuration'); + $errors .= '<li><span class="LC_error">'.$error.'</span></li>'; + } + } + if (ref($domconfig{$action}) eq 'HASH') { + my @todelete = &Apache::loncommon::get_env_multiple('form.lti_del'); + if (@todelete) { + map { $deletions{$_} = 1; } @todelete; + } + my $maxnum = $env{'form.lti_maxnum'}; + for (my $i=0; $i<=$maxnum; $i++) { + my $itemid = $env{'form.lti_id_'.$i}; + $itemid =~ s/\D+//g; + if (ref($domconfig{$action}{$itemid}) eq 'HASH') { + if ($deletions{$itemid}) { + $changes{$itemid} = $domconfig{$action}{$itemid}{'consumer'}; + } else { + push(@items,$i); + $itemids{$i} = $itemid; + } + } + } + } + foreach my $idx (@items) { + my $itemid = $itemids{$idx}; + next unless ($itemid); + my $position = $env{'form.lti_pos_'.$idx}; + $position =~ s/\D+//g; + if ($position ne '') { + $allpos[$position] = $itemid; + } + foreach my $item ('consumer','key','secret','lifetime') { + my $formitem = 'form.lti_'.$item.'_'.$idx; + $env{$formitem} =~ s/(`)/'/g; + if ($item eq 'lifetime') { + $env{$formitem} =~ s/[^\d.]//g; + } + if ($env{$formitem} ne '') { + if (($item eq 'key') || ($item eq 'secret')) { + $encconfig{$itemid}{$item} = $env{$formitem}; + } else { + $confhash{$itemid}{$item} = $env{$formitem}; + unless (($idx eq 'add') || ($changes{$itemid})) { + if ($domconfig{$action}{$itemid}{$item} ne $confhash{$itemid}{$item}) { + $changes{$itemid} = 1; + } + } + } + } + } + if ($env{'form.lti_version_'.$idx} eq 'LTI-1p0') { + $confhash{$itemid}{'version'} = $env{'form.lti_version_'.$idx}; + } + if ($env{'form.lti_mapuser_'.$idx} eq 'sourcedid') { + $confhash{$itemid}{'mapuser'} = 'lis_person_sourcedid'; + } elsif ($env{'form.lti_mapuser_'.$idx} eq 'email') { + $confhash{$itemid}{'mapuser'} = 'lis_person_contact_email_primary'; + } elsif ($env{'form.lti_mapuser_'.$idx} eq 'other') { + my $mapuser = $env{'form.lti_customuser_'.$idx}; + $mapuser =~ s/(`)/'/g; + $mapuser =~ s/^\s+|\s+$//g; + $confhash{$itemid}{'mapuser'} = $mapuser; + } + foreach my $ltirole (@lticourseroles) { + my $possrole = $env{'form.lti_maprole_'.$ltirole.'_'.$idx}; + if (grep(/^\Q$possrole\E$/,@courseroles)) { + $confhash{$itemid}{'maproles'}{$ltirole} = $possrole; + } + } + my @possmakeuser = &Apache::loncommon::get_env_multiple('form.lti_makeuser_'.$idx); + my @makeuser; + foreach my $ltirole (sort(@possmakeuser)) { + if ($posslti{$ltirole}) { + push(@makeuser,$ltirole); + } + } + $confhash{$itemid}{'makeuser'} = \@makeuser; + if (($env{'form.lti_mapcrs_'.$idx} eq 'course_offering_sourcedid') || + ($env{'form.lti_mapcrs_'.$idx} eq 'context_id')) { + $confhash{$itemid}{'mapcrs'} = $env{'form.lti_mapcrs_'.$idx}; + } elsif ($env{'form.lti_mapcrs_'.$idx} eq 'other') { + my $mapcrs = $env{'form.lti_mapcrsfield_'.$idx}; + $mapcrs =~ s/(`)/'/g; + $mapcrs =~ s/^\s+|\s+$//g; + $confhash{$itemid}{'mapcrs'} = $mapcrs; + } + my @posstypes = &Apache::loncommon::get_env_multiple('form.lti_mapcrstype_'.$idx); + my @crstypes; + foreach my $type (sort(@posstypes)) { + if ($posscrstype{$type}) { + push(@crstypes,$type); + } + } + $confhash{$itemid}{'mapcrstype'} = \@crstypes; + if ($env{'form.lti_makecrs_'.$idx}) { + $confhash{$itemid}{'makecrs'} = 1; + } + my @possenroll = &Apache::loncommon::get_env_multiple('form.lti_selfenroll_'.$idx); + my @selfenroll; + foreach my $type (sort(@possenroll)) { + if ($posslticrs{$type}) { + push(@selfenroll,$type); + } + } + $confhash{$itemid}{'selfenroll'} = \@selfenroll; + if ($env{'form.lti_crssec_'.$idx}) { + if ($env{'form.lti_crssecsrc_'.$idx} eq 'course_section_sourcedid') { + $confhash{$itemid}{'section'} = $env{'form.lti_crssecsrc_'.$idx}; + } elsif ($env{'form.lti_crssecsrc_'.$idx} eq 'other') { + my $section = $env{'form.lti_customsection_'.$idx}; + $section =~ s/(`)/'/g; + $section =~ s/^\s+|\s+$//g; + if ($section ne '') { + $confhash{$itemid}{'section'} = $section; + } + } + } + foreach my $field ('passback','roster') { + if ($env{'form.lti_'.$field.'_'.$idx}) { + $confhash{$itemid}{$field} = 1; + } + } + unless (($idx eq 'add') || ($changes{$itemid})) { + foreach my $field ('mapuser','mapcrs','section','passback','roster') { + if ($domconfig{$action}{$itemid}{$field} ne $confhash{$itemid}{$field}) { + $changes{$itemid} = 1; + } + } + foreach my $field ('makeuser','mapcrstype','selfenroll') { + unless ($changes{$itemid}) { + if (ref($domconfig{$action}{$itemid}{$field}) eq 'ARRAY') { + if (ref($confhash{$itemid}{$field}) eq 'ARRAY') { + my @diffs = &Apache::loncommon::compare_arrays($domconfig{$action}{$itemid}{$field}, + $confhash{$itemid}{$field}); + if (@diffs) { + $changes{$itemid} = 1; + } + } elsif (@{$domconfig{$action}{$itemid}{$field}} > 0) { + $changes{$itemid} = 1; + } + } elsif (ref($confhash{$itemid}{$field}) eq 'ARRAY') { + if (@{$confhash{$itemid}{$field}} > 0) { + $changes{$itemid} = 1; + } + } + } + } + unless ($changes{$itemid}) { + if (ref($domconfig{$action}{$itemid}{'maproles'}) eq 'HASH') { + if (ref($confhash{$itemid}{'maproles'}) eq 'HASH') { + foreach my $ltirole (keys(%{$domconfig{$action}{$itemid}{'maproles'}})) { + if ($domconfig{$action}{$itemid}{'maproles'}{$ltirole} ne + $confhash{$itemid}{'maproles'}{$ltirole}) { + $changes{$itemid} = 1; + last; + } + } + unless ($changes{$itemid}) { + foreach my $ltirole (keys(%{$confhash{$itemid}{'maproles'}})) { + if ($confhash{$itemid}{'maproles'}{$ltirole} ne + $domconfig{$action}{$itemid}{'maproles'}{$ltirole}) { + $changes{$itemid} = 1; + last; + } + } + } + } elsif (keys(%{$domconfig{$action}{$itemid}{'maproles'}}) > 0) { + $changes{$itemid} = 1; + } + } elsif (ref($confhash{$itemid}{'maproles'}) eq 'HASH') { + unless ($changes{$itemid}) { + if (keys(%{$confhash{$itemid}{'maproles'}}) > 0) { + $changes{$itemid} = 1; + } + } + } + } + } + } + if (@allpos > 0) { + my $idx = 0; + foreach my $itemid (@allpos) { + if ($itemid ne '') { + $confhash{$itemid}{'order'} = $idx; + if (ref($domconfig{$action}) eq 'HASH') { + if (ref($domconfig{$action}{$itemid}) eq 'HASH') { + if ($domconfig{$action}{$itemid}{'order'} ne $idx) { + $changes{$itemid} = 1; + } + } + } + $idx ++; + } + } + } + my %ltihash = ( + $action => { %confhash } + ); + my $putresult = &Apache::lonnet::put_dom('configuration',\%ltihash, + $dom); + if ($putresult eq 'ok') { + my %ltienchash = ( + $action => { %encconfig } + ); + &Apache::lonnet::put_dom('encconfig',\%ltienchash,$dom); + if (keys(%changes) > 0) { + my $cachetime = 24*60*60; + my %ltiall = %confhash; + foreach my $id (keys(%ltiall)) { + if (ref($encconfig{$id}) eq 'HASH') { + foreach my $item ('key','secret') { + $ltiall{$id}{$item} = $encconfig{$id}{$item}; + } + } + } + &Apache::lonnet::do_cache_new('lti',$dom,\%ltiall,$cachetime); + if (ref($lastactref) eq 'HASH') { + $lastactref->{'lti'} = 1; + } + $resulttext = &mt('Changes made:').'<ul>'; + my %bynum; + foreach my $itemid (sort(keys(%changes))) { + my $position = $confhash{$itemid}{'order'}; + $bynum{$position} = $itemid; + } + foreach my $pos (sort { $a <=> $b } keys(%bynum)) { + my $itemid = $bynum{$pos}; + if (ref($confhash{$itemid}) ne 'HASH') { + $resulttext .= '<li>'.&mt('Deleted: [_1]',$changes{$itemid}).'</li>'; + } else { + $resulttext .= '<li><b>'.$confhash{$itemid}{'consumer'}.'</b></li><ul>'; + my $position = $pos + 1; + $resulttext .= '<li>'.&mt('Order: [_1]',$position).'</li>'; + foreach my $item ('version','lifetime') { + if ($confhash{$itemid}{$item} ne '') { + $resulttext .= '<li>'.$lt{$item}.': '.$confhash{$itemid}{$item}.'</li>'; + } + } + if ($encconfig{$itemid}{'key'} ne '') { + $resulttext .= '<li>'.$lt{'key'}.': '.$encconfig{$itemid}{'key'}.'</li>'; + } + if ($encconfig{$itemid}{'secret'} ne '') { + $resulttext .= '<li>'.$lt{'secret'}.': '; + my $num = length($encconfig{$itemid}{'secret'}); + $resulttext .= ('*'x$num).'</li>'; + } + if ($confhash{$itemid}{'mapuser'}) { + my $shownmapuser; + if ($confhash{$itemid}{'mapuser'} eq 'lis_person_sourcedid') { + $shownmapuser = $lt{'sourcedid'}.' (lis_person_sourcedid)'; + } elsif ($confhash{$itemid}{'mapuser'} eq 'lis_person_contact_email_primary') { + $shownmapuser = $lt{'email'}.' (lis_person_contact_email_primary)'; + } else { + $shownmapuser = &mt('Other').' ('.$confhash{$itemid}{'mapuser'}.')'; + } + $resulttext .= '<li>'.&mt('LON-CAPA username').': '.$shownmapuser.'</li>'; + } + if (ref($confhash{$itemid}{'maproles'}) eq 'HASH') { + my $rolemaps; + foreach my $role (@ltiroles) { + if ($confhash{$itemid}{'maproles'}{$role}) { + $rolemaps .= (' 'x2).$role.'='. + &Apache::lonnet::plaintext($confhash{$itemid}{'maproles'}{$role}, + 'Course').','; + } + } + if ($rolemaps) { + $rolemaps =~ s/,$//; + $resulttext .= '<li>'.&mt('Role mapping:').$rolemaps.'</li>'; + } + } + if (ref($confhash{$itemid}{'makeuser'}) eq 'ARRAY') { + if (@{$confhash{$itemid}{'makeuser'}} > 0) { + $resulttext .= '<li>'.&mt('Following roles may create user accounts: [_1]', + join(', ',@{$confhash{$itemid}{'makeuser'}})).'</li>'; + } else { + $resulttext .= '<li>'.&mt('User account creation not permitted.').'</li>'; + } + } + if ($confhash{$itemid}{'mapcrs'}) { + $resulttext .= '<li>'.&mt('Unique course identifier').': '.$confhash{$itemid}{'mapcrs'}.'</li>'; + } + if (ref($confhash{$itemid}{'mapcrstype'}) eq 'ARRAY') { + if (@{$confhash{$itemid}{'mapcrstype'}} > 0) { + $resulttext .= '<li>'.&mt('Mapping for the following LON-CAPA course types: [_1]', + join(', ',map { $coursetypetitles{$_}; } @coursetypes)). + '</li>'; + } else { + $resulttext .= '<li>'.&mt('No mapping to LON-CAPA courses').'</li>'; + } + } + if ($confhash{$itemid}{'makecrs'}) { + $resulttext .= '<li>'.&mt('Instructor may create course (if absent).').'</li>'; + } else { + $resulttext .= '<li>'.&mt('Instructor may not create course (if absent).').'</li>'; + } + if (ref($confhash{$itemid}{'selfenroll'}) eq 'ARRAY') { + if (@{$confhash{$itemid}{'selfenroll'}} > 0) { + $resulttext .= '<li>'.&mt('Self-enrollment for following roles: [_1]', + join(', ',@{$confhash{$itemid}{'selfenroll'}})). + '</li>'; + } else { + $resulttext .= '<li>'.&mt('Self-enrollment not permitted').'</li>'; + } + } + if ($confhash{$itemid}{'section'}) { + if ($confhash{$itemid}{'section'} eq 'course_section_sourcedid') { + $resulttext .= '<li>'.&mt('User section from standard field:'). + ' (course_section_sourcedid)'.'</li>'; + } else { + $resulttext .= '<li>'.&mt('User section from:').' '. + $confhash{$itemid}{'section'}.'</li>'; + } + } else { + $resulttext .= '<li>'.&mt('No section assignment').'</li>'; + } + foreach my $item ('passback','roster') { + $resulttext .= '<li>'.$lt{$item}.' '; + if ($confhash{$itemid}{$item}) { + $resulttext .= &mt('Yes'); + } else { + $resulttext .= &mt('No'); + } + $resulttext .= '</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 get_lti_id { + my ($domain,$consumer) = @_; + # get lock on lti db + my $lockhash = { + lock => $env{'user.name'}. + ':'.$env{'user.domain'}, + }; + my $tries = 0; + my $gotlock = &Apache::lonnet::newput_dom('lti',$lockhash,$domain); + my ($id,$error); + + while (($gotlock ne 'ok') && ($tries<10)) { + $tries ++; + sleep (0.1); + $gotlock = &Apache::lonnet::newput_dom('lti',$lockhash,$domain); + } + if ($gotlock eq 'ok') { + my %currids = &Apache::lonnet::dump_dom('lti',$domain); + if ($currids{'lock'}) { + delete($currids{'lock'}); + if (keys(%currids)) { + my @curr = sort { $a <=> $b } keys(%currids); + if ($curr[-1] =~ /^\d+$/) { + $id = 1 + $curr[-1]; + } + } else { + $id = 1; + } + if ($id) { + unless (&Apache::lonnet::newput_dom('lti',{ $id => $consumer },$domain) eq 'ok') { + $error = 'nostore'; + } + } else { + $error = 'nonumber'; + } + } + my $dellockoutcome = &Apache::lonnet::del_dom('lti',['lock'],$domain); + } else { + $error = 'nolock'; + } return ($id,$error); }