--- loncom/interface/londocs.pm 2023/01/20 16:55:44 1.484.2.93.2.10
+++ loncom/interface/londocs.pm 2025/03/15 03:08:02 1.484.2.93.2.19
@@ -1,7 +1,7 @@
# The LearningOnline Network
# Documents
#
-# $Id: londocs.pm,v 1.484.2.93.2.10 2023/01/20 16:55:44 raeburn Exp $
+# $Id: londocs.pm,v 1.484.2.93.2.19 2025/03/15 03:08:02 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -50,6 +50,7 @@ use GDBM_File;
use File::MMagic;
use Apache::lonlocal;
use Cwd;
+use UUID::Tiny ':std';
use LONCAPA qw(:DEFAULT :match);
my $iconpath;
@@ -714,8 +715,8 @@ sub group_import {
$url = $1;
my $marker = $2;
my $info = $3;
- my ($toolid,%toolhash,%toolsettings);
- my @extras = ('linktext','explanation','crslabel','crstitle','crsappend');
+ my ($toolid,$toolprefix,$tooltype,%toolhash,%toolsettings);
+ my @extras = ('linktext','explanation','crslabel','crstitle','crsappend','returnurl','backtourl');
my @toolinfo = split(/:/,$info);
if ($residx) {
%toolsettings=&Apache::lonnet::dump('exttool_'.$marker,$coursedom,$coursenum);
@@ -723,97 +724,166 @@ sub group_import {
} else {
$toolid = shift(@toolinfo);
}
+ if ($toolid =~ /^c/) {
+ $tooltype = 'crs';
+ $toolprefix = 'c';
+ } else {
+ $tooltype = 'dom';
+ }
$toolid =~ s/\D//g;
($toolhash{'target'},$toolhash{'width'},$toolhash{'height'},
- $toolhash{'linktext'},$toolhash{'explanation'},
- $toolhash{'crslabel'},$toolhash{'crstitle'},$toolhash{'crsappend'}) = @toolinfo;
+ $toolhash{'linktext'},$toolhash{'explanation'},$toolhash{'crslabel'},
+ $toolhash{'crstitle'},$toolhash{'crsappend'},$toolhash{'gradable'},
+ $toolhash{'returnurl'},$toolhash{'backtourl'}) = @toolinfo;
foreach my $item (@extras) {
$toolhash{$item} = &unescape($toolhash{$item});
}
+ if ($folder =~ /^supplemental/) {
+ delete($toolhash{'gradable'});
+ } else {
+ $toolhash{'gradable'} =~ s/\D+//g;
+ }
if (ref($ltitoolsref) eq 'HASH') {
- my @deleted;
- if (ref($ltitoolsref->{$toolid}) eq 'HASH') {
- $toolhash{'id'} = $toolid;
- if (($toolhash{'target'} eq 'iframe') || ($toolhash{'target'} eq 'tab') ||
- ($toolhash{'target'} eq 'window')) {
- if ($toolhash{'target'} eq 'window') {
- foreach my $item ('width','height') {
- $toolhash{$item} =~ s/^\s+//;
- $toolhash{$item} =~ s/\s+$//;
- if ($toolhash{$item} =~ /\D/) {
- delete($toolhash{$item});
- if ($residx) {
- if ($toolsettings{$item}) {
- push(@deleted,$item);
+ if (ref($ltitoolsref->{$tooltype}) eq 'HASH') {
+ if (ref($ltitoolsref->{$tooltype}->{$toolid}) eq 'HASH') {
+ my %tools = %{$ltitoolsref->{$tooltype}->{$toolid}};
+ my @deleted;
+ $toolhash{'id'} = $toolprefix.$toolid;
+ if (($toolhash{'target'} eq 'iframe') || ($toolhash{'target'} eq 'tab') ||
+ ($toolhash{'target'} eq 'window')) {
+ if ($toolhash{'target'} eq 'window') {
+ foreach my $item ('width','height') {
+ $toolhash{$item} =~ s/^\s+//;
+ $toolhash{$item} =~ s/\s+$//;
+ if ($toolhash{$item} =~ /\D/) {
+ delete($toolhash{$item});
+ if ($residx) {
+ if ($toolsettings{$item}) {
+ push(@deleted,$item);
+ }
}
}
}
}
- }
- } elsif ($residx) {
- $toolhash{'target'} = $toolsettings{'target'};
- if ($toolhash{'target'} eq 'window') {
- foreach my $item ('width','height') {
- $toolhash{$item} = $toolsettings{$item};
+ } elsif ($residx) {
+ $toolhash{'target'} = $toolsettings{'target'};
+ if ($toolhash{'target'} eq 'window') {
+ foreach my $item ('width','height') {
+ $toolhash{$item} = $toolsettings{$item};
+ }
+ }
+ } elsif (ref($tools{'display'}) eq 'HASH') {
+ $toolhash{'target'} = $tools{'display'}{'target'};
+ if ($toolhash{'target'} eq 'window') {
+ $toolhash{'width'} = $tools{'display'}{'width'};
+ $toolhash{'height'} = $tools{'display'}{'height'};
}
}
- } elsif (ref($ltitoolsref->{$toolid}->{'display'}) eq 'HASH') {
- $toolhash{'target'} = $ltitoolsref->{$toolid}->{'display'}->{'target'};
- if ($toolhash{'target'} eq 'window') {
- $toolhash{'width'} = $ltitoolsref->{$toolid}->{'display'}->{'width'};
- $toolhash{'height'} = $ltitoolsref->{$toolid}->{'display'}->{'height'};
- }
- }
- if ($toolhash{'target'} eq 'iframe') {
- foreach my $item ('width','height','linktext','explanation') {
- delete($toolhash{$item});
- if ($residx) {
- if ($toolsettings{$item}) {
- push(@deleted,$item);
+ if ($toolhash{'target'} eq 'iframe') {
+ foreach my $item ('width','height','linktext','explanation') {
+ delete($toolhash{$item});
+ if ($residx) {
+ if ($toolsettings{$item}) {
+ push(@deleted,$item);
+ }
}
}
- }
- } elsif ($toolhash{'target'} eq 'tab') {
- foreach my $item ('width','height') {
- delete($toolhash{$item});
- if ($residx) {
- if ($toolsettings{$item}) {
- push(@deleted,$item);
+ } elsif ($toolhash{'target'} eq 'tab') {
+ foreach my $item ('width','height') {
+ delete($toolhash{$item});
+ if ($residx) {
+ if ($toolsettings{$item}) {
+ push(@deleted,$item);
+ }
}
}
}
- }
- if (ref($ltitoolsref->{$toolid}->{'crsconf'}) eq 'HASH') {
- foreach my $item ('label','title','linktext','explanation') {
- my $crsitem;
- if (($item eq 'label') || ($item eq 'title')) {
- $crsitem = 'crs'.$item;
- } else {
- $crsitem = $item;
- }
- if ($ltitoolsref->{$toolid}->{'crsconf'}->{$item}) {
- $toolhash{$crsitem} =~ s/^\s+//;
- $toolhash{$crsitem} =~ s/\s+$//;
- if ($toolhash{$crsitem} eq '') {
+ if (ref($tools{'crsconf'}) eq 'HASH') {
+ foreach my $item ('label','title','linktext','explanation') {
+ my $crsitem;
+ if (($item eq 'label') || ($item eq 'title')) {
+ $crsitem = 'crs'.$item;
+ } else {
+ $crsitem = $item;
+ }
+ if ($tools{'crsconf'}{$item}) {
+ $toolhash{$crsitem} =~ s/^\s+//;
+ $toolhash{$crsitem} =~ s/\s+$//;
+ if ($toolhash{$crsitem} eq '') {
+ delete($toolhash{$crsitem});
+ }
+ } else {
delete($toolhash{$crsitem});
}
+ if (($residx) && (exists($toolsettings{$crsitem}))) {
+ unless (exists($toolhash{$crsitem})) {
+ push(@deleted,$crsitem);
+ }
+ }
+ }
+ if ($tools{'crsconf'}{'returnurl'}) {
+ unless ($toolhash{'returnurl'} eq 'custom') {
+ delete($toolhash{'backtourl'});
+ }
} else {
- delete($toolhash{$crsitem});
+ delete($toolhash{'returnurl'});
+ delete($toolhash{'backtourl'});
+ }
+ }
+ if ($toolhash{'passback'}) {
+ my $gradesecret = UUID::Tiny::create_uuid_as_string(UUID_V4);
+ $toolhash{'gradesecret'} = $gradesecret;
+ $toolhash{'gradesecretdate'} = time;
+ }
+ if ($toolhash{'roster'}) {
+ my $rostersecret = UUID::Tiny::create_uuid_as_string(UUID_V4);
+ $toolhash{'rostersecret'} = $rostersecret;
+ $toolhash{'rostersecretdate'} = time;
+ }
+ my $changegradable;
+ if (($residx) && ($folder =~ /^default/)) {
+ if ($toolsettings{'gradable'}) {
+ unless (($toolhash{'gradable'}) || (defined($LONCAPA::map::zombies[$residx]))) {
+ push(@deleted,'gradable');
+ $changegradable = 1;
+ }
+ } elsif ($toolhash{'gradable'}) {
+ $changegradable = 1;
}
- if (($residx) && (exists($toolsettings{$crsitem}))) {
- unless (exists($toolhash{$crsitem})) {
- push(@deleted,$crsitem);
+ if (($caller eq 'londocs') && (defined($LONCAPA::map::zombies[$residx]))) {
+ $changegradable = 1;
+ if ($toolsettings{'gradable'}) {
+ $toolhash{'gradable'} = 1;
}
}
}
- }
- my $putres = &Apache::lonnet::put('exttool_'.$marker,\%toolhash,$coursedom,$coursenum);
- if ($putres eq 'ok') {
- if (@deleted) {
- &Apache::lonnet::del('exttool_'.$marker,\@deleted,$coursedom,$coursenum);
+ if ($residx) {
+ if (($toolsettings{'backtourl'} ne '') && (!exists($toolhash{'backtourl'}))) {
+ push(@deleted,'backtourl');
+ }
+ if (($toolsettings{'returnurl'} ne '') && (!exists($toolhash{'returnurl'}))) {
+ push(@deleted,'returnurl');
+ }
+ }
+ my $putres = &Apache::lonnet::put('exttool_'.$marker,\%toolhash,$coursedom,$coursenum);
+ if ($putres eq 'ok') {
+ if (@deleted) {
+ &Apache::lonnet::del('exttool_'.$marker,\@deleted,$coursedom,$coursenum);
+ }
+ if (($changegradable) && ($folder =~ /^default/)) {
+ my $val;
+ if ($toolhash{'gradable'}) {
+ $val = 'yes';
+ } else {
+ $val = 'no';
+ }
+ &LONCAPA::map::storeparameter($residx,'parameter_0_gradable',$val,
+ 'string_yesno');
+ &remember_parms($residx,'gradable','set',$val);
+ }
+ } else {
+ return (&mt('Failed to save update to external tool.'),1);
}
- } else {
- return (&mt('Failed to save update to external tool.'),1);
}
}
}
@@ -1002,7 +1072,7 @@ sub docs_change_log {
'// '."\n".
@@ -1052,6 +1122,7 @@ sub docs_change_log {
'encrypturl' => 'URL hidden',
'randompick' => 'Randomly pick',
'randomorder' => 'Randomly ordered',
+ 'gradable' => 'Grade can be assigned to External Tool',
'set' => 'set to',
'del' => 'deleted');
my $filter = &Apache::loncommon::display_filter('docslog')."\n".
@@ -1161,9 +1232,11 @@ sub docs_change_log {
my ($title,$url) = split(/\:/,$docslog{$id}{'logentry'}{'parameter_res'},3);
if ($title eq '') {
($title) = ($url =~ m{/([^/]+)$});
+ } elsif ($is_supp) {
+ $title = &Apache::loncommon::parse_supplemental_title($title);
}
$r->print(&LONCAPA::map::qtescape($title).':
');
- foreach my $parameter ('randompick','hiddenresource','encrypturl','randomorder') {
+ foreach my $parameter ('randompick','hiddenresource','encrypturl','randomorder','gradable') {
if ($docslog{$id}{'logentry'}{'parameter_action_'.$parameter}) {
# FIXME: internationalization seems wrong here
$r->print('- '.
@@ -1360,7 +1433,7 @@ sub print_paste_buffer {
}
my @currpaste = split(/,/,$env{'docs.markedcopies'});
- my ($pasteitems,@pasteable);
+ my ($pasteitems,@pasteable,$same_institution,$checkedsameinst);
my $clipboardcount = 0;
# Construct identifiers for current contents of user's paste buffer
@@ -1373,11 +1446,13 @@ sub print_paste_buffer {
($url ne '')) {
$clipboardcount ++;
my ($is_external,$othercourse,$fromsupp,$is_uploaded_map,$parent,
- $canpaste,$nopaste,$othercrs,$areachange,$is_exttool);
+ $canpaste,$nopaste,$othercrs,$areachange,$is_exttool,$toolcdom,
+ $toolcnum,$marker);
my $extension = (split(/\./,$env{'docs.markedcopy_url_'.$suffix}))[-1];
if ($url =~ m{^(?:/adm/wrapper/ext|(?:http|https)(?::|:))//} ) {
$is_external = 1;
- } elsif ($url =~ m{^/adm/$match_domain/$match_courseid/\d+/ext\.tool$}) {
+ } elsif ($url =~ m{^/adm/($match_domain)/($match_courseid)/(\d+)/ext\.tool$}) {
+ ($toolcdom,$toolcnum,$marker) = ($1,$2,$3);
$is_exttool = 1;
}
if ($folder =~ /^supplemental/) {
@@ -1415,11 +1490,42 @@ sub print_paste_buffer {
if ($cid ne $env{'request.course.id'}) {
my ($srcdom,$srcnum) = split(/_/,$cid);
if ($env{"user.priv.cm./$srcdom/$srcnum"} =~ /\Q:mdc&F\E/) {
- if (($is_exttool) && ($srcdom ne $coursedom)) {
- $canpaste = 0;
- $nopaste = &mt('Paste from another domain unavailable.');
- } else {
- $othercrs = '
'.&mt('(from another course)');
+ if ($is_exttool) {
+ if ($toolcdom ne $coursedom) {
+ $canpaste = 0;
+ $nopaste = &mt('Paste from another domain unavailable.');
+ } elsif ($toolcnum ne $coursenum) {
+ my %toolsettings =
+ &Apache::lonnet::dump('exttool_'.$marker,$toolcdom,$toolcnum);
+ my %tooltypes = &Apache::loncommon::usable_exttools();
+ if ((($toolsettings{'id'} =~ /^c\d+$/) && (!$tooltypes{'crs'})) ||
+ (($toolsettings{'id'} =~ /^\d+$/) && (!$tooltypes{'dom'}))) {
+ $canpaste = 0;
+ $nopaste = &mt('Paste from another course unavailable.');
+ } elsif ($toolsettings{'id'} =~ /^c\d+$/) {
+ unless ($checkedsameinst) {
+ my $primary_id = &Apache::lonnet::domain($coursedom,'primary');
+ my $intdom = &Apache::lonnet::internet_dom($primary_id);
+ if ($intdom ne '') {
+ my $internet_names =
+ &Apache::lonnet::get_internet_names($Apache::lonnet::perlvar{'lonHostID'});
+ if (ref($internet_names) eq 'ARRAY') {
+ if (grep(/^\Q$intdom\E$/,@{$internet_names})) {
+ $same_institution = 1;
+ }
+ }
+ }
+ $checkedsameinst = 1;
+ }
+ if ($same_institution) {
+ $othercrs = '
'.&mt('(from another course)');
+ } else {
+ $nopaste = &mt('Paste from another course unavailable.');
+ }
+ } else {
+ $othercrs = '
'.&mt('(from another course)');
+ }
+ }
}
} else {
$canpaste = 0;
@@ -1718,9 +1824,12 @@ sub do_paste_from_buffer {
return();
}
- my (%msgs,%before,%after,@dopaste,%is_map,%notinsupp,%notincrs,%notindom,%duplicate,
- %prefixchg,%srcdom,%srcnum,%srcmapidx,%marktomove,$save_err,$lockerrors,$allresult);
-
+ my (%msgs,%before,%after,@dopaste,%is_map,%notinsupp,%notincrs,%notindom,
+ %othcrstool,%duplicate,%prefixchg,%srcdom,%srcnum,%srcmapidx,
+ %marktomove,$save_err,$lockerrors,$allresult,%currcrsltitools,
+ %currltititles,$currltimax,$gotcrsltitools);
+ $currltimax = 0;
+ $gotcrsltitools = 0;
foreach my $suffix (@topaste) {
my $url=&LONCAPA::map::qtescape($env{'docs.markedcopy_url_'.$suffix});
my $cid=&LONCAPA::map::qtescape($env{'docs.markedcopy_crs_'.$suffix});
@@ -1746,7 +1855,9 @@ sub do_paste_from_buffer {
}
$srcdom{$suffix} = $srcd;
$srcnum{$suffix} = $srcn;
- } elsif ($url =~ m{^/adm/$match_domain/$match_courseid/\d+/ext\.tool$}) {
+ } elsif (($url =~ m{^/res/lib/templates/\w+\.problem$}) ||
+ ($url =~ m{^/adm/$match_domain/$match_username/\d+/(bulletinboard|smppg)$}) ||
+ ($url =~ m{^/adm/$match_domain/$match_courseid/\d+/ext\.tool$})) {
my ($srcd,$srcn) = split(/_/,$cid);
# When paste buffer was populated using an active role in a different course
# check for mdc privilege in the course from which the resource was pasted
@@ -1757,22 +1868,39 @@ sub do_paste_from_buffer {
}
}
# When buffer was populated using an active role in a different course
-# disallow pasting of External Tool if course is in a different domain.
- if ($srcd ne $coursedom) {
- $notindom{$suffix} = 1;
- next;
- }
- $srcdom{$suffix} = $srcd;
- $srcnum{$suffix} = $srcn;
- } elsif (($url =~ m{^/res/lib/templates/\w+\.problem$}) ||
- ($url =~ m{^/adm/$match_domain/$match_username/\d+/(bulletinboard|smppg)$})) {
- my ($srcd,$srcn) = split(/_/,$cid);
-# When paste buffer was populated using an active role in a different course
-# check for mdc privilege in the course from which the resource was pasted
- if (($srcd ne $coursedom) || ($srcn ne $coursenum)) {
- unless ($env{"user.priv.cm./$srcd/$srcn"} =~ /\Q:mdc&F\E/) {
- $notincrs{$suffix} = 1;
+# disallow pasting of External Tool if course is in a different domain,
+# or if External Tool use is not permitted in this course.
+ if ($url =~ m{^/adm/($match_domain)/($match_courseid)/(\d+)/ext\.tool$}) {
+ my ($toolcdom,$toolcnum,$marker) = ($1,$2,$3);
+ if ($toolcdom ne $coursedom) {
+ $notindom{$suffix} = 1;
next;
+ } elsif ($toolcnum ne $coursenum) {
+ my %toolsettings =
+ &Apache::lonnet::dump('exttool_'.$marker,$toolcdom,$toolcnum);
+ my %tooltypes = &Apache::loncommon::usable_exttools();
+ if ((($toolsettings{'id'} =~ /^c\d+$/) && (!$tooltypes{'crs'})) ||
+ (($toolsettings{'id'} =~ /^\d+$/) && (!$tooltypes{'dom'}))) {
+ $othcrstool{$suffix} = 1;
+ next;
+ }
+ if ($toolsettings{'id'} =~ /^c\d+$/) {
+ unless ($gotcrsltitools) {
+ %currcrsltitools =
+ &Apache::lonnet::get_course_lti($coursenum,$coursedom,'consumer');
+ foreach my $item (sort(keys(%currcrsltitools))) {
+ if (ref($currcrsltitools{$item}) eq 'HASH') {
+ $currltimax ++;
+ if (ref($currltititles{$currcrsltitools{$item}{'title'}}) eq 'ARRAY') {
+ push(@{$currltititles{$currcrsltitools{$item}{'title'}}},$item);
+ } else {
+ $currltititles{$currcrsltitools{$item}{'title'}} = [$item];
+ }
+ }
+ }
+ $gotcrsltitools = 1;
+ }
+ }
}
}
$srcdom{$suffix} = $srcd;
@@ -1783,7 +1911,6 @@ sub do_paste_from_buffer {
if ($url=~/\.(page|sequence)$/) {
$is_map{$suffix} = 1;
}
-
if ($url =~ m{^/uploaded/$match_domain/$match_courseid/([^/]+)}) {
my $oldprefix = $1;
# When pasting content from Main Content to Supplemental Content and vice versa
@@ -1828,7 +1955,8 @@ sub do_paste_from_buffer {
%msgs = &Apache::lonlocal::texthash (
notinsupp => 'Paste failed: content type is not supported within Supplemental Content',
notincrs => 'Paste failed: Item is from a different course which you do not have rights to edit.',
- notindom => 'Paste failed: Item is an external tool from a course in a different donain.',
+ notindom => 'Paste failed: Item is an external tool from a course in a different domain.',
+ othcrstool => 'Paste failed: Item is an external tool from a different course, for which use is not allowed in this course.',
duplicate => 'Paste failed: only one instance of a particular published sequence or page is allowed within each course.',
);
@@ -1857,7 +1985,9 @@ sub do_paste_from_buffer {
# Retrieve information about all course maps in main content area
my $allmaps = {};
- my (@toclear,%mapurls,%lockerrs,%msgerrs,%results,$donechk);
+ my (@toclear,%mapurls,%lockerrs,%msgerrs,%results,$donechk,
+ @updatetoolsenc,$updatetoolscache,$checkedsameinst,
+ $same_institution);
# Loop over the items to paste
foreach my $suffix (@dopaste) {
@@ -1946,17 +2076,37 @@ sub do_paste_from_buffer {
cdom => $coursedom,
cnum => $coursenum,
);
+ if ($prefix eq 'ext.tool') {
+ if ($prefixchg{$suffix} eq 'docstosupp') {
+ $info{'delgradable'} = 1;
+ }
+ }
if (($srcdom{$suffix} =~ /^$match_domain$/) && ($srcnum{$suffix} =~ /^$match_courseid$/)) {
unless (($srcdom{$suffix} eq $coursedom) && ($srcnum{$suffix} eq $coursenum)) {
$fromothercrs = 1;
$info{'cdom'} = $srcdom{$suffix};
$info{'cnum'} = $srcnum{$suffix};
+ unless ($checkedsameinst) {
+ my $primary_id = &Apache::lonnet::domain($coursedom,'primary');
+ my $intdom = &Apache::lonnet::internet_dom($primary_id);
+ if ($intdom ne '') {
+ my $internet_names =
+ &Apache::lonnet::get_internet_names($Apache::lonnet::perlvar{'lonHostID'});
+ if (ref($internet_names) eq 'ARRAY') {
+ if (grep(/^\Q$intdom\E$/,@{$internet_names})) {
+ $same_institution = 1;
+ }
+ }
+ }
+ $checkedsameinst = 1;
+ }
}
}
unless (($env{'form.docs.markedcopy_options_'.$suffix} eq 'move') && (!$fromothercrs)) {
my (%lockerr,$msg);
my ($newurl,$result,$errtext) =
- &dbcopy(\%info,$coursedom,$coursenum,\%lockerr);
+ &dbcopy(\%info,$coursedom,$coursenum,\%lockerr,\%currltititles,
+ \$currltimax,\@updatetoolsenc,\$updatetoolscache,$same_institution);
if ($result eq 'ok') {
$url = $newurl;
$title=&mt('Copy of').' '.$title;
@@ -2037,6 +2187,18 @@ sub do_paste_from_buffer {
©_templated_files($url,$srcdom{$suffix},$srcnum{$suffix},$srcmapidx{$suffix},
$coursedom,$coursenum,$template,$newidx,"$folder.$container");
}
+ } elsif ($url =~ /ext\.tool$/) {
+ if (($newidx) && ($folder=~/^default/)) {
+ my $marker = (split(m{/},$url))[4];
+ my %toolsettings = &Apache::lonnet::dump('exttool_'.$marker,$coursedom,$coursenum);
+ my $val = 'no';
+ if ($toolsettings{'gradable'}) {
+ $val = 'yes';
+ }
+ &LONCAPA::map::storeparameter($newidx,'parameter_0_gradable',$val,
+ 'string_yesno');
+ &remember_parms($newidx,'gradable','set',$val);
+ }
}
$LONCAPA::map::resources[$newidx]=$title.':'.&LONCAPA::map::qtunescape($url).
':'.$ext.':normal:res';
@@ -2113,6 +2275,10 @@ sub do_paste_from_buffer {
}
}
}
+ if (($updatetoolscache) || (@updatetoolsenc)) {
+ &update_ltitools_caches($coursedom,$coursenum,$updatetoolscache,
+ \@updatetoolsenc);
+ }
&clear_from_buffer(\@toclear,\@currpaste);
my $msgsarray;
foreach my $suffix (keys(%msgs)) {
@@ -2161,6 +2327,30 @@ sub clear_from_buffer {
return $numdel;
}
+sub update_ltitools_caches {
+ my ($coursedom,$coursenum,$updatetoolscache,$updatetoolsenc) = @_;
+ my $hashid=$coursedom.'_'.$coursenum;
+ if ($updatetoolscache) {
+ &Apache::lonnet::devalidate_cache_new('courseltitools',$hashid);
+ }
+ if ((ref($updatetoolsenc) eq 'ARRAY') &&
+ (@{$updatetoolsenc})) {
+ my @ids=&Apache::lonnet::current_machine_ids();
+ my $updatedone;
+ foreach my $lonhost (@{$updatetoolsenc}) {
+ if (grep(/^\Q$lonhost\E$/,@ids)) {
+ unless ($updatedone) {
+ &Apache::lonnet::devalidate_cache_new('crsltitoolsenc',$hashid);
+ }
+ $updatedone = 1;
+ } else {
+ &Apache::lonnet::remote_devalidate_cache($lonhost,["crsltitoolsenc:$hashid"]);
+ }
+ }
+ }
+ return;
+}
+
sub get_newmap_url {
my ($url,$folder,$prefixchg,$coursedom,$coursenum,$srcdom,$srcnum,
$titleref,$allmaps,$newurls) = @_;
@@ -2222,7 +2412,8 @@ sub get_newmap_url {
}
sub dbcopy {
- my ($dbref,$coursedom,$coursenum,$lockerrorsref) = @_;
+ my ($dbref,$coursedom,$coursenum,$lockerrorsref,$currltititles,
+ $currltimax,$updatetoolsenc,$updatetoolscache,$same_institution) = @_;
my ($url,$result,$errtext);
if (ref($dbref) eq 'HASH') {
$url = $dbref->{'src'};
@@ -2266,6 +2457,117 @@ sub dbcopy {
my %contents=&Apache::lonnet::dump($db_name,
$dbref->{'cdom'},
$dbref->{'cnum'});
+ my ($toolcopyerror,$toolpassback,$toolroster,%toolinfo,$oldtoolid,$defincrs);
+ if ($url eq '/adm/'.$dbref->{'cdom'}.'/'.$dbref->{'cnum'}."/$marker/ext.tool") {
+ if ($contents{'id'} =~ /^(|c)(\d+)$/) {
+ $oldtoolid = $2;
+ if ($1 eq 'c') {
+ $defincrs = 1;
+ %toolinfo =
+ &Apache::lonnet::get('ltitools',[$oldtoolid],$dbref->{'cdom'},$dbref->{'cnum'});
+ } else {
+ %toolinfo= &Apache::lonnet::get_domain_lti($dbref->{'cdom'},'consumer');
+ }
+ if (ref($toolinfo{$oldtoolid}) eq 'HASH') {
+ if ($toolinfo{$oldtoolid}{'passback'}) {
+ $toolpassback = 1;
+ }
+ if ($toolinfo{$oldtoolid}{'roster'}) {
+ $toolroster = 1;
+ }
+ } else {
+ $toolcopyerror = 1;
+ $errtext = &mt('Could not retrieve original settings for pasted external tool.');
+ }
+ }
+ unless (($dbref->{'cnum'} eq $coursenum) && ($dbref->{'cdom'} eq $coursedom)) {
+ $url = "/adm/$coursedom/$coursenum/$marker/ext.tool";
+ if ($contents{'crstitle'} ne '') {
+ $contents{'crstitle'} = $env{'course.'.$coursedom.'_'.$coursenum.'.description'};
+ }
+ if (($defincrs) && (!$toolcopyerror)) {
+ my %newtool;
+ my $oldcdom = $dbref->{'cdom'};
+ my $oldcnum = $dbref->{'cnum'};
+ my $title = $toolinfo{$oldtoolid}{'title'};
+ if (ref($currltititles) eq 'HASH') {
+ if (exists($currltititles->{$title})) {
+ $title .= ' (copied from another course)';
+ }
+ }
+ my ($newid,$iderror) =
+ &Apache::lonnet::get_ltitools_id('course',$coursedom,$coursenum,$title);
+ if ($newid =~ /^\d+$/) {
+ %{$newtool{$newid}} = %{$toolinfo{$oldtoolid}};
+ $newtool{$newid}{'title'} = $title;
+ if (ref($currltimax)) {
+ $newtool{$newid}{'order'} = $$currltimax;
+ }
+ if ($newtool{$newid}{'image'} =~ m{^\Q/uploaded/$oldcdom/$oldcnum/toollogo/$oldtoolid/\E([^/]+)$}) {
+ my $fname = $1;
+ my $content = &Apache::lonnet::getfile($newtool{$newid}{'image'});
+ if ($content eq '-1') {
+ delete($newtool{$newid}{'image'});
+ } else {
+ $env{'form.'.$suffix.'.image'} = $content;
+ my $newlogo =
+ &Apache::lonnet::finishuserfileupload($coursenum,$coursedom,$suffix.'.image',"toollogo/$newid/$fname");
+ delete($env{'form.'.$suffix.'.image'});
+ if ($newlogo =~ m{^/uploaded/}) {
+ $newtool{$newid}{'image'} = $newlogo;
+ } else {
+ delete($newtool{$newid}{'image'});
+ }
+ }
+ }
+ my $newusable;
+ if ($same_institution) {
+ my %oldtoolsenc = &Apache::lonnet::eget('nohist_toolsenc',[$oldtoolid],$oldcdom,$oldcnum);
+ if (ref($oldtoolsenc{$oldtoolid}) eq 'HASH') {
+ my %newtoolsenc;
+ %{$newtoolsenc{$newid}} = %{$oldtoolsenc{$oldtoolid}};
+ my $putres = &Apache::lonnet::put('nohist_toolsenc',\%newtoolsenc,$coursedom,$coursenum,1);
+ if ($putres eq 'ok') {
+ if (ref($updatetoolsenc) eq 'ARRAY') {
+ my $newhome = &Apache::lonnet::homeserver($coursenum,$coursedom);
+ unless (grep(/^\Q$newhome\E$/,@{$updatetoolsenc})) {
+ push(@{$updatetoolsenc},$newhome);
+ }
+ }
+ $newusable = 1;
+ }
+ }
+ }
+ if ($newtool{$newid}{'usable'}) {
+ unless ($newusable) {
+ delete($newtool{$newid}{'usable'});
+ }
+ }
+ my $putres = &Apache::lonnet::put('ltitools',\%newtool,$coursedom,$coursenum);
+ if ($putres eq 'ok') {
+ $contents{'id'} = "c$newid";
+ if (ref($updatetoolscache)) {
+ $$updatetoolscache ++;
+ }
+ if (ref($currltititles->{$title}) eq 'ARRAY') {
+ push(@{$currltititles->{$title}},$newid);
+ } else {
+ $currltititles->{$title} = [$newid];
+ }
+ if (ref($currltimax)) {
+ $$currltimax ++;
+ }
+ } else {
+ $toolcopyerror = 1;
+ $errtext = &mt('Unable to save external tool definition in Course Settings.');
+ }
+ } else {
+ $toolcopyerror = 1;
+ $errtext = &mt('Unable to retrieve new tool ID when adding external tool definition to Course Settings.');
+ }
+ }
+ }
+ }
if (exists($contents{'uploaded.photourl'})) {
my $photo = $contents{'uploaded.photourl'};
my ($subdir,$fname) =
@@ -2285,10 +2587,40 @@ sub dbcopy {
}
}
$db_name =~ s{_\d*$ }{_$suffix}x;
- $result=&Apache::lonnet::put($db_name,\%contents,
- $coursedom,$coursenum);
- if ($result eq 'ok') {
- $url =~ s{/(\d*)/(smppg|bulletinboard|ext\.tool)$}{/$suffix/$2}x;
+ if ($prefix eq 'exttool') {
+ unless ($toolcopyerror) {
+ foreach my $key ('oldgradesecret','gradesecret','gradesecretdate','oldrostersecret','rostersecret','rostersecretdate') {
+ if (exists($contents{$key})) {
+ delete($contents{$key});
+ }
+ }
+ if ($dbref->{'delgradable'}) {
+ if (exists($contents{'gradable'})) {
+ delete($contents{'gradable'});
+ }
+ }
+ if ($toolpassback) {
+ if ($contents{'gradable'}) {
+ my $gradesecret = UUID::Tiny::create_uuid_as_string(UUID_V4);
+ $contents{'gradesecret'} = $gradesecret;
+ $contents{'gradesecretdate'} = time;
+ }
+ }
+ if ($toolroster) {
+ my $rostersecret = UUID::Tiny::create_uuid_as_string(UUID_V4);
+ $contents{'rostersecret'} = $rostersecret;
+ $contents{'rostersecretdate'} = time;
+ }
+ }
+ }
+ if (($prefix eq 'exttool') && ($toolcopyerror)) {
+ $result = 'error';
+ } else {
+ $result=&Apache::lonnet::put($db_name,\%contents,
+ $coursedom,$coursenum);
+ if ($result eq 'ok') {
+ $url =~ s{/(\d*)/(smppg|bulletinboard|ext\.tool)$}{/$suffix/$2}x;
+ }
}
}
if (($freedlock ne 'ok') && (ref($lockerrorsref) eq 'HASH')) {
@@ -2443,7 +2775,23 @@ sub contained_map_check {
if ($token->[1] eq 'resource') {
next if ($token->[2]->{'type'} eq 'zombie');
my $ressrc = $token->[2]->{'src'};
- if ($folder =~ /^supplemental/) {
+ if ($ressrc =~ m{^/adm/($match_domain)/($match_courseid)/(\d+)/ext\.tool$}) {
+ my ($srcdom,$srcnum,$marker) = ($1,$2,$3);
+ unless ($srcdom eq $coursedom) {
+ $removefrommap->{$url}{$token->[2]->{'id'}} = $ressrc;
+ next;
+ }
+ unless ($srcnum eq $coursenum) {
+ my %toolsettings =
+ &Apache::lonnet::dump('exttool_'.$marker,$srcdom,$srcnum);
+ my %tooltypes = &Apache::loncommon::usable_exttools();
+ if ((($toolsettings{'id'} =~ /^c\d+$/) && (!$tooltypes{'crs'})) ||
+ (($toolsettings{'id'} =~ /^\d+$/) && (!$tooltypes{'dom'}))) {
+ $removefrommap->{$url}{$token->[2]->{'id'}} = $ressrc;
+ next;
+ }
+ }
+ } elsif ($folder =~ /^supplemental/) {
unless (&supp_pasteable($ressrc)) {
$removefrommap->{$url}{$token->[2]->{'id'}} = $ressrc;
next;
@@ -2566,16 +2914,38 @@ sub url_paste_fixups {
$changed = 1;
}
}
- } elsif ($ressrc =~ m{^/adm/($match_domain)/($match_courseid)/.+$}) {
+ } elsif ($ressrc =~ m{^/adm/($match_domain)/($match_courseid)/(.+)$}) {
next if ($skip);
my $srcdom = $1;
my $srcnum = $2;
+ my $rem = $3;
+ my ($is_exttool,$exttoolchg);
+ if ($rem =~ m{\d+/ext\.tool$}) {
+ $is_exttool = 1;
+ }
if (($srcdom ne $cdom) || ($srcnum ne $cnum)) {
$rewrites->{$oldurl}{$id} = $ressrc;
$dbcopies->{$oldurl}{$id}{'src'} = $ressrc;
$dbcopies->{$oldurl}{$id}{'cdom'} = $srcdom;
$dbcopies->{$oldurl}{$id}{'cnum'} = $srcnum;
$changed = 1;
+ if ($is_exttool) {
+ $exttoolchg = 1;
+ }
+ } elsif (($is_exttool) &&
+ ($env{'form.docs.markedcopy_options'} ne 'move')) {
+ $dbcopies->{$oldurl}{$id}{'src'} = $ressrc;
+ $dbcopies->{$oldurl}{$id}{'cdom'} = $srcdom;
+ $dbcopies->{$oldurl}{$id}{'cnum'} = $srcnum;
+ $changed = 1;
+ $exttoolchg = 1;
+ }
+ if (($is_exttool) && ($prefixchg)) {
+ if ($oldurl =~ m{^/uploaded/$match_domain/$match_courseid/default}) {
+ if ($exttoolchg) {
+ $dbcopies->{$oldurl}{$id}{'delgradable'} = 1;
+ }
+ }
}
} elsif ($ressrc =~ m{^/adm/$match_domain/$match_username/\d+/(smppg|bulletinboard)$}) {
if (($fromcdom ne $cdom) || ($fromcnum ne $cnum) ||
@@ -2622,7 +2992,9 @@ sub apply_fixups {
$oldurl,$url,$caller) = @_;
my (%rewrites,%zombies,%removefrommap,%removeparam,%dbcopies,%retitles,
%params,%newsubdir,%before,%after,%copies,%docmoves,%mapmoves,@msgs,
- %resdatacopy,%lockerrors,$lockmsg);
+ %resdatacopy,%lockerrors,$lockmsg,%currcrsltitools,$gotcrsltitools,
+ %currltititles,$currltimax);
+ $currltimax = 0;
if (ref($updated) eq 'HASH') {
if (ref($updated->{'rewrites'}) eq 'HASH') {
%rewrites = %{$updated->{'rewrites'}};
@@ -2773,6 +3145,7 @@ sub apply_fixups {
}
}
}
+ my ($updatetoolscache,@updatetoolsenc,$same_institution,$checkedsameinst);
foreach my $key (keys(%updates)) {
my (%torewrite,%toretitle,%toremove,%remparam,%currparam,%zombie,%newdb);
if (ref($rewrites{$key}) eq 'HASH') {
@@ -2793,10 +3166,63 @@ sub apply_fixups {
if (ref($dbcopies{$key}) eq 'HASH') {
foreach my $idx (keys(%{$dbcopies{$key}})) {
if (ref($dbcopies{$key}{$idx}) eq 'HASH') {
+ my $oldurl = $dbcopies{$key}{$idx}{'src'};
+ my $oldcdom = $dbcopies{$key}{$idx}{'cdom'};
+ my $oldcnum = $dbcopies{$key}{$idx}{'cnum'};
+ my $oldmarker;
+ if ($oldurl =~ m{^\Q/adm/$oldcdom/$oldcnum/\E(\d+)/ext\.tool$}) {
+ $oldmarker = $1;
+ unless (($gotcrsltitools) ||
+ (($oldcnum eq $cnum) && ($oldcdom eq $cdom))) {
+ my %oldtoolsettings=&Apache::lonnet::dump('exttool_'.$oldmarker,$oldcdom,$oldcnum);
+ if ($oldtoolsettings{'id'} =~ /^c\d+$/) {
+ unless ($gotcrsltitools) {
+ %currcrsltitools =
+ &Apache::lonnet::get_course_lti($cnum,$cdom,'consumer');
+ foreach my $item (sort(keys(%currcrsltitools))) {
+ if (ref($currcrsltitools{$item}) eq 'HASH') {
+ $currltimax ++;
+ if (ref($currltititles{$currcrsltitools{$item}{'title'}}) eq 'ARRAY') {
+ push(@{$currltititles{$currcrsltitools{$item}{'title'}}},$item);
+ } else {
+ $currltititles{$currcrsltitools{$item}{'title'}} = [$item];
+ }
+ }
+ }
+ $gotcrsltitools = 1;
+ }
+ unless ($checkedsameinst) {
+ my $primary_id = &Apache::lonnet::domain($cdom,'primary');
+ my $intdom = &Apache::lonnet::internet_dom($primary_id);
+ if ($intdom ne '') {
+ my $internet_names =
+ &Apache::lonnet::get_internet_names($Apache::lonnet::perlvar{'lonHostID'});
+ if (ref($internet_names) eq 'ARRAY') {
+ if (grep(/^\Q$intdom\E$/,@{$internet_names})) {
+ $same_institution = 1;
+ }
+ }
+ }
+ $checkedsameinst = 1;
+ }
+ }
+ }
+ }
my ($newurl,$result,$errtext) =
- &dbcopy($dbcopies{$key}{$idx},$cdom,$cnum,\%lockerrors);
+ &dbcopy($dbcopies{$key}{$idx},$cdom,$cnum,\%lockerrors,\%currltititles,
+ \$currltimax,\@updatetoolsenc,\$updatetoolscache,$same_institution);
if ($result eq 'ok') {
$newdb{$idx} = $newurl;
+ if ($newurl =~ /ext\.tool$/) {
+ if ($torewrite{$idx} eq "/adm/$oldcdom/$oldcnum/$oldmarker/ext.tool") {
+ if ($newurl =~ m{^\Q/adm/$cdom/$cnum/\E(\d+)/ext.tool$}) {
+ my $newmarker = $1;
+ unless ($oldmarker eq $newmarker) {
+ $torewrite{$idx} = "/adm/$oldcdom/$oldcnum/$newmarker/ext.tool";
+ }
+ }
+ }
+ }
} elsif (ref($errors) eq 'HASH') {
$errors->{$key} = 1;
}
@@ -2935,6 +3361,10 @@ sub apply_fixups {
}
}
}
+ if (($updatetoolscache) || (@updatetoolsenc)) {
+ &update_ltitools_caches($cdom,$cnum,$updatetoolscache,
+ \@updatetoolsenc);
+ }
}
return ('ok',\@msgs,$lockmsg);
}
@@ -3114,8 +3544,9 @@ sub update_parameter {
sub handle_edit_cmd {
my ($coursenum,$coursedom) =@_;
+ my $haschanges = 0;
if ($env{'form.cmd'} eq '') {
- return 0;
+ return $haschanges;
}
my ($cmd,$idx)=split('_',$env{'form.cmd'});
@@ -3130,19 +3561,19 @@ sub handle_edit_cmd {
&LONCAPA::map::makezombie($LONCAPA::map::order[$idx]);
}
splice(@LONCAPA::map::order, $idx, 1);
-
+ $haschanges = 1;
} elsif ($cmd eq 'cut') {
&LONCAPA::map::makezombie($LONCAPA::map::order[$idx]);
splice(@LONCAPA::map::order, $idx, 1);
-
+ $haschanges = 1;
} elsif ($cmd eq 'up'
&& ($idx) && (defined($LONCAPA::map::order[$idx-1]))) {
@LONCAPA::map::order[$idx-1,$idx] = @LONCAPA::map::order[$idx,$idx-1];
-
+ $haschanges = 1;
} elsif ($cmd eq 'down'
&& defined($LONCAPA::map::order[$idx+1])) {
@LONCAPA::map::order[$idx+1,$idx] = @LONCAPA::map::order[$idx,$idx+1];
-
+ $haschanges = 1;
} elsif ($cmd eq 'rename') {
my $comment = &LONCAPA::map::qtunescape($env{'form.title'});
if ($comment=~/\S/) {
@@ -3152,11 +3583,9 @@ sub handle_edit_cmd {
# Devalidate title cache
my $renamed_url=&LONCAPA::map::qtescape($url);
&Apache::lonnet::devalidate_title_cache($renamed_url);
-
- } else {
- return 0;
+ $haschanges = 1;
}
- return 1;
+ return $haschanges;
}
sub editor {
@@ -3645,7 +4074,7 @@ sub multiple_check_form {
return unless (ref($listsref) eq 'HASH');
my $disabled;
unless ($canedit) {
- $disabled = 'disabled="disabled"';
+ $disabled = ' disabled="disabled"';
}
my $output =
'
SUPDOCFORM