--- loncom/interface/lonrequestcourse.pm 2020/07/20 11:30:21 1.95.2.5.2.1 +++ loncom/interface/lonrequestcourse.pm 2025/01/10 22:45:55 1.118 @@ -1,7 +1,7 @@ # The LearningOnline Network # Request a course # -# $Id: lonrequestcourse.pm,v 1.95.2.5.2.1 2020/07/20 11:30:21 raeburn Exp $ +# $Id: lonrequestcourse.pm,v 1.118 2025/01/10 22:45:55 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # @@ -163,6 +163,23 @@ sub handler { } if ($canreq) { + if (($env{'form.crstype'} eq 'lti') && ($env{'request.lti.login'}) && + ($env{'form.lti.reqrole'} eq 'cc') && ($env{'form.lti.reqcrs'}) && + ($env{'form.lti.sourcecrs'} ne '')) { + if ($action eq 'process') { + if ($can_request{'lti'}) { + my %domconfig = &Apache::lonnet::get_dom('configuration',['requestcourses'],$dom); + &process_textbook_request($r,$dom,$action,\%domdefs,\%domconfig,\%can_request,'lti'); + } else { + $r->print(&header('Course Request','','','',{ 'only_body' => 1}). + '
'. + '

'.&mt('You do not have privileges to request creation of LTI courses.').'

'. + '
'. + &Apache::loncommon::end_page()); + } + } + return OK; + } if (($env{'form.crstype'} eq 'textbook') || (scalar(keys(%can_request)) == 1) && ($can_request{'textbook'})) { my %domconfig = &Apache::lonnet::get_dom('configuration',['requestcourses'],$dom); @@ -200,7 +217,8 @@ sub handler { } } else { if ($can_request{'textbook'}) { - &print_textbook_form($r,$dom,\@incdoms,\%domdefs,$domconfig{'requestcourses'},\%can_request); + &print_textbook_form($r,$dom,\@incdoms,\%domdefs,$domconfig{'requestcourses'}, + \%can_request,'textbook'); } else { &textbook_request_disabled($r,$dom,$action,\%can_request); } @@ -319,8 +337,6 @@ sub handler { $jscript = &Apache::lonhtmlcommon::set_form_elements($elementsref,\%stored); if ($state eq 'courseinfo') { $jscript .= &cloning_javascript(); - } elsif ($state eq 'process') { - $jscript .= &processing_javascript(); } } } @@ -432,17 +448,6 @@ function setCloneDisplay(courseForm) { END } -sub processing_javascript { - return <<"END"; -function hideProcessing() { - if (document.getElementById('processing')) { - document.getElementById('processing').style.display="none"; - } -} - -END -} - sub get_breadcrumbs { my ($dom,$action,$state,$states,$trail) = @_; my ($crumb,$newinstcode,$codechk,$checkedcode,$numtitles,$description); @@ -721,9 +726,6 @@ sub onload_action { if ($state eq 'courseinfo') { $loaditems{'onload'} .= 'javascript:setCloneDisplay(document.requestcrs);'; } - if ($state eq 'process') { - $loaditems{'onload'} .= 'javascript:hideProcessing();'; - } } return \%loaditems; } @@ -769,6 +771,7 @@ function check_can_request(crschoice,act var unofficial = ''; var community = ''; var textbook = ''; + var placement = ''; END if (ref($can_request) eq 'HASH') { foreach my $item (keys(%{$can_request})) { @@ -782,6 +785,7 @@ END unofficial => 'You are not permitted to request creation of an unofficial course in this domain.', community => 'You are not permitted to request creation of a community in this domain.', textbook => 'You are not permitted to request creation of a textbook course in this domain', + placement => 'You are not permitted to request creation of a placement test in this domain', all => 'You must choose a specific course type when making a new course request.', allt => '"All types" is not allowed.', ); @@ -811,9 +815,16 @@ END return false; } } else { - if (actionchoice == 'new') { - alert('$js_lt{'all'}'+'\\n'+'$js_lt{'allt'}'); - return false; + if (crschoice == 'placement') { + if (placement != 1) { + alert("$js_lt{'placement'}"); + return false; + } + } else { + if (actionchoice == 'new') { + alert('$js_lt{'all'}'+'\\n'+'$js_lt{'allt'}'); + return false; + } } } } @@ -824,7 +835,7 @@ END END my ($pagetitle,$pageinfo,$domaintitle,$earlyout); if (ref($can_request) eq 'HASH') { - if (($can_request->{'official'}) || ($can_request->{'unofficial'}) || $can_request->{'textbook'}) { + if (($can_request->{'official'}) || ($can_request->{'unofficial'}) || ($can_request->{'textbook'}) || ($can_request->{'placement'})) { if ($can_request->{'community'}) { $pagetitle = 'Course/Community Requests'; $pageinfo = &mt('Request creation of a new course or community, or review your pending requests.'); @@ -1001,6 +1012,8 @@ END $title = &mt('Pending requests for unofficial courses'); } elsif ($env{'form.crstype'} eq 'textbook') { $title = &mt('Pending requests for textbook courses'); + } elsif ($env{'form.crstype'} eq 'textbook') { + $title = &mt('Pending requests for placement tests'); } else { $title = &mt('Pending course/community requests'); } @@ -2102,7 +2115,8 @@ sub print_personnel_menu { official => 'Requestor is automatically assigned Course Coordinator role.', ); $lt{'unofficial'} = $lt{'official'}; - $lt{'textbook'} = $lt{'textbook'}; + $lt{'textbook'} = $lt{'official'}; + $lt{'placement'} = $lt{'official'}; $output .= &Apache::lonhtmlcommon::row_headline(). '

'.&Apache::loncommon::help_open_topic('Course_Request_Personnel').' '.$lt{$crstype}.' '.&mt('Include other personnel?').'

'; } @@ -2118,14 +2132,15 @@ sub print_personnel_menu { } } } - for (my $i=0; $i<$persontotal; $i++) { + my ($trusted,$untrusted) = &Apache::lonnet::trusted_domains('enroll',$dom); + for (my $i=0; $i<$persontotal; $i++) { my @linkargs = map { 'person_'.$i.'_'.$_ } (@items); my $linkargstr = join("','",@linkargs); my $uname_form = ''; my $onchange = 'javascript:fix_domain('."'$formname','person_".$i."_dom',". "'person_".$i."_hidedom','person_".$i."_uname'".');'; my $udom_form = &Apache::loncommon::select_dom_form($dom,'person_'.$i.'_dom','', - 1,$onchange). + 1,$onchange,undef,$trusted,$untrusted). ''; my %form_elems; foreach my $item (@items) { @@ -2396,7 +2411,7 @@ sub print_cancel_request { &Apache::loncommon::start_data_table_row(). ''.$history{details}{'cdescr'}.''. &Apache::lonlocal::locallocaltime($timestamp).''. - ''.$showtype.''. + ''.&mt($showtype).''. &Apache::loncommon::end_data_table_row(). &Apache::loncommon::end_data_table(). '
'; @@ -2530,7 +2545,7 @@ sub print_request_logs { if (ref($domconfig{'requestcourses'}) eq 'HASH') { if (ref($domconfig{'requestcourses'}{'uniquecode'}) eq 'HASH') { if ($curr{'crstype'} eq 'any') { - my @types = qw(official unofficial community textbook); + my @types = qw(official unofficial community textbook placement); foreach my $type (@types) { if ($domconfig{'requestcourses'}{'uniquecode'}{$type}) { $showuniquecode = 1; @@ -2698,7 +2713,7 @@ sub reqstatus_names { rejected => 'Request rejected', cancelled => 'Request cancelled', ); - if (($crstype eq 'official') || ($crstype eq 'unofficial') || ($crstype eq 'textbook')) { + if (($crstype eq 'official') || ($crstype eq 'unofficial') || ($crstype eq 'textbook') || ($crstype eq 'placement')) { $statusnames{'created'} = &mt('Course created'); } elsif ($crstype eq 'community') { $statusnames{'created'} = &mt('Community created'); @@ -2711,7 +2726,7 @@ sub requestlog_display_filter { my $nolink = 1; my $output = ''; my $startform = @@ -2750,7 +2765,7 @@ sub requestlog_display_filter { $typename = $typenames->{$crstype}; } } - $output .= ''."\n"; + $output .= ''."\n"; } $output .= ''; } @@ -3111,6 +3126,7 @@ sub courseinfo_form { &js_escape(\%js_lt); $js_lt{'unofficial'} = $js_lt{'official'}; $js_lt{'textbook'} = $js_lt{'official'}; + $js_lt{'placement'} = $js_lt{'official'}; my $js_validate = <<"ENDJS"; +ENDCLOSE + my %prog_state = &Apache::lonhtmlcommon::Create_PrgWin($r,undef,$preamble); + &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,&mt('Processing ...')); $r->rflush(); if (ref($details) eq 'HASH') { if ($details->{'clonecrs'}) { @@ -3851,6 +3900,9 @@ sub process_request { 'autocreate',$details,\$logmsg,$clonemsg,\$newusermsg, \$addresult,\$enrollcount,\$response,\$keysmsg,\%domdefs, \%longroles,\$code,\%customitems); + &Apache::lonhtmlcommon::Update_PrgWin($r,\%prog_state,&mt('Finished!')); + &Apache::lonhtmlcommon::Close_PrgWin($r,\%prog_state); + $r->print($closure); if (ref($postprocess) eq 'HASH') { $customized = $postprocess->{'createdcustomized'}; } @@ -3867,7 +3919,7 @@ sub process_request { if (($code) || ((ref($postprocess) eq 'HASH') && (($postprocess->{'createdweb'}) || ($postprocess->{'createdmsg'})))) { $output .= ¬ification_information($disposition,$env{'user.name'}.':'.$env{'user.domain'}, - $dom,$cnum,$now,$code,$postprocess); + $dom,$cnum,$now,$code,$postprocess,$crstype); } if ($code) { $reqhash{'code'} = $code; @@ -3980,7 +4032,7 @@ sub process_request { unless ($disposition eq 'pending') { $output .= '
'. ¬ification_information($disposition,$req_notifylist, - $dom,$cnum,$now); + $dom,$cnum,$now,'','',$crstype); } } else { $reqstatus = 'domainerror'; @@ -4013,25 +4065,25 @@ sub process_request { $output .= '

'.&mt('Your course request has been updated').'

'; } if ($disposition eq 'approval') { - $output .= ¬ification_information($disposition,$req_notifylist,$dom,$cnum,$now); + $output .= ¬ification_information($disposition,$req_notifylist,$dom,$cnum,$now,'','',$crstype); } } if ($disposition eq 'approval') { if ((ref($postprocess) eq 'HASH') && ((ref($postprocess->{'queuedmsg'}) eq 'HASH') || ($postprocess->{'queuedweb'}))) { - ¬ification_information($disposition,undef,$dom,$cnum,$now,undef,$postprocess); + ¬ification_information($disposition,undef,$dom,$cnum,$now,undef,$postprocess,$crstype); $customized = $postprocess->{'createdcustomized'}; } } elsif ($disposition eq 'pending') { my $pendingform; if ($crstype ne 'official') { - $pendingform = &pending_validation_form($dom,$cnum,$crstype,$now,$token, + $pendingform = &pending_validation_form($r,$dom,$cnum,$crstype,$now,$token, $lonhost,$env{'form.cdescr'}); } if ($pendingform) { $output .= $pendingform; } else { - $output .= ¬ification_information($disposition,undef,$dom,$cnum,$now,undef,$postprocess); + $output .= ¬ification_information($disposition,undef,$dom,$cnum,$now,undef,$postprocess,$crstype); } if (ref($postprocess) eq 'HASH') { $customized = $postprocess->{'createdcustomized'}; @@ -4229,7 +4281,7 @@ sub update_requestors_roles { } sub notification_information { - my ($disposition,$req_notifylist,$dom,$cnum,$now,$code,$postprocess) = @_; + my ($disposition,$req_notifylist,$dom,$cnum,$now,$code,$postprocess,$crstype) = @_; my %emails = &Apache::loncommon::getemails(); my $address; if (($emails{'permanentemail'} ne '') || ($emails{'notification'} ne '')) { @@ -4245,12 +4297,46 @@ sub notification_information { if ($address ne '') { $output.= &mt('An e-mail will also be sent to: [_1] when this occurs.',$address).'
'; } + my %possemails; + my $fullname = &Apache::loncommon::plainname($env{'user.name'}, + $env{'user.domain'}); + my $emailto = &Apache::loncommon::build_recipient_list(undef,'requestsmail',$dom); + if ($emailto) { + map { $possemails{$_} = 1; } (split(/,/,$emailto)); + } if ($req_notifylist) { - my $fullname = &Apache::loncommon::plainname($env{'user.name'}, - $env{'user.domain'}); + if ($emailto) { + foreach my $recip (split(/,/,$req_notifylist)) { + my ($uname,$udom) = split(/:/,$recip); + my %emails = &Apache::loncommon::getemails($uname,$udom); + foreach my $type ('permanentemail','notification') { + if ((exists($emails{$type})) && ($emails{$type} ne '')) { + my @to = split(/,/,$emails{$type}); + foreach my $addr (@to) { + if (($addr ne '') && ($addr =~ m/\@/)) { + if (exists($possemails{$addr})) { + delete($possemails{$addr}); + } + } + } + } + } + } + } my $sender = $env{'user.name'}.':'.$env{'user.domain'}; &Apache::loncoursequeueadmin::send_selfserve_notification($req_notifylist,"$fullname ($env{'user.name'}:$env{'user.domain'})", - 'undef',$env{'form.cdescr'},$now,'coursereq',$sender); + undef,$env{'form.cdescr'},$now,'coursereq',$sender,'','',$crstype); + } +# +# If domain configuration for "E-mail addresses and helpform" has values set +# for "E-mail from course requests requiring approval", send email to those +# addresse(es) when a course request is queued, pending approval, unless +# the email address will already receive a notification email, because of +# values set for "Receive notification of course requests requiring approval" +# in "Request creation of courses" configuration item. +# + if ($emailto && keys(%possemails)) { + ¬ify_admin($dom,$crstype,$env{'form.cdescr'},"$fullname ($env{'user.name'}:$env{'user.domain'})",$now,\%possemails); } if (ref($postprocess) eq 'HASH') { if (ref($postprocess->{'queuedmsg'}) eq 'ARRAY') { @@ -4331,7 +4417,7 @@ sub notification_information { $type = 'uniquecode'; } &Apache::loncoursequeueadmin::send_selfserve_notification($recipient,$addmsg,$dom.'_'.$cnum,$env{'form.cdescr'}, - $now,$type,$sender); + $now,$type,$sender,'','',$crstype); } } } else { @@ -4342,8 +4428,54 @@ sub notification_information { return $output; } +sub notify_admin { + my ($dom,$crstype,$contextdesc,$textstr,$timestamp,$emailsref) = @_; + if ((ref($emailsref) eq 'HASH') && (keys(%{$emailsref}))) { + my $emailto = join(',',sort(keys(%{$emailsref}))); + my (@rawmsg,$rawsubj,$msgtxt); + if ($crstype eq 'community') { + $rawsubj = 'Community request to review'; + $msgtxt = 'Creation of the following community: [_1]was requested by [_2] on [_3].'; + } else { + $rawsubj = 'Course request to review'; + $msgtxt = 'Creation of the following course: [_1]was requested by [_2] on [_3].'; + } + $timestamp =&Apache::lonlocal::locallocaltime($timestamp); + push(@rawmsg,{ + mt => $msgtxt, + args => ["\n $contextdesc\n",$textstr,$timestamp], + }, + { + mt =>'[_1]A Domain Coordinator will use: [_2]Main Menu -> Course and community creation -> Approve or reject requests[_3]to display a list of pending requests, which can either be approved or rejected.', + args => ["\n","\n\n","\n\n"], + }); + + my $sender_lh = &Apache::loncommon::user_lang($env{'user.name'},$env{'user.domain'}); + my $subject = &mt_user($sender_lh,$rawsubj); + my $message = ''; + foreach my $item (@rawmsg) { + if (ref($item) eq 'HASH') { + if (ref($item->{args}) eq 'ARRAY') { + $message .= &mt_user($sender_lh,$item->{mt},@{$item->{args}})."\n"; + } else { + $message .= &mt_user($sender_lh,$item->{mt})."\n"; + } + } + } + my $chgmail = "To: $emailto\n". + "Subject: $subject\n". + "Content-type: text/plain\; charset=UTF-8\n". + "MIME-Version: 1.0\n\n". + "$message\n\n"; + if (open(my $mailh, "|/usr/lib/sendmail -oi -t -odb")) { + print $mailh $chgmail; + close($mailh); + } + } +} + sub pending_validation_form { - my ($cdom,$cnum,$crstype,$now,$token,$lonhost,$cdesc) = @_; + my ($r,$cdom,$cnum,$crstype,$now,$token,$lonhost,$cdesc) = @_; my $output; my %postvalues = ( 'owner' => $env{'user.name'}.':'.$env{'user.domain'}, @@ -4379,6 +4511,8 @@ sub pending_validation_form { my $hostname = &Apache::lonnet::hostname($lonhost); my $protocol = $Apache::lonnet::protocol{$lonhost}; $protocol = 'http' if ($protocol ne 'https'); + my $alias = &Apache::lonnet::use_proxy_alias($r,$lonhost); + $hostname = $alias if ($alias ne ''); my $crscreator = $protocol.'://'.$hostname.'/cgi-bin/createpending.pl'; $output .= ''."\n". ''."\n". @@ -4404,7 +4538,7 @@ sub check_autolimit { if (($crstype eq 'community') && (exists($crsroles{$cnum.':'.$cdom.':co'}))) { $count ++; - } elsif ((($crstype eq 'official') || ($crstype eq 'unofficial') || ($crstype eq 'textbook')) && + } elsif ((($crstype eq 'official') || ($crstype eq 'unofficial') || ($crstype eq 'textbook') || ($crstype eq 'placement')) && (exists($crsroles{$cnum.':'.$cdom.':cc'}))) { $count ++; } @@ -4455,7 +4589,7 @@ sub retrieve_settings { } $env{'form.datemode'} = $reqinfo{'datemode'}; $env{'form.dateshift'} = $reqinfo{'dateshift'}; - $env{'form.tinyurls'} = $reqinfo{'tinyurls'}; + $env{'form.tinyurls'} = $reqinfo{'tinyurls'}; if ($reqinfo{'crstype'} eq 'official') { $env{'form.autoadds'} = $reqinfo{'autoadds'}; $env{'form.autodrops'} = $reqinfo{'autodrops'}; @@ -4607,9 +4741,11 @@ sub generate_date_items { } sub print_textbook_form { - my ($r,$dom,$incdoms,$domdefs,$settings,$can_request) = @_; + my ($r,$dom,$incdoms,$domdefs,$settings,$can_request,$crstype,$formhash) = @_; my (%prefab,%ordered,%numprefab); - my $crstype = 'textbook'; + if ($crstype eq '') { + $crstype = 'textbook'; + } # # Retrieve list of prefabricated courses (textbook courses and templates) cloneable by user # @@ -4669,7 +4805,7 @@ sub print_textbook_form { owner => $courseinfo{'internal.courseowner'}, releaserequired => $courseinfo{'internal.releaserequired'}, type => $courseinfo{'type'}, - }; + }; } } @@ -4726,31 +4862,37 @@ sub print_textbook_form { my $jscript = &textbook_request_javascript(\%numprefab,$numcurrent,$numdomcourses,$customvalidationjs); $jscript .= $customjs; - my %loaditems; + my (%loaditems,$args); $loaditems{'onload'} = 'javascript:uncheckAllRadio();'.$customonload; - $r->print(&header('Course Request',$jscript,\%loaditems)); + if ($crstype eq 'lti') { + $args = { 'only_body' => 1}; + } + $r->print(&header('Course Request',$jscript,\%loaditems,undef,$args)); if (ref($can_request) eq 'HASH') { - unless ((scalar(keys(%{$can_request})) == 1) && ($can_request->{'textbook'})) { + unless (((scalar(keys(%{$can_request})) == 1) && ($can_request->{'textbook'})) || + ($crstype eq 'lti')) { &Apache::lonhtmlcommon::add_breadcrumb( { href => '/adm/requestcourse', text => 'Pick action', }); } } - &Apache::lonhtmlcommon::add_breadcrumb({text=>'Course Request'}); - $r->print(&Apache::lonhtmlcommon::breadcrumbs('Course Requests','Course_Requests')); + unless ($crstype eq 'lti') { + &Apache::lonhtmlcommon::add_breadcrumb({text=>'Course Request'}); + $r->print(&Apache::lonhtmlcommon::breadcrumbs('Course Requests','Course_Requests')); - &startContentScreen($r,'textbookrequests'); + &startContentScreen($r,'textbookrequests'); # # Show domain selector form, if required. # - if (@{$incdoms} > 1) { - my $onchange = 'this.form.submit()'; - $r->print(''. - '
'.&mt('Domain').''. - &Apache::loncommon::select_dom_form($dom,'showdom','',1,$onchange,$incdoms). - '
'); + if (@{$incdoms} > 1) { + my $onchange = 'this.form.submit()'; + $r->print('
'. + '
'.&mt('Domain').''. + &Apache::loncommon::select_dom_form($dom,'showdom','',1,$onchange,$incdoms). + '
'); + } } # @@ -4869,7 +5011,7 @@ sub print_textbook_form { '
'. - ''. + ''. '
'. ''.$lt{'dpl'}.'
'. - ''. + ''. '
'. ''.$lt{'dpl'}.'
'. ''.&mt('Records/page:').'
'. - &Apache::lonmeta::selectbox('show',$curr->{'show'},undef, + &Apache::lonmeta::selectbox('show',$curr->{'show'},'',undef, (&mt('all'),5,10,20,50,100,1000,10000)). '