--- loncom/interface/lonuserutils.pm 2007/11/06 18:23:14 1.3
+++ loncom/interface/lonuserutils.pm 2017/08/08 15:34:32 1.186
@@ -1,7 +1,7 @@
# The LearningOnline Network with CAPA
# Utility functions for managing LON-CAPA user accounts
#
-# $Id: lonuserutils.pm,v 1.3 2007/11/06 18:23:14 raeburn Exp $
+# $Id: lonuserutils.pm,v 1.186 2017/08/08 15:34:32 raeburn Exp $
#
# Copyright Michigan State University Board of Trustees
#
@@ -30,23 +30,40 @@
package Apache::lonuserutils;
+=pod
+
+=head1 NAME
+
+Apache::lonuserutils.pm
+
+=head1 SYNOPSIS
+
+ Utilities for management of users and custom roles
+
+ Provides subroutines called by loncreateuser.pm
+
+=head1 OVERVIEW
+
+=cut
+
use strict;
use Apache::lonnet;
use Apache::loncommon();
use Apache::lonhtmlcommon;
use Apache::lonlocal;
-use LONCAPA();
+use Apache::longroup;
+use HTML::Entities;
+use LONCAPA qw(:DEFAULT :match);
###############################################################
###############################################################
# Drop student from all sections of a course, except optional $csec
sub modifystudent {
- my ($udom,$unam,$courseid,$csec,$desiredhost)=@_;
+ my ($udom,$unam,$courseid,$csec,$desiredhost,$context)=@_;
# if $csec is undefined, drop the student from all the courses matching
# this one. If $csec is defined, drop them from all other sections of
# this course and add them to section $csec
- my $cdom = $env{'course.'.$courseid.'.domain'};
- my $cnum = $env{'course.'.$courseid.'.num'};
+ my ($cnum,$cdom) = &get_course_identity($courseid);
my %roles = &Apache::lonnet::dump('roles',$udom,$unam);
my ($tmp) = keys(%roles);
# Bail out if we were unable to get the students roles
@@ -68,14 +85,15 @@ sub modifystudent {
my $reply=&Apache::lonnet::modifystudent
# dom name id mode pass f m l g
($udom,$unam,'', '', '',undef,undef,undef,undef,
- $section,time,undef,undef,$desiredhost);
+ $section,time,undef,undef,$desiredhost,'','manual',
+ '',$courseid,'',$context);
$result .= $reply.':';
}
}
}
}
if ($result eq '') {
- $result = 'Unable to find section for this student';
+ $result = &mt('Unable to find section for this student');
} else {
$result =~ s/(ok:)+/ok/g;
}
@@ -85,43 +103,147 @@ sub modifystudent {
sub modifyuserrole {
my ($context,$setting,$changeauth,$cid,$udom,$uname,$uid,$umode,$upass,
$first,$middle,$last,$gene,$sec,$forceid,$desiredhome,$email,$role,
- $end,$start) = @_;
- my ($scope,$userresult,$authresult,$roleresult);
+ $end,$start,$checkid,$inststatus) = @_;
+ my ($scope,$userresult,$authresult,$roleresult,$idresult);
if ($setting eq 'course' || $context eq 'course') {
$scope = '/'.$cid;
$scope =~ s/\_/\//g;
- if ($role ne 'cc' && $sec ne '') {
+ if (($role ne 'cc') && ($role ne 'co') && ($sec ne '')) {
$scope .='/'.$sec;
}
- } elsif ($setting eq 'domain') {
+ } elsif ($context eq 'domain') {
$scope = '/'.$env{'request.role.domain'}.'/';
- } elsif ($setting eq 'construction_space') {
+ } elsif ($context eq 'author') {
$scope = '/'.$env{'user.domain'}.'/'.$env{'user.name'};
}
if ($context eq 'domain') {
my $uhome = &Apache::lonnet::homeserver($uname,$udom);
if ($uhome ne 'no_host') {
- if (($changeauth) && (&Apache::lonnet::allowed('mau',$udom))) {
+ if (($changeauth eq 'Yes') && (&Apache::lonnet::allowed('mau',$udom))) {
if ((($umode =~ /^krb4|krb5|internal$/) && $upass ne '') ||
($umode eq 'localauth')) {
$authresult = &Apache::lonnet::modifyuserauth($udom,$uname,$umode,$upass);
}
}
+ if (($forceid) && (&Apache::lonnet::allowed('mau',$udom)) &&
+ ($env{'form.recurseid'}) && ($checkid)) {
+ my %userupdate = (
+ lastname => $last,
+ middlename => $middle,
+ firstname => $first,
+ generation => $gene,
+ id => $uid,
+ );
+ $idresult = &propagate_id_change($uname,$udom,\%userupdate);
+ }
}
}
$userresult =
&Apache::lonnet::modifyuser($udom,$uname,$uid,$umode,$upass,$first,
$middle,$last,$gene,$forceid,$desiredhome,
- $email,$role,$start,$end);
+ $email,$inststatus);
if ($userresult eq 'ok') {
- if ($role ne '') {
+ if ($role ne '') {
+ $role =~ s/_/\//g;
$roleresult = &Apache::lonnet::assignrole($udom,$uname,$scope,
- $role,$end,$start);
+ $role,$end,$start,'',
+ '',$context);
+ }
+ }
+ return ($userresult,$authresult,$roleresult,$idresult);
+}
+
+sub propagate_id_change {
+ my ($uname,$udom,$user) = @_;
+ my (@types,@roles);
+ @types = ('active','future');
+ @roles = ('st');
+ my $idresult;
+ my %roleshash = &Apache::lonnet::get_my_roles($uname,
+ $udom,'userroles',\@types,\@roles);
+ my %args = (
+ one_time => 1,
+ );
+ foreach my $item (keys(%roleshash)) {
+ my ($cnum,$cdom,$role) = split(/:/,$item,-1);
+ my ($start,$end) = split(/:/,$roleshash{$item});
+ if (&Apache::lonnet::is_course($cdom,$cnum)) {
+ my $result = &update_classlist($cdom,$cnum,$udom,$uname,$user);
+ my %coursehash =
+ &Apache::lonnet::coursedescription($cdom.'_'.$cnum,\%args);
+ my $cdesc = $coursehash{'description'};
+ if ($cdesc eq '') {
+ $cdesc = $cdom.'_'.$cnum;
+ }
+ if ($result eq 'ok') {
+ $idresult .= &mt('Classlist update for "[_1]" in "[_2]".',$uname.':'.$udom,$cdesc).'
'."\n";
+ } else {
+ $idresult .= &mt('Error: "[_1]" during classlist update for "[_2]" in "[_3]".',$result,$uname.':'.$udom,$cdesc).'
'."\n";
+ }
+ }
+ }
+ return $idresult;
+}
+
+sub update_classlist {
+ my ($cdom,$cnum,$udom,$uname,$user,$newend) = @_;
+ my ($uid,$classlistentry);
+ my $fullname =
+ &Apache::lonnet::format_name($user->{'firstname'},$user->{'middlename'},
+ $user->{'lastname'},$user->{'generation'},
+ 'lastname');
+ my %classhash = &Apache::lonnet::get('classlist',[$uname.':'.$udom],
+ $cdom,$cnum);
+ my @classinfo = split(/:/,$classhash{$uname.':'.$udom});
+ my $ididx=&Apache::loncoursedata::CL_ID() - 2;
+ my $nameidx=&Apache::loncoursedata::CL_FULLNAME() - 2;
+ my $endidx = &Apache::loncoursedata::CL_END() - 2;
+ my $startidx = &Apache::loncoursedata::CL_START() - 2;
+ for (my $i=0; $i<@classinfo; $i++) {
+ if ($i == $endidx) {
+ if ($newend ne '') {
+ $classlistentry .= $newend.':';
+ } else {
+ $classlistentry .= $classinfo[$i].':';
+ }
+ } elsif ($i == $startidx) {
+ if ($newend ne '') {
+ if ($classinfo[$i] > $newend) {
+ $classlistentry .= $newend.':';
+ } else {
+ $classlistentry .= $classinfo[$i].':';
+ }
+ } else {
+ $classlistentry .= $classinfo[$i].':';
+ }
+ } elsif ($i == $ididx) {
+ if (defined($user->{'id'})) {
+ $classlistentry .= $user->{'id'}.':';
+ } else {
+ $classlistentry .= $classinfo[$i].':';
+ }
+ } elsif ($i == $nameidx) {
+ if (defined($user->{'lastname'})) {
+ $classlistentry .= $fullname.':';
+ } else {
+ $classlistentry .= $classinfo[$i].':';
+ }
+ } else {
+ $classlistentry .= $classinfo[$i].':';
}
}
- return ($userresult,$authresult,$roleresult);
+ $classlistentry =~ s/:$//;
+ my $reply=&Apache::lonnet::cput('classlist',
+ {"$uname:$udom" => $classlistentry},
+ $cdom,$cnum);
+ if (($reply eq 'ok') || ($reply eq 'delayed')) {
+ return 'ok';
+ } else {
+ return 'error: '.$reply;
+ }
}
+
###############################################################
###############################################################
# build a role type and role selection form
@@ -130,8 +252,10 @@ sub domain_roles_select {
# domain context
#
# Role types
- my @roletypes = ('domain','construction_space','course');
+ my @roletypes = ('domain','author','course','community');
my %lt = &role_type_names();
+ my $onchangefirst = "updateCols('showrole')";
+ my $onchangesecond = "updateCols('showrole')";
#
# build up the menu information to be passed to
# &Apache::loncommon::linked_select_forms
@@ -142,6 +266,10 @@ sub domain_roles_select {
foreach my $roletype (@roletypes) {
# set up the text for this domain
$select_menus{$roletype}->{'text'}= $lt{$roletype};
+ my $crstype;
+ if ($roletype eq 'community') {
+ $crstype = 'Community';
+ }
# we want a choice of 'default' as the default in the second menu
if ($env{'form.roletype'} ne '') {
$select_menus{$roletype}->{'default'} = $env{'form.showrole'};
@@ -152,22 +280,30 @@ sub domain_roles_select {
my @roles;
if ($roletype eq 'domain') {
@roles = &domain_roles();
- } elsif ($roletype eq 'construction_space') {
+ } elsif ($roletype eq 'author') {
@roles = &construction_space_roles();
} else {
- @roles = &course_roles('domain');
+ my $custom = 1;
+ @roles = &course_roles('domain',undef,$custom,$roletype);
}
my $order = ['Any',@roles];
$select_menus{$roletype}->{'order'} = $order;
foreach my $role (@roles) {
- $select_menus{$roletype}->{'select2'}->{$role} =
- &Apache::lonnet::plaintext($role);
+ if ($role eq 'cr') {
+ $select_menus{$roletype}->{'select2'}->{$role} =
+ &mt('Custom role');
+ } else {
+ $select_menus{$roletype}->{'select2'}->{$role} =
+ &Apache::lonnet::plaintext($role,$crstype);
+ }
}
$select_menus{$roletype}->{'select2'}->{'Any'} = &mt('Any');
}
my $result = &Apache::loncommon::linked_select_forms
('studentform',(' 'x3).&mt('Role: '),$env{'form.roletype'},
- 'roletype','showrole',\%select_menus,['domain','construction_space','course']);
+ 'roletype','showrole',\%select_menus,
+ ['domain','author','course','community'],$onchangefirst,
+ $onchangesecond);
return $result;
}
@@ -179,7 +315,8 @@ sub hidden_input {
}
sub print_upload_manager_header {
- my ($r,$datatoken,$distotal,$krbdefdom,$context)=@_;
+ my ($r,$datatoken,$distotal,$krbdefdom,$context,$permission,$crstype,
+ $can_assign)=@_;
my $javascript;
#
if (! exists($env{'form.upfile_associate'})) {
@@ -193,9 +330,9 @@ sub print_upload_manager_header {
}
}
if ($env{'form.upfile_associate'} eq 'reverse') {
- $javascript=&upload_manager_javascript_reverse_associate();
+ $javascript=&upload_manager_javascript_reverse_associate($can_assign);
} else {
- $javascript=&upload_manager_javascript_forward_associate();
+ $javascript=&upload_manager_javascript_forward_associate($can_assign);
}
#
# Deal with restored settings
@@ -209,15 +346,31 @@ sub print_upload_manager_header {
$password_choice = 'int';
}
#
+ my $groupslist;
+ if ($context eq 'course') {
+ $groupslist = &get_groupslist();
+ }
my $javascript_validations =
- &javascript_validations('auth',$krbdefdom,$password_choice,undef,
- $env{'request.role.domain'});
- my $checked=(($env{'form.noFirstLine'})?' checked="checked" ':'');
- $r->print(&mt('Total number of records found in file: [_1].',$distotal).
- "
\n");
- $r->print('
'. + &mt('Total number of records found in file: [_1]' + ,''.$distotal.''). + "
\n" + ); + if ($distotal == 0) { + $r->print(''.&mt('None found').'
'); + } + $r->print( + ''.
+ &mt('Enter as many fields as you can.').'
'.
+ &mt('The system will inform you and bring you back to this page,[_1]if the data selected are insufficient to add users.','
').
+ "
'.&mt('Change authentication for existing users to these settings?').'
'; + $Str .= '' + .&mt('Change authentication for existing users in domain "[_1]" to these settings?' + ,$defdom) + .' ' + .' ' + .'
'; } else { - $Str .= "\n". - &mt('Note: this will not take effect if the user already exists'). + $Str .= '
'."\n". + &mt('This will not take effect if the user already exists.'). &Apache::loncommon::help_open_topic('Auth_Options'). "
\n"; } $Str .= &set_login($defdom,$krbform,$intform,$locform); + my ($home_server_pick,$numlib) = &Apache::loncommon::home_server_form_item($defdom,'lcserver', 'default','hide'); if ($numlib > 1) { - $Str .= '\n".$date_table."
\n"; - if ($context eq 'domain') { - $Str .= '\n".$date_table."
\n" + .&Apache::lonhtmlcommon::row_closure(); + if ($context eq 'domain') { - $Str .= ''.&mt('Domain Level').'\n".'
'."\n".
- &mt('(only do if you know what you are doing.)')."
\n"; - $Str .= '
'.&Apache::lonhtmlcommon::start_pick_box()."\n". - &Apache::lonhtmlcommon::start_pick_box()."\n". - &Apache::lonhtmlcommon::row_title(&mt('Select Course(s)'), - 'LC_oddrow_value')."\n". + &Apache::lonhtmlcommon::course_selection($formname,$numcodes, + $codetitles,$idlist,$idlist_titles,$crstype, + \@standardnames); + $r->print('
'. + &list_submit_button(&mt('Update Display')). + "\n".'
'.$warning.''."\n"); + $clearcoursepick = 0; + if (($env{'form.origroletype'} ne '') && + ($env{'form.origroletype'} ne $env{'form.roletype'})) { + $clearcoursepick = 1; + } + if (($env{'form.coursepick'}) && (!$clearcoursepick)) { + $r->print(''.$msg.'
'."\n") if $msg; } else { # Print out the available choices + my $usercount; if ($env{'form.action'} eq 'modifystudent') { - &show_users_list($r,$context,'view','modify', - $env{'form.Status'},\%userlist,$keylist); + ($usercount) = &show_users_list($r,$context,'view',$permission, + $env{'form.Status'},\%userlist,$keylist,'', + $showcredits); } else { - &show_users_list($r,$context,$env{'form.output'},'aboutme', - $env{'form.Status'},\%userlist,$keylist); + ($usercount) = &show_users_list($r,$context,$env{'form.output'}, + $permission,$env{'form.Status'},\%userlist, + $keylist,'',$showcredits,$needauthorquota,$needauthorusage); + } + if (!$usercount) { + $r->print('\n". + if ($actionselect) { + $output .= <<"END"; +
+END + my @allroles; + if ($env{'form.showrole'} eq 'Any') { + my $custom = 1; + if ($context eq 'domain') { + @allroles = &roles_by_context($setting,$custom,$crstype); + } else { + @allroles = &roles_by_context($context,$custom,$crstype); + } + } else { + @allroles = ($env{'form.showrole'}); + } + foreach my $role (@allroles) { + if ($context eq 'domain') { + if ($setting eq 'domain') { + if (&Apache::lonnet::allowed('c'.$role, + $env{'request.role.domain'})) { + $canchange{$role} = 1; + } + } elsif ($setting eq 'author') { + if (&Apache::lonnet::allowed('c'.$role, + $env{'request.role.domain'})) { + $canchange{$role} = 1; + } + } + } elsif ($context eq 'author') { + if (&Apache::lonnet::allowed('c'.$role, + $env{'user.domain'}.'/'.$env{'user.name'})) { + $canchange{$role} = 1; + } + } elsif ($context eq 'course') { + if (&Apache::lonnet::allowed('c'.$role,$env{'request.course.id'})) { + $canchange{$role} = 1; + } elsif ($env{'request.course.sec'} ne '') { + if (&Apache::lonnet::allowed('c'.$role,$env{'request.course.id'}.'/'.$env{'request.course.sec'})) { + $canchangesec{$role} = $env{'request.course.sec'}; + } + } elsif ((($role eq 'co') && ($crstype eq 'Community')) || + (($role eq 'cc') && ($crstype eq 'Course'))) { + if (&is_courseowner($env{'request.course.id'}, + $env{'course.'.$env{'request.course.id'}.'.internal.courseowner'})) { + $canchange{$role} = 1; + } + } + } + } + } + $output .= ''; + } + $output .= "\n".''."\n". &Apache::loncommon::start_data_table(). - &Apache::loncommon::start_data_table_header_row()); + &Apache::loncommon::start_data_table_header_row(); if ($mode eq 'autoenroll') { - $r->print(" -' + .&mt('Problems occurred in writing the CSV file.') + .' '.&mt('This error has been logged.') + .' '.&mt('Please alert your LON-CAPA administrator.') + .'
' + ); $CSVfile = undef; } # @@ -1785,7 +2810,7 @@ END print $CSVfile '"'.$results_description.'"'."\n"; print $CSVfile '"'.join('","',map { &Apache::loncommon::csv_translate($lt{$_}) - } (@cols)).'"'."\n"; + } (@cols))."\"\n"; } elsif ($mode eq 'excel') { # Create the excel spreadsheet ($excel_workbook,$excel_filename,$format) = @@ -1795,27 +2820,67 @@ END $excel_sheet->write($row++,0,$results_description,$format->{'h2'}); # my @colnames = map {$lt{$_}} (@cols); + $excel_sheet->write($row++,0,\@colnames,$format->{'bold'}); } # Done with header lines in all formats - my %index; my $i; foreach my $idx (@$keylist) { $index{$idx} = $i++; } + my $usercount = 0; + my ($secfilter,$grpfilter); + if ($context eq 'course') { + $secfilter = $env{'form.secfilter'}; + $grpfilter = $env{'form.grpfilter'}; + if ($secfilter eq '') { + $secfilter = 'all'; + } + if ($grpfilter eq '') { + $grpfilter = 'all'; + } + } + my %ltstatus = &Apache::lonlocal::texthash( + Active => 'Active', + Future => 'Future', + Expired => 'Expired', + ); + # If this is for a single course get last course "log-in". + my %crslogins; + if ($context eq 'course') { + %crslogins=&Apache::lonnet::dump('nohist_crslastlogin',$cdom,$cnum); + } # Get groups, role, permanent e-mail so we can sort on them if # necessary. foreach my $user (keys(%{$userlist})) { + if ($user eq '' ) { + delete($userlist->{$user}); + next; + } + if ($context eq 'domain' && $user eq $env{'request.role.domain'}.'-domainconfig:'.$env{'request.role.domain'}) { + delete($userlist->{$user}); + next; + } my ($uname,$udom,$role,$groups,$email); + if (($statusmode ne 'Any') && + ($userlist->{$user}->[$index{'status'}] ne $statusmode)) { + delete($userlist->{$user}); + next; + } if ($context eq 'domain') { if ($env{'form.roletype'} eq 'domain') { ($role,$uname,$udom) = split(/:/,$user); - - } elsif ($env{'form.roletype'} eq 'construction_space') { + if (($uname eq $env{'request.role.domain'}.'-domainconfig') && + ($udom eq $env{'request.role.domain'})) { + delete($userlist->{$user}); + next; + } + } elsif ($env{'form.roletype'} eq 'author') { ($uname,$udom,$role) = split(/:/,$user,-1); - } elsif ($env{'form.roletype'} eq 'course') { + } elsif (($env{'form.roletype'} eq 'course') || + ($env{'form.roletype'} eq 'community')) { ($uname,$udom,$role) = split(/:/,$user); } } else { @@ -1829,119 +2894,288 @@ END delete($userlist->{$user}); next; } - if (ref($classgroups) eq 'HASH') { - $groups = $classgroups->{$user}; - } - if (ref($groups->{active}) eq 'HASH') { - $userlist->{$user}->[$index{'groups'}] = join(', ',keys(%{$groups->{'active'}})); + if ($context eq 'course') { + my @ac_groups; + if (ref($classgroups) eq 'HASH') { + $groups = $classgroups->{$user}; + } + if (ref($groups->{'active'}) eq 'HASH') { + @ac_groups = keys(%{$groups->{'active'}}); + $userlist->{$user}->[$index{'groups'}] = join(', ',@ac_groups); + } + if ($mode ne 'autoenroll') { + my $section = $userlist->{$user}->[$index{'section'}]; + if (($env{'request.course.sec'} ne '') && + ($section ne $env{'request.course.sec'})) { + if ($role eq 'st') { + delete($userlist->{$user}); + next; + } + } + if ($secfilter eq 'none') { + if ($section ne '') { + delete($userlist->{$user}); + next; + } + } elsif ($secfilter ne 'all') { + if ($section ne $secfilter) { + delete($userlist->{$user}); + next; + } + } + if ($grpfilter eq 'none') { + if (@ac_groups > 0) { + delete($userlist->{$user}); + next; + } + } elsif ($grpfilter ne 'all') { + if (!grep(/^\Q$grpfilter\E$/,@ac_groups)) { + delete($userlist->{$user}); + next; + } + } + if ($env{'course.'.$env{'request.course.id'}.'.internal.showphoto'}) { + if ((grep/^photo$/,@cols) && ($role eq 'st')) { + $userlist->{$user}->[$index{'photo'}] = + &Apache::lonnet::retrievestudentphoto($udom,$uname,'jpg'); + $userlist->{$user}->[$index{'thumbnail'}] = + &Apache::lonnet::retrievestudentphoto($udom,$uname, + 'gif','thumbnail'); + } + } + if (($role eq 'st') && ($defaultcredits)) { + if ($userlist->{$user}->[$index{'credits'}] eq '') { + $userlist->{$user}->[$index{'credits'}] = $defaultcredits; + } + } + } } my %emails = &Apache::loncommon::getemails($uname,$udom); if ($emails{'permanentemail'} =~ /\S/) { $userlist->{$user}->[$index{'email'}] = $emails{'permanentemail'}; } + if (($context eq 'domain') && ($env{'form.roletype'} eq 'domain') && + ($role eq 'au')) { + my ($disk_quota,$current_disk_usage,$percent); + if (($needauthorusage) || ($needauthorquota)) { + $disk_quota = &Apache::loncommon::get_user_quota($uname,$udom,'author'); + } + if ($needauthorusage) { + $current_disk_usage = + &Apache::lonnet::diskusage($udom,$uname,"$londocroot/priv/$udom/$uname"); + if ($disk_quota == 0) { + $percent = 100.0; + } else { + $percent = $current_disk_usage/(10 * $disk_quota); + } + $userlist->{$user}->[$index{'authorusage'}] = sprintf("%.0f",$percent); + } + if ($needauthorquota) { + $userlist->{$user}->[$index{'authorquota'}] = sprintf("%.2f",$disk_quota); + } + } + $usercount ++; + } + my $autocount = 0; + my $manualcount = 0; + my $lockcount = 0; + my $unlockcount = 0; + if ($usercount) { + $r->print($output); + } else { + if ($mode eq 'autoenroll') { + return ($usercount,$autocount,$manualcount,$lockcount,$unlockcount); + } else { + return; + } } - # # Sort the users my $index = $index{$sortby}; my $second = $index{'username'}; my $third = $index{'domain'}; - my @sorted_users = sort { - lc($userlist->{$a}->[$index]) cmp lc($userlist->{$b}->[$index]) - || - lc($userlist->{$a}->[$second]) cmp lc($userlist->{$b}->[$second]) || - lc($userlist->{$a}->[$third]) cmp lc($userlist->{$b}->[$third]) - } (keys(%$userlist)); - my $usercount = 0; - my $autocount = 0; - my $manualcount = 0; - my $lockcount = 0; - my $unlockcount = 0; + my @sorted_users; + if (($sortby eq 'authorquota') || ($sortby eq 'authorusage')) { + @sorted_users = sort { + $userlist->{$b}->[$index] <=> $userlist->{$a}->[$index] || + lc($userlist->{$a}->[$second]) cmp lc($userlist->{$b}->[$second]) || + lc($userlist->{$a}->[$third]) cmp lc($userlist->{$b}->[$third]) + } (keys(%$userlist)); + } else { + @sorted_users = sort { + lc($userlist->{$a}->[$index]) cmp lc($userlist->{$b}->[$index]) || + lc($userlist->{$a}->[$second]) cmp lc($userlist->{$b}->[$second]) || + lc($userlist->{$a}->[$third]) cmp lc($userlist->{$b}->[$third]) + } (keys(%$userlist)); + } + my $rowcount = 0; + my $disabled; + if ($mode eq 'autoenroll') { + unless ($permission->{'cusr'}) { + $disabled = ' disabled="disabled"'; + } + } foreach my $user (@sorted_users) { - my $sdata = $userlist->{$user}; my %in; + my $sdata = $userlist->{$user}; + $rowcount ++; foreach my $item (@{$keylist}) { $in{$item} = $sdata->[$index{$item}]; } - next if (($statusmode ne 'Any') && ($in{'status'} ne $statusmode)); - $in{'role'}=&Apache::lonnet::plaintext($sdata->[$index{'role'}]); - if (! defined($in{'start'}) || $in{'start'} == 0) { - $in{'start'} = &mt('none'); - } else { - $in{'start'} = &Apache::lonlocal::locallocaltime($in{'start'}); + my $clickers = (&Apache::lonnet::userenvironment($in{'domain'},$in{'username'},'clickers'))[1]; + if ($clickers!~/\w/) { $clickers='-'; } + $in{'clicker'} = $clickers; + my $role = $in{'role'}; + $in{'role'}=&Apache::lonnet::plaintext($sdata->[$index{'role'}],$crstype); + unless ($mode eq 'excel') { + if (! defined($in{'start'}) || $in{'start'} == 0) { + $in{'start'} = &mt('none'); + } else { + $in{'start'} = &Apache::lonlocal::locallocaltime($in{'start'}); + } + if (! defined($in{'end'}) || $in{'end'} == 0) { + $in{'end'} = &mt('none'); + } else { + $in{'end'} = &Apache::lonlocal::locallocaltime($in{'end'}); + } } - if (! defined($in{'end'}) || $in{'end'} == 0) { - $in{'end'} = &mt('none'); - } else { - $in{'end'} = &Apache::lonlocal::locallocaltime($in{'end'}); + if ($context eq 'course') { + my $lastlogin = $crslogins{$in{'username'}.':'.$in{'domain'}.':'.$in{'section'}.':'.$role}; + if ($lastlogin ne '') { + $in{'lastlogin'} = &Apache::lonlocal::locallocaltime($lastlogin); + } } - $usercount ++; - if ($mode eq 'view' || $mode eq 'html' || $mode eq 'autoenroll') { + if ($mode eq 'view' || $mode eq 'html' || $mode eq 'autoenroll' || $mode eq 'pickauthor') { $r->print(&Apache::loncommon::start_data_table_row()); - $r->print("'.&mt('manual').' | |
';
+ $manualcount ++;
+ if ($in{'lockedtype'}) {
+ $cellentry .= ' |
'. - &mt('Your Excel spreadsheet').' '.&mt('is ready for download').'.
'."\n"); + $r->print(''.&mt('[_1]Your Excel spreadsheet[_2] is ready for download.', '','')."
\n"); } elsif ($mode eq 'csv') { close($CSVfile); - $r->print(''. - &mt('Your CSV file').' is ready for download.'. - "\n"); + $r->print(''.&mt('[_1]Your CSV file[_2] is ready for download.', '','')."
\n"); $r->rflush(); } if ($mode eq 'autoenroll') { return ($usercount,$autocount,$manualcount,$lockcount,$unlockcount); + } else { + return ($usercount); } - return; +} + +sub bulkaction_javascript { + my ($formname,$caller) = @_; + my $docstart = 'document'; + if ($caller eq 'popup') { + $docstart = 'opener.document'; + } + my %lt = &Apache::lonlocal::texthash( + acwi => 'Access will be set to start immediately', + asyo => 'as you did not select an end date in the pop-up window', + accw => 'Access will be set to continue indefinitely', + asyd => 'as you did not select an end date in the pop-up window', + sewi => "Sections will be switched to 'No section'", + ayes => "as you either selected the 'No section' option", + oryo => 'or you did not select a section in the pop-up window', + arol => 'A role with no section will be added', + swbs => 'Sections will be switched to:', + rwba => 'Roles will be added for section(s):', + ); + my $alert = &mt("You must select at least one user by checking a user's 'Select' checkbox"); + my $noaction = &mt("You need to select an action to take for the user(s) you have selected"); + my $singconfirm = &mt(' for a single user?'); + my $multconfirm = &mt(' for multiple users?'); + &js_escape(\$alert); + &js_escape(\$noaction); + &js_escape(\$singconfirm); + &js_escape(\$multconfirm); + my $output = <<"ENDJS"; +function verify_action (field) { + var numchecked = 0; + var singconf = '$singconfirm'; + var multconf = '$multconfirm'; + if ($docstart.$formname.elements[field].length > 0) { + for (i=0; i<$docstart.$formname.elements[field].length; i++) { + if ($docstart.$formname.elements[field][i].checked == true) { + numchecked ++; + } + } + } else { + if ($docstart.$formname.elements[field].checked == true) { + numchecked ++; + } + } + if (numchecked == 0) { + alert("$alert"); + return; + } else { + var message = $docstart.$formname.bulkaction[$docstart.$formname.bulkaction.selectedIndex].text; + var choice = $docstart.$formname.bulkaction[$docstart.$formname.bulkaction.selectedIndex].value; + if (choice == '') { + alert("$noaction"); + return; + } else { + if (numchecked == 1) { + message += singconf; + } else { + message += multconf; + } +ENDJS + if ($caller ne 'popup') { + $output .= <<"NEWWIN"; + if (choice == 'chgdates' || choice == 'reenable' || choice == 'activate' || choice == 'chgsec') { + opendatebrowser(document.$formname,'$formname','go'); + return; + + } else { + if (confirm(message)) { + document.$formname.phase.value = 'bulkchange'; + document.$formname.submit(); + return; + } + } +NEWWIN + } else { + $output .= <<"POPUP"; + if (choice == 'chgdates' || choice == 'reenable' || choice == 'activate') { + var datemsg = ''; + if (($docstart.$formname.startdate_month.value == '') && + ($docstart.$formname.startdate_day.value == '') && + ($docstart.$formname.startdate_year.value == '')) { + datemsg = "\\n$lt{'acwi'},\\n$lt{'asyo'}.\\n"; + } + if (($docstart.$formname.enddate_month.value == '') && + ($docstart.$formname.enddate_day.value == '') && + ($docstart.$formname.enddate_year.value == '')) { + datemsg += "\\n$lt{'accw'},\\n$lt{'asyd'}.\\n"; + } + if (datemsg != '') { + message += "\\n"+datemsg; + } + } + if (choice == 'chgsec') { + var rolefilter = $docstart.$formname.showrole.options[$docstart.$formname.showrole.selectedIndex].value; + var retained = $docstart.$formname.retainsec.value; + var secshow = $docstart.$formname.newsecs.value; + if (secshow == '') { + if (rolefilter == 'st' || retained == 0 || retained == "") { + message += "\\n\\n$lt{'sewi'},\\n$lt{'ayes'},\\n$lt{'oryo'}.\\n"; + } else { + message += "\\n\\n$lt{'arol'}\\n$lt{'ayes'},\\n$lt{'oryo'}.\\n"; + } + } else { + if (rolefilter == 'st' || retained == 0 || retained == "") { + message += "\\n\\n$lt{'swbs'} "+secshow+".\\n"; + } else { + message += "\\n\\n$lt{'rwba'} "+secshow+".\\n"; + } + } + } + if (confirm(message)) { + $docstart.$formname.phase.value = 'bulkchange'; + $docstart.$formname.submit(); + window.close(); + } +POPUP + } + $output .= ' + } + } +} +'; + return $output; +} + +sub print_username_link { + my ($mode,$in) = @_; + my $output; + if ($mode eq 'autoenroll') { + $output = $in->{'username'}; + } else { + $output = '{'username'}','$in->{'domain'}'".')">'. + $in->{'username'}.''; + } + return $output; } sub role_type_names { my %lt = &Apache::lonlocal::texthash ( - 'domain' => 'Domain Roles', - 'construction_space' => 'Co-Author Roles', - 'course' => 'Course Roles', + 'domain' => 'Domain Roles', + 'author' => 'Co-Author Roles', + 'course' => 'Course Roles', + 'community' => 'Community Roles', ); return %lt; } -sub results_header_row { - my ($rolefilter,$statusmode,$context) = @_; - my $description; +sub select_actions { + my ($context,$setting,$statusmode,$formname) = @_; + my %lt = &Apache::lonlocal::texthash( + revoke => "Revoke user roles", + delete => "Delete user roles", + reenable => "Re-enable expired user roles", + activate => "Make future user roles active now", + chgdates => "Change starting/ending dates", + chgsec => "Change section associated with user roles", + ); + # FIXME Add an option to change credits for student roles. + my ($output,$options,%choices); + # FIXME Disable actions for now for roletype=course in domain context + if ($context eq 'domain' && $setting eq 'course') { + return; + } if ($context eq 'course') { - $description = &mt('Course - ').$env{'course.'.$env{'request.course.id'}.'.description'}.': '; - if ($statusmode eq 'Expired') { - $description .= &mt('Users in course with expired [_1] roles',$rolefilter); + if ($env{'form.showrole'} ne 'Any') { + my $showactions; + if (&Apache::lonnet::allowed('c'.$env{'form.showrole'}, + $env{'request.course.id'})) { + $showactions = 1; + } elsif ($env{'request.course.sec'} ne '') { + if (&Apache::lonnet::allowed('c'.$env{'form.showrole'},$env{'request.course.id'}.'/'.$env{'request.course.sec'})) { + $showactions = 1; + } + } + unless ($showactions) { + unless (&is_courseowner($env{'request.course.id'}, + $env{'course.'.$env{'request.course.id'}.'.internal.courseowner'})) { + return; + } + } } + } + if ($statusmode eq 'Any') { + $options .= ' +'.&mt('Existing sections')."\n".
+ ' '.$sections_select.' | '.
+ &mt('New section').' '."\n". + ''."\n". + ''."\n". + ' |
- + END - -my %lt=&Apache::lonlocal::texthash('usrn' => "username", - 'dom' => "domain", - 'sn' => "student name", - 'sec' => "section", - 'start' => "start date", - 'end' => "end date", - 'groups' => "active groups", - ); + my ($indexhash,$keylist) = &make_keylist_array(); + my $studentcount = 0; + if (ref($classlist) eq 'HASH') { + foreach my $student (keys(%{$classlist})) { + my $sdata = $classlist->{$student}; + my $status = $sdata->[$indexhash->{'status'}]; + my $section = $sdata->[$indexhash->{'section'}]; + if ($status ne 'Active') { + delete($classlist->{$student}); + next; + } + if ($env{'request.course.sec'} ne '') { + if ($section ne $env{'request.course.sec'}) { + delete($classlist->{$student}); + next; + } + } + $studentcount ++; + } + } + if (!$studentcount) { + my $msg = ''; + if ($crstype eq 'Community') { + $msg = &mt('There are no members to drop.'); + } else { + $msg = &mt('There are no students to drop.'); + } + $r->print('
'.$msg.'
'); + return; + } + my ($classgroups) = &Apache::loncoursedata::get_group_memberships( + $classlist,$keylist,$cdom,$cnum); + my %lt=&Apache::lonlocal::texthash('usrn' => "username", + 'dom' => "domain", + 'id' => "ID", + 'sn' => "student name", + 'mn' => "member name", + 'sec' => "section", + 'start' => "start date", + 'end' => "end date", + 'groups' => "active groups", + ); + my $nametitle = $lt{'sn'}; + if ($crstype eq 'Community') { + $nametitle = $lt{'mn'}; + } if ($nosort) { - $r->print(&Apache::loncommon::start_data_table()); + $r->print(&Apache::loncommon::start_data_table(). + &Apache::loncommon::start_data_table_header_row()); $r->print(<- - -
++ + +
++ +
END return; } @@ -2245,30 +4067,52 @@ END sub print_first_users_upload_form { my ($r,$context) = @_; my $str; - $str = ''; + $str = ''; $str .= ''; - $str .= ''; - $str .= "\n";
- $str .= ''."\n";
- $str .= '
'."\n" + .&mt('Please upload an UTF8 encoded file to ensure a correct character encoding in your classlist.')."\n" + .'
'."\n"; + } + } + $str .= &Apache::loncommon::upfile_select_html() + .&Apache::lonhtmlcommon::row_closure() + .&Apache::lonhtmlcommon::row_title( + '' + .'' + .'
'; + $r->print($str); return; } # ================================================= Drop/Add from uploaded file sub upfile_drop_add { - my ($r,$context) = @_; + my ($r,$context,$permission,$showcredits) = @_; &Apache::loncommon::load_tmp_file($r); my @userdata=&Apache::loncommon::upfile_record_sep(); if($env{'form.noFirstLine'}){shift(@userdata);} @@ -2283,36 +4127,91 @@ sub upfile_drop_add { $fields{$env{'form.f'.$i}}=$keyfields[$i]; } } + if ($env{'form.fullup'} ne 'yes') { + $r->print(''); + $r->print(&print_namespacing_alerts($domain,\%alerts,\%curr_rules)); ##################################### - # Drop students # + # Display list of students to drop # ##################################### if ($env{'form.fullup'} eq 'yes') { - $r->print(''.&mt('Dropped [_1] user(s).',$count).'
'); - $r->print(''.&mt('Re-enrollment will re-activate data.')) if ($count);
+ return $dates;
}
-sub section_check_js {
- my $groupslist;
- my %curr_groups = &Apache::longroup::coursegroups();
- if (%curr_groups) {
- $groupslist = join('","',sort(keys(%curr_groups)));
+sub classlist_drop {
+ my ($scope,$uname,$udom,$now) = @_;
+ my ($cdom,$cnum) = ($scope=~m{^/($match_domain)/($match_courseid)});
+ if (&Apache::lonnet::is_course($cdom,$cnum)) {
+ if (!&active_student_roles($cnum,$cdom,$uname,$udom)) {
+ my %user;
+ my $result = &update_classlist($cdom,$cnum,$udom,$uname,\%user,$now);
+ return &mt('Drop from classlist: [_1]',
+ ''.$result.'').'
';
+ }
}
+}
+
+sub active_student_roles {
+ my ($cnum,$cdom,$uname,$udom) = @_;
+ my %roles =
+ &Apache::lonnet::get_my_roles($uname,$udom,'userroles',
+ ['future','active'],['st']);
+ return exists($roles{"$cnum:$cdom:st"});
+}
+
+sub section_check_js {
+ my $groupslist= &get_groupslist();
+ my %js_lt = &Apache::lonlocal::texthash(
+ mayn => 'may not be used as the name for a section, as it is a reserved word.',
+ plch => 'Please choose a different section name.',
+ mnot => 'may not be used as a section name, as it is the name of a course group.',
+ secn => 'Section names and group names must be distinct. Please choose a different section name.',
+ );
+ &js_escape(\%js_lt);
return <<"END";
function validate(caller) {
- var groups = new Array("$groupslist");
+ var groups = new Array($groupslist);
var secname = caller.value;
if ((secname == 'all') || (secname == 'none')) {
- alert("'"+secname+"' may not be used as the name for a section, as it is a reserved word.\\nPlease choose a different section name.");
+ alert("'"+secname+"' $js_lt{'mayn'}\\n$js_lt{'plch'}");
return 'error';
}
if (secname != '') {
for (var k=0; k
+END
+}
+
+sub custom_coursetype_switch {
+ my ($crstype,$prefix) = @_;
+ my ($checkedcourse,$checkedcommunity);
+ if ($crstype eq 'Community') {
+ $checkedcommunity = ' checked="checked"';
+ } else {
+ $checkedcourse = ' checked="checked"';
+ }
+ my %lt = &Apache::lonlocal::texthash(
+ cont => 'Context',
+ cour => 'Course',
+ comm => 'Community',
+ );
+ return <<"END";
+'.$lt{'prv'}.' '.$lt{'crl'}.' '.$lt{'dml'}.
+ ' '.$lt{'ssl'}.' '.
+ &Apache::loncommon::end_data_table_header_row();
+ foreach my $priv (sort(keys(%{$full}))) {
+ my $privtext = &Apache::lonnet::plaintext($priv,$crstype);
+ $output .= &Apache::loncommon::start_data_table_row().
+ ''.$privtext.' ';
+ foreach my $type ('course','domain','system') {
+ if (($type eq 'system') && ($priv eq 'bre') && ($crstype eq 'Community')) {
+ $output .= ' ';
+ } else {
+ $output .= ''.
+ ($levels->{$type}{$priv}?'{$type}{$priv}?' checked="checked"':'').' />':' ').
+ ' ';
+ }
+ }
+ $output .= &Apache::loncommon::end_data_table_row();
+ }
+ $output .= &Apache::loncommon::end_data_table();
+ return $output;
+}
+
+sub custom_role_privs {
+ my ($privs,$full,$levels,$levelscurrent)= @_;
+ return unless ((ref($privs) eq 'HASH') && (ref($full) eq 'HASH') &&
+ (ref($levels) eq 'HASH') && (ref($levelscurrent) eq 'HASH'));
+ my %cr = (
+ course => 'cr:c',
+ domain => 'cr:d',
+ system => 'cr:s',
+ );
+ foreach my $type ('course','domain','system') {
+ foreach my $item (split(/\:/,$Apache::lonnet::pr{$cr{$type}})) {
+ my ($priv,$restrict)=split(/\&/,$item);
+ if (!$restrict) { $restrict='F'; }
+ $levels->{$type}->{$priv}=$restrict;
+ if ($privs->{$type}=~/\:$priv/) {
+ $levelscurrent->{$type}->{$priv}=1;
+ }
+ $full->{$priv}=1;
+ }
+ }
+ return;
+}
+
+sub custom_template_roles {
+ my ($context,$crstype) = @_;
+ my @template_roles = ("in","ta","ep");
+ if (($context eq 'domain') || ($context eq 'domprefs')) {
+ push(@template_roles,"ad");
+ }
+ push(@template_roles,"st");
+ if ($context eq 'domain') {
+ unshift(@template_roles,('co','cc'));
+ } else {
+ if ($crstype eq 'Community') {
+ unshift(@template_roles,'co');
+ } else {
+ unshift(@template_roles,'cc');
+ }
+ }
+ return @template_roles;
+}
+
+sub custom_roledefs_js {
+ my ($context,$crstype,$formname,$full,$templaterolesref,$jsback) = @_;
+ my $button_code = "\n";
+ my $head_script = "\n";
+ my (%roletitlestr,$rolenamestr);
+ my %role_titles = (
+ Course => [],
+ Community => [],
+ );
+ $head_script .= ''."\n";
+ return $head_script;
+}
+
+# --------------------------------------------------------
+sub make_script_template {
+ my ($role,$crstype,$formname) = @_;
+ my $return_script = 'function set_'.$role.'(prefix) {'."\n";
+ my (%full_by_level,%role_priv);
+ foreach my $level ('c','d','s') {
+ foreach my $item (split(/\:/,$Apache::lonnet::pr{'cr:'.$level})) {
+ next if (($level eq 's') && ($crstype eq 'Community') && ($item eq 'bre&S'));
+ my ($priv,$restrict)=split(/\&/,$item);
+ $full_by_level{$level}{$priv}=1;
+ }
+ $role_priv{$level} = {};
+ my @temp = split(/:/,$Apache::lonnet::pr{$role.':'.$level});
+ foreach my $priv (@temp) {
+ my ($priv_item, $dummy) = split(/\&/,$priv);
+ $role_priv{$level}{$priv_item} = 1;
+ }
+ }
+ my %to_check = (
+ c => ['c','d','s'],
+ d => ['d','s'],
+ s => ['s'],
+ );
+ foreach my $level ('c','d','s') {
+ if (ref($full_by_level{$level}) eq 'HASH') {
+ foreach my $priv (keys(%{$full_by_level{$level}})) {
+ my $value = 'false';
+ if (ref($to_check{$level}) eq 'ARRAY') {
+ foreach my $lett (@{$to_check{$level}}) {
+ if (exists($role_priv{$lett}{$priv})) {
+ $value = 'true';
+ last;
+ }
+ }
+ $return_script .= "document.$formname.elements[prefix+'".$priv."_".$level."'].checked = $value;\n";
+ }
+ }
+ }
+ }
+ $return_script .= '}'."\n";
+ return ($return_script);
+}
+# ----------------------------------------------------------
+sub make_button_code {
+ my ($role,$crstype,$display,$prefix) = @_;
+ my $label = &Apache::lonnet::plaintext($role,$crstype);
+ my $button_code = '';
+ return ($button_code);
+}
+
+sub custom_role_update {
+ my ($rolename,$prefix) = @_;
+# ------------------------------------------------------- What can be assigned?
+ my %privs = (
+ c => '',
+ d => '',
+ s => '',
+ );
+ foreach my $level (keys(%privs)) {
+ foreach my $item (split(/\:/,$Apache::lonnet::pr{'cr:'.$level})) {
+ my ($priv,$restrict)=split(/\&/,$item);
+ if (!$restrict) { $restrict=''; }
+ if ($env{'form.'.$prefix.$priv.'_'.$level}) {
+ $privs{$level} .=':'.$item;
+ }
+ }
+ }
+ return %privs;
+}
+
+sub adhoc_status_types {
+ my ($cdom,$context,$role,$selectedref,$othertitle,$usertypes,$types,$disabled) = @_;
+ my $output = &Apache::loncommon::start_data_table();
+ my $numinrow = 3;
+ my $rem;
+ if (ref($types) eq 'ARRAY') {
+ for (my $i=0; $i<@{$types}; $i++) {
+ if (defined($usertypes->{$types->[$i]})) {
+ my $rem = $i%($numinrow);
+ if ($rem == 0) {
+ if ($i > 0) {
+ $output .= &Apache::loncommon::end_data_table_row();
+ }
+ $output .= &Apache::loncommon::start_data_table_row();
+ }
+ my $check;
+ if (ref($selectedref) eq 'ARRAY') {
+ if (grep(/^\Q$types->[$i]\E$/,@{$selectedref})) {
+ $check = ' checked="checked"';
+ }
+ }
+ $output .= ''.
+ ' ';
+ }
+ }
+ $rem = @{$types}%($numinrow);
+ }
+ my $colsleft = $numinrow - $rem;
+ if (($rem == 0) && (@{$types} > 0)) {
+ $output .= &Apache::loncommon::start_data_table_row();
+ }
+ if ($colsleft > 1) {
+ $output .= '';
+ } else {
+ $output .= ' ';
+ }
+ my $defcheck;
+ if (ref($selectedref) eq 'ARRAY') {
+ if (grep(/^default$/,@{$selectedref})) {
+ $defcheck = ' checked="checked"';
+ }
+ }
+ $output .= ' '.
+ &Apache::loncommon::end_data_table_row().
+ &Apache::loncommon::end_data_table();
+ return $output;
+}
+
+sub adhoc_staff {
+ my ($access,$context,$role,$selectedref,$adhocref,$disabled) = @_;
+ my $output;
+ if (ref($adhocref) eq 'HASH') {
+ my %by_fullname;
+ my $numinrow = 4;
+ my $rem;
+ my @personnel = keys(%{$adhocref});
+ if (@personnel) {
+ foreach my $person (@personnel) {
+ my ($uname,$udom) = split(/:/,$person);
+ my $fullname = &Apache::loncommon::plainname($uname,$udom,'lastname');
+ $by_fullname{$fullname} = $person;
+ }
+ my @sorted = sort(keys(%by_fullname));
+ my $count = scalar(@sorted);
+ $output = &Apache::loncommon::start_data_table();
+ for (my $i=0; $i<$count; $i++) {
+ my $rem = $i%($numinrow);
+ if ($rem == 0) {
+ if ($i > 0) {
+ $output .= &Apache::loncommon::end_data_table_row();
+ }
+ $output .= &Apache::loncommon::start_data_table_row();
+ }
+ my $check;
+ my $user = $by_fullname{$sorted[$i]};
+ if (ref($selectedref) eq 'ARRAY') {
+ if (grep(/^\Q$user\E$/,@{$selectedref})) {
+ $check = ' checked="checked"';
+ }
+ }
+ if ($i == $count-1) {
+ my $colsleft = $numinrow - $rem;
+ if ($colsleft > 1) {
+ $output .= '';
+ } else {
+ $output .= ' ';
+ }
+ } else {
+ $output .= ' ';
+ }
+ $output .= ' ';
+ if ($i == $count-1) {
+ $output .= &Apache::loncommon::end_data_table_row();
+ }
+ }
+ $output .= &Apache::loncommon::end_data_table();
+ }
+ }
+ return $output;
+}
+
+
1;