--- loncom/interface/courseprefs.pm 2023/01/23 21:34:29 1.49.2.28.2.14 +++ loncom/interface/courseprefs.pm 2023/07/05 17:05:40 1.49.2.28.2.15 @@ -1,7 +1,7 @@ # The LearningOnline Network with CAPA # Handler to set configuration settings for a course # -# $Id: courseprefs.pm,v 1.49.2.28.2.14 2023/01/23 21:34:29 raeburn Exp $ +# $Id: courseprefs.pm,v 1.49.2.28.2.15 2023/07/05 17:05:40 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -225,6 +225,7 @@ 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; @@ -372,6 +373,8 @@ sub handler { my %values=&Apache::lonnet::dump('environment',$cdom,$cnum); my %linkprot=&Apache::lonnet::dump('lti',$cdom,$cnum,undef,undef,undef,1); 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') { @@ -389,11 +392,27 @@ sub handler { 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','linkprot','spreadsheet','bridgetasks', - 'other'); - + 'menuitems','ltitools','linkprot','spreadsheet', + 'bridgetasks','other'); my %prefs = ( 'courseinfo' => { text => $lt{'gens'}, @@ -407,7 +426,7 @@ sub handler { 'co-owners' => $lt{'cown'}, 'description' => $lt{'desc'}, 'courseid' => $lt{'idnu'}, - 'uniquecode' => $lt{'unco'}, + 'uniquecode' => $lt{'unco'}, 'categories' => $lt{'catg'}, 'hidefromcat' => $lt{'excc'}, 'cloners' => $lt{'clon'}, @@ -455,7 +474,7 @@ sub handler { 'plc.users.denied' => 'No Chat room use', allow_limited_html_in_feedback => 'Allow limited HTML in discussion', allow_discussion_post_editing => 'Users can edit/delete own discussion posts', - discussion_post_fonts => 'Discussion post fonts based on likes/unlikes', + discussion_post_fonts => 'Discussion post fonts based on likes/unlikes', }, }, 'classlists' => @@ -578,6 +597,14 @@ sub handler { col2 => 'Settings', }], }, + 'ltitools' => + { + text => 'External tools', + help => 'Course_Prefs_ExternalTools', + header => [{col1 => 'Item', + col2 => 'Settings', + }], + }, 'other' => { text => 'Other settings', help => 'Course_Prefs_Other', @@ -785,6 +812,12 @@ sub print_config_box { $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 'menuitems') { $output .= &print_menuitems('bottom',$cdom,$settings,$itemtext,\$rowtotal,$crstype,$noedit); } elsif ($action eq 'linkprot') { @@ -819,7 +852,7 @@ sub process_changes { } } } - } elsif ($action eq 'linkprot') { + } elsif (($action eq 'linkprot') || ($action eq 'ltitools')) { if (ref($values->{$action}) eq 'HASH') { foreach my $id (keys(%{$values->{$action}})) { if ($id =~ /^\d+$/) { @@ -828,8 +861,8 @@ sub process_changes { } } @ordered = sort { $a <=> $b } @ordered; - if (($env{'form.linkprot_add'}) && ($env{'form.linkprot_maxnum'} =~ /^\d+$/)) { - push(@ordered,$env{'form.linkprot_maxnum'}); + 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') { @@ -972,6 +1005,12 @@ sub process_changes { 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') { @@ -1477,7 +1516,7 @@ sub process_changes { sub process_linkprot { my ($cdom,$cnum,$values,$changes,$context,$lastactref) = @_; - my ($home,$dest,$ltiauth,$privkey,$privnum,$cipher,$errors,%linkprot); + my ($dest,$ltiauth,$privnum,$cipher,$errors,%linkprot); if (ref($values) eq 'HASH') { foreach my $id (keys(%{$values})) { if ($id =~ /^\d+$/) { @@ -1487,31 +1526,7 @@ sub process_linkprot { } } } - 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); - } - if ((($context eq 'domain') && ($domdefs{'linkprotenc_dom'})) || - (($context eq 'course') && ($domdefs{'linkprotenc_crs'}))) { - unless (($home eq 'no_host') || ($home eq '')) { - if (grep(/^\Q$home\E$/,@ids)) { - if (ref($domdefs{'privhosts'}) eq 'ARRAY') { - if (grep(/^\Q$home\E$/,@{$domdefs{'privhosts'}})) { - my %privhash = &Apache::lonnet::restore_dom('lti','private',$cdom,$home,1); - $privkey = $privhash{'key'}; - $privnum = $privhash{'version'}; - if (($privnum) && ($privkey ne '')) { - $cipher = Crypt::CBC->new({'key' => $privkey, - 'cipher' => 'DES'}); - } - } - } - } - } - } + ($cipher,$privnum) = &get_credentials($cdom,$cnum,'lti',$context); if ($context eq 'domain') { $dest = '/adm/domainprefs'; $ltiauth = 1; @@ -1595,15 +1610,32 @@ sub process_linkprot { } 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; @@ -1764,6 +1796,671 @@ sub get_linkprot_id { 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) = &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 get_ltitools_id { + my ($context,$cdom,$cnum,$title) = @_; + my ($lockhash,$tries,$gotlock,$id,$error); + + # get lock on ltitools db + $lockhash = { + lock => $env{'user.name'}. + ':'.$env{'user.domain'}, + }; + $tries = 0; + if ($context eq 'domain') { + $gotlock = &Apache::lonnet::newput_dom('ltitools',$lockhash,$cdom); + } else { + $gotlock = &Apache::lonnet::newput('ltitools',$lockhash,$cdom,$cnum); + } + while (($gotlock ne 'ok') && ($tries<10)) { + $tries ++; + sleep (0.1); + if ($context eq 'domain') { + $gotlock = &Apache::lonnet::newput_dom('ltitools',$lockhash,$cdom); + } else { + $gotlock = &Apache::lonnet::newput('ltitools',$lockhash,$cdom,$cnum); + } + } + if ($gotlock eq 'ok') { + my %currids; + if ($context eq 'domain') { + %currids = &Apache::lonnet::dump_dom('ltitools',$cdom); + } else { + %currids = &Apache::lonnet::dump('ltitools',$cdom,$cnum); + } + if ($currids{'lock'}) { + delete($currids{'lock'}); + if (keys(%currids)) { + my @curr = sort { $a <=> $b } keys(%currids); + if ($curr[-1] =~ /^\d+$/) { + $id = 1 + $curr[-1]; + } + } else { + $id = 1; + } + if ($id) { + if ($context eq 'domain') { + unless (&Apache::lonnet::newput_dom('ltitools',{ $id => $title },$cdom) eq 'ok') { + $error = 'nostore'; + } + } else { + unless (&Apache::lonnet::newput('ltitools',{ $id => $title },$cdom,$cnum) eq 'ok') { + $error = 'nostore'; + } + } + } else { + $error = 'nonumber'; + } + } + my $dellockoutcome; + if ($context eq 'domain') { + $dellockoutcome = &Apache::lonnet::del_dom('ltitools',['lock'],$cdom); + } else { + $dellockoutcome = &Apache::lonnet::del('ltitools',['lock'],$cdom,$cnum); + } + } else { + $error = 'nolock'; + } + return ($id,$error); +} + +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 { my ($entry,$num) = @_; my @secs = &Apache::loncommon::get_env_multiple('form.'.$entry.'_sections_'.$num); @@ -1806,12 +2503,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,%oldlinkprot); + 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') { @@ -1826,6 +2526,8 @@ sub store_changes { $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.'); @@ -1862,6 +2564,8 @@ sub store_changes { } } 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'}}; @@ -2319,7 +3023,7 @@ sub store_linkprot { my %values = %{$changes->{$id}}; my %desc = &linkprot_names(); my $display; - foreach my $title ('name','lifetime','version','key','secret','returnurl') { + 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})) { @@ -2339,6 +3043,12 @@ sub store_linkprot { 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}.', '; } @@ -2395,6 +3105,214 @@ sub store_linkprot { 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; @@ -2529,6 +3447,11 @@ sub get_jscript { 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') { @@ -2780,7 +3703,7 @@ ENDSCRIPT $cloners_js."\n".$instcode_js."\n".$localization_js."\n". $syllabus_js."\n".$menuitems_js."\n".$extresource_js."\n". &linkprot_javascript()."\n".'//]]>'."\n". - ''."\n".$stubrowse_js."\n"; + ''."\n".$stubrowse_js."\n".$ltitools_js."\n"; return $jscript; } @@ -2867,38 +3790,6 @@ ENDSCRIPT sub linkprot_javascript { return <<"ENDSCRIPT"; -function toggleLinkProt(form,num,item) { - var radioname = ''; - var currdivid = ''; - var newdivid = ''; - if ((document.getElementById('linkprot_divcurr'+item+'_'+num)) && - (document.getElementById('linkprot_divchg'+item+'_'+num))) { - currdivid = document.getElementById('linkprot_divcurr'+item+'_'+num); - newdivid = document.getElementById('linkprot_divchg'+item+'_'+num); - radioname = form.elements['linkprot_change'+item+'_'+num]; - if (radioname) { - if (radioname.length > 0) { - var setvis; - for (var i=0; i{$item} ne '*') { my @entries = split(/,/,$settings->{$item}); if (@entries > 0) { @@ -4349,7 +5230,7 @@ sub print_localization { } } unless ($noedit) { - $datatable .= + $datatable .= &Apache::loncommon::start_data_table_row(). ''. &mt('Additional language:'). '
    '. @@ -4445,7 +5326,7 @@ sub user_table { if ($currvalue eq '') { unless ($noedit) { $output .= &select_recipient($item,'0',$cdom,$sections); - } + } } else { my $num = 0; my @curr = split(/,/,$currvalue); @@ -5203,6 +6084,553 @@ 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 = ''. + &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 .= ''; + $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 .= '' + .''.(' '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 .= '
    '.&mt('Configurable in course').''; + 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"; + $itemcount ++; + } + } + $css_class = $itemcount%2?' class="LC_odd_row"':''; + my $chgstr = ' onchange="javascript:reorderLTITools(this.form,'."'ltitools_add_pos'".');"'; + $datatable .= ''."\n". + ''."\n". + ' '."\n". + ''.&mt('Add').''."\n". + ''. + '
    '.&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').'
    '. + '
    '. + '
    '.&mt('Configurable in course').''; + 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". + ''."\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 ltimenu_titles { return &Apache::lonlocal::texthash( fullname => 'Full name', @@ -5536,7 +6964,27 @@ sub print_linkprotection { ''. ''; - my ($usersty,$onclickrequser,%checkedrequser,$onclickreturnurl,%checkedreturnurl); + 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'".');"'; @@ -5562,7 +7010,7 @@ sub print_linkprotection { $checkedreturnurl{'yes'} = $checkedreturnurl{'no'}; $checkedreturnurl{'no'} = ''; } - $datatable .= + $datatable .= ''.$desc{'name'}. ': '. (' 'x2). @@ -5591,9 +7039,9 @@ sub print_linkprotection { $datatable .= '
    '. $desc{'secret'}.': ['.&mt('not shown').'] '.(' 'x2).'
    '. ''.&mt('Change secret?'). - ''. + ''. (' 'x2). - ''.(' 'x2). + ''.(' 'x2). ''; @@ -5608,9 +7056,9 @@ sub print_linkprotection { $datatable .= '
    '. $desc{'secret'}.': ['.&mt('not shown').'] '.(' 'x2).'
    '. ''.&mt('Change?'). - ''. + ''. (' 'x2). - '