--- loncom/interface/courseprefs.pm 2020/10/29 23:24:13 1.91 +++ loncom/interface/courseprefs.pm 2024/02/24 23:41:44 1.131 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Handler to set configuration settings for a course # -# $Id: courseprefs.pm,v 1.91 2020/10/29 23:24:13 raeburn Exp $ +# $Id: courseprefs.pm,v 1.131 2024/02/24 23:41:44 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -52,12 +52,16 @@ This module is used for configuration of =item process_changes() +=item process_linkprot() + =item get_sec_str() =item check_clone() =item store_changes() +=item store_linkprot() + =item update_env() =item display_disallowed() @@ -112,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() @@ -220,6 +224,8 @@ use Apache::lonparmset; use Apache::courseclassifier; use Apache::lonlocal; use LONCAPA qw(:DEFAULT :match); +use Crypt::CBC; +use Time::HiRes qw( sleep ); my $registered_cleanup; my $modified_courses; @@ -288,7 +294,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', @@ -310,14 +316,14 @@ sub handler { idnu => 'Course ID or number', unco => 'Unique code', desc => 'Course Description', - cred => 'Student credits', + cred => 'Student credits', ownr => 'Course Owner', cown => 'Course Co-owners', catg => 'Categorize course', 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', @@ -365,10 +371,54 @@ sub handler { } my %values=&Apache::lonnet::dump('environment',$cdom,$cnum); + my %linkprot=&Apache::lonnet::dump('lti',$cdom,$cnum,undef,undef,undef,1); + my %domdefs = &Apache::lonnet::get_domain_defaults($cdom); + unless ($phase eq 'process') { + if (ref($domdefs{'linkprotsuggested'}) eq 'HASH') { + $values{'suggested'} = $domdefs{'linkprotsuggested'}; + } + } + my %ltienc = &Apache::lonnet::dump('nohist_ltienc',$cdom,$cnum,undef,undef,undef,1); + my %ltitools = &Apache::lonnet::dump('ltitools',$cdom,$cnum,undef,undef,undef,1); + my %ltitoolsenc = &Apache::lonnet::dump('nohist_toolsenc',$cdom,$cnum,undef,undef,undef,1); + foreach my $id (keys(%linkprot)) { + if (ref($linkprot{$id}) eq 'HASH') { + if (ref($ltienc{$id}) eq 'HASH') { + $values{'linkprot'}{$id} = { %{$linkprot{$id}}, %{$ltienc{$id}} }; + } else { + $values{'linkprot'}{$id} = $linkprot{$id}; + } + } + unless ($phase eq 'process') { + if (ref($values{'linkprot'}{$id}) eq 'HASH') { + delete($values{'linkprot'}{$id}{'secret'}); + } + } + } + if ($linkprot{'lock'}) { + delete($linkprot{'lock'}); + } + foreach my $id (keys(%ltitools)) { + if (ref($ltitools{$id}) eq 'HASH') { + if (ref($ltitoolsenc{$id}) eq 'HASH') { + $values{'ltitools'}{$id} = { %{$ltitools{$id}}, %{$ltitoolsenc{$id}} }; + } else { + $values{'ltitools'}{$id} = $ltitools{$id}; + } + } + unless ($phase eq 'process') { + if (ref($values{'ltitools'}{$id}) eq 'HASH') { + delete($values{'ltitools'}{$id}{'secret'}); + } + } + } + if ($ltitools{'lock'}) { + delete($ltitools{'lock'}); + } my @prefs_order = ('courseinfo','localization','feedback','discussion', 'classlists','appearance','grading','printouts', - 'menuitems','spreadsheet','bridgetasks','lti','other'); - + 'menuitems','ltitools','linkprot','spreadsheet', + 'bridgetasks','lti','other'); my %prefs = ( 'courseinfo' => { text => $lt{'gens'}, @@ -467,7 +517,8 @@ sub handler { help => 'Course_Prefs_Display', ordered => ['default_xml_style','pageseparators', 'disable_receipt_display','texengine', - 'tthoptions','uselcmath','usejsme'], + 'tthoptions','uselcmath','usejsme', + 'inline_chem','extresource'], itemtext => { default_xml_style => 'Default XML style file', pageseparators => 'Visibly Separate Items on Pages', @@ -476,6 +527,8 @@ sub handler { tthoptions => 'Default set of options to pass to tth/m when converting TeX', 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' => @@ -557,6 +610,22 @@ sub handler { menucollections => 'Menu collections', }, }, + 'linkprot' => + { + text => 'Link protection', + help => 'Course_Prefs_Linkprotection', + header => [{col1 => 'Item', + col2 => 'Settings', + }], + }, + 'ltitools' => + { + text => 'External tools', + help => 'Course_Prefs_ExternalTools', + header => [{col1 => 'Item', + col2 => 'Settings', + }], + }, 'other' => { text => 'Other settings', help => 'Course_Prefs_Other', @@ -567,10 +636,14 @@ sub handler { ); if (($phase eq 'process') && ($parm_permission->{'process'})) { my @allitems = &get_allitems(%prefs); - &Apache::lonconfigsettings::make_changes($r,$cdom,$phase,$context, - \@prefs_order,\%prefs,\%values, - $cnum,undef,\@allitems, - 'coursepref',$parm_permission); + my $result = &Apache::lonconfigsettings::make_changes($r,$cdom,$phase,$context, + \@prefs_order,\%prefs,\%values, + $cnum,undef,\@allitems, + 'coursepref',$parm_permission); + if ((ref($result) eq 'HASH') && (keys(%{$result}))) { + $r->rflush(); + &devalidate_remote_courseprefs($cdom,$cnum,$result); + } } elsif (($phase eq 'display') && ($parm_permission->{'display'})) { my $noedit; if (ref($parm_permission) eq 'HASH') { @@ -581,7 +654,7 @@ sub handler { my $jscript = &get_jscript($cid,$cdom,$phase,$crstype,\%values,$noedit); my @allitems = &get_allitems(%prefs); &Apache::lonconfigsettings::display_settings($r,$cdom,$phase,$context, - \@prefs_order,\%prefs,\%values,undef,$jscript,\@allitems,$crstype, + \@prefs_order,\%prefs,\%values,$cnum,$jscript,\@allitems,$crstype, 'coursepref',$parm_permission); } else { &Apache::lonconfigsettings::display_choices($r,$phase,$context, @@ -627,6 +700,8 @@ sub get_allitems { if ($item eq 'feedback') { push(@allitems,(map { $_.'.text'; } @{$prefs{$item}{'ordered'}})); } + } elsif (($item eq 'linkprot') || ($item eq 'ltitools')) { + push(@allitems,$item); } } } @@ -634,7 +709,7 @@ sub get_allitems { } sub print_config_box { - my ($r,$cdom,$phase,$action,$item,$settings,$allitems,$crstype,$parm_permission) = @_; + my ($r,$cdom,$cnum,$phase,$action,$item,$settings,$allitems,$crstype,$parm_permission) = @_; my $ordered = $item->{'ordered'}; my $itemtext = $item->{'itemtext'}; my $noedit; @@ -724,9 +799,38 @@ sub print_config_box { '; + if ($action eq 'linkprot') { + if ((ref($settings) eq 'HASH') && (ref($settings->{'suggested'}) eq 'HASH')) { + my $hints; + my $hintcount = 0; + foreach my $key (sort { $a <=> $b } keys(%{$settings->{'suggested'}})) { + if ((ref($settings->{'suggested'}->{$key}) eq 'HASH')) { + if (($settings->{'suggested'}->{$key}-{'name'} ne '') && + ($settings->{'suggested'}->{$key}-{'info'} ne '')) { + my $css_class = $hintcount%2?' class="LC_odd_row"':' class="LC_even_row"'; + $hints .= ''. + ''; + $hintcount ++; + } + } + } + if ($hintcount) { + $output .= ''. + ''. + ''."\n". + ''. + ''. + ''."\n". + $hints; + } + } + } if (exists $item->{'header'}->[0]->{'col1'} || exists $item->{'header'}->[0]->{'col2'}) { - $output .= ' + $output .= ' '; if (($action eq 'courseinfo') || ($action eq 'localization') || @@ -753,17 +857,25 @@ sub print_config_box { } elsif ($action eq 'appearance') { $output .= &print_appearance($cdom,$settings,$ordered,$itemtext,\$rowtotal,$crstype,$noedit); } elsif ($action eq 'grading') { - $output .= &print_grading($cdom,$settings,$ordered,$itemtext,\$rowtotal,$crstype,$noedit); + $output .= &print_grading($cdom,$cnum,$settings,$ordered,$itemtext,\$rowtotal,$crstype,$noedit); } elsif ($action eq 'printouts') { $output .= &print_printouts($cdom,$settings,$ordered,$itemtext,\$rowtotal,$crstype,$noedit); } elsif ($action eq 'spreadsheet') { $output .= &print_spreadsheet($cdom,$settings,$ordered,$itemtext,\$rowtotal,$crstype,$noedit); } elsif ($action eq 'bridgetasks') { $output .= &print_bridgetasks($cdom,$settings,$ordered,$itemtext,\$rowtotal,$crstype,$noedit); + } elsif ($action eq 'ltitools') { + my $currtools = {}; + if ((ref($settings) eq 'HASH') && (ref($settings->{'ltitools'}))) { + $currtools = $settings->{'ltitools'}; + } + $output .= &print_ltitools($cdom,$cnum,$currtools,\$rowtotal,$crstype,$noedit,'course'); } elsif ($action eq 'lti') { $output .= &print_lti($cdom,$settings,$ordered,$itemtext,\$rowtotal,$crstype,$noedit); } elsif ($action eq 'menuitems') { $output .= &print_menuitems('bottom',$cdom,$settings,$itemtext,\$rowtotal,$crstype,$noedit); + } elsif ($action eq 'linkprot') { + $output .= &print_linkprotection($cdom,$cnum,$settings,\$rowtotal,$crstype,$noedit,'course'); } elsif ($action eq 'other') { $output .= &print_other($cdom,$settings,$allitems,\$rowtotal,$crstype,$noedit); } @@ -776,8 +888,8 @@ sub print_config_box { } sub process_changes { - my ($cdom,$action,$values,$item,$changes,$allitems,$disallowed,$crstype) = @_; - my %newvalues; + my ($cdom,$cnum,$action,$values,$item,$changes,$allitems,$disallowed,$crstype,$lastactref) = @_; + my (%newvalues,$errors); if (ref($item) eq 'HASH') { if (ref($changes) eq 'HASH') { my @ordered; @@ -794,6 +906,18 @@ sub process_changes { } } } + } elsif (($action eq 'linkprot') || ($action eq 'ltitools')) { + if (ref($values->{$action}) eq 'HASH') { + foreach my $id (keys(%{$values->{$action}})) { + if ($id =~ /^\d+$/) { + push(@ordered,$id); + } + } + } + @ordered = sort { $a <=> $b } @ordered; + if (($env{'form.'.$action.'_add'}) && ($env{'form.'.$action.'_maxnum'} =~ /^\d+$/)) { + push(@ordered,$env{'form.'.$action.'_maxnum'}); + } } elsif (ref($item->{'ordered'}) eq 'ARRAY') { if ($action eq 'courseinfo') { my ($can_toggle_cat,$can_categorize) = @@ -884,10 +1008,10 @@ sub process_changes { my $currdef = $values->{'menudefault'}; my $possdef = $env{'form.menudefault'}; if (($possdef =~ /^\d+$/) && (grep(/^$possdef$/,@colls))) { - if ($values->{'menudefault'} ne $possdef) { + if ($currdef ne $possdef) { $changes->{'menudefault'} = $possdef; } - } elsif ($values->{'menudefault'}) { + } elsif ($currdef) { $changes->{'menudefault'} = ''; } my $menucoll; @@ -931,6 +1055,16 @@ sub process_changes { } elsif ($values->{'menucollections'}) { $changes->{'menucollections'} = ''; } + } elsif ($action eq 'linkprot') { + if (ref($values) eq 'HASH') { + $errors = &process_linkprot($cdom,$cnum,$values->{$action},$changes,'course',$lastactref); + } + } elsif ($action eq 'ltitools') { + if (ref($values) eq 'HASH') { + my $switchserver = &check_switchserver($cdom,$cnum,'course','/adm/courseprefs'); + $errors = &process_ltitools('',$cdom,$cnum,$values->{$action},$changes,'course',$lastactref, + 'ok','','ok'); + } } else { foreach my $entry (@ordered) { if ($entry eq 'cloners') { @@ -971,7 +1105,7 @@ sub process_changes { my $clonedom = $env{'form.cloners_newdom'}; if (&check_clone($clonedom,$disallowed) eq 'ok') { my $newdom = '*:'.$env{'form.cloners_newdom'}; - if (@clonedoms) { + if (@clonedoms) { if (!grep(/^\Q$newdom\E$/,@clonedoms)) { $newvalues{$entry} .= ','.$newdom; } @@ -1060,7 +1194,9 @@ sub process_changes { $autocoowner = $domconf{'autoenroll'}{'co-owners'}; } } - unless ($autocoowner) { + if ($autocoowner) { + $newvalues{'co-owners'} = $values->{'internal.co-owners'}; + } else { my @keepcoowners = &Apache::loncommon::get_env_multiple('form.coowners'); my @pendingcoowners = &Apache::loncommon::get_env_multiple('form.pendingcoowners'); my @invitecoowners = &Apache::loncommon::get_env_multiple('form.invitecoowners'); @@ -1105,6 +1241,8 @@ sub process_changes { if ($pendingcoowners ne '') { @newpending = @pendingcoown; } + } else { + @newcoown = @currcoown; } $newvalues{'pendingco-owners'} = join(',',sort(@newpending)); $newvalues{'co-owners'} = join(',',sort(@newcoown)); @@ -1236,14 +1374,14 @@ sub process_changes { my ($classorder,$classtitles) = &discussion_vote_classes(); my $fontchange = 0; foreach my $class (@{$classorder}) { - my $ext_entry = $entry.'_'.$class; + my $ext_entry = $entry.'_'.$class; my $size = $env{'form.'.$ext_entry.'_size'}; my $unit = $env{'form.'.$ext_entry.'_unit'}; my $weight = $env{'form.'.$ext_entry.'_weight'}; my $style = $env{'form.'.$ext_entry.'_style'}; my $other = $env{'form.'.$ext_entry.'_other'}; $size =~ s/,//g; - $unit =~ s/,//g; + $unit =~ s/,//g; $weight =~ s/,//g; $style =~ s/,//g; $other =~ s/[^\w;:\s\-\%.]//g; @@ -1251,7 +1389,7 @@ sub process_changes { $newvalues{$ext_entry} = join(',',($size.$unit,$weight,$style,$other)); my $current = $values->{$ext_entry}; if ($values->{$ext_entry} eq '') { - $current = ',,,'; + $current = ',,,'; } if ($newvalues{$ext_entry} ne $current) { $changes->{$ext_entry} = $newvalues{$ext_entry}; @@ -1260,7 +1398,7 @@ sub process_changes { } if ($fontchange) { $changes->{$entry} = 1; - } + } } elsif ($entry eq 'nothideprivileged') { my @curr_nothide; my @new_nothide; @@ -1335,7 +1473,7 @@ sub process_changes { my $newtext = $maxnum-1; $newhdr[$env{'form.printfmthdr_pos_'.$newtext}] = $env{'form.printfmthdr_text_'.$newtext}; $newvalues{$entry} = join('',@newhdr); - } elsif (($entry eq 'languages') || + } elsif (($entry eq 'languages') || ($entry eq 'checkforpriv')) { my $settings; my $total = $env{'form.'.$entry.'_total'}; @@ -1351,7 +1489,7 @@ sub process_changes { } if ($env{'form.'.$entry.'_'.$total} ne '') { my $new = $env{'form.'.$entry.'_'.$total}; - if ($entry eq 'languages') { + if ($entry eq 'languages') { my %langchoices = &get_lang_choices(); if ($langchoices{$new}) { $settings .= $new; @@ -1404,10 +1542,81 @@ 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} = ''; + } + } elsif ($entry eq 'grading') { + if ($env{'form.'.$entry} eq 'standard') { + if ($env{'form.hidetotals'}) { + my %sections = &Apache::loncommon::get_sections($cdom,$cnum); + if (keys(%sections)) { + my @secs = &Apache::loncommon::get_env_multiple('form.hidetotals_sections'); + if (grep(/^all$/,@secs)) { + $newvalues{'hidetotals'} = 'all'; + } elsif (@secs) { + $newvalues{'hidetotals'} = ''; + foreach my $sec (sort {$a <=> $b} @secs) { + if (exists($sections{$sec})) { + $newvalues{'hidetotals'} .= $sec.',' + } + } + $newvalues{'hidetotals'} =~ s/,$//; + } + } else { + $newvalues{'hidetotals'} = 'all'; + } + } + } + if ($newvalues{'hidetotals'} ne $values->{'hidetotals'}) { + $changes->{'hidetotals'} = $newvalues{'hidetotals'}; + $changes->{'grading'} = $env{'form.'.$entry}; + } + $newvalues{$entry} = $env{'form.'.$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}; } @@ -1417,7 +1626,887 @@ sub process_changes { } } } - return; + return $errors; +} + +sub process_linkprot { + my ($cdom,$cnum,$values,$changes,$context,$lastactref) = @_; + my ($dest,$ltiauth,$privnum,$cipher,$errors,%linkprot); + if (ref($values) eq 'HASH') { + foreach my $id (keys(%{$values})) { + if ($id =~ /^\d+$/) { + unless (ref($values->{$id}) eq 'HASH') { + $linkprot{$id} = ''; + } + } + } + } + ($cipher,$privnum) = &get_credentials($cdom,$cnum,'lti',$context); + if ($context eq 'domain') { + $dest = '/adm/domainprefs'; + $ltiauth = 1; + } else { + $dest = '/adm/courseprefs'; + if (exists($env{'course.'.$env{'request.course.id'}.'.internal.ltiauth'})) { + $ltiauth = $env{'course.'.$env{'request.course.id'}.'.internal.ltiauth'}; + } else { + my %domdefs = &Apache::lonnet::get_domain_defaults($cdom); + $ltiauth = $domdefs{'crsltiauth'}; + } + } + my $switchserver = &check_switchserver($cdom,$cnum,$context,$dest); + my (@items,%deletions,%itemids,%haschanges); + if ($env{'form.linkprot_add'}) { + my $name = $env{'form.linkprot_name_add'}; + $name =~ s/(`)/'/g; + my ($newid,$error) = &get_linkprot_id($cdom,$cnum,$name,$context); + if ($newid) { + $itemids{'add'} = $newid; + push(@items,'add'); + $haschanges{$newid} = 1; + } else { + $errors .= ''. + &mt('Failed to acquire unique ID for link protection'). + ''; + } + } + 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++) { + my $itemid = $env{'form.linkprot_id_'.$i}; + $itemid =~ s/\D+//g; + if ($itemid) { + if (ref($values->{$itemid}) eq 'HASH') { + push(@items,$i); + $itemids{$i} = $itemid; + if ((@todelete > 0) && (grep(/^$i$/,@todelete))) { + $deletions{$itemid} = $values->{$itemid}->{'name'}; + } + } + } + } + } + foreach my $idx (@items) { + my $itemid = $itemids{$idx}; + next unless ($itemid); + if (exists($deletions{$itemid})) { + $linkprot{$itemid} = $deletions{$itemid}; + $haschanges{$itemid} = 1; + next; + } + my %current; + if (ref($values) eq 'HASH') { + if (ref($values->{$itemid}) eq 'HASH') { + foreach my $key (keys(%{$values->{$itemid}})) { + $current{$key} = $values->{$itemid}->{$key}; + } + } + } + foreach my $inner ('name','lifetime','version') { + my $formitem = 'form.linkprot_'.$inner.'_'.$idx; + $env{$formitem} =~ s/(`)/'/g; + if ($inner eq 'lifetime') { + $env{$formitem} =~ s/[^\d.]//g; + } + unless ($idx eq 'add') { + if ($current{$inner} ne $env{$formitem}) { + $haschanges{$itemid} = 1; + } + } + if ($env{$formitem} ne '') { + $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} = ''; + } + my $passback = 'form.linkprot_passback_'.$idx; + my $passbackparamname = 'form.linkprot_passbackformat_'.$idx; + if ($env{$passback} == 1) { + unless ($env{$passbackparamname} =~ /^1\.(0|1)$/) { + $env{$passbackparamname} = ''; + } + } elsif (exists($env{$passbackparamname})) { + $env{$passbackparamname} = ''; + } + unless ($idx eq 'add') { + if ((!$current{'returnurl'} && ($env{$urlparamname} ne '')) || + ($current{'returnurl'} && ($env{$urlparamname} eq ''))) { + $haschanges{$itemid} = 1; + } + if ((!$current{'passback'} && ($env{$passbackparamname} ne '')) || + ($current{'passback'} && ($env{$passbackparamname} eq ''))) { + $haschanges{$itemid} = 1; + } + } + if ($env{$urlparamname} ne '') { + $linkprot{$itemid}{'returnurl'} = $env{$urlparamname}; + } + if ($env{$passbackparamname} ne '') { + $linkprot{$itemid}{'passback'} = 1; + $linkprot{$itemid}{'passbackformat'} = $env{$passbackparamname}; + } + if ($ltiauth) { + my $reqitem = 'form.linkprot_requser_'.$idx; + $env{$reqitem} =~ s/(`)/'/g; + unless ($idx eq 'add') { + if ((!$current{'requser'} && $env{$reqitem}) || + ($current{'requser'} && !$env{$reqitem})) { + $haschanges{$itemid} = 1; + } + } + if ($env{$reqitem} == 1) { + $linkprot{$itemid}{'requser'} = $env{$reqitem}; + foreach my $inner ('mapuser','notstudent') { + my $formitem = 'form.linkprot_'.$inner.'_'.$idx; + $env{$formitem} =~ s/(`)/'/g; + if ($inner eq 'mapuser') { + if ($env{$formitem} eq 'other') { + my $mapuser = $env{'form.linkprot_customuser_'.$idx}; + $mapuser =~ s/(`)/'/g; + $mapuser =~ s/^\s+|\s+$//g; + if ($mapuser ne '') { + $linkprot{$itemid}{$inner} = $mapuser; + } else { + delete($linkprot{$itemid}{'requser'}); + last; + } + } elsif ($env{$formitem} eq 'sourcedid') { + $linkprot{$itemid}{$inner} = 'lis_person_sourcedid'; + } elsif ($env{$formitem} eq 'email') { + $linkprot{$itemid}{$inner} = 'lis_person_contact_email_primary'; + } + } else { + $linkprot{$itemid}{$inner} = $env{$formitem}; + } + unless ($idx eq 'add') { + if ($current{$inner} ne $linkprot{$itemid}{$inner}) { + $haschanges{$itemid} = 1; + } + } + } + } + } + unless ($switchserver) { + my $keyitem = 'form.linkprot_key_'.$idx; + $env{$keyitem} =~ s/(`)/'/g; + unless ($idx eq 'add') { + if ($current{'key'} ne $env{$keyitem}) { + $haschanges{$itemid} = 1; + } + } + if ($env{$keyitem} ne '') { + $linkprot{$itemid}{'key'} = $env{$keyitem}; + } + my $secretitem = 'form.linkprot_secret_'.$idx; + $env{$secretitem} =~ s/(`)/'/g; + if ($current{'usable'}) { + if ($env{'form.linkprot_changesecret_'.$idx}) { + if ($env{$secretitem} ne '') { + if ($privnum && $cipher) { + $linkprot{$itemid}{'secret'} = $cipher->encrypt_hex($env{$secretitem}); + $linkprot{$itemid}{'cipher'} = $privnum; + } else { + $linkprot{$itemid}{'secret'} = $env{$secretitem}; + } + $haschanges{$itemid} = 1; + } + } else { + $linkprot{$itemid}{'secret'} = $current{'secret'}; + $linkprot{$itemid}{'cipher'} = $current{'cipher'}; + } + } elsif ($env{$secretitem} ne '') { + if ($privnum && $cipher) { + $linkprot{$itemid}{'secret'} = $cipher->encrypt_hex($env{$secretitem}); + $linkprot{$itemid}{'cipher'} = $privnum; + } else { + $linkprot{$itemid}{'secret'} = $env{$secretitem}; + } + $haschanges{$itemid} = 1; + } + } + } + if (keys(%haschanges)) { + foreach my $entry (keys(%haschanges)) { + $changes->{$entry} = $linkprot{$entry}; + } + if (ref($lastactref) eq 'HASH') { + $lastactref->{'courselti'} = 1; + } + } + return $errors; +} + +sub get_linkprot_id { + my ($cdom,$cnum,$name,$context) = @_; + # get lock on lti db in course or linkprot db in domain + my $lockhash = { + lock => $env{'user.name'}. + ':'.$env{'user.domain'}, + }; + my $tries = 0; + my $gotlock; + if ($context eq 'domain') { + $gotlock = &Apache::lonnet::newput_dom('linkprot',$lockhash,$cdom); + } else { + $gotlock = &Apache::lonnet::newput('lti',$lockhash,$cdom,$cnum); + } + my ($id,$error); + while (($gotlock ne 'ok') && ($tries<10)) { + $tries ++; + sleep (0.1); + if ($context eq 'domain') { + $gotlock = &Apache::lonnet::newput_dom('linkprot',$lockhash,$cdom); + } else { + $gotlock = &Apache::lonnet::newput('lti',$lockhash,$cdom,$cnum); + } + } + if ($gotlock eq 'ok') { + my %currids; + if ($context eq 'domain') { + %currids = &Apache::lonnet::dump_dom('linkprot',$cdom); + } else { + %currids = &Apache::lonnet::dump('lti',$cdom,$cnum,undef,undef,undef,1); + } + 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; + } + } else { + $id = 1; + } + if ($id) { + if ($context eq 'domain') { + unless (&Apache::lonnet::newput_dom('linkprot',{ $id => $name },$cdom) eq 'ok') { + $error = 'nostore'; + } + } else { + unless (&Apache::lonnet::newput('lti',{ $id => $name },$cdom,$cnum) eq 'ok') { + $error = 'nostore'; + } + } + } else { + $error = 'nonumber'; + } + } + my $dellockoutcome; + if ($context eq 'domain') { + $dellockoutcome = &Apache::lonnet::del_dom('linkprot',['lock'],$cdom); + } else { + $dellockoutcome = &Apache::lonnet::del('lti',['lock'],$cdom,$cnum); + } + } else { + $error = 'nolock'; + } + return ($id,$error); +} + +sub get_credentials { + my ($cdom,$cnum,$type,$context) = @_; + my ($cipher,$privnum,$home); + my %domdefs = &Apache::lonnet::get_domain_defaults($cdom); + my @ids=&Apache::lonnet::current_machine_ids(); + if ($context eq 'domain') { + $home = &Apache::lonnet::domain($cdom,'primary'); + } else { + $home = &Apache::lonnet::homeserver($cnum,$cdom); + } + my ($hostskey,$domkey,$crskey); + if ($type eq 'ltitools') { + $hostskey = 'toolprivhosts'; + $domkey = 'toolenc_dom'; + $crskey = 'toolenc_crs'; + } else { + $hostskey = 'ltiprivhosts'; + $domkey = 'linkprotenc_dom'; + $crskey = 'linkprotenc_crs'; + } + if ((($context eq 'domain') && ($domdefs{$domkey})) || + (($context eq 'course') && ($domdefs{$crskey}))) { + unless (($home eq 'no_host') || ($home eq '')) { + if (grep(/^\Q$home\E$/,@ids)) { + if (ref($domdefs{$hostskey}) eq 'ARRAY') { + if (grep(/^\Q$home\E$/,@{$domdefs{$hostskey}})) { + my %privhash = &Apache::lonnet::restore_dom($type,'private',$cdom,$home,1); + my $privkey = $privhash{'key'}; + $privnum = $privhash{'version'}; + if (($privnum) && ($privkey ne '')) { + $cipher = Crypt::CBC->new({'key' => $privkey, + 'cipher' => 'DES'}); + } + } + } + } + } + } + return ($cipher,$privnum); +} + +sub process_ltitools { + my ($r,$cdom,$cnum,$values,$changes,$context,$lastactref,$configuserok,$lonhost, + $author_ok,$confname) = @_; + my (%currconfig,$newid,@allpos,%changes,%ltitools,$errors); + + my (%posslti,%possfield); + my @courseroles = ('cc','in','ta','ep','st'); + my @ltiroles = qw(Instructor ContentDeveloper TeachingAssistant Learner); + map { $posslti{$_} = 1; } @ltiroles; + my @allfields = ('fullname','firstname','lastname','email','user','roles'); + map { $possfield{$_} = 1; } @allfields; + + my ($dest,$privnum,$cipher); + + ($cipher,$privnum) = &get_credentials($cdom,$cnum,'ltitools',$context); + if ($context eq 'domain') { + $dest = '/adm/domainprefs'; + } else { + $dest = '/adm/courseprefs'; + } + my $switchserver = &check_switchserver($cdom,$cnum,$context,$dest); + + my (@allpos,@items,%deletions,%itemids,%haschanges); + if ($env{'form.ltitools_add'}) { + my $title = $env{'form.ltitools_add_title'}; + $title =~ s/(`)/'/g; + my ($newid,$error) = &Apache::lonnet::get_ltitools_id($context,$cdom,$cnum,$title); + if ($newid) { + my $position = $env{'form.ltitools_add_pos'}; + $position =~ s/\D+//g; + if ($position ne '') { + $allpos[$position] = $newid; + } + $haschanges{$newid} = 1; + foreach my $item ('title','url','lifetime') { + $env{'form.ltitools_add_'.$item} =~ s/(`)/'/g; + if ($item eq 'lifetime') { + $env{'form.ltitools_add_'.$item} =~ s/[^\d.]//g; + } + if ($env{'form.ltitools_add_'.$item}) { + $ltitools{$newid}{$item} = $env{'form.ltitools_add_'.$item}; + } + } + if ($env{'form.ltitools_add_version'} eq 'LTI-1p0') { + $ltitools{$newid}{'version'} = $env{'form.ltitools_add_version'}; + } + if ($env{'form.ltitools_add_msgtype'} eq 'basic-lti-launch-request') { + $ltitools{$newid}{'msgtype'} = $env{'form.ltitools_add_msgtype'}; + } + if ($env{'form.ltitools_add_sigmethod'} eq 'HMAC-SHA256') { + $ltitools{$newid}{'sigmethod'} = $env{'form.ltitools_add_sigmethod'}; + } else { + $ltitools{$newid}{'sigmethod'} = 'HMAC-SHA1'; + } + foreach my $item ('width','height','linktext','explanation') { + $env{'form.ltitools_add_'.$item} =~ s/^\s+//; + $env{'form.ltitools_add_'.$item} =~ s/\s+$//; + if (($item eq 'width') || ($item eq 'height')) { + if ($env{'form.ltitools_add_'.$item} =~ /^\d+$/) { + $ltitools{$newid}{'display'}{$item} = $env{'form.ltitools_add_'.$item}; + } + } else { + if ($env{'form.ltitools_add_'.$item} ne '') { + $ltitools{$newid}{'display'}{$item} = $env{'form.ltitools_add_'.$item}; + } + } + } + if ($env{'form.ltitools_add_target'} eq 'window') { + $ltitools{$newid}{'display'}{'target'} = $env{'form.ltitools_add_target'}; + } elsif ($env{'form.ltitools_add_target'} eq 'tab') { + $ltitools{$newid}{'display'}{'target'} = $env{'form.ltitools_add_target'}; + } else { + $ltitools{$newid}{'display'}{'target'} = 'iframe'; + } + foreach my $item ('passback','roster') { + if ($env{'form.ltitools_'.$item.'_add'}) { + $ltitools{$newid}{$item} = 1; + if ($env{'form.ltitools_'.$item.'valid_add'} ne '') { + my $lifetime = $env{'form.ltitools_'.$item.'valid_add'}; + $lifetime =~ s/^\s+|\s+$//g; + if ($lifetime =~ /^\d+\.?\d*$/) { + $ltitools{$newid}{$item.'valid'} = $lifetime; + } + } + } + } + if ($env{'form.ltitools_add_image.filename'} ne '') { + my ($imageurl,$error) = + &process_ltitools_image($r,$context,$cdom,$cnum,$confname,'ltitools_add_image', + $newid,$configuserok,$lonhost,$author_ok); + if ($imageurl) { + $ltitools{$newid}{'image'} = $imageurl; + } + if ($error) { + &Apache::lonnet::logthis($error); + $errors .= '
  • '.$error.'
  • '; + } + } + my @fields = &Apache::loncommon::get_env_multiple('form.ltitools_add_fields'); + foreach my $field (@fields) { + if ($possfield{$field}) { + if ($field eq 'roles') { + foreach my $role (@courseroles) { + my $choice = $env{'form.ltitools_add_roles_'.$role}; + if (($choice ne '') && ($posslti{$choice})) { + $ltitools{$newid}{'roles'}{$role} = $choice; + if ($role eq 'cc') { + $ltitools{$newid}{'roles'}{'co'} = $choice; + } + } + } + } else { + $ltitools{$newid}{'fields'}{$field} = 1; + } + } + } + if (ref($ltitools{$newid}{'fields'}) eq 'HASH') { + if ($ltitools{$newid}{'fields'}{'user'}) { + if ($env{'form.ltitools_add_userincdom'}) { + $ltitools{$newid}{'incdom'} = 1; + } + } + } + my @courseconfig = &Apache::loncommon::get_env_multiple('form.ltitools_add_courseconfig'); + foreach my $item (@courseconfig) { + $ltitools{$newid}{'crsconf'}{$item} = 1; + } + if ($env{'form.ltitools_add_custom'}) { + my $name = $env{'form.ltitools_add_custom_name'}; + my $value = $env{'form.ltitools_add_custom_value'}; + $value =~ s/(`)/'/g; + $name =~ s/(`)/'/g; + $ltitools{$newid}{'custom'}{$name} = $value; + } + unless ($switchserver) { + my $keyitem = 'form.ltitools_add_key'; + $env{$keyitem} =~ s/(`)/'/g; + if ($env{$keyitem} ne '') { + $ltitools{$newid}{'key'} = $env{$keyitem}; + } + my $secretitem = 'form.ltitools_secret_add'; + $env{$secretitem} =~ s/(`)/'/g; + if ($env{$secretitem} ne '') { + if ($privnum && $cipher) { + $ltitools{$newid}{'secret'} = $cipher->encrypt_hex($env{$secretitem}); + $ltitools{$newid}{'cipher'} = $privnum; + } else { + $ltitools{$newid}{'secret'} = $env{$secretitem}; + } + } + } + } else { + $errors .= '
  • '. + &mt('Failed to acquire unique ID for new external tool'). + '
  • '; + } + } + if (ref($values) eq 'HASH') { + my %deletions; + my @todelete = &Apache::loncommon::get_env_multiple('form.ltitools_del'); + if (@todelete) { + map { $deletions{$_} = 1; } @todelete; + } + my %customadds; + my @newcustom = &Apache::loncommon::get_env_multiple('form.ltitools_customadd'); + if (@newcustom) { + map { $customadds{$_} = 1; } @newcustom; + } + my %imgdeletions; + my @todeleteimages = &Apache::loncommon::get_env_multiple('form.ltitools_image_del'); + if (@todeleteimages) { + map { $imgdeletions{$_} = 1; } @todeleteimages; + } + my $maxnum = $env{'form.ltitools_maxnum'}; + for (my $i=0; $i<=$maxnum; $i++) { + my $itemid = $env{'form.ltitools_id_'.$i}; + $itemid =~ s/\D+//g; + if (ref($values->{$itemid}) eq 'HASH') { + if ($deletions{$itemid}) { + if ($values->{$itemid}{'image'}) { + #FIXME need to obsolete item in RES space + } + $haschanges{$itemid} = $values->{$itemid}{'title'}; + next; + } else { + my $newpos = $env{'form.ltitools_'.$itemid}; + $newpos =~ s/\D+//g; + foreach my $item ('title','url','lifetime') { + $ltitools{$itemid}{$item} = $env{'form.ltitools_'.$item.'_'.$i}; + if ($values->{$itemid}{$item} ne $ltitools{$itemid}{$item}) { + $haschanges{$itemid} = 1; + } + } + if ($env{'form.ltitools_version_'.$i} eq 'LTI-1p0') { + $ltitools{$itemid}{'version'} = $env{'form.ltitools_version_'.$i}; + } + if ($env{'form.ltitools_msgtype_'.$i} eq 'basic-lti-launch-request') { + $ltitools{$itemid}{'msgtype'} = $env{'form.ltitools_msgtype_'.$i}; + } + if ($env{'form.ltitools_sigmethod_'.$i} eq 'HMAC-SHA256') { + $ltitools{$itemid}{'sigmethod'} = $env{'form.ltitools_sigmethod_'.$i}; + } else { + $ltitools{$itemid}{'sigmethod'} = 'HMAC-SHA1'; + } + if ($values->{$itemid}{'sigmethod'} eq '') { + if ($ltitools{$itemid}{'sigmethod'} ne 'HMAC-SHA1') { + $haschanges{$itemid} = 1; + } + } elsif ($values->{$itemid}{'sigmethod'} ne $ltitools{$itemid}{'sigmethod'}) { + $haschanges{$itemid} = 1; + } + foreach my $size ('width','height') { + $env{'form.ltitools_'.$size.'_'.$i} =~ s/^\s+//; + $env{'form.ltitools_'.$size.'_'.$i} =~ s/\s+$//; + if ($env{'form.ltitools_'.$size.'_'.$i} =~ /^\d+$/) { + $ltitools{$itemid}{'display'}{$size} = $env{'form.ltitools_'.$size.'_'.$i}; + if (ref($values->{$itemid}{'display'}) eq 'HASH') { + if ($values->{$itemid}{'display'}{$size} ne $ltitools{$itemid}{'display'}{$size}) { + $haschanges{$itemid} = 1; + } + } else { + $haschanges{$itemid} = 1; + } + } elsif (ref($values->{$itemid}{'display'}) eq 'HASH') { + if ($values->{$itemid}{'display'}{$size} ne '') { + $haschanges{$itemid} = 1; + } + } + } + foreach my $item ('linktext','explanation') { + $env{'form.ltitools_'.$item.'_'.$i} =~ s/^\s+//; + $env{'form.ltitools_'.$item.'_'.$i} =~ s/\s+$//; + if ($env{'form.ltitools_'.$item.'_'.$i} ne '') { + $ltitools{$itemid}{'display'}{$item} = $env{'form.ltitools_'.$item.'_'.$i}; + if (ref($values->{$itemid}{'display'}) eq 'HASH') { + if ($values->{$itemid}{'display'}{$item} ne $ltitools{$itemid}{'display'}{$item}) { + $haschanges{$itemid} = 1; + } + } else { + $haschanges{$itemid} = 1; + } + } elsif (ref($values->{$itemid}{'display'}) eq 'HASH') { + if ($values->{$itemid}{'display'}{$item} ne '') { + $haschanges{$itemid} = 1; + } + } + } + if ($env{'form.ltitools_target_'.$i} eq 'window') { + $ltitools{$itemid}{'display'}{'target'} = $env{'form.ltitools_target_'.$i}; + } elsif ($env{'form.ltitools_target_'.$i} eq 'tab') { + $ltitools{$itemid}{'display'}{'target'} = $env{'form.ltitools_target_'.$i}; + } else { + $ltitools{$itemid}{'display'}{'target'} = 'iframe'; + } + if (ref($values->{$itemid}{'display'}) eq 'HASH') { + if ($values->{$itemid}{'display'}{'target'} ne $ltitools{$itemid}{'display'}{'target'}) { + $haschanges{$itemid} = 1; + } + } else { + $haschanges{$itemid} = 1; + } + foreach my $extra ('passback','roster') { + if ($env{'form.ltitools_'.$extra.'_'.$i}) { + $ltitools{$itemid}{$extra} = 1; + if ($env{'form.ltitools_'.$extra.'valid_'.$i} ne '') { + my $lifetime = $env{'form.ltitools_'.$extra.'valid_'.$i}; + $lifetime =~ s/^\s+|\s+$//g; + if ($lifetime =~ /^\d+\.?\d*$/) { + $ltitools{$itemid}{$extra.'valid'} = $lifetime; + } + } + } + if ($values->{$itemid}{$extra} ne $ltitools{$itemid}{$extra}) { + $haschanges{$itemid} = 1; + } + if ($values->{$itemid}{$extra.'valid'} ne $ltitools{$itemid}{$extra.'valid'}) { + $haschanges{$itemid} = 1; + } + } + my @courseconfig = &Apache::loncommon::get_env_multiple('form.ltitools_courseconfig_'.$i); + foreach my $item ('label','title','target','linktext','explanation','append') { + if (grep(/^\Q$item\E$/,@courseconfig)) { + $ltitools{$itemid}{'crsconf'}{$item} = 1; + if (ref($values->{$itemid}{'crsconf'}) eq 'HASH') { + if ($values->{$itemid}{'crsconf'}{$item} ne $ltitools{$itemid}{'crsconf'}{$item}) { + $haschanges{$itemid} = 1; + } + } else { + $haschanges{$itemid} = 1; + } + } + } + my @fields = &Apache::loncommon::get_env_multiple('form.ltitools_fields_'.$i); + foreach my $field (@fields) { + if ($possfield{$field}) { + if ($field eq 'roles') { + foreach my $role (@courseroles) { + my $choice = $env{'form.ltitools_roles_'.$role.'_'.$i}; + if (($choice ne '') && ($posslti{$choice})) { + $ltitools{$itemid}{'roles'}{$role} = $choice; + if ($role eq 'cc') { + $ltitools{$itemid}{'roles'}{'co'} = $choice; + } + } + if (ref($values->{$itemid}{'roles'}) eq 'HASH') { + if ($values->{$itemid}{'roles'}{$role} ne $ltitools{$itemid}{'roles'}{$role}) { + $haschanges{$itemid} = 1; + } + } elsif ($ltitools{$itemid}{'roles'}{$role}) { + $haschanges{$itemid} = 1; + } + } + } else { + $ltitools{$itemid}{'fields'}{$field} = 1; + if (ref($values->{$itemid}{'fields'}) eq 'HASH') { + if ($values->{$itemid}{'fields'}{$field} ne $ltitools{$itemid}{'fields'}{$field}) { + $haschanges{$itemid} = 1; + } + } else { + $haschanges{$itemid} = 1; + } + } + } + } + if (ref($ltitools{$itemid}{'fields'}) eq 'HASH') { + if ($ltitools{$itemid}{'fields'}{'user'}) { + if ($env{'form.ltitools_userincdom_'.$i}) { + $ltitools{$itemid}{'incdom'} = 1; + } + if ($values->{$itemid}{'incdom'} ne $ltitools{$itemid}{'incdom'}) { + $haschanges{$itemid} = 1; + } + } + } + $allpos[$newpos] = $itemid; + } + if ($imgdeletions{$itemid}) { + $haschanges{$itemid} = 1; + if ($context eq 'course') { + my $currimgurl = $values->{$itemid}{'image'}; + if ($currimgurl =~ m{^(\Q/uploaded/$cdom/$cnum/toollogo/$itemid\E)/([^/]+)$}) { + my ($path,$imagefile) = ($1,$2); + if ($imagefile =~ /^tn\-(.+)$/) { + my $origimg = $1; + &Apache::lonnet::removeuploadedurl("$path/$origimg"); + } + &Apache::lonnet::removeuploadedurl($currimgurl); + } + } + #FIXME need to obsolete item in RES space + } elsif ($env{'form.ltitools_image_'.$i.'.filename'}) { + my $currimgurl = $values->{$itemid}{'image'}; + my ($imgurl,$error) = &process_ltitools_image($r,$context,$cdom,$cnum,$confname,'ltitools_image_'.$i, + $itemid,$configuserok,$lonhost,$author_ok,$currimgurl); + if ($imgurl) { + $ltitools{$itemid}{'image'} = $imgurl; + $haschanges{$itemid} = 1; + } + if ($error) { + &Apache::lonnet::logthis($error); + $errors .= '
  • '.$error.'
  • '; + } + } elsif ($values->{$itemid}{'image'}) { + $ltitools{$itemid}{'image'} = $values->{$itemid}{'image'}; + } + if ($customadds{$i}) { + my $name = $env{'form.ltitools_custom_name_'.$i}; + $name =~ s/(`)/'/g; + $name =~ s/^\s+//; + $name =~ s/\s+$//; + my $value = $env{'form.ltitools_custom_value_'.$i}; + $value =~ s/(`)/'/g; + $value =~ s/^\s+//; + $value =~ s/\s+$//; + if ($name ne '') { + $ltitools{$itemid}{'custom'}{$name} = $value; + $haschanges{$itemid} = 1; + } + } + my %customdels; + my @customdeletions = &Apache::loncommon::get_env_multiple('form.ltitools_customdel_'.$i); + if (@customdeletions) { + $haschanges{$itemid} = 1; + } + map { $customdels{$_} = 1; } @customdeletions; + if (ref($values->{$itemid}{'custom'}) eq 'HASH') { + foreach my $key (keys(%{$values->{$itemid}{'custom'}})) { + unless ($customdels{$key}) { + if ($env{'form.ltitools_customval_'.$key.'_'.$i} ne '') { + $ltitools{$itemid}{'custom'}{$key} = $env{'form.ltitools_customval_'.$key.'_'.$i}; + } + if ($values->{$itemid}{'custom'}{$key} ne $env{'form.ltitools_customval_'.$key.'_'.$i}) { + $haschanges{$itemid} = 1; + } + } + } + } + unless ($switchserver) { + my $keyitem = 'form.ltitools_key_'.$i; + $env{$keyitem} =~ s/(`)/'/g; + if ($values->{$itemid}{'key'} ne $env{$keyitem}) { + $haschanges{$itemid} = 1; + } + if ($env{$keyitem} ne '') { + $ltitools{$itemid}{'key'} = $env{$keyitem}; + } + my $secretitem = 'form.ltitools_secret_'.$i; + $env{$secretitem} =~ s/(`)/'/g; + if ($values->{$itemid}{'usable'}) { + if ($env{'form.ltitools_changesecret_'.$i}) { + if ($env{$secretitem} ne '') { + if ($privnum && $cipher) { + $ltitools{$itemid}{'secret'} = $cipher->encrypt_hex($env{$secretitem}); + $ltitools{$itemid}{'cipher'} = $privnum; + } else { + $ltitools{$itemid}{'secret'} = $env{$secretitem}; + } + $haschanges{$itemid} = 1; + } + } else { + $ltitools{$itemid}{'secret'} = $values->{$itemid}{'secret'}; + $ltitools{$itemid}{'cipher'} = $values->{$itemid}{'cipher'}; + } + } elsif ($env{$secretitem} ne '') { + if ($privnum && $cipher) { + $ltitools{$itemid}{'secret'} = $cipher->encrypt_hex($env{$secretitem}); + $ltitools{$itemid}{'cipher'} = $privnum; + } else { + $ltitools{$itemid}{'secret'} = $env{$secretitem}; + } + $haschanges{$itemid} = 1; + } + } + unless ($haschanges{$itemid}) { + foreach my $key (keys(%{$values->{$itemid}})) { + if (ref($values->{$itemid}{$key}) eq 'HASH') { + if (ref($ltitools{$itemid}{$key}) eq 'HASH') { + foreach my $innerkey (keys(%{$values->{$itemid}{$key}})) { + unless (exists($ltitools{$itemid}{$key}{$innerkey})) { + $haschanges{$itemid} = 1; + last; + } + } + } elsif (keys(%{$values->{$itemid}{$key}}) > 0) { + $haschanges{$itemid} = 1; + } + } + last if ($haschanges{$itemid}); + } + } + } + } + } + if (@allpos > 0) { + my $idx = 0; + foreach my $itemid (@allpos) { + if ($itemid ne '') { + $ltitools{$itemid}{'order'} = $idx; + if (ref($values) eq 'HASH') { + if (ref($values->{$itemid}) eq 'HASH') { + if ($values->{$itemid}{'order'} ne $idx) { + $haschanges{$itemid} = 1; + } + } + } + $idx ++; + } + } + } + if (keys(%haschanges)) { + foreach my $entry (keys(%haschanges)) { + $changes->{$entry} = $ltitools{$entry}; + } + if (ref($lastactref) eq 'HASH') { + $lastactref->{'courseltitools'} = 1; + } + } + return $errors; +} + +sub process_ltitools_image { + my ($r,$context,$dom,$cnum,$confname,$caller,$itemid,$configuserok,$switch,$author_ok,$currimg) = @_; + my $filename = $env{'form.'.$caller.'.filename'}; + my ($error,$url); + my ($width,$height) = (21,21); + if ($configuserok eq 'ok') { + if ($switch) { + $error = &mt('Upload of Tool Provider (LTI) icon is not permitted to this server: [_1]', + $switch); + } elsif ($author_ok eq 'ok') { + my ($result,$imageurl,$madethumb); + if ($context eq 'domain') { + ($result,$imageurl,$madethumb) = + &Apache::lonconfigsettings::publishlogo($r,'upload',$caller,$dom,$confname, + "ltitools/$itemid/icon",$width,$height); + } else { + ($result,$imageurl,$madethumb) = &processlogo($dom,$cnum,$caller,$currimg,$itemid,$width,$height); + } + if ($result eq 'ok') { + if ($madethumb) { + my ($path,$imagefile) = ($imageurl =~ m{^(.+)/([^/]+)$}); + my $imagethumb = "$path/tn-".$imagefile; + $url = $imagethumb; + } else { + $url = $imageurl; + } + } else { + if ($context eq 'domain') { + $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 error occurred. 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 processlogo { + my ($dom,$cnum,$caller,$currimg,$itemid,$width,$height) = @_; + my ($result,$imageurl,$madethumb); + if ($env{"form.$caller.filename"} ne '') { + unless ($caller eq 'ltitools_add_image') { + if ($currimg =~ m{^(\Q/uploaded/$dom/$cnum/toollogo/$itemid\E)/([^/]+)$}) { + my ($path,$imagefile) = ($1,$2); + if ($imagefile =~ /^tn\-(.+)$/) { + my $origimg = $1; + &Apache::lonnet::removeuploadedurl("$path/$origimg"); + } + &Apache::lonnet::removeuploadedurl($currimg); + } + } + $imageurl = &Apache::lonnet::userfileupload($caller,'toollogo',"toollogo/$itemid", + '','','',$cnum,$dom,$width,$height); + if ($imageurl =~ m{^(\Q/uploaded/$dom/$cnum/toollogo/$itemid\E)/([^/]+)$}) { + my ($path,$imagefile) = ($1,$2); + $result = 'ok'; + my $thumburl = "$path/tn-".$imagefile; + my ($rtncode,$info); + my $res = &Apache::lonnet::getuploaded('HEAD',$thumburl,$dom,$cnum,\$info,\$rtncode); + if ($res eq 'ok') { + $madethumb = 1; + } + } elsif ($imageurl eq '/adm/notfound.html') { + undef($imageurl); + $result = 'store failed'; + } elsif ($imageurl =~ /^error: (.+)$/) { + $result = $1; + } + } + return ($result,$imageurl,$madethumb); } sub get_sec_str { @@ -1462,8 +2551,15 @@ sub check_clone { sub store_changes { my ($cdom,$cnum,$prefs_order,$actions,$prefs,$values,$changes,$crstype) = @_; my ($chome,$output); - my (%storehash,@delkeys,@need_env_update,@oldcloner); + my (%storehash,@delkeys,@need_env_update,@oldcloner,%oldlinkprot,%oldltitools); if ((ref($values) eq 'HASH') && (ref($changes) eq 'HASH')) { + if (ref($values->{'linkprot'}) eq 'HASH') { + %oldlinkprot = %{$values->{'linkprot'}}; + } + delete($values->{'linkprot'}); + if (ref($values->{'ltitools'}) eq 'HASH') { + %oldltitools = %{$values->{'ltitools'}}; + } %storehash = %{$values}; } else { if ($crstype eq 'Community') { @@ -1473,6 +2569,22 @@ sub store_changes { } return $output; } + my ($numchanges,$skipstore); + if (ref($changes) eq 'HASH') { + $numchanges = scalar(keys(%{$changes})); + if (($numchanges == 1) && (exists($changes->{'linkprot'}))) { + $skipstore = 1; + } elsif (($numchanges == 1) && (exists($changes->{'ltitools'}))) { + $skipstore = 1; + } elsif (!$numchanges) { + if ($crstype eq 'Community') { + $output = &mt('No changes made to community settings.'); + } else { + $output = &mt('No changes made to course settings.'); + } + return $output; + } + } my %yesno = ( hidefromcat => '1', problem_stream_switch => '1', @@ -1498,6 +2610,10 @@ sub store_changes { "'$storehash{$key}'")).''; } } + } elsif ($item eq 'linkprot') { + $output .= &store_linkprot($cdom,$cnum,'course',$changes->{$item},\%oldlinkprot); + } elsif ($item eq 'ltitools') { + $output .= &store_ltitools($cdom,$cnum,'course',$changes->{$item},\%oldltitools); } else { if (ref($prefs->{$item}->{'ordered'}) eq 'ARRAY') { my @settings = @{$prefs->{$item}->{'ordered'}}; @@ -1529,14 +2645,88 @@ 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 'grading') && ($key eq 'grading')) { + if ($displayval eq 'standard') { + my $hidetotals; + if (exists($changes->{$item}{'hidetotals'})) { + if ($changes->{$item}{'hidetotals'} eq '') { + if (exists($values->{'hidetotals'})) { + push(@delkeys,'hidetotals'); + } + } else { + $hidetotals = $changes->{$item}{'hidetotals'}; + } + } elsif (exists($values->{'hidetotals'})) { + $hidetotals = $values->{'hidetotals'}; + } + if ($hidetotals eq '') { + $displayval = &mt('standard with "hide course totals" set to "No"'); + if (exists($values->{'hidetotals'})) { + push(@delkeys,'hidetotals'); + } + } elsif ($hidetotals =~ /^([\w,]+)$/) { + my $secstr = $1; + my @secs = split(/,/,$secstr); + if (grep(/^all$/,@secs)) { + $displayval = &mt('standard with "hide course totals" set to "Yes" for all users'); + $hidetotals = 'all'; + } else { + $displayval = &mt('standard with "hide course totals" set to "Yes" for users in section(s): [_1]',join(', ',@secs)); + } + $storehash{'hidetotals'} = $hidetotals; + } + } elsif (exists($values->{'hidetotals'})) { + push(@delkeys,'hidetotals'); + } + } if ($item eq 'feedback') { if ($key =~ /^(question|policy|comment)(\.email)\.text$/) { $text = $prefs->{$item}->{'itemtext'}{$1.$2}; @@ -1652,12 +2842,44 @@ sub store_changes { $displayval = &Apache::lonlocal::locallocaltime($displayval); } elsif ($key eq 'categories') { $displayval = $env{'form.categories_display'}; - } elsif (($key eq 'canuse_pdfforms') || ($key eq 'usejsme') || ($key eq 'uselcmath')) { + } elsif (($key eq 'canuse_pdfforms') || ($key eq 'usejsme') || + ($key eq 'uselcmath') || ($key eq 'inline_chem')) { if ($changes->{$item}{$key} eq '1') { $displayval = &mt('Yes'); } 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') { @@ -1729,9 +2951,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)').': '. @@ -1785,6 +3009,9 @@ sub store_changes { } } } + if ($skipstore) { + return $output; + } if (&Apache::lonnet::put('environment',\%storehash,$cdom,$cnum) eq 'ok') { if (ref($changes) eq 'HASH') { if (ref($changes->{'courseinfo'}) eq 'HASH') { @@ -1827,6 +3054,384 @@ sub store_changes { return $output; } +sub store_linkprot { + my ($cdom,$cnum,$context,$changes,$oldlinkprot) = @_; + my ($ltiauth,$home,$lti_save_error,$output,$error,%ltienc,@deletions); + if ($context eq 'domain') { + $ltiauth = 1; + $home = &Apache::lonnet::domain($cdom,'primary'); + } else { + $home = &Apache::lonnet::homeserver($cnum,$cdom); + if (exists($env{'course.'.$env{'request.course.id'}.'.internal.ltiauth'})) { + $ltiauth = $env{'course.'.$env{'request.course.id'}.'.internal.ltiauth'}; + } else { + my %domdefs = &Apache::lonnet::get_domain_defaults($cdom); + $ltiauth = $domdefs{'crsltiauth'}; + } + } + if (ref($changes) eq 'HASH') { + foreach my $id (sort { $a <=> $b } keys(%{$changes})) { + if (ref($changes->{$id}) eq 'HASH') { + if (exists($changes->{$id}->{'key'})) { + $ltienc{$id}{'key'} = $changes->{$id}->{'key'}; + delete($changes->{$id}->{'key'}); + } + if (exists($changes->{$id}->{'secret'})) { + $ltienc{$id}{'secret'} = $changes->{$id}->{'secret'}; + delete($changes->{$id}->{'secret'}); + } elsif (ref($oldlinkprot->{$id}) eq 'HASH') { + if (exists($oldlinkprot->{$id}{'usable'})) { + $changes->{$id}->{'usable'} = 1; + } + if (exists($oldlinkprot->{$id}{'cipher'})) { + $changes->{$id}->{'cipher'} = $oldlinkprot->{$id}{'cipher'}; + } + } + } + } + } + my @ids=&Apache::lonnet::current_machine_ids(); + if (keys(%ltienc) > 0) { + if ($context eq 'domain') { + foreach my $id (keys(%ltienc)) { + if (exists($ltienc{$id}{'secret'})) { + $changes->{$id}->{'usable'} = 1; + } + } + } else { + unless (($home eq 'no_host') || ($home eq '')) { + my $allowed; + foreach my $id (@ids) { if ($id eq $home) { $allowed=1; } } + if ($allowed) { + if (&Apache::lonnet::put('nohist_ltienc',\%ltienc,$cdom,$cnum,1) eq 'ok') { + foreach my $id (keys(%ltienc)) { + if (exists($ltienc{$id}{'secret'})) { + $changes->{$id}->{'usable'} = 1; + } + } + } else { + $lti_save_error = 1; + } + } + } + } + } + unless ($lti_save_error) { + if ($context eq 'course') { + if (&Apache::lonnet::put('lti',$changes,$cdom,$cnum,1) eq 'ok') { + my $hashid=$cdom.'_'.$cnum; + &Apache::lonnet::devalidate_cache_new('courselti',$hashid); + unless (($home eq 'no_host') || ($home eq '')) { + if (grep(/^\Q$home\E$/,@ids)) { + &Apache::lonnet::devalidate_cache_new('courseltienc',$hashid); + } + } + } else { + $lti_save_error = 1; + } + } + unless ($lti_save_error) { + foreach my $id (sort { $a <=> $b } %{$changes}) { + if (ref($changes->{$id}) eq 'HASH') { + my %values = %{$changes->{$id}}; + my %desc = &linkprot_names(); + my $display; + foreach my $title ('name','lifetime','version','key','secret','returnurl','passbackformat') { + if (($title eq 'key') || ($title eq 'secret')) { + if (ref($ltienc{$id}) eq 'HASH') { + if (exists($ltienc{$id}{$title})) { + if ($title eq 'secret') { + my $length = length($ltienc{$id}{$title}); + $display .= $desc{$title}.': ['.&mt('not shown').'], '; + } else { + $display .= $desc{$title}.': '.$ltienc{$id}{$title}.', '; + } + } + } + } elsif ($title eq 'version') { + 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}.', '; + } + } elsif ($title eq 'passbackformat') { + if ($values{$title} eq '1.0') { + $display .= &mt('Can return grades to Launcher with Outcomes Service 1.0 format').', '; + } elsif ($values{$title} eq '1.1') { + $display .= &mt('Can return grades to Launcher with Outcomes Service 1.1 format').', '; + } + } else { + $display .= $desc{$title}.': '.$values{$title}.', '; + } + } + if ($ltiauth) { + if (($values{'requser'}) && ($values{'mapuser'} ne '')) { + if ($values{'mapuser'} eq 'lis_person_contact_email_primary') { + $display .= &mt('Source of username: Email address [_1]', + '(lis_person_contact_email_primary)').', '; + } elsif ($values{'mapuser'} eq 'lis_person_sourcedid') { + $display .= &mt('Source of username: User ID [_1]', + '(lis_person_sourcedid)').', '; + } else { + $display .= &mt('Source of username: [_1]',$values{'mapuser'}).', '; + } + if ($values{'notstudent'} eq 'auth') { + $display .= &mt('Display LON-CAPA login page if no match').', '; + } elsif ($values{'notstudent'} eq 'reject') { + $display .= &mt('Discontinue launch if no match').', '; + } + } + } + $display =~ s/, $//; + $output .= '
  • '.&Apache::lonhtmlcommon::confirm_success(&mt('[_1] set to [_2]',''.$id.'', + "'$display'")).'
  • '; + } elsif (ref($oldlinkprot->{$id}) eq 'HASH') { + my $oldname = $oldlinkprot->{$id}{'name'}; + $output .= '
  • '.&Apache::lonhtmlcommon::confirm_success(&mt('Deleted setting for [_1]',''."$id ($oldname)".'')).'
  • '; + } + } + } else { + $lti_save_error = 1; + } + } + unless ($lti_save_error) { + foreach my $id (sort { $a <=> $b } keys(%{$changes})) { + unless (ref($changes->{$id}) eq 'HASH') { + push(@deletions,$id); + } + } + if (@deletions) { + if ($context eq 'course') { + &Apache::lonnet::del('nohist_ltienc',\@deletions,$cdom,$cnum); + } + } + } + if ($lti_save_error) { + $output .= '
  • '. + ''. + &mt('An error occurred when saving changes to link protection settings, which remain unchanged.'). + ''. + '
  • '; + } + return $output; +} + +sub store_ltitools { + my ($cdom,$cnum,$context,$changes,$oldltitools) = @_; + my ($home,$ltitools_save_error,$output,$error,%toolsenc,@deletions); + my %lt = <itools_names(); + my @courseroles = ('cc','in','ta','ep','st'); + my @allfields = ('fullname','firstname','lastname','email','user','roles'); + if ($context eq 'domain') { + $home = &Apache::lonnet::domain($cdom,'primary'); + } else { + $home = &Apache::lonnet::homeserver($cnum,$cdom); + } + if (ref($changes) eq 'HASH') { + foreach my $id (sort { $a <=> $b } keys(%{$changes})) { + if (ref($changes->{$id}) eq 'HASH') { + if (exists($changes->{$id}->{'key'})) { + $toolsenc{$id}{'key'} = $changes->{$id}->{'key'}; + delete($changes->{$id}->{'key'}); + } + if (exists($changes->{$id}->{'secret'})) { + $toolsenc{$id}{'secret'} = $changes->{$id}->{'secret'}; + delete($changes->{$id}->{'secret'}); + } elsif (ref($oldltitools->{$id}) eq 'HASH') { + if (exists($oldltitools->{$id}{'usable'})) { + $changes->{$id}->{'usable'} = 1; + } + if (exists($oldltitools->{$id}{'cipher'})) { + $changes->{$id}->{'cipher'} = $oldltitools->{$id}{'cipher'}; + } + } + } + } + } + my @ids=&Apache::lonnet::current_machine_ids(); + if (keys(%toolsenc) > 0) { + unless (($home eq 'no_host') || ($home eq '')) { + my $allowed; + foreach my $id (@ids) { if ($id eq $home) { $allowed=1; } } + if ($allowed) { + if (($context eq 'domain') || + (($context eq 'course') && + (&Apache::lonnet::put('nohist_toolsenc',\%toolsenc,$cdom,$cnum,1) eq 'ok'))) { + foreach my $id (keys(%toolsenc)) { + if (exists($toolsenc{$id}{'secret'})) { + $changes->{$id}->{'usable'} = 1; + } + } + } else { + $ltitools_save_error = 1; + } + } + } + } + unless ($ltitools_save_error) { + if ($context eq 'course') { + if (&Apache::lonnet::put('ltitools',$changes,$cdom,$cnum,1) eq 'ok') { + my $hashid=$cdom.'_'.$cnum; + &Apache::lonnet::devalidate_cache_new('courseltitools',$hashid); + unless (($home eq 'no_host') || ($home eq '')) { + if (grep(/^\Q$home\E$/,@ids)) { + &Apache::lonnet::devalidate_cache_new('crsltitoolsenc',$hashid); + } + } + } else { + $ltitools_save_error = 1; + } + } + unless ($ltitools_save_error) { + my %bynum; + foreach my $itemid (sort(keys(%{$changes}))) { + my $position = $changes->{$itemid}{'order'}; + $bynum{$position} = $itemid; + } + foreach my $pos (sort { $a <=> $b } keys(%bynum)) { + my $itemid = $bynum{$pos}; + if (ref($changes->{$itemid}) ne 'HASH') { + $output .= '
  • '.&mt('Deleted: [_1]',$changes->{$itemid}).'
  • '; + } else { + $output .= '
  • '.$changes->{$itemid}{'title'}.''; + if ($changes->{$itemid}{'image'}) { + $output .= ' '. + ''.&mt('Tool Provider icon').''; + } + $output .= '
  • '; + } + } + } + } + return $output; +} + sub update_env { my ($cnum,$cdom,$chome,$need_env_update,$storehash) = @_; my $count = 0; @@ -1959,10 +3564,15 @@ 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); + my $currltitools; + if (ref($settings) eq 'HASH') { + $currltitools = $settings->{'ltitools'}; + } + my $ltitools_js = &Apache::lonconfigsettings::ltitools_javascript($currltitools); my @code_order; if ($crstype ne 'Community') { if (ref($settings) eq 'HASH') { @@ -1982,7 +3592,7 @@ sub get_jscript { $local_to_standard{$code_order[$i]} = $standardnames[$i]; } foreach my $cloner (@cloners) { - if (($cloner !~ /^\Q*:\E$match_domain$/) && + if (($cloner !~ /^\Q*:\E$match_domain$/) && ($cloner !~ /^$match_username\:$match_domain$/)) { foreach my $item (split(/\&/,$cloner)) { my ($key,$val) = split(/\=/,$item); @@ -2083,7 +3693,7 @@ function syllabusinfo() { } } ENDSCRIPT - my $menuitems_js; + my ($menuitems_js,$grading_js); unless ($noedit) { my $collections; my $next = 1; @@ -2165,13 +3775,98 @@ function toggleAddmenucoll() { } } ENDSCRIPT + $grading_js = <<"ENDSCRIPT"; +function toggleGrading(form) { + if (document.getElementById('hidetotalsdiv')) { + var totalsdivid = document.getElementById('hidetotalsdiv'); + var selname = form.elements['grading']; + if (selname) { + var current = selname.options[selname.selectedIndex].value + if (current == 'standard') { + totalsdivid.style.display = 'inline-block'; + } else { + totalsdivid.style.display = 'none'; + } + } + } + return; +} + +function toggleHiddenTotalsSec(form) { + if (document.getElementById('sectotalsdiv')) { + var sectotalsdivid = document.getElementById('sectotalsdiv'); + var radioname = form.elements['hidetotals']; + if (radioname) { + if (radioname.length > 0) { + var setvis; + for (var i=0; i'."\n". - ''."\n".$stubrowse_js."\n"; + $cloners_js."\n".$instcode_js."\n".$localization_js."\n". + $syllabus_js."\n".$menuitems_js."\n".$extresource_js."\n". + $grading_js."\n".&linkprot_javascript()."\n".'//]]>'."\n". + ''."\n".$stubrowse_js."\n".$ltitools_js."\n"; return $jscript; } @@ -2256,6 +3951,36 @@ function getIndexByName(item) { ENDSCRIPT } +sub linkprot_javascript { + return <<"ENDSCRIPT"; +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]; + if (itemname) { + if (itemname.length > 0) { + var setvis; + for (var i=0; i'; $editsyllabus = &mt('[_1]Edit[_2]','', - ''); + ''); } my %items = ( 'url' => { @@ -2431,7 +4156,7 @@ sub print_courseinfo { next if (!$env{'course.'.$env{'request.course.id'}.'.internal.uniquecode'}); } unless (($item eq 'cloners') || ($item eq 'rolenames')) { - $colspan = 2; + $colspan = 2; } $count ++; if (exists $items{$item}{advanced} && $items{$item}{advanced} == 1) { @@ -2614,12 +4339,12 @@ sub print_courseinfo { if ($clonesrc =~ m{/$match_domain/$match_courseid}) { my %clonesrcinfo = &Apache::lonnet::coursedescription($clonesrc); if ($clonesrcinfo{'description'}) { - $clonedfrom = $clonesrcinfo{'description'}.' '.($clonesrc); + $clonedfrom = $clonesrcinfo{'description'}.' '.($clonesrc); } } $datatable .= $clonedfrom; } elsif ($item eq 'uniquecode') { - my $code = $env{'course.'.$env{'request.course.id'}.'.internal.uniquecode'}; + my $code = $env{'course.'.$env{'request.course.id'}.'.internal.uniquecode'}; if ($code) { $datatable .= $code; } @@ -2666,12 +4391,12 @@ sub print_courseinfo { } elsif ($uploaded) { $datatable .= &mt('Uploaded file'); } else { - $datatable .= &mt('Standard template'); + $datatable .= &mt('Standard template'); } $datatable .= (' ' x 2). &mt('[_1]View[_2]', '', - ''); + ''); } elsif ($item eq 'loncaparev') { my $loncaparev = $env{'course.'.$env{'request.course.id'}.'.internal.releaserequired'}; my $showreqd; @@ -2696,7 +4421,7 @@ sub new_cloners_dom_row { my ($output,$checkedon,$checkedoff); if ($newdom ne '') { if ($num eq $default) { - $checkedon = 'checked="checked" '; + $checkedon = 'checked="checked" '; } else { $checkedoff = 'checked="checked" '; } @@ -2847,7 +4572,7 @@ ENDSCRIPT sub display_loncaparev_constraints { my ($r,$navmap,$loncaparev,$crstype) = @_; - my ($reqdmajor,$reqdminor); + my ($reqdmajor,$reqdminor); my $cid = $env{'request.course.id'}; my $cdom = $env{'course.'.$cid.'.domain'}; my $cnum = $env{'course.'.$cid.'.num'}; @@ -3117,7 +4842,7 @@ sub display_loncaparev_constraints { if (ref($fromblocks{$type}) eq 'HASH') { foreach my $rev (keys(%{$fromblocks{$type}})) { my ($major,$minor) = split(/\./,$rev); - ($reqdmajor,$reqdminor) = + ($reqdmajor,$reqdminor) = &Apache::lonrelrequtils::update_reqd_loncaparev($major,$minor,$reqdmajor,$reqdminor); $output .= &Apache::loncommon::start_data_table_row(). ''.&Apache::loncommon::end_data_table_row(); - } - $datatable .= &Apache::loncommon::end_data_table().'
    '; + } + $datatable .= &Apache::loncommon::end_data_table().'
    '; } } $datatable .= &item_table_row_end(); @@ -3873,8 +5609,8 @@ sub print_feedback { } if ($position eq 'top') { my $includeempty = 0; - $datatable .= ''. ''. ''. - ''. + ''. &Apache::loncommon::end_data_table_row(). &set_discussion_fonts($cdom,$cnum,$item,$settings,$noedit). &Apache::loncommon::end_data_table().'
    '; @@ -4187,7 +5931,7 @@ sub role_checkboxes { my $count = 0; my $disabled; if ($noedit) { - $disabled = ' disabled="disabled"'; + $disabled = ' disabled="disabled"'; } foreach my $role (@roles) { my $checked = ''; @@ -4315,7 +6059,7 @@ sub set_discussion_fonts { sub discussion_vote_classes { my $classorder = ['twoplus','oneplus','zero','oneminus','twominus']; - my %classtitles = &Apache::lonlocal::texthash( + my %classtitles = &Apache::lonlocal::texthash( 'twoplus' => 'Two sigma above mean', 'oneplus' => 'One sigma above mean', 'zero' => 'Within one sigma of mean', @@ -4381,7 +6125,7 @@ sub print_classlists { 'defaultcredits' => { text => ''.&mt($itemtext->{'defaultcredits'}).'', }, - + 'nothideprivileged' => { text => ''.&mt($itemtext->{'nothideprivileged'}).'', input => 'checkbox', @@ -4468,12 +6212,26 @@ sub print_appearance { text => ''.&mt($itemtext->{'usejsme'}).'', input => 'radio', }, + 'inline_chem' => { + 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); } sub print_grading { - my ($cdom,$settings,$ordered,$itemtext,$rowtotal,$crstype,$noedit) = @_; + my ($cdom,$cnum,$settings,$ordered,$itemtext,$rowtotal,$crstype,$noedit) = @_; unless ((ref($settings) eq 'HASH') && (ref($ordered) eq 'ARRAY') && (ref($itemtext) eq 'HASH')) { return; } @@ -4493,7 +6251,7 @@ sub print_grading { }, 'rndseed' => { text => ''.&mt($itemtext->{'rndseed'}).''. - ''.'
    '. + '
    '. &mt('Modifying this will make problems have different numbers and answers!'). '', input => 'selectbox', @@ -4525,7 +6283,7 @@ sub print_grading { input => 'radio', }, ); - return &make_item_rows($cdom,\%items,$ordered,$settings,$rowtotal,$crstype,'grading',$noedit); + return &make_item_rows($cdom,\%items,$ordered,$settings,$rowtotal,$crstype,'grading',$noedit,$cnum); } sub print_printouts { @@ -4659,6 +6417,565 @@ sub print_bridgetasks { return &make_item_rows($cdom,\%items,$ordered,$settings,$rowtotal,$crstype,'bridgetasks',$noedit); } +sub print_ltitools { + my ($cdom,$cnum,$settings,$rowtotal,$crstype,$noedit,$context) = @_; + my ($datatable,$disabled,$css_class,$dest); + if ($noedit) { + $disabled = ' disabled="disabled"'; + } + my $itemcount = 1; + unless ($context eq 'domain') { + my %tooltypes = &Apache::loncommon::usable_exttools(); + unless ($tooltypes{'crs'}) { + my $showtype = 'course'; + if ($crstype eq 'Community') { + $showtype = lc($crstype); + } + $css_class = $itemcount%2?' class="LC_odd_row"':''; + $datatable = '
    '; + $itemcount ++; + return $datatable; + } + } + my %lt = <itools_names(); + my $maxnum = 0; + my %ordered; + if (ref($settings) eq 'HASH') { + foreach my $item (keys(%{$settings})) { + if (ref($settings->{$item}) eq 'HASH') { + my $num = $settings->{$item}{'order'}; + $ordered{$num} = $item; + } + } + } + + if ($context eq 'domain') { + $dest = '/adm/domainprefs'; + } else { + $dest = '/adm/courseprefs'; + } + my ($switchserver,$switchmessage); + $switchserver = &check_switchserver($cdom,$cnum,$context,$dest); + if ($switchserver) { + if ($context eq 'domain') { + $switchmessage = &mt("submit from domain's primary library server: [_1].",$switchserver); + } elsif ($crstype eq 'Community') { + $switchmessage = &mt("submit from community's home server: [_1].",$switchserver); + } else { + $switchmessage = &mt("submit from course's home server: [_1].",$switchserver); + } + } + my $maxnum = scalar(keys(%ordered)); + my @courseroles = ('cc','in','ta','ep','st'); + my @ltiroles = qw(Instructor ContentDeveloper TeachingAssistant Learner); + my @fields = ('fullname','firstname','lastname','email','roles','user'); + if (keys(%ordered)) { + my @items = sort { $a <=> $b } keys(%ordered); + for (my $i=0; $i<@items; $i++) { + $css_class = $itemcount%2?' class="LC_odd_row"':''; + my $item = $ordered{$items[$i]}; + my ($title,$key,$url,$usable,$lifetime,$imgsrc,%sigsel); + if (ref($settings->{$item}) eq 'HASH') { + $title = $settings->{$item}->{'title'}; + $url = $settings->{$item}->{'url'}; + $key = $settings->{$item}->{'key'}; + $usable = $settings->{$item}->{'usable'}; + $lifetime = $settings->{$item}->{'lifetime'}; + my $image = $settings->{$item}->{'image'}; + if ($image ne '') { + $imgsrc = ''.&mt('Tool Provider icon').''; + } + if ($settings->{$item}->{'sigmethod'} eq 'HMAC-256') { + $sigsel{'HMAC-256'} = ' selected="selected"'; + } else { + $sigsel{'HMAC-SHA1'} = ' selected="selected"'; + } + } + my $chgstr = ' onchange="javascript:reorderLTITools(this.form,'."'ltitools_".$item."'".');"'; + $datatable .= ''. + ''."\n"; + $itemcount ++; + } + } + $css_class = $itemcount%2?' class="LC_odd_row"':''; + my $chgstr = ' onchange="javascript:reorderLTITools(this.form,'."'ltitools_add_pos'".');"'; + $datatable .= ''."\n". + ''."\n". + ''."\n"; + $itemcount ++; + return $datatable; +} + +sub ltitools_names { + my %lt = &Apache::lonlocal::texthash( + 'title' => 'Title', + 'version' => 'Version', + 'msgtype' => 'Message Type', + 'sigmethod' => 'Signature Method', + 'url' => 'URL', + 'key' => 'Key', + 'lifetime' => 'Nonce lifetime (s)', + 'secret' => 'Secret', + 'icon' => 'Icon', + 'user' => 'User', + 'fullname' => 'Full Name', + 'firstname' => 'First Name', + 'lastname' => 'Last Name', + 'email' => 'E-mail', + 'roles' => 'Role', + 'window' => 'Window', + 'tab' => 'Tab', + 'iframe' => 'iFrame', + 'height' => 'Height', + 'width' => 'Width', + 'linktext' => 'Default Link Text', + 'explanation' => 'Default Explanation', + 'passback' => 'Tool can return grades:', + 'roster' => 'Tool can retrieve roster:', + 'crstarget' => 'Display target', + 'crslabel' => 'Course label', + 'crstitle' => 'Course title', + 'crslinktext' => 'Link Text', + 'crsexplanation' => 'Explanation', + 'crsappend' => 'Provider URL', + ); + return %lt; +} + sub print_lti { my ($cdom,$settings,$ordered,$itemtext,$rowtotal,$crstype,$noedit) = @_; unless ((ref($settings) eq 'HASH') && (ref($ordered) eq 'ARRAY') && (ref($itemtext) eq 'HASH')) { @@ -4861,7 +7178,7 @@ sub print_menuitems { my %checked; my $on = ' checked="checked"'; foreach my $key (keys(%{$menu{$num}})) { - if (($key eq 'top') || ($key eq 'inline') || ($key eq 'main')) { + if (($key eq 'top') || ($key eq 'inline') || ($key eq 'foot') || ($key eq 'main')) { if ($menu{$num}{$key} eq 'y') { $checked{$key} = $on; } @@ -4878,7 +7195,13 @@ sub print_menuitems { foreach my $category (@order) { if ((ref($categories{$category}) eq 'ARRAY') && (@{$categories{$category}} > 0)) { $datatable .= '
    '.$menutitles{$category}.''."\n"; + if ($category eq 'text') { + $datatable .= ''.&mt('Header').'
    '; + } foreach my $field (@{$categories{$category}}) { + if ($field eq 'disc') { + $datatable .= '
    '.&mt('Footer').'
    '; + } $datatable .= '
    '; } @@ -4928,10 +7251,10 @@ sub menuitems_abbreviations { sub menuitems_categories { my @order = ('shown','text','links','list','inline'); my %categories = ( - shown => ['top','inline','main'], - text => ['name','role','crs'], - links => ['personal','menu','comm','roles','help','logout'], - list => ['about','prefs','port','wish','anno','rss'], + shown => ['top','inline','foot','main'], + text => ['name','role','crs','disc','fdbk'], + links => ['pers','logo','menu','comm','roles','help','logout'], + list => ['about','prefs','port','wish','anno','rss','srch','brse'], inline => ['cont','grades','chat','people','groups','resv','syll','feeds'], ); return (\@order,\%categories); @@ -4940,7 +7263,7 @@ sub menuitems_categories { sub menuitems_titles { return &Apache::lonlocal::texthash ( shown => 'Hierarchy', - text => 'Header text', + text => 'Text', links => 'Header links', list => 'Drop-down list', inline => 'Inline links', @@ -4951,8 +7274,10 @@ sub menuitems_fields { return &Apache::lonlocal::texthash ( top => 'Display header', inline => 'Display inline menu', + foot => 'Display footer', main => 'Access to main menu', - personal => 'Personal', + pers => 'Personal', + logo => 'LON-CAPA', menu => 'Home', comm => 'Messages', roles => 'Roles/Courses', @@ -4961,12 +7286,16 @@ sub menuitems_fields { name => 'Fullname', crs => 'Course Title', role => 'Current Role', + disc => 'Discussion', + fdbk => 'Feedback', about => 'Information', prefs => 'Preferences', port => 'Portfolio', wish => 'Stored Links', anno => 'Calendar', rss => 'RSS Feeds', + srch => 'Search Library', + brse => 'Browse Library', cont => 'Contents', grades => 'Grades', chat => 'Chat', @@ -5002,7 +7331,7 @@ sub menucollections_display { foreach my $num (@current) { my %checked; foreach my $key (keys(%{$menu{$num}})) { - if (($key eq 'top') || ($key eq 'inline') || ($key eq 'main')) { + if (($key eq 'top') || ($key eq 'inline') || ($key eq 'foot') || ($key eq 'main')) { if ($menu{$num}{$key} eq 'y') { $checked{$key} = 1; } @@ -5020,7 +7349,13 @@ sub menucollections_display { if ((ref($categories{$category}) eq 'ARRAY') && (@{$categories{$category}} > 0)) { $output .= '
    '. ''.$menutitles{$category}.''."\n"; + if ($category eq 'text') { + $output .= ''.&mt('Header Text').'

    '; + } foreach my $field (@{$categories{$category}}) { + if ($field eq 'disc') { + $output .= '
    '.&mt('Footer Text').'

    '; + } if ($checked{$field}) { $output .= &Apache::lonhtmlcommon::confirm_success($menufields{$field}); } else { @@ -5038,6 +7373,432 @@ sub menucollections_display { return $output; } +sub print_linkprotection { + my ($cdom,$cnum,$settings,$rowtotal,$crstype,$noedit,$context) = @_; + + my %linkprotection; + my $count = 0; + my $next = 1; + my ($datatable,$disabled,$css_class,$dest); + if ($noedit) { + $disabled = ' disabled="disabled"'; + } + my %desc = &linkprot_names(); + my %lt = &Apache::lonlocal::texthash ( + 'requ' => 'Required settings', + 'opti' => 'Optional settings', + ); + my $itemcount = 0; + + my $ltiauth; + if ($context eq 'domain') { + $ltiauth = 1; + } else { + if (exists($env{'course.'.$env{'request.course.id'}.'.internal.ltiauth'})) { + $ltiauth = $env{'course.'.$env{'request.course.id'}.'.internal.ltiauth'}; + } else { + my %domdefs = &Apache::lonnet::get_domain_defaults($cdom); + $ltiauth = $domdefs{'crsltiauth'}; + } + } + if ($context eq 'domain') { + $dest = '/adm/domainprefs'; + } else { + $dest = '/adm/courseprefs'; + } + + my ($switchserver,$switchmessage); + $switchserver = &check_switchserver($cdom,$cnum,$context,$dest); + if ($switchserver) { + if ($context eq 'domain') { + $switchmessage = &mt("submit from domain's primary library server: [_1].",$switchserver); + } elsif ($crstype eq 'Community') { + $switchmessage = &mt("submit from community's home server: [_1].",$switchserver); + } else { + $switchmessage = &mt("submit from course's home server: [_1].",$switchserver); + } + } + + if ((ref($settings) eq 'HASH') && (ref($settings->{'linkprot'}) eq 'HASH')) { + if (keys(%{$settings->{'linkprot'}})) { + my @current = sort { $a <=> $b } keys(%{$settings->{'linkprot'}}); + $next += $current[-1]; + for (my $i=0; $i<@current; $i++) { + my $num = $current[$i]; + my %values; + if (ref($settings->{'linkprot'}->{$num}) eq 'HASH') { + %values = %{$settings->{'linkprot'}->{$num}}; + } else { + next; + } + my $selected; + if (($values{'version'} eq 'LTI-1p0') || ($values{'version'} eq '')) { + $selected = ' selected="selected"'; + } + $css_class = $itemcount%2?' class="LC_odd_row"':''; + $datatable .= + '
    '; + $itemcount ++; + } + } + } + $css_class = $itemcount%2?' class="LC_odd_row"':''; + $datatable .= ''."\n". + ''; + $$rowtotal ++; + return $datatable; +} + +sub linkprot_names { + return &Apache::lonlocal::texthash( + 'version' => 'LTI Version', + 'key' => 'Key', + 'lifetime' => 'Nonce lifetime (s)', + 'name' => 'Launcher Application', + 'secret' => 'Secret', + 'passback' => 'Can return grades to Launcher', + 'returnurl' => 'Launcher return URL', + 'requser' => 'Use identity', + 'email' => 'Email address', + 'sourcedid' => 'User ID', + 'other' => 'Other', + 'auth' => 'Display LON-CAPA login page', + 'reject' => 'Discontinue launch process', + ); +} + +sub check_switchserver { + my ($cdom,$cnum,$context,$dest) = @_; + my ($allowed,$switchserver,$home); + if ($context eq 'domain') { + $home = &Apache::lonnet::domain($cdom,'primary'); + } else { + $home = &Apache::lonnet::homeserver($cnum,$cdom); + } + unless (($home eq 'no_host') || ($home eq '')) { + my @ids=&Apache::lonnet::current_machine_ids(); + foreach my $id (@ids) { if ($id eq $home) { $allowed=1; } } + if (!$allowed) { + $switchserver=''.&mt('Switch Server').''; + } + } + return $switchserver; +} + +sub linkprot_options { + my ($num,$itemcount,$disabled,$current,$desc) = @_; + my %lt; + if (ref($desc) eq 'HASH') { + %lt = %{$desc}; + } + my $userfieldsty = 'none'; + my (%checked,$userfield); + $checked{'sourcedid'} = ' checked="checked"'; + $checked{'reject'} = ' checked="checked"'; + if (ref($current) eq 'HASH') { + if (($current->{'mapuser'} ne '') && ($current->{'mapuser'} ne 'lis_person_sourcedid')) { + $checked{'sourcedid'} = ''; + if ($current->{'mapuser'} eq 'lis_person_contact_email_primary') { + $checked{'email'} = ' checked="checked"'; + } else { + $checked{'other'} = ' checked="checked"'; + $userfield = $current->{'mapuser'}; + $userfieldsty = 'inline-block'; + } + } + if (($current->{'notstudent'} ne '') && ($current->{'notstudent'} ne 'reject')) { + $checked{'reject'} = ''; + $checked{'auth'} = ' checked="checked"'; + } + } + 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') { + $output .= ''. + ($option eq 'other' ? '' : (' 'x2) ); + } + $output .= '
    '. + '
    '. + '
    '; + $output .= '
    '. + '
    '. + &mt('Action when username is not for an enrolled student').': '; + foreach my $option ('reject','auth') { + $output .= ''. + ($option eq 'auth' ? '' : (' 'x2) ); + } + $output .= '
    '; + 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')) { @@ -5060,7 +7821,7 @@ sub print_other { input => 'textbox', size => '30', }; - my $output = &make_item_rows($cdom,\%items,\@ordered,$settings,$rowtotal,$crstype,'other',$noedit); + return &make_item_rows($cdom,\%items,\@ordered,$settings,$rowtotal,$crstype,'other',$noedit); } sub get_other_items { @@ -5115,7 +7876,7 @@ sub item_table_row_end { } sub yesno_radio { - my ($item,$settings,$unsetdefault,$valueyes,$valueno,$noedit) = @_; + my ($item,$settings,$unsetdefault,$valueyes,$valueno,$noedit,$onclick,$reverse) = @_; my $itemon = ' '; my $itemoff = ' checked="checked" '; if (($valueyes eq '') && ($valueno eq '')) { @@ -5137,12 +7898,22 @@ sub yesno_radio { my $disabled; if ($noedit) { $disabled = ' disabled="disabled"'; + } elsif ($onclick) { + $onclick = ' onclick="'.$onclick.'"'; + } + if ($reverse) { + return ' '. + ''; + } else { + return ' '. + ''; } - return ' '. - ''; } sub select_from_options { @@ -5198,7 +7969,7 @@ sub select_from_options { } sub make_item_rows { - my ($cdom,$items,$ordered,$settings,$rowtotal,$crstype,$caller,$noedit) = @_; + my ($cdom,$items,$ordered,$settings,$rowtotal,$crstype,$caller,$noedit,$cnum) = @_; my $datatable; if ((ref($items) eq 'HASH') && (ref($ordered) eq 'ARRAY')) { my $count = 0; @@ -5209,10 +7980,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'}; @@ -5231,6 +8008,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) { @@ -5241,7 +8020,7 @@ sub make_item_rows { $settings->{$item},$disabled); } elsif ($items->{$item}{input} eq 'radio') { my ($unsetdefault,$valueyes,$valueno); - if (($item eq 'usejsme') || ($item eq 'uselcmath')) { + if (($item eq 'usejsme') || ($item eq 'uselcmath') || ($item eq 'inline_chem')) { my %domdefs = &Apache::lonnet::get_domain_defaults($cdom); unless ($domdefs{$item} eq '0') { $unsetdefault = 1; @@ -5253,16 +8032,65 @@ sub make_item_rows { } $datatable .= &yesno_radio($item,$settings,$unsetdefault,$valueyes,$valueno,$noedit); } elsif ($items->{$item}{input} eq 'selectbox') { - my $id; + my ($id,$onchange); if ($caller eq 'menuitems') { $id = $item; + } elsif ($caller eq 'grading') { + if ($item eq 'grading') { + $onchange = ' onchange="javascript:toggleGrading(this.form);"'; + $id = $item; + } } my $curr = $settings->{$item}; $datatable .= &select_from_options($item,$items->{$item}{'order'}, $items->{$item}{'options'},$curr, $items->{$item}{'nullval'}, - undef,undef,undef,$noedit,$id); + undef,undef,$onchange,$noedit,$id); + if ($item eq 'grading') { + my ($unsetdefault,$valueyes,$valueno,$sectionbox); + my $gradingsty = 'none'; + my $gradingsecsty = 'none'; + $unsetdefault = 0; + my (@selsec,@sections,%current); + if ($curr eq 'standard') { + $gradingsty = 'inline-block'; + if (ref($settings) eq 'HASH') { + $current{'hidetotals'} = $settings->{'hidetotals'}; + if ($settings->{'hidetotals'} =~ /^([\w,]+)$/) { + my $secstr = $1; + unless ($secstr eq 'all') { + @selsec = split(/,/,$secstr); + } + $current{'hidetotals'} = 1; + } + } + } + $valueyes = "1"; + $valueno = "0"; + my %sections = &Apache::loncommon::get_sections($cdom,$cnum); + if (keys(%sections)) { + @sections = sort( { $a <=> $b } keys(%sections)); + } + my $onclick = 'javascript:toggleHiddenTotalsSec(this.form);'; + my $sectionbox = '
    '; + my $reverse = 1; + if (@sections) { + $sectionbox .= ' ... '.&mt('hidden in sections').': '. + '
    '. + &select_sections('hidetotals','',\@sections,\@selsec,$noedit,'all'). + '
    '; + } + $sectionbox .= '
    '; + $datatable .= '
    '. + ''. + &mt('Hide Course Points Totals').''. + '
    '. + &yesno_radio('hidetotals',\%current,$unsetdefault,$valueyes,$valueno,$noedit, + $onclick,$reverse).'
    '. + $sectionbox. + '
    '; + } } elsif ($items->{$item}{input} eq 'textbox') { my $disabled; if ($noedit) { @@ -5297,7 +8125,7 @@ sub nothidepriv_row { } } if ($settings->{'checkforpriv'}) { - @checkdoms = split(/,/,$settings->{'checkforpriv'}); + @checkdoms = split(/,/,$settings->{'checkforpriv'}); } } push(@checkdoms,$cdom); @@ -5311,7 +8139,7 @@ sub nothidepriv_row { if ($end == -1 || $start == -1) { next; } - foreach my $dom (@checkdoms) { + foreach my $dom (@checkdoms) { if (&Apache::lonnet::privileged($uname,$udom,\@checkdoms,['dc','su'])) { unless (grep(/^\Q$user\E$/,@privusers)) { push(@privusers,$user); @@ -5387,7 +8215,7 @@ sub checkforpriv_row { my $domdesc = &Apache::lonnet::domain($currdom,'description'); if ($domdesc eq '') { $domdesc = $currdom; - } + } $datatable .= &Apache::loncommon::start_data_table_row(). ''. &Apache::loncommon::end_data_table_row(); $num ++; - unless (grep(/^\Q$currdom\E$/,@excdoms)) { + unless (grep(/^\Q$currdom\E$/,@excdoms)) { push(@excdoms,$currdom); } } } if ((scalar(keys(%domains)) - scalar(@excdoms)) > 0) { - $datatable .= + $datatable .= &Apache::loncommon::start_data_table_row(). ''. '
    '. + $settings->{'suggested'}->{$key}->{'name'}.'
    '.
    +                                      $settings->{'suggested'}->{$key}->{'info'}.
    +                                      '
    '.&mt('Recommendation(s) for specific launcher application(s)').'
    '.&mt('Launcher Application').''.&mt('Recommendation(s)').'
    '.&mt($item->{'header'}->[0]->{'col1'}).''.$rev.''.$lt{$type}.''; @@ -3204,7 +4929,7 @@ sub display_loncaparev_constraints { &Apache::lonrelrequtils::update_reqd_loncaparev($major,$minor, $reqdmajor,$reqdminor); $checkedrev{$key} = 1; - } + } push(@{$byresponsetype{$symb}{$rev}},$key); $allmaps{$enclosing_map} = 1; } @@ -3241,10 +4966,7 @@ sub display_loncaparev_constraints { } } } - my $suppmap = 'supplemental.sequence'; - my ($suppcount,$supptools,$errors) = (0,0,0); - ($suppcount,$supptools,$errors) = &Apache::loncommon::recurse_supplemental($cnum,$cdom, - $suppmap,$suppcount,$supptools,$errors); + my $supptools = &Apache::lonnet::count_supptools($cnum,$cdom,1,1); my $mapres_header = '

    '. &mt('Requirements for specific folders or resources'). '

    '; @@ -3459,7 +5181,7 @@ sub show_contents_view { sub releases_by_map { my ($r,$bymap,$url,$scopeorder,$lt) = @_; return unless ((ref($bymap) eq 'HASH') && (ref($scopeorder) eq 'ARRAY')); - my $newrow = 0; + my $newrow = 0; if (ref($bymap->{$url}) eq 'HASH') { foreach my $rev (sort(keys(%{$bymap->{$url}}))) { if ($newrow) { @@ -3601,7 +5323,7 @@ sub coowner_invitations { @pendingcoown = split(',',$pendingcoowners); } if (ref($currcoownref) eq 'ARRAY') { - @currcoown == @{$currcoownref}; + @currcoown = @{$currcoownref}; } my $disabled; if ($noedit) { @@ -3665,7 +5387,7 @@ sub manage_coownership { @pendingcoown = split(',',$pendingcoowners); } if (ref($currcoownref) eq 'ARRAY') { - @currcoown == @{$currcoownref}; + @currcoown = @{$currcoownref}; } my $disabled; if ($noedit) { @@ -3751,9 +5473,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(); @@ -3766,7 +5502,7 @@ sub print_localization { undef,$includeempty,$disabled); } else { if ($settings->{$item} eq '') { - unless ($noedit) { + unless ($noedit) { $datatable .= '
    '. &Apache::loncommon::select_language('languages_0','',1); } @@ -3803,8 +5539,8 @@ sub print_localization { &Apache::loncommon::select_language('languages_'.$num,'',1). ''. ''. - &user_table($cdom,$item,\@sections, + $datatable .= ''. + &user_table($cdom,$item,\@sections, $settings->{$item},\%lt,$noedit); } else { $datatable .= &Apache::lonhtmlcommon::textbox($item.'.text', @@ -3978,7 +5714,7 @@ sub select_recipient { } sub select_sections { - my ($item,$num,$sections,$selected,$noedit) = @_; + my ($item,$num,$sections,$selected,$noedit,$allval) = @_; my ($output,@currsecs,$allsec,$disabled); if (ref($selected) eq 'ARRAY') { @currsecs = @{$selected}; @@ -3994,16 +5730,24 @@ sub select_sections { my $mult; if (@{$sections} > 1) { $mult = ' multiple="multiple"'; + my $size; if (@{$sections} > 3) { - $mult .= ' size="4"'; + $size = 4; + } else { + $size = 1 + scalar(@{$sections}); } + $mult .= ' size="'.$size.'"'; + } + my $name = $item.'_sections'; + unless ($item eq 'hidetotals') { + $name .= '_'.$num; } - $output = ''. + ' '; foreach my $sec (@{$sections}) { my $is_sel; if ((@currsecs) && (grep(/^\Q$sec\E$/,@currsecs))) { - $is_sel = 'selected="selected"'; + $is_sel = ' selected="selected"'; } $output .= ''; } @@ -4090,15 +5834,15 @@ sub print_discussion { my $colspan; if ($item eq 'allow_limited_html_in_feedback') { $colspan = 2; - } + } $datatable .= &item_table_row_start($items{$item}{text},$count,undef,$colspan); if ($item eq 'plc.roles.denied') { $datatable .= ''. ''.&role_checkboxes($cdom,$cnum,$item,$settings,undef,undef,$noedit). '
    '; } elsif ($item eq 'plc.users.denied') { - $datatable .= '
    '. - &user_table($cdom,$item,undef, + $datatable .= ''. + &user_table($cdom,$item,undef, $settings->{$item},\%lt,$noedit); } elsif ($item eq 'pch.roles.denied') { $datatable .= ''. @@ -4135,7 +5879,7 @@ sub print_discussion { ''.&mt('font-size').''.&mt('font-weight').''.&mt('font-style').''.&mt('Other css').''.&mt('Other css').'
    '. + &mt("Definition of external tools is not enabled for this $showtype.").'
    '; + if ($tooltypes{'dom'}) { + $datatable .= &mt("Contact an administrator for the $showtype domain ([_1]) to request this feature be enabled.", + ''.$cdom.''). + '

    '. + &mt("Use of external tools defined at a domain level is enabled, so the $showtype editor can be used to add tool(s), if any have been defined."); + } else { + $datatable .= &mt("Use of external tools defined at a domain level is not enabled, either, for this $showtype."). + '

    '. + &mt("Contact an administrator for the $showtype domain ([_1]) to request changes.", + ''.$cdom.''); + + } + $datatable .= '
    ' + .''.(' 'x2). + ''. + '
    '.&mt('Required settings').''. + ''.$lt{'title'}.': '. + (' 'x2). + ''.$lt{'version'}.': '. + (' 'x2). + ''.$lt{'msgtype'}.': '. + (' 'x2). + ''.$lt{'sigmethod'}.':'. + '

    '. + ''.$lt{'url'}.':'. + (' 'x2). + ''.$lt{'lifetime'}.':'. + '

    '; + if ($key ne '') { + $datatable .= ''.$lt{'key'}; + if ($noedit) { + $datatable .= ': ['.&mt('not shown').']'; + } elsif ($switchserver) { + $datatable .= ': ['.&mt('[_1] to view/edit',$switchserver).']'; + } else { + $datatable .= ':'; + } + $datatable .= ' '.(' 'x2); + } elsif (!$switchserver) { + $datatable .= ''.$lt{'key'}.':'. + ''. + ' '.(' 'x2); + } + if ($switchserver) { + if ($usable ne '') { + $datatable .= '
    '. + $lt{'secret'}.': ['.&mt('not shown').'] '.(' 'x2).'
    '. + ''.&mt('Change secret?'). + ''. + (' 'x2). + ''.(' 'x2). + ''; + } elsif ($key eq '') { + $datatable .= ''.&mt('Key and Secret are required').' - '.$switchmessage.''."\n"; + } else { + $datatable .= ''.&mt('Secret required').' - '.$switchmessage.''."\n"; + } + $datatable .= ''; + } else { + if ($usable ne '') { + $datatable .= '
    '. + $lt{'secret'}.': ['.&mt('not shown').'] '.(' 'x2).'
    '. + ''.&mt('Change?'). + ''. + (' 'x2). + '  '; + } else { + $datatable .= + ''.$lt{'secret'}.':'. + ''. + ''. + ''; + } + } + $datatable .= '
    '. + '
    '.&mt('Optional settings').''. + ''.&mt('Display target:'); + my %currdisp; + if (ref($settings->{$item}->{'display'}) eq 'HASH') { + if ($settings->{$item}->{'display'}->{'target'} eq 'window') { + $currdisp{'window'} = ' checked="checked"'; + } elsif ($settings->{$item}->{'display'}->{'target'} eq 'tab') { + $currdisp{'tab'} = ' checked="checked"'; + } else { + $currdisp{'iframe'} = ' checked="checked"'; + } + if ($settings->{$item}->{'display'}->{'width'} =~ /^(\d+)$/) { + $currdisp{'width'} = $1; + } + if ($settings->{$item}->{'display'}->{'height'} =~ /^(\d+)$/) { + $currdisp{'height'} = $1; + } + $currdisp{'linktext'} = $settings->{$item}->{'display'}->{'linktext'}; + $currdisp{'explanation'} = $settings->{$item}->{'display'}->{'explanation'}; + } else { + $currdisp{'iframe'} = ' checked="checked"'; + } + foreach my $disp ('iframe','tab','window') { + $datatable .= ''.(' 'x2); + } + $datatable .= (' 'x4); + foreach my $dimen ('width','height') { + $datatable .= ''. + (' 'x2); + } + $datatable .= '
    '. + '
    '.$lt{'linktext'}.'
    '. + '
    '. + '
    '.$lt{'explanation'}.'
    '. + '

    '; + my %units = ( + 'passback' => 'days', + 'roster' => 'seconds', + ); + foreach my $extra ('passback','roster') { + my $validsty = 'none'; + my $currvalid; + my $checkedon = ''; + my $checkedoff = ' checked="checked"'; + if ($settings->{$item}->{$extra}) { + $checkedon = $checkedoff; + $checkedoff = ''; + $validsty = 'inline-block'; + if ($settings->{$item}->{$extra.'valid'} =~ /^\d+\.?\d*$/) { + $currvalid = $settings->{$item}->{$extra.'valid'}; + } + } + my $onclick = ' onclick="toggleLTITools(this.form,'."'$extra','$i'".');"'; + $datatable .= '
    '.$lt{$extra}.' '. + ''.(' 'x2). + '
    '. + '
    '. + ''. + &mt("until at least [_1] $units{$extra} after launch", + ''). + '
    '; + } + $datatable .= ''.$lt{'icon'}.': '; + if ($imgsrc) { + $datatable .= $imgsrc. + ' '. + ' '.&mt('Replace:').' '; + } else { + $datatable .= '('.&mt('if larger than 21x21 pixels, image will be scaled').') '; + } + if ($switchserver) { + $datatable .= &mt('Upload to library server: [_1]',$switchserver); + } else { + $datatable .= ''; + } + $datatable .= '
    '; + my (%checkedfields,%rolemaps,$userincdom); + if (ref($settings->{$item}) eq 'HASH') { + if (ref($settings->{$item}->{'fields'}) eq 'HASH') { + %checkedfields = %{$settings->{$item}->{'fields'}}; + } + $userincdom = $settings->{$item}->{'incdom'}; + if (ref($settings->{$item}->{'roles'}) eq 'HASH') { + %rolemaps = %{$settings->{$item}->{'roles'}}; + $checkedfields{'roles'} = 1; + } + } + $datatable .= '
    '.&mt('User data sent on launch').''. + ''; + my $userfieldstyle = 'display:none;'; + my $seluserdom = ''; + my $unseluserdom = ' selected="selected"'; + foreach my $field (@fields) { + my ($checked,$onclick,$id,$spacer); + if ($checkedfields{$field}) { + $checked = ' checked="checked"'; + } + if ($field eq 'user') { + $id = ' id="ltitools_user_field_'.$i.'"'; + $onclick = ' onclick="toggleLTITools(this.form,'."'$field','$i'".')"'; + if ($checked) { + $userfieldstyle = 'display:inline-block'; + if ($userincdom) { + $seluserdom = $unseluserdom; + $unseluserdom = ''; + } + } + } else { + $spacer = (' ' x2); + } + $datatable .= ''.$spacer; + } + $datatable .= ''; + $datatable .= '
    '. + ' : '. + '
    '; + $datatable .= '
    '. + '
    '.&mt('Role mapping').''; + foreach my $role (@courseroles) { + my ($selected,$selectnone); + if (!$rolemaps{$role}) { + $selectnone = ' selected="selected"'; + } + $datatable .= ''; + } + $datatable .= '
    '. + &Apache::lonnet::plaintext($role,'Course').'
    '. + '
    '; + my %courseconfig; + if (ref($settings->{$item}) eq 'HASH') { + if (ref($settings->{$item}->{'crsconf'}) eq 'HASH') { + %courseconfig = %{$settings->{$item}->{'crsconf'}}; + } + } + $datatable .= '
    '; + if ($context eq 'domain') { + $datatable .= &mt('Configurable in course'); + } else { + $datatable .= &mt('Configurable for each instance of tool in course'); + } + $datatable .= ''; + foreach my $item ('label','title','target','linktext','explanation','append') { + my $checked; + if ($courseconfig{$item}) { + $checked = ' checked="checked"'; + } + $datatable .= '  '."\n"; + } + $datatable .= '
    '. + '
    '.&mt('Custom items sent on launch').''. + ''; + if (ref($settings->{$item}->{'custom'}) eq 'HASH') { + my %custom = %{$settings->{$item}->{'custom'}}; + if (keys(%custom) > 0) { + foreach my $key (sort(keys(%custom))) { + $datatable .= ''. + ''; + } + } + } + $datatable .= ''; + $datatable .= '
    '.&mt('Action').''.&mt('Name').''.&mt('Value').'
    '. + ''.$key.'
    '. + ''. + '
    '."\n". + ''."\n". + ' '."\n". + ''.&mt('Add').''. + '
    '.&mt('Required settings').''. + ''.$lt{'title'}.': '."\n". + (' 'x2). + ''.$lt{'version'}.': '."\n". + (' 'x2). + ''.$lt{'msgtype'}.': '. + ''.$lt{'sigmethod'}.':'. + '
    '. + ''.$lt{'url'}.': '."\n". + (' 'x2). + ''.$lt{'lifetime'}.':
    '; + if ($switchserver) { + $datatable .= ''.&mt('Key and Secret are required').' - '.$switchmessage.''."\n"; + } else { + $datatable .= ''.$lt{'key'}.': '."\n". + (' 'x2). + ''.$lt{'secret'}.':'. + ' '."\n"; + } + $datatable .= '

    '. + '
    '. + '
    '.&mt('Optional settings').''. + ''.&mt('Display target:'); + my %defaultdisp; + $defaultdisp{'iframe'} = ' checked="checked"'; + foreach my $disp ('iframe','tab','window') { + $datatable .= ''.(' 'x2); + } + $datatable .= (' 'x4); + foreach my $dimen ('width','height') { + $datatable .= ''. + (' 'x2); + } + $datatable .= '
    '. + '
    '.$lt{'linktext'}.'
    '. + '
    '. + '
    '.$lt{'explanation'}.'
    '. + ''. + '

    '; + my %units = ( + 'passback' => 'days', + 'roster' => 'seconds', + ); + my %defaulttimes = ( + 'passback' => '7', + 'roster' => '300', + ); + foreach my $extra ('passback','roster') { + my $onclick = ' onclick="toggleLTITools(this.form,'."'$extra','add'".');"'; + $datatable .= '
    '.$lt{$extra}.' '. + ''.(' 'x2).''. + '
    '. + '
    '; + } + $datatable .= ''.$lt{'icon'}.': '. + '('.&mt('if larger than 21x21 pixels, image will be scaled').') '; + if ($switchserver) { + $datatable .= &mt('Upload to library server: [_1]',$switchserver); + } else { + $datatable .= ''; + } + $datatable .= '
    '. + '
    '.&mt('User data sent on launch').''. + ''; + foreach my $field (@fields) { + my ($id,$onclick,$spacer); + if ($field eq 'user') { + $id = ' id="ltitools_user_field_add"'; + $onclick = ' onclick="toggleLTITools(this.form,'."'$field','add'".')"'; + } else { + $spacer = (' ' x2); + } + $datatable .= ''.$spacer; + } + $datatable .= ''. + '
    '; + $datatable .= '
    '.&mt('Role mapping').''; + foreach my $role (@courseroles) { + my ($checked,$checkednone); + $datatable .= ''; + } + $datatable .= '
    '. + &Apache::lonnet::plaintext($role,'Course').'
    '. + '
    '. + '
    '; + if ($context eq 'domain') { + $datatable .= &mt('Configurable in course'); + } else { + $datatable .= &mt('Configurable for each instance of tool in course'); + } + $datatable .= ''; + foreach my $item ('label','title','target','linktext','explanation','append') { + $datatable .= ''.(' ' x2)."\n"; + } + $datatable .= '
    '. + '
    '.&mt('Custom items sent on launch').''. + ''. + ''. + '
    '.&mt('Action').''.&mt('Name').''.&mt('Value').'
    '. + ''. + '
    '."\n". + '
    '. + ''; + my ($usersty,$onclickrequser,%checkedrequser,$onclickreturnurl,%checkedreturnurl, + $onclickpassback,%checkedpassback,$passbacksty,%checkedpassbackfmt); + $passbacksty = 'none'; + $onclickpassback = ' onclick="toggleLinkProtExtra(this.form,'."'passback','passbackparam','1','inline-block','$i'".');"'; + %checkedpassback = ( + 'no' => ' checked="checked"', + 'yes' => '', + ); + %checkedpassbackfmt = ( + '1p1' => ' checked="checked"', + '1p0' => '', + ); + if ($values{'passback'} ne '') { + $passbacksty = 'inline-block'; + $checkedpassback{'yes'} = ' checked="checked"'; + $checkedpassback{'no'} = ''; + if ($values{'passbackformat'} eq '1.0') { + $checkedpassbackfmt{'1p0'} = ' checked="checked"'; + $checkedpassbackfmt{'1p1'} = ''; + } + } + if ($ltiauth) { + $usersty = 'display:none'; + $onclickrequser = ' onclick="toggleLinkProtExtra(this.form,'."'requser','optional','1','block','$i'".');"'; + %checkedrequser = ( + no => ' checked="checked"', + yes => '', + ); + if ($values{'requser'}) { + $checkedrequser{'yes'} = $checkedrequser{'no'}; + $checkedrequser{'no'} = ''; + } + $datatable .= '
    '.$lt{'requ'}.''; + if ($values{'requser'}) { + $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'}. + ': '. + (' 'x2). + ''.$desc{'version'}.': '."\n". + (' 'x2). + ''.$desc{'lifetime'}.':

    '; + if ($values{'key'} ne '') { + $datatable .= ''.$desc{'key'}; + if ($noedit) { + $datatable .= ': ['.&mt('not shown').']'; + } elsif ($switchserver) { + $datatable .= ': ['.&mt('[_1] to view/edit',$switchserver).']'; + } else { + $datatable .= ':'; + } + $datatable .= ' '.(' 'x2); + } elsif (!$switchserver) { + $datatable .= ''.$desc{'key'}.':'. + ''. + ' '.(' 'x2); + } + if ($switchserver) { + if ($values{'usable'} ne '') { + $datatable .= '
    '. + $desc{'secret'}.': ['.&mt('not shown').'] '.(' 'x2).'
    '. + ''.&mt('Change secret?'). + ''. + (' 'x2). + ''.(' 'x2). + ''; + } elsif ($values{'key'} eq '') { + $datatable .= ''.&mt('Key and Secret are required').' - '.$switchmessage.''."\n"; + } else { + $datatable .= ''.&mt('Secret required').' - '.$switchmessage.''."\n"; + } + $datatable .= ''; + } else { + if ($values{'usable'} ne '') { + $datatable .= '
    '. + $desc{'secret'}.': ['.&mt('not shown').'] '.(' 'x2).'
    '. + ''.&mt('Change?'). + ''. + (' 'x2). + '  '; + } else { + $datatable .= + ''.$desc{'secret'}.':'. + ''. + ''. + ''; + } + } + $datatable .= '

    '. + ''.$desc{'passback'}.'?'. + ' '. + '
    '. + '
    '. + ''.&mt('Grade format'). + ''.(' 'x2). + '
    '. + '
    '. + '
    '. + ''.$desc{'returnurl'}.'?'. + ' '. + ''. + '   '; + if ($ltiauth) { + $datatable .= (' 'x2).''.$desc{'requser'}.'?'. + ' '. + ''. + '
    '. + '
    '.$lt{'opti'}.''. + &linkprot_options($i,$itemcount,$disabled,\%values,\%desc). + '
    '; + } + $datatable .= '
    '."\n". + ''."\n". + ''.&mt('Add').''; + my ($usersty,$onclickrequser,%checkedrequser,$onclickreturnurl,%checkedreturnurl, + $onclickpassback,%checkedpassback,%checkedpassbackfmt); + if ($ltiauth) { + $usersty = 'display:none'; + $onclickrequser = ' onclick="toggleLinkProtExtra(this.form,'."'requser','optional','1','block','add'".');"'; + %checkedrequser = ( + no => ' checked="checked"', + yes => '', + ); + $datatable .= '
    '.$lt{'requ'}.''; + } + $onclickpassback = ' onclick="toggleLinkProtExtra(this.form,'."'passback','passbackparam','1','inline-block','add'".');"'; + %checkedpassback = ( + 'no' => ' checked="checked"', + 'yes' => '', + ); + %checkedpassbackfmt = ( + '1p1' => ' checked="checked"', + '1p0' => '', + ); + $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 ($switchserver) { + $datatable .= ''.&mt('Key and Secret are required').' - '.$switchmessage.''."\n"; + } else { + $datatable .= ''.$desc{'key'}.': '."\n". + (' 'x2). + ''.$desc{'secret'}.':'. + ' '."\n"; + } + $datatable .= '

    '. + ''.$desc{'passback'}.'?'. + ' '. + ''. + '
    '. + '
    '. + '
    '. + ''.$desc{'returnurl'}.'?'. + ' '. + ''. + '   '; + if ($ltiauth) { + $datatable .= (' 'x2).''.$desc{'requser'}.'?'. + ' '. + ''. + '
    '. + '
    '.$lt{'opti'}.''. + &linkprot_options('add',$itemcount,$disabled,{},\%desc). + '
    '; + } + $datatable .= '
    '. @@ -5399,13 +8227,13 @@ sub checkforpriv_row { &mt('Delete').''. &mt('Additional domain:'). '
    '. @@ -5519,7 +8347,7 @@ ENDJS } } my $pos = $currnum+1; - unless ($noedit) { + unless ($noedit) { $output .= '
    '. @@ -5657,6 +8485,30 @@ sub change_clone { } } } + return; +} + +sub devalidate_remote_courseprefs { + my ($cdom,$cnum,$cachekeys) = @_; + return unless (ref($cachekeys) eq 'HASH'); + my %servers = &Apache::lonnet::internet_dom_servers($cdom); + my %thismachine; + map { $thismachine{$_} = 1; } &Apache::lonnet::current_machine_ids(); + my @posscached = ('courselti','courseltitools'); + if (keys(%servers)) { + foreach my $server (keys(%servers)) { + next if ($thismachine{$server}); + my @cached; + foreach my $name (@posscached) { + if ($cachekeys->{$name}) { + push(@cached,&escape($name).':'.&escape($cdom.'_'.$cnum)); + } + } + if (@cached) { + &Apache::lonnet::remote_devalidate_cache($server,\@cached); + } + } + } return; }