--- loncom/interface/courseprefs.pm 2022/04/05 12:27:39 1.108 +++ loncom/interface/courseprefs.pm 2022/10/18 23:28:00 1.116 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Handler to set configuration settings for a course # -# $Id: courseprefs.pm,v 1.108 2022/04/05 12:27:39 raeburn Exp $ +# $Id: courseprefs.pm,v 1.116 2022/10/18 23:28:00 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -116,7 +116,7 @@ This module is used for configuration of =item item_table_row_end() -=item yes_no_radio() +=item yesno_radio() =item select_from_options() @@ -293,7 +293,7 @@ sub handler { excc => 'Exclude from community catalog', clon => 'Users allowed to clone community', rept => 'Replacement titles for standard community roles', - time => 'Timezone where the community is located', + time => 'Time Zone where the community is located', date => 'Locale used for community calendar', coco => 'Community Content', copo => 'Community Policy', @@ -322,7 +322,7 @@ sub handler { excc => 'Exclude from course catalog', clon => 'Users allowed to clone course', rept => 'Replacement titles for standard course roles', - time => 'Timezone in which the course takes place', + time => 'Time Zone in which the course takes place', date => 'Locale used for course calendar', coco => 'Course Content', copo => 'Course Policy', @@ -492,7 +492,8 @@ sub handler { help => 'Course_Prefs_Display', ordered => ['default_xml_style','pageseparators', 'disable_receipt_display','texengine', - 'tthoptions','uselcmath','usejsme','inline_chem'], + 'tthoptions','uselcmath','usejsme', + 'inline_chem','extresource'], itemtext => { default_xml_style => 'Default XML style file', pageseparators => 'Visibly Separate Items on Pages', @@ -502,6 +503,7 @@ sub handler { uselcmath => 'Student formula entry uses inline preview, not DragMath pop-up', usejsme => 'Molecule editor uses JSME (HTML5) in place of JME (Java)', inline_chem => 'Chemical reaction response uses inline preview, not pop-up', + extresource => 'Display of external resources', }, }, 'grading' => @@ -1464,10 +1466,54 @@ sub process_changes { $newvalues{$entry} = ''; } } + } elsif ($entry eq 'extresource') { + if ($env{'form.'.$entry} =~ /^iframe|tab|window$/) { + $newvalues{$entry} = $env{'form.'.$entry}; + if ($env{'form.'.$entry} ne 'iframe') { + if ($env{'form.extwintabreuse'}) { + $newvalues{$entry} .= ':1'; + } else { + $newvalues{$entry} .= ':0'; + } + if ($env{'form.'.$entry} eq 'window') { + foreach my $dim ('width','height') { + $env{'form.extreswin'.$dim} =~ s/^\s+|\s+$//g; + if ($env{'form.extreswin'.$dim} =~ /^\d+$/) { + $newvalues{$entry} .= ':'.$env{'form.extreswin'.$dim}; + } else { + $newvalues{$entry} .= ':'; + } + } + } + } + } + unless (($newvalues{$entry} eq 'iframe') && ($values->{$entry} eq '')) { + if ($newvalues{$entry} ne $values->{$entry}) { + $changes->{$entry} = $newvalues{$entry}; + } + } + } elsif ($entry eq 'timezone') { + if ($env{'form.'.$entry}) { + $newvalues{$entry} = $env{'form.'.$entry}; + if ($newvalues{$entry} ne $values->{$entry}) { + $changes->{$entry} = $newvalues{$entry}; + } + if ($env{'form.tzover'}) { + $newvalues{'tzover'} = $env{'form.tzover'}; + if ($newvalues{'tzover'} ne $values->{'tzover'}) { + $changes->{'tzover'} = $newvalues{'tzover'}; + } + } elsif ($values->{'tzover'}) { + $changes->{'tzover'} = ''; + } + } elsif ($values->{$entry}) { + $changes->{$entry} = ''; + } } else { $newvalues{$entry} = $env{'form.'.$entry}; } - unless (($entry eq 'co-owners') || ($entry eq 'discussion_post_fonts')) { + unless (($entry eq 'co-owners') || ($entry eq 'discussion_post_fonts') || + ($entry eq 'extresource') || ($entry eq 'timezone')) { if ($newvalues{$entry} ne $values->{$entry}) { $changes->{$entry} = $newvalues{$entry}; } @@ -1548,7 +1594,7 @@ sub process_linkprot { if (ref($values) eq 'HASH') { my @todelete = &Apache::loncommon::get_env_multiple('form.linkprot_del'); my $maxnum = $env{'form.linkprot_maxnum'}; - for (my $i=0; $i<=$maxnum; $i++) { + for (my $i=0; $i<$maxnum; $i++) { my $itemid = $env{'form.linkprot_id_'.$i}; $itemid =~ s/\D+//g; if ($itemid) { @@ -1593,6 +1639,22 @@ sub process_linkprot { $linkprot{$itemid}{$inner} = $env{$formitem}; } } + my $urlitem = 'form.linkprot_returnurl_'.$idx; + my $urlparamname = 'form.linkprot_urlparam_'.$idx; + if ($env{$urlitem} == 1) { + $env{$urlparamname} =~ s/(`)/'/g; + } elsif (exists($env{$urlparamname})) { + $env{$urlparamname} = ''; + } + unless ($idx eq 'add') { + if ((!$current{'returnurl'} && ($env{$urlparamname} ne '')) || + ($current{'returnurl'} && ($env{$urlparamname} eq ''))) { + $haschanges{$itemid} = 1; + } + } + if ($env{$urlparamname} ne '') { + $linkprot{$itemid}{'returnurl'} = $env{$urlparamname}; + } if ($ltiauth) { my $reqitem = 'form.linkprot_requser_'.$idx; $env{$reqitem} =~ s/(`)/'/g; @@ -1678,7 +1740,7 @@ sub process_linkprot { $changes->{$entry} = $linkprot{$entry}; } if (ref($lastactref) eq 'HASH') { - $lastactref->{'courselti'} = 1'; + $lastactref->{'courselti'} = 1; } } return $errors; @@ -1882,12 +1944,52 @@ sub store_changes { if ($msg ne '') { $output .= '
  • '.&Apache::lonhtmlcommon::confirm_success(&mt($displayname)).'
  • '; } + } elsif ($key eq 'timezone') { + next unless ((exists($changes->{$item}{$key})) || (exists($changes->{$item}{'tzover'}))); + my ($displayname,$text); + $text = $prefs->{$item}->{'itemtext'}{$key}; + my $displayval; + if (exists($changes->{$item}{$key})) { + $displayname = &mt($text); + $storehash{$key} = $changes->{$item}{$key}; + if ($changes->{$item}{$key} ne '') { + $displayval = ''.$changes->{$item}{$key}.''; + } else { + push(@delkeys,$key); + if (exists($values->{'tzover'})) { + push(@delkeys,'tzover'); + } + $output .= '
  • '.&Apache::lonhtmlcommon::confirm_success(&mt('Deleted setting for [_1]', + ''.$displayname.'')).'
  • '; + } + } + unless (grep(/^\Q$key\E$/,@delkeys)) { + if (exists($changes->{$item}{'tzover'})) { + $storehash{'tzover'} = $changes->{$item}{'tzover'}; + my $tzovertext; + if ($changes->{$item}{'tzover'} ne '') { + $tzovertext = &mt('Course Time Zone overrides individual user preference'); + } else { + push(@delkeys,'tzover'); + $tzovertext = &mt('Course Time Zone does not override individual user preference'); + } + if ($displayval eq '') { + $output .= '
  • '.&Apache::lonhtmlcommon::confirm_success($tzovertext).'
  • '; + } else { + $displayval .= '
    '.(' 'x5).$tzovertext; + } + } + if ($displayval ne '') { + $output .= '
  • '.&Apache::lonhtmlcommon::confirm_success(&mt('[_1] set to [_2]', + ''.$displayname.'',$displayval)).'
  • '; + } + } } else { next if (!exists($changes->{$item}{$key})); my ($displayname,$text); $text = $prefs->{$item}->{'itemtext'}{$key}; my $displayval; - unless (($key eq 'co-owners') || ($key eq 'discussion_post_fonts')) { + unless (($key eq 'co-owners') || ($key eq 'discussion_post_fonts') || ($key eq 'extresource')) { $displayval = $changes->{$item}{$key}; } if ($item eq 'feedback') { @@ -2012,6 +2114,37 @@ sub store_changes { } elsif ($changes->{$item}{$key} eq '0') { $displayval = &mt('No'); } + } elsif ($key eq 'extresource') { + if ($changes->{$item}{$key} eq 'iframe') { + $displayval = &mt('In iframe'); + } else { + my ($selected,$reuse,$width,$height) = split(/:/,$changes->{$item}{$key}); + if ($selected eq 'tab') { + if ($reuse) { + $displayval = &mt('[_1]In tab[_2],[_3] and tab re-used for different external resources in course', + "'","'",'
    '); + } else { + $displayval = &mt('[_1]In tab[_2],[_3] with new tab for each external resource in course', + "'","'",'
    '); + } + } elsif ($selected eq 'window') { + if ($reuse) { + $displayval = &mt('[_1]In pop-up window[_2],[_3] and window re-used for different external resources in course', + "'","'",'
    '); + } else { + $displayval = &mt('[_1]In pop-up window[_2],[_3] with new window for each external resource in course', + "'","'",'
    '); + } + if (($width ne '') || ($height ne '')) { + if ($width ne '') { + $displayval .= '
    '.&mt('Window width: [_1]px',$width); + } + if ($height ne '') { + $displayval .= '
    '.&mt('Window height: [_1]px',$height); + } + } + } + } } if ($key eq 'co-owners') { if (ref($changes->{$item}{$key}) eq 'HASH') { @@ -2083,9 +2216,11 @@ sub store_changes { $output .= '
  • '.&Apache::lonhtmlcommon::confirm_success(&mt('Numbered menu collections:')).'
    '. $displayval.'
  • '; } else { + unless (($key eq 'extresource') && ($changes->{$item}{$key} ne 'iframe')) { + $displayval = "'$displayval'"; + } $output .= '
  • '.&Apache::lonhtmlcommon::confirm_success(&mt('[_1] set to [_2]', - ''.$displayname.'', - "'$displayval'")); + ''.$displayname.'',$displayval)); if ($key eq 'url') { my $bkuptime=time; $output .= (' 'x2).&mt('(Previous URL backed up)').': '. @@ -2213,6 +2348,9 @@ sub store_linkprot { if (exists($oldlinkprot->{$id}{'usable'})) { $changes->{$id}->{'usable'} = 1; } + if (exists($oldlinkprot->{$id}{'cipher'})) { + $changes->{$id}->{'cipher'} = $oldlinkprot->{$id}{'cipher'}; + } } } } @@ -2263,7 +2401,7 @@ sub store_linkprot { my %values = %{$changes->{$id}}; my %desc = &linkprot_names(); my $display; - foreach my $title ('name','lifetime','version','key','secret') { + foreach my $title ('name','lifetime','version','key','secret','returnurl') { if (($title eq 'key') || ($title eq 'secret')) { if (ref($ltienc{$id}) eq 'HASH') { if (exists($ltienc{$id}{$title})) { @@ -2279,6 +2417,10 @@ sub store_linkprot { if ($values{$title} eq 'LTI-1p0') { $display .= $desc{$title}.': 1.1, '; } + } elsif ($title eq 'returnurl') { + if ($values{$title}) { + $display .= &mt('Return URL parameter').': '.$values{$title}.', '; + } } else { $display .= $desc{$title}.': '.$values{$title}.', '; } @@ -2467,7 +2609,7 @@ sub get_course { sub get_jscript { my ($cid,$cdom,$phase,$crstype,$settings,$noedit) = @_; my ($can_toggle_cat,$can_categorize) = &can_modify_catsettings($cdom,$crstype); - my ($jscript,$categorize_js,$loncaparev_js,$instcode_js); + my ($jscript,$categorize_js,$loncaparev_js,$instcode_js,$extresource_js,$localization_js); my $stubrowse_js = &Apache::loncommon::studentbrowser_javascript(); my $browse_js = &Apache::loncommon::browser_and_searcher_javascript('parmset'); my $cloners_js = &cloners_javascript($phase); @@ -2674,11 +2816,53 @@ function toggleAddmenucoll() { } ENDSCRIPT } + $extresource_js = <<"ENDSCRIPT"; +function toggleExtRes() { + if (document.getElementById('LC_extresource')) { + var extressel = document.getElementById('LC_extresource').value; + if (document.getElementById('LC_extresreusediv')) { + var extresreuse = document.getElementById('LC_extresreusediv'); + if (document.getElementById('LC_extressize')) { + var extressize = document.getElementById('LC_extressize'); + var setvis; + if ((extressel == 'tab') || (extressel == 'window')) { + extresreuse.style.display = 'inline-block'; + setvis = 1; + if (extressel == 'window') { + extressize.style.display = 'inline-block'; + } else { + extressize.style.display = 'none'; + } + } + if (!setvis) { + extresreuse.style.display = 'none'; + extressize.style.display = 'none'; + } + } + } + } +} +ENDSCRIPT + $localization_js = <<"ENDSCRIPT"; +function toggleTimeZone() { + if (document.getElementById('LC_set_timezone')) { + var timezonesel = document.getElementById('LC_set_timezone').value; + if (document.getElementById('LC_tzoverdiv')) { + var tzoverdiv = document.getElementById('LC_tzoverdiv'); + if (timezonesel == '') { + tzoverdiv.style.display = 'none'; + } else { + tzoverdiv.style.display = 'block'; + } + } + } +} +ENDSCRIPT $jscript = ''."\n".$stubrowse_js."\n"; return $jscript; @@ -2799,7 +2983,7 @@ function toggleLinkProt(form,num,item) { return; } -function toggleLinkProtReqUser(form,item,extra,valon,styleon,num) { +function toggleLinkProtExtra(form,item,extra,valon,styleon,num) { if (document.getElementById('linkprot_'+extra+'_'+num)) { var extraid = document.getElementById('linkprot_'+extra+'_'+num); var itemname = form.elements['linkprot_'+item+'_'+num]; @@ -4332,9 +4516,23 @@ sub print_localization { if ($item eq 'timezone') { my $includeempty = 1; my $timezone = &Apache::lonlocal::gettimezone(); + my $onchange; + unless ($noedit) { + $onchange = ' onchange="javascript:toggleTimeZone();"'; + } + my $id = ' id="LC_set_timezone"'; $datatable .= - &Apache::loncommon::select_timezone($item,$timezone,undef, - $includeempty,$disabled); + &Apache::loncommon::select_timezone($item,$timezone,$onchange, + $includeempty,$id,$disabled); + my $tzsty = 'none'; + if ($timezone ne '') { + $tzsty = 'block'; + } + $datatable .= '
    '. + ''. + &mt('Override individual user preference?'). + &yesno_radio('tzover',$settings,undef,1,'',$noedit). + '
    '; } elsif ($item eq 'datelocale') { my $includeempty = 1; my $locale_obj = &Apache::lonlocal::getdatelocale(); @@ -5053,6 +5251,16 @@ sub print_appearance { text => ''.&mt($itemtext->{'inline_chem'}).'', input => 'radio', }, + 'extresource' => { + text => ''.&mt($itemtext->{'extresource'}).'', + input => 'selectbox', + options => { + iframe => 'In iframe', + tab => 'In new tab', + window => 'In pop-up window', + }, + order => ['iframe','tab','window'], + }, ); return &make_item_rows($cdom,\%items,$ordered,$settings,$rowtotal,$crstype,'appearance',$noedit); } @@ -5706,10 +5914,10 @@ sub print_linkprotection { ''. ''; - my ($usersty,$onclickrequser,%checkedrequser); + my ($usersty,$onclickrequser,%checkedrequser,$onclickreturnurl,%checkedreturnurl); if ($ltiauth) { $usersty = 'display:none'; - $onclickrequser = ' onclick="toggleLinkProtReqUser(this.form,'."'requser','optional','1','block','$i'".');"'; + $onclickrequser = ' onclick="toggleLinkProtExtra(this.form,'."'requser','optional','1','block','$i'".');"'; %checkedrequser = ( no => ' checked="checked"', yes => '', @@ -5723,6 +5931,15 @@ sub print_linkprotection { $usersty = 'display:inline-block'; } } + $onclickreturnurl = ' onclick="toggleLinkProtExtra(this.form,'."'returnurl','divurlparam','1','inline-block','$i'".');"'; + %checkedreturnurl = ( + no => ' checked="checked"', + yes => '', + ); + if ($values{'returnurl'} ne '') { + $checkedreturnurl{'yes'} = $checkedreturnurl{'no'}; + $checkedreturnurl{'no'} = ''; + } $datatable .= ''.$desc{'name'}. ': '. @@ -5731,15 +5948,7 @@ sub print_linkprotection { ' '."\n". (' 'x2). ''.$desc{'lifetime'}.':'; - if ($ltiauth) { - $datatable .= (' 'x2).''.$desc{'requser'}.'?'. - ' '. - ''; - } - $datatable .= '

    '; + ' value="'.$values{'lifetime'}.'" size="3"'.$disabled.' />

    '; if ($values{'key'} ne '') { $datatable .= ''.$desc{'key'}; if ($noedit) { @@ -5771,6 +5980,7 @@ sub print_linkprotection { } else { $datatable .= ''.&mt('Secret required').' - '.$switchmessage.''."\n"; } + $datatable .= ''; } else { if ($values{'usable'} ne '') { $datatable .= '
    '. @@ -5781,19 +5991,33 @@ sub print_linkprotection { '  '; } else { $datatable .= ''.$desc{'secret'}.':'. - ''. + ''. ''. ''; } } + $datatable .= '

    '. + ''.$desc{'returnurl'}.'?'. + ' '. + ''. + '   '; if ($ltiauth) { - $datatable .= + $datatable .= (' 'x2).''.$desc{'requser'}.'?'. + ' '. + ''. ''. '
    '.$lt{'opti'}.''. &linkprot_options($i,$itemcount,$disabled,\%values,\%desc). @@ -5809,41 +6033,54 @@ sub print_linkprotection { ''."\n". ''.&mt('Add').''."\n". ''; - my ($usersty,$onclickrequser,%checkedrequser); + my ($usersty,$onclickrequser,%checkedrequser,$onclickreturnurl,%checkedreturnurl); if ($ltiauth) { $usersty = 'display:none'; - $onclickrequser = ' onclick="toggleLinkProtReqUser(this.form,'."'requser','optional','1','block','add'".');"'; + $onclickrequser = ' onclick="toggleLinkProtExtra(this.form,'."'requser','optional','1','block','add'".');"'; %checkedrequser = ( no => ' checked="checked"', yes => '', ); $datatable .= '
    '.$lt{'requ'}.''; } + $onclickreturnurl = ' onclick="toggleLinkProtExtra(this.form,'."'returnurl','divurlparam','1','inline-block','add'".');"'; + %checkedreturnurl = ( + no => ' checked="checked"', + yes => '', + ); $datatable .= ''.$desc{'name'}. ': '."\n". (' 'x2). ''.$desc{'version'}.': '."\n". (' 'x2). - ''.$desc{'lifetime'}.': '."\n"; - if ($ltiauth) { - $datatable .= (' 'x2).''.$desc{'requser'}.'?'. - ' '. - ''; - } - $datatable .= '

    '; + ''.$desc{'lifetime'}.': '."\n". + '

    '; if ($switchserver) { $datatable .= ''.&mt('Key and Secret are required').' - '.$switchmessage.''."\n"; } else { $datatable .= ''.$desc{'key'}.': '."\n". (' 'x2). - ''.$desc{'secret'}.':'. + ''.$desc{'secret'}.':'. ' '."\n"; } + $datatable .= '

    '. + ''.$desc{'returnurl'}.'?'. + ' '. + ''. + '  
    '; if ($ltiauth) { - $datatable .= '
    '. + $datatable .= (' 'x2).''.$desc{'requser'}.'?'. + ' '. + ''. + '
    '. '
    '.$lt{'opti'}.''. &linkprot_options('add',$itemcount,$disabled,{},\%desc). '
    '; @@ -5860,6 +6097,7 @@ sub linkprot_names { 'lifetime' => 'Nonce lifetime (s)', 'name' => 'Launcher Application', 'secret' => 'Secret', + 'returnurl' => 'Launcher return URL', 'requser' => 'Use identity', 'email' => 'Email address', 'sourcedid' => 'User ID', @@ -5915,7 +6153,7 @@ sub linkprot_options { $checked{'auth'} = ' checked="checked"'; } } - my $onclickuser = ' onclick="toggleLinkProtReqUser(this.form,'."'mapuser','userfield','other','inline-block','$num'".');"'; + my $onclickuser = ' onclick="toggleLinkProtExtra(this.form,'."'mapuser','userfield','other','inline-block','$num'".');"'; my $output = '
    '. &mt('Source of LON-CAPA username in LTI request').': '; foreach my $option ('sourcedid','email','other') { @@ -5939,6 +6177,45 @@ sub linkprot_options { return $output; } +sub print_extresource_row { + my ($item,$config,$curr,$noedit) = @_; + my $onchange; + unless ($noedit) { + $onchange = ' onchange="javascript:toggleExtRes();"'; + } + my $id = 'LC_'.$item; + my ($selected,$reuse,$width,$height) = split(/:/,$curr); + my $output = &select_from_options($item,$config->{'order'}, + $config->{'options'},$selected, + $config->{'nullval'}, + undef,undef,$onchange,$noedit,$id); + my ($checked,$reusesty,$sizesty); + if ($reuse) { + $checked = ' checked="checked"'; + } + $reusesty = 'none'; + $sizesty = 'none'; + if (($selected eq 'window') || ($selected eq 'tab')) { + $reusesty = 'inline-block'; + if ($selected eq 'window') { + $sizesty = 'inline-block'; + } + } + $output .= '
    '. + ''. + ''. + '
    '. + '
    '. + ''.&mt('Window size (optional)').''. + ''. + &mt('width').':px'. + (' ' x 3). + &mt('height').':px'. + '
    '; + return $output; +} + sub print_other { my ($cdom,$settings,$allitems,$rowtotal,$crstype,$noedit) = @_; unless ((ref($settings) eq 'HASH') && (ref($allitems) eq 'ARRAY')) { @@ -6110,10 +6387,16 @@ sub make_item_rows { (($caller eq 'printouts') && ($item ne 'print_header_format'))) { $colspan = 2; } + my $rowdesc; + if ($caller eq 'appearance') { + $rowdesc = ''.$items->{$item}{text}.''; + } else { + $rowdesc = $items->{$item}{text}; + } if (exists $items->{$item}{advanced} && $items->{$item}{advanced} == 1) { - $datatable .= &item_table_row_start($items->{$item}{text},$count,"advanced",$colspan); + $datatable .= &item_table_row_start($rowdesc,$count,"advanced",$colspan); } else { - $datatable .= &item_table_row_start($items->{$item}{text},$count,undef,$colspan); + $datatable .= &item_table_row_start($rowdesc,$count,undef,$colspan); } if ($item eq 'defaultcredits') { my $defaultcredits = $env{'course.'.$env{'request.course.id'}.'.internal.defaultcredits'}; @@ -6132,6 +6415,8 @@ sub make_item_rows { $datatable .= &print_hdrfmt_row($item,$settings,$noedit); } elsif ($item eq 'lti.lcmenu') { $datatable .= &lcmenu_checkboxes($cdom,$item,$settings,$crstype,$noedit); + } elsif ($item eq 'extresource') { + $datatable .= &print_extresource_row($item,$items->{$item},$settings->{$item},$noedit); } elsif ($items->{$item}{input} eq 'dates') { my $disabled; if ($noedit) {