![]() ![]() | ![]() |
- Customization for GCI_3. - Backport 1.14.
1: # The LearningOnline Network 2: # Utilities to administer domain course requests and course self-enroll requests 3: # 4: # $Id: loncoursequeueadmin.pm,v 1.12.2.5 2010/01/15 05:26:51 raeburn Exp $ 5: # 6: # Copyright Michigan State University Board of Trustees 7: # 8: # This file is part of the LearningOnline Network with CAPA (LON-CAPA). 9: # 10: # LON-CAPA is free software; you can redistribute it and/or modify 11: # it under the terms of the GNU General Public License as published by 12: # the Free Software Foundation; either version 2 of the License, or 13: # (at your option) any later version. 14: # 15: # LON-CAPA is distributed in the hope that it will be useful, 16: # but WITHOUT ANY WARRANTY; without even the implied warranty of 17: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 18: # GNU General Public License for more details. 19: # 20: # You should have received a copy of the GNU General Public License 21: # along with LON-CAPA; if not, write to the Free Software 22: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 23: # 24: # /home/httpd/html/adm/gpl.txt 25: # 26: # http://www.lon-capa.org/ 27: # 28: ### 29: 30: =head1 NAME 31: 32: Apache::loncoursequeueadmin.pm 33: 34: =head1 SYNOPSIS 35: 36: Utilities used by domain coordinators to administer queued course creation requests, 37: and by course coordinators for queued self-enrollment requests, and by general 38: users to display their queued self-enrollment requests. 39: 40: This is part of the LearningOnline Network with CAPA project 41: described at http://www.lon-capa.org. 42: 43: =head1 SUBROUTINES 44: 45: =over 46: 47: =item send_selfserve_notification() 48: 49: =item display_queued_requests() 50: 51: =item update_request_queue() 52: 53: =item get_student_counts() 54: 55: =item course_creation() 56: 57: =item build_batchcreatehash() 58: 59: =item can_clone_course() 60: 61: =item queued_selfenrollment() 62: 63: =back 64: 65: =cut 66: 67: package Apache::loncoursequeueadmin; 68: 69: use strict; 70: use Apache::Constants qw(:common :http); 71: use Apache::lonnet; 72: use Apache::loncommon; 73: use Apache::lonmsg; 74: use Apache::lonlocal; 75: use Apache::lonuserutils; 76: use LONCAPA; 77: 78: sub send_selfserve_notification { 79: my ($notifylist,$textstr,$cid,$contextdesc,$timestamp,$context,$sender, 80: $approvedlist,$rejectedlist,$crstype) = @_; 81: # FIXME locallocaltime needs to be able to take $sender_lh as an argument 82: # so this can be localized to the recipients date display format/time zone 83: $timestamp =&Apache::lonlocal::locallocaltime($timestamp); 84: my $msgcc; 85: my ($rawsubj,@rawmsg,$subject,$message,$reviewer,$msgtxt); 86: if ($context eq 'coursemanagers') { 87: $rawsubj = 'Self-enrollment requests processed'; 88: push(@rawmsg,{ 89: mt => 'Enrollment requests in the following course: [_1] have been processed.', 90: args => ["\n $contextdesc"], 91: }); 92: } elsif ($context eq 'domainmanagers') { 93: $rawsubj = 'Course/Community requests reviewed'; 94: push(@rawmsg,{ 95: mt => 'Course/Community creation requests in the following domain: [_1] have been reviewed.', 96: args => ["\n $contextdesc"], 97: }); 98: if (ref($textstr) eq 'ARRAY') { 99: push(@rawmsg,@{$textstr}); 100: } 101: } elsif ($context eq 'enroller') { 102: $rawsubj = 'Enrollment request'; 103: if ($crstype eq 'community') { 104: $msgtxt = 'Your request for enrollment in the following community: [_1]requested on [_2]has been reviewed by a Coordinator.' 105: } else { 106: $msgtxt = 'Your request for enrollment in the following course: [_1]requested on [_2]has been reviewed by a Course Coordinator.'; 107: } 108: push(@rawmsg,{ 109: mt => $msgtxt, 110: args => ["\n ".$contextdesc.",\n",$timestamp.",\n"], 111: 112: }); 113: if (ref($textstr) eq 'ARRAY') { 114: push(@rawmsg,@{$textstr}); 115: } 116: } elsif ($context eq 'courserequestor') { 117: if ($crstype eq 'Community') { 118: $rawsubj = 'Community request'; 119: $msgtxt = 'Your request for creation of the following community: [_1]requested on [_2]has been reviewed by a Domain Coordinator.'; 120: } else { 121: $rawsubj = 'Course request'; 122: $msgtxt = 'Your request for creation of the following course: [_1]requested on [_2]has been reviewed by a Domain Coordinator.'; 123: } 124: push(@rawmsg,{ 125: mt => $msgtxt, 126: args => ["\n".$contextdesc.",\n",$timestamp.",\n"], 127: 128: }); 129: if (ref($textstr) eq 'ARRAY') { 130: push(@rawmsg,@{$textstr}); 131: } 132: } elsif ($context eq 'coursereq') { 133: if ($crstype eq 'community') { 134: $rawsubj = 'Community request to review'; 135: $msgtxt = 'Creation of the following community: [_1]was requested by [_2] on [_3].'; 136: } else { 137: $rawsubj = 'Course request to review'; 138: $msgtxt = 'Creation of the following course: [_1]was requested by [_2] on [_3].'; 139: } 140: push(@rawmsg,{ 141: mt => $msgtxt, 142: args => ["\n $contextdesc\n",$textstr,$timestamp], 143: }, 144: { 145: mt =>'[_1]As Domain Coordinator, use: [_2]Main Menu -> Course and community creation -> Approve or reject requests[_3]to display a list of pending requests, which you can either approve or reject.', 146: args => ["\n","\n\n ","\n\n"], 147: }); 148: } elsif ($context eq 'selfenrollreq') { 149: $rawsubj = 'Self-enrollment request'; 150: if ($crstype eq 'community') { 151: $msgtxt = 'Enrollment in the following community: [_1] was requested by [_2] on [_3].' 152: } else { 153: $msgtxt = 'Enrollment in the following course: [_1] was requested by [_2] on [_3].' 154: } 155: push(@rawmsg,{ 156: mt => $msgtxt, 157: args => ["\n $contextdesc\n",$textstr,$timestamp."\n"], 158: }); 159: my $directions; 160: if ($crstype eq 'community') { 161: $directions = 'As Coordinator, use: [_1]Main Menu -> Manage Community Users -> Enrollment Requests[_2]to display a list of pending enrollment requests, which you can either approve or reject.'; 162: } else { 163: $directions = 'As Course Coordinator, use: [_1]Main Menu -> Manage Course Users -> Enrollment Requests[_2]to display a list of pending enrollment requests, which you can either approve or reject.'; 164: } 165: push(@rawmsg, 166: { 167: mt => $directions, 168: args => [" \n\n","\n"], 169: }); 170: 171: } 172: my @to_notify = split(/,/,$notifylist); 173: my $numsent = 0; 174: my @recusers; 175: my @recudoms; 176: foreach my $cc (@to_notify) { 177: my ($ccname,$ccdom) = split(/:/,$cc); 178: if (!exists($msgcc->{$ccname.':'.$ccdom})) { 179: push(@recusers,$ccname); 180: push(@recudoms,$ccdom); 181: $msgcc->{$ccname.':'.$ccdom}=''; 182: $numsent ++; 183: } 184: } 185: my %reciphash = ( 186: cc => $msgcc, 187: ); 188: my ($uname,$udom); 189: if ($sender =~ /:/) { 190: ($uname,$udom) = split(/:/,$sender); 191: } elsif ($context eq 'course') { 192: $uname = $sender; 193: my %courseinfo = &Apache::lonnet::coursedescription($cid); 194: $udom = $courseinfo{'num'}; 195: } 196: my %sentmessage; 197: my $stamp = time; 198: my $msgcount = &Apache::lonmsg::get_uniq(); 199: my $sender_lh = &Apache::loncommon::user_lang($uname,$udom,$cid); 200: $subject = &Apache::lonlocal::mt_user($sender_lh,$rawsubj); 201: $message = ''; 202: foreach my $item (@rawmsg) { 203: if (ref($item) eq 'HASH') { 204: $message .= &Apache::lonlocal::mt_user($sender_lh,$item->{mt},@{$item->{args}})."\n"; 205: } 206: } 207: &Apache::lonmsg::process_sent_mail($subject,'',$numsent,$stamp,$uname,$udom,$msgcount,$cid,$$,$message,\@recusers,\@recudoms); 208: my ($recipid,$recipstatus) = &Apache::lonmsg::store_recipients($subject,$uname,$udom,\%reciphash); 209: my $status; 210: foreach my $recip (sort(keys(%{$msgcc}))) { 211: my ($ccname,$ccdom) = split(/:/,$recip); 212: my $recip_lh = &Apache::loncommon::user_lang($ccname,$ccdom,$cid); 213: my $subject = &Apache::lonlocal::mt_user($sender_lh,$rawsubj); 214: my $message = ''; 215: foreach my $item (@rawmsg) { 216: if (ref($item) eq 'HASH') { 217: $message .= &Apache::lonlocal::mt_user($sender_lh,$item->{mt}, 218: @{$item->{args}})."\n"; 219: } 220: } 221: if ($context eq 'coursemanagers') { 222: if ($approvedlist) { 223: $message .= "\n\n".&Apache::lonlocal::mt_user($sender_lh,'Approved enrollments:')."\n".$approvedlist; 224: } 225: if ($rejectedlist) { 226: $message .= "\n\n".&Apache::lonlocal::mt_user($sender_lh,'Rejected enrollments:')."\n".$rejectedlist; 227: } 228: } elsif ($context eq 'domainmanagers') { 229: if ($approvedlist) { 230: $message .= "\n\n".&Apache::lonlocal::mt_user($sender_lh,'Approved course requests:')."\n".$approvedlist; 231: } 232: if ($rejectedlist) { 233: $message .= "\n\n".&Apache::lonlocal::mt_user($sender_lh,'Rejected course requests:')."\n".$rejectedlist; 234: } 235: } 236: $status .= &Apache::lonmsg::user_normal_msg($ccname,$ccdom,$subject,$message,undef,undef,undef,1,\%sentmessage,undef,undef,undef,1,$recipid).','; 237: } 238: $status =~ s/,$//; 239: return ($recipstatus,$status); 240: } 241: 242: sub display_queued_requests { 243: my ($context,$dom,$cnum) = @_; 244: my ($namespace,$formaction,$nextelement,%requesthash); 245: if ($context eq 'course') { 246: $formaction = '/adm/createuser'; 247: $namespace = 'selfenrollrequests'; 248: %requesthash = &Apache::lonnet::dump($namespace,$dom,$cnum); 249: $nextelement = '<input type="hidden" name="state" value="done" />'; 250: } else { 251: $formaction = '/adm/createcourse'; 252: $namespace = 'courserequestqueue'; 253: %requesthash = &Apache::lonnet::dump_dom($namespace,$dom,'_approval'); 254: $nextelement = '<input type="hidden" name="phase" value="requestchange" />'; 255: } 256: my ($output,%queue_by_date,%crstypes); 257: if (keys(%requesthash) > 0) { 258: $output = '<form method="post" name="changequeue" action="'.$formaction.'" />'."\n". 259: '<input type="hidden" name="action" value="'.$env{'form.action'}.'" />'."\n". 260: $nextelement."\n". 261: &Apache::loncommon::start_data_table(). 262: &Apache::loncommon::start_data_table_header_row(). 263: '<th>'.&mt('Action').'</th>'. 264: '<th>'.&mt('Requestor').'</th>'; 265: if ($context eq 'course') { 266: $output .= '<th>'.&mt('Section').'</th>'. 267: '<th>'.&mt('Date requested').'</th>'; 268: } else { 269: %crstypes = &Apache::lonlocal::texthash ( 270: official => 'Official course', 271: unofficial => 'Unofficial course', 272: community => 'Community', 273: ); 274: $output .= '<th>'.&mt('Type').'</th>'. 275: '<th>'.&mt('Date requested').'</th>'. 276: '<th>'.&mt('Details').'</th>'; 277: } 278: $output .= &Apache::loncommon::end_data_table_header_row(); 279: foreach my $item (keys(%requesthash)) { 280: my ($timestamp,$entry); 281: if ($context eq 'course') { 282: ($timestamp, my $usec) = split(/:/,$requesthash{$item}); 283: $entry = $item.':'.$usec; 284: } else { 285: $timestamp = $requesthash{$item}{'timestamp'}; 286: if (ref($requesthash{$item}) eq 'HASH') { 287: my ($cnum,$disposition) = split('_',$item); 288: $entry = $cnum.':'.$requesthash{$item}{'ownername'}.':'. 289: $requesthash{$item}{'ownerdom'}.':'. 290: $requesthash{$item}{'crstype'}.':'. 291: $requesthash{$item}{'description'}; 292: } 293: } 294: if ($entry ne '') { 295: if (exists($queue_by_date{$timestamp})) { 296: if (ref($queue_by_date{$timestamp}) eq 'ARRAY') { 297: push(@{$queue_by_date{$timestamp}},$entry); 298: } 299: } else { 300: @{$queue_by_date{$timestamp}} = ($entry); 301: } 302: } 303: } 304: my @sortedtimes = sort {$a <=> $b} (keys(%queue_by_date)); 305: my $count = 0; 306: foreach my $item (@sortedtimes) { 307: if (ref($queue_by_date{$item}) eq 'ARRAY') { 308: foreach my $request (sort(@{$queue_by_date{$item}})) { 309: my ($row,$approve,$reject,$showtime,$showsec,$namelink, 310: $detailslink,$crstype); 311: $showtime = &Apache::lonlocal::locallocaltime($item); 312: if ($context eq 'course') { 313: my ($puname,$pudom,$pusec) = split(/:/,$request); 314: $approve = $count.':'.$puname.':'.$pudom.':'.$pusec; 315: $reject = $puname.':'.$pudom; 316: $showsec = $pusec; 317: if ($showsec eq '') { 318: $showsec = &mt('none'); 319: } 320: $namelink = &Apache::loncommon::aboutmewrapper( 321: &Apache::loncommon::plainname($puname,$pudom), 322: $puname,$pudom); 323: 324: } else { 325: my ($cnum,$ownername,$ownerdom,$type,$cdesc)=split(/:/,$request,5); 326: $detailslink='<a href="javascript:opencoursereqdisplay('. 327: "'$dom','$cnum'".');">'.$cdesc.'</a>'; 328: $crstype = $type; 329: if (defined($crstypes{$type})) { 330: $crstype = $crstypes{$type}; 331: } 332: $approve = $count.':'.$cnum; 333: $reject = $cnum; 334: $namelink = &Apache::loncommon::aboutmewrapper( 335: &Apache::loncommon::plainname($ownername,$ownerdom), 336: $ownername,$ownerdom); 337: } 338: $row = '<td><span class="LC_nobreak"><label>'. 339: '<input type="checkbox" value="'.$approve.'" name="approvereq" />'.&mt('Approve').'</label></span><br />'. 340: '<span class="LC_nobreak"><label>'. 341: '<input type="checkbox" value="'.$reject.'" name="rejectreq" />'.&mt('Reject').'</label></span><br /></td>'. 342: '<td>'.$namelink.'</td>'."\n"; 343: if ($context eq 'course') { 344: $row .= '<td>'.$showsec.'</td>'."\n". 345: '<td>'.$showtime.'</td>'."\n"; 346: } else { 347: $row .= '<td>'.$crstype.'</td>'."\n". 348: '<td>'.$showtime.'</td>'."\n". 349: '<td>'.$detailslink.'</td>'."\n"; 350: } 351: $output .= &Apache::loncommon::start_data_table_row()."\n". 352: $row. 353: &Apache::loncommon::end_data_table_row()."\n"; 354: $count ++; 355: } 356: } 357: } 358: $output .= &Apache::loncommon::end_data_table(). 359: '<input type="submit" name="processqueue" value="'.&mt('Save'). 360: '" /></form>'; 361: } else { 362: if ($context eq 'course') { 363: $output .= &mt('There are currently no enrollment requests.'); 364: } else { 365: $output .= &mt('There are currently no course or community requests awaiting approval.'); 366: } 367: } 368: return $output; 369: } 370: 371: sub update_request_queue { 372: my ($context,$cdom,$cnum,$coursedesc) = @_; 373: my ($output,$access_start,$access_end,$limit,$cap,$notifylist,$namespace, 374: $stucounts,$idx,$classlist,%requesthash,$cid,$hostname,$protocol, 375: $domdesc,$now,$sender,$approvedmsg,$rejectedmsg,$beneficiary, 376: @existing,@missingreq,@invalidusers,@limitexceeded,@completed, 377: @processing_errors,@warn_approves,@warn_rejects,@approvals, 378: @rejections,@rejectionerrors,@nopermissions,%courseroles, 379: %communityroles,%domdefs,%approvalmsg,%rejectionmsg,$crstype, 380: @warn_coursereqs); 381: @approvals = &Apache::loncommon::get_env_multiple('form.approvereq'); 382: @rejections = &Apache::loncommon::get_env_multiple('form.rejectreq'); 383: $now = time; 384: $sender = $env{'user.name'}.':'.$env{'user.domain'}; 385: if ($context eq 'course') { 386: $namespace = 'selfenrollrequests'; 387: $beneficiary = 'enroller'; 388: $cid = $env{'request.course.id'}; 389: $crstype = lc(&Apache::loncommon::course_type()); 390: my $chome = &Apache::lonnet::homeserver($cnum,$cdom); 391: $hostname = &Apache::lonnet::hostname($chome); 392: $protocol = $Apache::lonnet::protocol{$chome}; 393: $protocol = 'http' if ($protocol ne 'https'); 394: %requesthash = &Apache::lonnet::dump($namespace,$cdom,$cnum); 395: $access_start = $env{'course.'.$cid.'.internal.selfenroll_start_access'}; 396: $access_end = $env{'course.'.$cid.'.internal.selfenroll_end_access'}; 397: $limit = $env{'course.'.$cid.'.internal.selfenroll_limit'}; 398: $cap = $env{'course.'.$cid.'.internal.selfenroll_cap'}; 399: $notifylist = $env{'course.'.$cid.'.internal.selfenroll_notifylist'}; 400: ($stucounts,$idx,$classlist) = &get_student_counts($cdom,$cnum); 401: $approvedmsg = [{ 402: mt => 'Your request for enrollment has been approved.', 403: }, 404: { 405: mt => 'Visit [_1], to log-in and access the course', 406: args => [$protocol.'://'.$hostname], 407: }]; 408: $rejectedmsg = [{ 409: mt => 'Your request for enrollment has not been approved.', 410: }]; 411: } else { 412: $domdesc = &Apache::lonnet::domain($cdom); 413: $namespace = 'courserequestqueue'; 414: $beneficiary = 'courserequestor'; 415: %requesthash = &Apache::lonnet::dump_dom($namespace,$cdom,'_approval'); 416: my $chome = &Apache::lonnet::domain($cdom,'primary'); 417: $hostname = &Apache::lonnet::hostname($chome); 418: $protocol = $Apache::lonnet::protocol{$chome}; 419: $protocol = 'http' if ($protocol ne 'https'); 420: my %domconfig = &Apache::lonnet::get_dom('configuration',['requestcourses'],$cdom); 421: if (ref($domconfig{'requestcourses'}) eq 'HASH') { 422: if (ref($domconfig{'requestcourses'}{'notify'}) eq 'HASH') { 423: $notifylist = $domconfig{'requestcourses'}{'notify'}{'approval'}; 424: } 425: } 426: $approvalmsg{'course'} = 427: [{ 428: mt => 'Your course request has been approved.', 429: }, 430: { 431: mt => 'Visit [_1], to log-in and access the course', 432: args => [$protocol.'://'.$hostname], 433: }]; 434: $rejectionmsg{'course'} = 435: [{ 436: mt => 'Your course request has not been approved.', 437: }]; 438: 439: $approvalmsg{'community'} = 440: [{ 441: mt => 'Your community request has been approved.', 442: }, 443: { 444: mt => 'Visit [_1], to log-in and access the community', 445: args => [$protocol.'://'.$hostname], 446: }]; 447: 448: $rejectionmsg{'community'} = 449: [{ 450: mt => 'Your community request has not been approved.', 451: }]; 452: 453: %domdefs = &Apache::lonnet::get_domain_defaults($cdom); 454: my @roles = &Apache::lonuserutils::roles_by_context('course'); 455: foreach my $role (@roles) { 456: $courseroles{$role}=&Apache::lonnet::plaintext($role,'Course'); 457: } 458: foreach my $role (@roles) { 459: $communityroles{$role}=&Apache::lonnet::plaintext($role,'Community'); 460: } 461: 462: } 463: foreach my $item (sort {$a <=> $b} @approvals) { 464: if ($context eq 'course') { 465: my ($num,$uname,$udom,$usec) = split(/:/,$item); 466: my $uhome = &Apache::lonnet::homeserver($uname,$udom); 467: if ($uhome ne 'no_host') { 468: if (exists($requesthash{$uname.':'.$udom})) { 469: if ($cdom eq 'gci' && $cnum eq '9615072b469884921gcil1') { 470: my $enresult = &enable_gci_submission($udom,$uname, 471: $access_end,$access_start); 472: } 473: if (exists($classlist->{$uname.':'.$udom})) { 474: if (ref($classlist->{$uname.':'.$udom}) eq 'ARRAY') { 475: if (($classlist->{$uname.':'.$udom}->[$idx->{'status'}] eq 'Active') || 476: ($classlist->{$uname.':'.$udom}->[$idx->{'status'}] eq 'Future')) { 477: push(@existing,$uname.':'.$udom); 478: next; 479: } 480: } 481: } 482: } else { 483: push(@missingreq,$uname.':'.$udom); 484: next; 485: } 486: if (!grep(/^\Q$item\E$/,@rejections)) { 487: if ($limit eq 'allstudents') { 488: if ($stucounts->{$limit} >= $cap) { 489: push(@limitexceeded,$uname.':'.$udom); 490: last; 491: } 492: } elsif ($limit eq 'selfenrolled') { 493: if ($stucounts->{$limit} >= $cap) { 494: push(@limitexceeded,$uname.':'.$udom); 495: last; 496: } 497: } 498: my $result = 499: &Apache::lonnet::modify_student_enrollment($udom,$uname,undef,undef,undef,undef,undef,$usec,$access_end,$access_start,'selfenroll',undef,$cdom.'_'.$cnum,1); 500: if ($result eq 'ok') { 501: push(@completed,$uname.':'.$udom); 502: $stucounts->{'allstudents'} ++; 503: $stucounts->{'selfenrolled'} ++; 504: &send_selfserve_notification($uname.':'.$udom,$approvedmsg, 505: $cid,$coursedesc,$now,$beneficiary,$sender,undef,undef,$crstype); 506: my %userrequest = ( 507: $cdom.'_'.$cnum => { 508: timestamp => $now, 509: section => $usec, 510: adjudicator => $env{'user.name'}.':'.$env{'user.domain'}, 511: status => 'approved', 512: } 513: ); 514: my $userresult = 515: &Apache::lonnet::put($namespace,\%userrequest,$udom,$uname); 516: if ($userresult ne 'ok') { 517: push(@warn_approves,$uname.':'.$udom); 518: } elsif ($udom eq 'gci') { 519: my %changehash = ( 520: 'reqcrsotherdom.unofficial' => 'gcitest:autolimit=', 521: ); 522: my $reqresult = &Apache::lonnet::put('environment',\%changehash, 523: $udom,$uname); 524: if ($reqresult ne 'ok') { 525: push(@warn_coursereqs,$uname.':'.$udom); 526: } 527: } 528: } else { 529: push(@processing_errors,$uname.':'.$udom); 530: } 531: } 532: } else { 533: push(@invalidusers,$uname.':'.$udom); 534: } 535: } else { 536: my ($num,$cnum) = split(':',$item); 537: if (ref($requesthash{$cnum.'_approval'}) eq 'HASH') { 538: if (&Apache::lonnet::homeserver($cnum,$cdom) eq 'no_host') { 539: my $ownername = $requesthash{$cnum.'_approval'}{'ownername'}; 540: my $ownerdom = $requesthash{$cnum.'_approval'}{'ownerdom'}; 541: $crstype = $requesthash{$cnum.'_approval'}{'crstype'}; 542: my $coursedesc = $requesthash{$cnum.'_approval'}{'description'}; 543: my $longroles = \%courseroles; 544: if ($crstype eq 'community') { 545: $longroles = \%communityroles; 546: } 547: my $cancreate; 548: if ($cdom eq $ownerdom) { 549: if (&Apache::lonnet::usertools_access($ownername,$ownerdom,$crstype, 550: undef,'requestcourses')) { 551: $cancreate = 1; 552: } 553: } else { 554: my %userenv = &Apache::lonnet::userenvironment($ownerdom,$ownername,'reqcrsotherdom.'.$crstype); 555: if ($userenv{'reqcrsotherdom.'.$crstype}) { 556: my @doms = split(',',$userenv{'reqcrsotherdom.'.$crstype}); 557: if (grep(/^\Q$cdom\E:/,@doms)) { 558: $cancreate = 1; 559: } 560: } 561: } 562: if ($cancreate) { 563: my $requestkey = $cdom.'_'.$cnum; 564: my %history = 565: &Apache::lonnet::restore($requestkey,'courserequests', 566: $ownerdom,$ownername); 567: if ((ref($history{'details'}) eq 'HASH') && 568: ($history{'disposition'} eq 'approval')) { 569: my ($logmsg,$newusermsg,$addresult,$enrollcount,$response,$keysmsg); 570: my $result = &course_creation($cdom,$cnum,$context,$history{'details'},\$logmsg, 571: \$newusermsg,\$addresult,\$enrollcount, 572: \$response,\$keysmsg,\%domdefs,$longroles); 573: if ($result eq 'created') { 574: if ($crstype eq 'community') { 575: $approvedmsg = $approvalmsg{'community'}; 576: } else { 577: $approvedmsg = $approvalmsg{'course'}; 578: } 579: push(@completed,$cnum); 580: &send_selfserve_notification($ownername.':'.$ownerdom,$approvedmsg, 581: $cid,$coursedesc,$now,$beneficiary,$sender,undef,undef,$crstype); 582: my %reqhash = ( 583: reqtime => $history{'reqtime'}, 584: crstype => $history{'crstype'}, 585: details => $history{'details'}, 586: disposition => $history{'disposition'}, 587: status => 'created', 588: adjudicator => $env{'user.name'}.':'. 589: $env{'user.domain'}, 590: ); 591: my $userresult = 592: &Apache::lonnet::store_userdata(\%reqhash,$requestkey, 593: 'courserequests',$ownerdom,$ownername); 594: if ($userresult eq 'ok') { 595: my %status = ( 596: 'status:'.$cdom.':'.$cnum => 'created' 597: ); 598: my $statusresult = 599: &Apache::lonnet::put('courserequests',\%status, 600: $ownerdom,$ownername); 601: if ($statusresult ne 'ok') { 602: push(@warn_approves,$cnum); 603: } 604: } 605: if ($userresult ne 'ok') { 606: push(@warn_approves,$cnum); 607: } 608: } else { 609: push(@processing_errors,$cnum); 610: } 611: } else { 612: push(@processing_errors,$cnum); 613: } 614: } else { 615: push(@nopermissions,$cnum); 616: } 617: } else { 618: push(@existing,$cnum); 619: } 620: } else { 621: push(@missingreq,$cnum); 622: } 623: } 624: } 625: my @changes = (@completed,@rejections); 626: if ($context eq 'domain') { 627: @changes = map {$_.'_approval'} (@changes); 628: } 629: if (@rejections) { 630: foreach my $item (@rejections) { 631: if ($context eq 'course') { 632: my $user = $item; 633: &send_selfserve_notification($user,$rejectedmsg,$cid,$coursedesc, 634: $now,$beneficiary,$sender,undef,undef,$crstype); 635: my ($uname,$udom) = split(/:/,$user); 636: my %userrequest = ( 637: $cdom.'_'.$cnum => { 638: timestamp => $now, 639: adjudicator => $env{'user.name'}.':'.$env{'user.domain'}, 640: status => 'rejected', 641: } 642: ); 643: my $userresult = 644: &Apache::lonnet::put($namespace,\%userrequest,$udom,$uname); 645: if ($userresult ne 'ok') { 646: push(@warn_rejects,$user); 647: } 648: } else { 649: my $cnum = $item; 650: if (ref($requesthash{$cnum.'_approval'}) eq 'HASH') { 651: if (&Apache::lonnet::homeserver($cnum,$cdom) eq 'no_host') { 652: my $requestkey = $cdom.'_'.$cnum; 653: my $ownername = $requesthash{$cnum.'_approval'}{'ownername'}; 654: my $ownerdom = $requesthash{$cnum.'_approval'}{'ownerdom'}; 655: my $coursedesc = $requesthash{$cnum.'_approval'}{'description'}; 656: $crstype = $requesthash{$cnum.'_approval'}{'crstype'}; 657: if ($crstype eq 'community') { 658: $rejectedmsg = $rejectionmsg{'community'}; 659: } else { 660: $rejectedmsg = $rejectionmsg{'course'}; 661: } 662: &send_selfserve_notification($ownername.':'.$ownerdom,$rejectedmsg, 663: $cid,$coursedesc,$now,$beneficiary, 664: $sender,undef,undef,$crstype); 665: my %history = 666: &Apache::lonnet::restore($requestkey,'courserequests', 667: $ownerdom,$ownername); 668: if ((ref($history{'details'}) eq 'HASH') && 669: ($history{'disposition'} eq 'approval')) { 670: my %reqhash = ( 671: reqtime => $history{'reqtime'}, 672: crstype => $history{'crstype'}, 673: details => $history{'details'}, 674: disposition => $history{'disposition'}, 675: status => 'rejected', 676: adjudicator => $env{'user.name'}.':'.$env{'user.domain'}, 677: ); 678: my $userresult = 679: &Apache::lonnet::store_userdata(\%reqhash,$requestkey, 680: 'courserequests',$ownerdom,$ownername); 681: if ($userresult eq 'ok') { 682: my %status = ( 683: 'status:'.$cdom.':'.$cnum => 'rejected' 684: ); 685: my $statusresult = 686: &Apache::lonnet::put('courserequests',\%status, 687: $ownerdom,$ownername); 688: if ($statusresult ne 'ok') { 689: push(@warn_rejects,$cnum); 690: } 691: } else { 692: push(@warn_rejects,$cnum); 693: } 694: } else { 695: push(@warn_rejects,$cnum); 696: } 697: } else { 698: push(@existing,$cnum); 699: } 700: } else { 701: push(@rejectionerrors,$cnum); 702: } 703: } 704: } 705: } 706: if (@changes) { 707: my $delresult; 708: if ($context eq 'course') { 709: $delresult = &Apache::lonnet::del($namespace,\@changes,$cdom,$cnum); 710: } else { 711: $delresult = &Apache::lonnet::del_dom($namespace,\@changes,$cdom); 712: } 713: if ($delresult eq 'ok') { 714: my $namelink = 715: &Apache::loncommon::plainname($env{'user.name'},$env{'user.domain'}).' ('.$env{'user.name'}.':'.$env{'user.domain'}.')'; 716: my ($chgmsg,$approvedlist,$rejectedlist); 717: if ($context eq 'course') { 718: $chgmsg = "'Action was taken on the following enrollment requests by [_1].',$namelink"; 719: if (@completed) { 720: $approvedlist = join("\n",@completed); 721: if ($crstype eq 'community') { 722: $output .= '<p>'.&mt('The following were enrolled in the community:').'<ul>'; 723: } else { 724: $output .= '<p>'.&mt('The following were enrolled in the course:').'<ul>'; 725: } 726: foreach my $user (@completed) { 727: my ($uname,$udom) = split(/:/,$user); 728: my $userlink = 729: &Apache::loncommon::aboutmewrapper(&Apache::loncommon::plainname($uname,$udom),$uname,$udom); 730: $output .= '<li>'.$userlink.'</li>'; 731: } 732: $output .= '</ul></p>'; 733: } 734: if (@rejections) { 735: $rejectedlist = join("\n",@rejections); 736: $output .= '<p>'.&mt('The following enrollment requests were rejected:').'<ul>'; 737: foreach my $user (@rejections) { 738: $output .= '<li>'.$user.'</li>'; 739: } 740: $output .= '</ul></p>'; 741: } 742: if ($notifylist ne '') { 743: &send_selfserve_notification($notifylist,$chgmsg,$cid,$coursedesc, 744: $now,'coursemanagers',$sender, 745: $approvedlist,$rejectedlist,$crstype); 746: } 747: } else { 748: $chgmsg = "'Action was taken on the following course and community requests by [_1].',$namelink"; 749: if (@completed) { 750: $approvedlist = join("\n",@completed); 751: $output .= '<p>'.&mt('The following courses/communities were created:').'<ul>'; 752: foreach my $cnum (@completed) { 753: my $showcourse; 754: if (ref($requesthash{$cnum.'_approval'})) { 755: $showcourse = $requesthash{$cnum.'_approval'}{'description'}; 756: } else { 757: $showcourse = $cnum; 758: } 759: my $syllabuslink = 760: &Apache::loncommon::syllabuswrapper($showcourse,$cnum,$cdom); 761: $output .= '<li>'.$syllabuslink.'</li>'; 762: } 763: $output .= '</ul></p>'; 764: } 765: if (@rejections) { 766: $rejectedlist = join("\n",@rejections); 767: $output .= '<p>'.&mt('The following requests were rejected:').'<ul>'; 768: foreach my $cnum (@rejections) { 769: my $showcourse; 770: if (ref($requesthash{$cnum.'_approval'})) { 771: $showcourse = $requesthash{$cnum.'_approval'}{'description'}; 772: } else { 773: $showcourse = $cnum; 774: } 775: $output .= '<li>'.$showcourse.'</li>'; 776: } 777: $output .= '</ul></p>'; 778: } 779: if ($notifylist ne '') { 780: &send_selfserve_notification($notifylist,$chgmsg,$cid,$domdesc, 781: $now,'domainmanagers',$sender, 782: $approvedlist,$rejectedlist,$crstype); 783: } 784: } 785: } 786: } 787: if (@existing) { 788: if ($context eq 'course') { 789: $output .= '<p>'.&mt('The following enrollment requests were deleted because the user is already enrolled in the course:').'<ul>'; 790: foreach my $user (@existing) { 791: $output .= '<li>'.$user.'</li>'; 792: } 793: $output .= '</ul></p>'; 794: } else { 795: $output .= '<p>'.&mt('The following course/community creation requests were deleted because the course or community has already been created:').'<ul>'; 796: foreach my $cnum (@existing) { 797: my $showcourse; 798: my %coursehash = &Apache::lonnet::coursedescription($cdom.'/'.$cnum); 799: if ($coursehash{'description'} ne '') { 800: $showcourse = $coursehash{'description'}; 801: } else { 802: $showcourse = $cnum; 803: } 804: $output .= '<li>'.$showcourse.'</li>'; 805: } 806: $output .= '</ul></p>'; 807: } 808: } 809: if (@missingreq) { 810: if ($context eq 'course') { 811: $output .= '<p>'.&mt('The following enrollment requests were ignored because the request is no longer in the enrollment queue:').'<ul>'; 812: foreach my $user (@missingreq) { 813: $output .= '<li>'.$user.'</li>'; 814: } 815: $output .= '</ul></p>'; 816: } else { 817: $output .= '<p>'.&mt('The following course/community creation requests were ignored because the request is no longer in the queue:').'<ul>'; 818: foreach my $cnum (@missingreq) { 819: $output .= '<li>'.$cnum.'</li>'; 820: } 821: $output .= '</ul></p>'; 822: 823: } 824: } 825: if (@invalidusers) { 826: if ($context eq 'course') { 827: $output .= '<p>'.&mt('The following enrollment requests were deleted because the requestor does not have a LON-CAPA account:').'<ul>'; 828: foreach my $user (@invalidusers) { 829: $output .= '<li>'.$user.'</li>'; 830: } 831: $output .= '</ul></p>'; 832: } 833: } 834: if (@limitexceeded) { 835: if ($context eq 'course') { 836: $output .= '<p>'.&mt('The following enrollment requests were skipped because the enrollment limit has been reached for the course:').'<ul>'; 837: foreach my $user (@limitexceeded) { 838: $output .= '<li>'.$user.'</li>'; 839: } 840: $output .= '</ul></p>'; 841: } 842: } 843: if (@nopermissions) { 844: $output .= '<p>'.&mt('The following course/community creation requests could not be processed because the owner does not have rights to create this type of course:').'<ul>'; 845: foreach my $cnum (@nopermissions) { 846: my $showcourse; 847: if (ref($requesthash{$cnum.'_approval'})) { 848: $showcourse = $requesthash{$cnum.'_approval'}{'description'}; 849: } else { 850: $showcourse = $cnum; 851: } 852: $output .= '<li>'.$showcourse.'</li>'; 853: } 854: $output .= '</ul></p>'; 855: } 856: if (@processing_errors) { 857: if ($context eq 'course') { 858: $output .= '<p>'.&mt('The following enrollment requests could not be processed because an error occurred:').'<ul>'; 859: foreach my $user (@processing_errors) { 860: $output .= '<li>'.$user.'</li>'; 861: } 862: $output .= '</ul></p>'; 863: } else { 864: $output .= '<p>'.&mt('The following course/community creation requests could not be processed because an error occurred:').'<ul>'; 865: foreach my $cnum (@processing_errors) { 866: my $showcourse; 867: if (ref($requesthash{$cnum.'_approval'})) { 868: $showcourse = $requesthash{$cnum.'_approval'}{'description'}; 869: } else { 870: $showcourse = $cnum; 871: } 872: $output .= '<li>'.$showcourse.'</li>'; 873: } 874: $output .= '</ul></p>'; 875: } 876: } 877: if (@rejectionerrors) { 878: $output .= '<p>'.&mt('The following course/community creation request rejections could not be fully processed because an error occurred:').'<ul>'; 879: foreach my $cnum (@rejectionerrors) { 880: my $showcourse; 881: if (ref($requesthash{$cnum.'_approval'})) { 882: $showcourse = $requesthash{$cnum.'_approval'}{'description'}; 883: } else { 884: $showcourse = $cnum; 885: } 886: $output .= '<li>'.$showcourse.'</li>'; 887: } 888: $output .= '</ul></p>'; 889: } 890: if (@warn_approves || @warn_rejects) { 891: if ($context eq 'course') { 892: $output .= '<p>'.&mt("For the following users, an error occurred when updating the user's own self-enroll requests record:").'<ul>'; 893: foreach my $user (@warn_approves) { 894: $output .= '<li>'.$user.'</li>'; 895: } 896: $output .= '</ul></p>'; 897: } else { 898: $output .= '<p>'.&mt("For the following course/community requests an error occurred when updating the requestor's own requests record:").'<ul>'; 899: foreach my $cnum (@warn_approves,@warn_rejects) { 900: my $showcourse; 901: if (ref($requesthash{$cnum.'_approval'})) { 902: $showcourse = $requesthash{$cnum.'_approval'}{'description'}; 903: } else { 904: $showcourse = $cnum; 905: } 906: $output .= '<li>'.$showcourse.'</li>'; 907: } 908: $output .= '</ul></p>'; 909: } 910: } 911: if (@warn_coursereqs) { 912: $output .= '<p>'..&mt("For the following users, an error occurred when setting rights to request creation of Concept Test courses:").'<ul>'; 913: foreach my $user (@warn_coursereqs) { 914: $output .= '<li>'.$user.'</li>'; 915: } 916: $output .= '</ul></p>'; 917: } 918: return $output; 919: } 920: 921: sub enable_gci_submission { 922: my ($udom,$uname,$access_end,$access_start) = @_; 923: my $cdom = 'gci'; 924: my $cnum = '1H96711d710194bfegcil1'; 925: my ($stucounts,$idx,$classlist) = &get_student_counts($cdom,$cnum); 926: if (exists($classlist->{$uname.':'.$udom})) { 927: if (ref($classlist->{$uname.':'.$udom}) eq 'ARRAY') { 928: if (($classlist->{$uname.':'.$udom}->[$idx->{'status'}] eq 'Active') || 929: ($classlist->{$uname.':'.$udom}->[$idx->{'status'}] eq 'Future')) { 930: return; 931: } 932: } 933: } 934: return 935: &Apache::lonnet::modify_student_enrollment($udom,$uname,undef,undef,undef,undef,undef,undef,$access_end,$access_start,'selfenroll',undef,$cdom.'_'.$cnum,1); 936: } 937: 938: sub get_student_counts { 939: my ($cdom,$cnum) = @_; 940: my (%idx,%stucounts); 941: my $classlist = &Apache::loncoursedata::get_classlist($cdom,$cnum); 942: $idx{'type'} = &Apache::loncoursedata::CL_TYPE(); 943: $idx{'status'} = &Apache::loncoursedata::CL_STATUS(); 944: while (my ($student,$data) = each(%$classlist)) { 945: if (($data->[$idx{'status'}] eq 'Active') || 946: ($data->[$idx{'status'}] eq 'Future')) { 947: if ($data->[$idx{'type'}] eq 'selfenroll') { 948: $stucounts{'selfenroll'} ++; 949: } 950: $stucounts{'allstudents'} ++; 951: } 952: } 953: return (\%stucounts,\%idx,$classlist); 954: } 955: 956: sub course_creation { 957: my ($dom,$cnum,$context,$details,$logmsg,$newusermsg,$addresult,$enrollcount,$output, 958: $keysmsg,$domdefs,$longroles) = @_; 959: unless ((ref($details) eq 'HASH') && (ref($domdefs) eq 'HASH') && 960: (ref($longroles) eq 'HASH')) { 961: return 'error: Invalid request'; 962: } 963: my ($result,$ownername,$ownerdom); 964: my $crstype = $details->{'crstype'}; 965: if ($context eq 'domain') { 966: $ownername = $details->{'owner'}; 967: $ownerdom = $details->{'domain'}; 968: } else { 969: $ownername = $env{'user.name'}; 970: $ownerdom = $env{'user.domain'}; 971: } 972: my $owneremail; 973: my %emails = &Apache::loncommon::getemails($ownername,$ownerdom); 974: foreach my $email ('permanentemail','critnotification','notification') { 975: $owneremail = $emails{$email}; 976: last if ($owneremail ne ''); 977: } 978: my %reqdetails = &build_batchcreatehash($dom,$context,$details,$owneremail,$domdefs); 979: my $cid = &LONCAPA::batchcreatecourse::build_course($dom,$cnum,'requestcourses', 980: \%reqdetails,$longroles,\$logmsg,\$newusermsg,\$addresult, 981: \$enrollcount,\$output,\$keysmsg,$ownerdom,$ownername,$cnum,$crstype); 982: if ($cid eq "/$dom/$cnum") { 983: $result = 'created'; 984: } else { 985: $result = 'error: '.$cid; 986: } 987: return $result; 988: } 989: 990: sub build_batchcreatehash { 991: my ($dom,$context,$details,$owneremail,$domdefs) = @_; 992: my %batchhash; 993: my @items = qw{owner domain coursehome clonecrs clonedom datemode dateshift enrollstart enrollend accessstart accessend sections crosslists users}; 994: if ($dom eq 'gcitest') { 995: push(@items,'firstres'); 996: } 997: if ((ref($details) eq 'HASH') && (ref($domdefs) eq 'HASH')) { 998: my $emailenc = &Apache::lonnet::escape($owneremail); 999: my $owner = $details->{'owner'}.':'.$details->{'domain'}; 1000: foreach my $item (@items) { 1001: $batchhash{$item} = $details->{$item}; 1002: } 1003: $batchhash{'title'} = $details->{'cdescr'}; 1004: $batchhash{'coursecode'} = $details->{'instcode'}; 1005: $batchhash{'emailenc'} = $emailenc; 1006: $batchhash{'adds'} = $details->{'autoadds'}; 1007: $batchhash{'drops'} = $details->{'autodrops'}; 1008: $batchhash{'authtype'} = $domdefs->{'auth_def'}; 1009: $batchhash{'authparam'} = $domdefs->{'auth_arg_def'}; 1010: if ($details->{'crstype'} eq 'community') { 1011: $batchhash{'crstype'} = 'Community'; 1012: } else { 1013: $batchhash{'crstype'} = 'Course'; 1014: } 1015: my ($owner_firstname,$owner_lastname); 1016: if ($context eq 'domain') { 1017: my %userenv = &Apache::lonnet::userenvironment($details->{'domain'}, 1018: $details->{'owner'}, 1019: 'firstname','lastname'); 1020: $owner_firstname = $userenv{'firstname'}; 1021: $owner_lastname = $userenv{'lastname'}; 1022: } else { 1023: $owner_firstname = $env{'environment.firstname'}; 1024: $owner_lastname = $env{'environment.lastname'}; 1025: } 1026: if (ref($details->{'personnel'}) eq 'HASH') { 1027: %{$batchhash{'users'}} = %{$details->{'personnel'}}; 1028: if (ref($batchhash{'users'}) eq 'HASH') { 1029: foreach my $userkey (keys(%{$batchhash{'users'}})) { 1030: if (ref($batchhash{'users'}{$userkey}) eq 'HASH') { 1031: if (ref($batchhash{'users'}{$userkey}{'roles'}) eq 'ARRAY') { 1032: foreach my $role (@{$batchhash{'users'}{$userkey}{'roles'}}) { 1033: my $start = ''; 1034: my $end = ''; 1035: if ($role eq 'st') { 1036: $start = $details->{'accessstart'}; 1037: $end = $details->{'accessend'}; 1038: } 1039: $batchhash{'users'}{$userkey}{$role}{'start'} = $start; 1040: $batchhash{'users'}{$userkey}{$role}{'end'} = $end; 1041: } 1042: } 1043: } 1044: } 1045: } 1046: } 1047: $batchhash{'users'}{$owner}{firstname} = $owner_firstname; 1048: $batchhash{'users'}{$owner}{lastname} = $owner_lastname; 1049: $batchhash{'users'}{$owner}{emailenc} = $emailenc; 1050: $batchhash{'users'}{$owner}{owneremail} = $owneremail; 1051: } 1052: return %batchhash; 1053: } 1054: 1055: sub can_clone_course { 1056: my ($uname,$udom,$clonecrs,$clonedom,$crstype) = @_; 1057: my $canclone; 1058: my $ccrole = 'cc'; 1059: if ($crstype eq 'community') { 1060: $ccrole = 'co'; 1061: } 1062: my %roleshash = &Apache::lonnet::get_my_roles($uname,$udom,'userroles',['active'], 1063: [$ccrole],[$clonedom]); 1064: if (exists($roleshash{$clonecrs.':'.$clonedom.':'.$ccrole})) { 1065: $canclone = 1; 1066: } else { 1067: my %courseenv = &Apache::lonnet::userenvironment($clonedom,$clonecrs,('cloners')); 1068: my $cloners = $courseenv{'cloners'}; 1069: if ($cloners ne '') { 1070: my @cloneable = split(',',$cloners); 1071: if (grep(/^\*$/,@cloneable)) { 1072: $canclone = 1; 1073: } 1074: if (grep(/^\*:\Q$udom\E$/,@cloneable)) { 1075: $canclone = 1; 1076: } 1077: if (grep(/^\Q$uname\E:\Q$udom\E$/,@cloneable)) { 1078: $canclone = 1; 1079: } 1080: } 1081: } 1082: return $canclone; 1083: } 1084: 1085: sub queued_selfenrollment { 1086: my ($notitle) = @_; 1087: my $output; 1088: my %selfenrollrequests = &Apache::lonnet::dump('selfenrollrequests'); 1089: my %reqs_by_date; 1090: foreach my $item (keys(%selfenrollrequests)) { 1091: if (ref($selfenrollrequests{$item}) eq 'HASH') { 1092: if ($selfenrollrequests{$item}{'status'} eq 'request') { 1093: if ($selfenrollrequests{$item}{'timestamp'}) { 1094: push(@{$reqs_by_date{$selfenrollrequests{$item}{'timestamp'}}},$item); 1095: } 1096: } 1097: } 1098: } 1099: if (keys(%reqs_by_date)) { 1100: my $rolename = &Apache::lonnet::plaintext('st'); 1101: unless ($notitle) { 1102: $output .= '<b>'.&mt('Enrollment requests pending Course Coordinator approval').'</b><br />'; 1103: } 1104: $output .= &Apache::loncommon::start_data_table(). 1105: &Apache::loncommon::start_data_table_header_row(). 1106: '<th>'.&mt('Date requested').'</th><th>'.&mt('Course title').'</th>'. 1107: '<th>'.&mt('User role').'</th><th>'.&mt('Section').'</th>'. 1108: &Apache::loncommon::end_data_table_header_row(); 1109: my @sorted = sort { $a <=> $b } (keys(%reqs_by_date)); 1110: foreach my $item (@sorted) { 1111: if (ref($reqs_by_date{$item}) eq 'ARRAY') { 1112: foreach my $crs (@{$reqs_by_date{$item}}) { 1113: my %courseinfo = &Apache::lonnet::coursedescription($crs); 1114: my $usec = $selfenrollrequests{$crs}{'section'}; 1115: if ($usec eq '') { 1116: $usec = &mt('No section'); 1117: } 1118: $output .= &Apache::loncommon::start_data_table_row(). 1119: '<td>'.&Apache::lonlocal::locallocaltime($item).'</td>'. 1120: '<td>'.$courseinfo{'description'}.'</td>'. 1121: '<td>'.$rolename.'</td><td>'.$usec.'</td>'. 1122: &Apache::loncommon::end_data_table_row(); 1123: } 1124: } 1125: } 1126: $output .= &Apache::loncommon::end_data_table(); 1127: } 1128: return $output; 1129: } 1130: 1131: 1;