#!/usr/bin/perl # The LearningOnline Network # # $Id: rebuild_lastlogin.pl,v 1.1 2013/10/26 20:19:56 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # # This file is part of the LearningOnline Network with CAPA (LON-CAPA). # # LON-CAPA is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # LON-CAPA is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with LON-CAPA; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # /home/httpd/html/adm/gpl.txt # # http://www.lon-capa.org/ # ################################################# =pod =head1 NAME rebuild_lastlogin.pl =head1 SYNOPSIS rebuild_lastlogin.pl is run on a library server and gathers last login information for users in course(s) for which the current server is the home server. =head1 DESCRIPTION For each selected course a hash containing keys of: username:domain:section:role with values: UNIX timestamp of most recent role/section selection, i.e., "last log-in" will be stored in a nohist_crslastlogin.db file for the course. usage: either enter the script name followed by at least one argument: the time interval prior to now, i.e., day, week, month, year or all for which last log-in activity is to be retrieved or enter the script name followed by at least two arguments: 1. a specific courseID and 2. a specific course domain. Both cases support an additional, optional, final argument: a two letter code for the desired language. ar => ﺎﻠﻋﺮﺒﻳﺓ de => Deutsch en => English es => español fa => ﺍیﺭﺎﻧی fr => français he => עברית ja => 日本語 pt => Português ru => Русский tr => türkçe zh => 简体中文 The default language is en. If a translation exists for the language specified as the final argument, then if a time interval is being used that will be specified in that language, in the first argument when calling the script. The script must be run as the user www. Because the script will attempt to retrieve user activity log from the home server of each student or course personnel, the LON-CAPA daemons need to be running in case that person's home server is not the same as the course's home server. =cut ################################################# #! /usr/bin/perl use strict; use lib '/home/httpd/lib/perl/'; use Apache::lonnet; use Apache::loncommon; use Apache::loncoursedata; use Apache::lonlocal; use LONCAPA qw(:DEFAULT :match); my %languages = ( ar => 'ﺎﻠﻋﺮﺒﻳﺓ', de => 'Deutsch', en => 'English', es => 'español', fa => 'ﺍیﺭﺎﻧی', fr => 'français', he => 'עברית', ja => '日本語', pt => 'Português', ru => 'Русский', tr => 'türkçe', zh => '简体中文', ); my $langchoices = "\n"; foreach my $key (sort(keys(%languages))) { $langchoices .= ' '.$key.' => '.$languages{$key}."\n"; } my $lang = 'en'; my @args = @ARGV; my @domains = sort(&Apache::lonnet::current_machine_domains()); my @ids=&Apache::lonnet::current_machine_ids(); my ($domfilter,$possdomstr); my $crsfilter = '.'; if (@domains > 1) { $possdomstr = join('|',@domains); } else { $possdomstr = $domains[0]; } if (@args>3) { if (exists($languages{$args[2]})) { $lang = $args[2]; } &Apache::lonlocal::get_language_handle(undef,$lang); print &mt('[_1] takes a maximum of 3 arguments.','rebuild_lastlogin.pl')."\n"; exit; } elsif ((@args==3) && exists($languages{$args[2]})) { $lang = pop(@args); } elsif ((@args==2) && exists($languages{$args[1]})) { $lang = pop(@args); } &Apache::lonlocal::get_language_handle(undef,$lang); if (@args == 2) { my ($cnum,$cdom) = @args; my $invalid; if ($cnum =~ /^$match_courseid$/ && $cdom =~ /^$match_domain$/) { if (grep(/^\Q$cdom\E$/,@domains)) { my %crshash = &Apache::lonnet::coursedescription("$cdom/$cnum",{'one_time' => 1}); if ($crshash{'num'} eq $cnum) { $crsfilter = $cnum; $domfilter = $cdom; } else { $invalid = 1; } } else { $invalid = 1; } } else { $invalid = 1; } if ($invalid) { print "\n".&mt('usage for a single course: [_1] where [_2] is an optional two letter code.', 'rebuild_lastlogin.pl [COURSEID] [COURSEDOMAIN] ', '')."\n"; exit; } } if ($crsfilter eq '.') { if (!@args || ($args[0] ne &mt('all') && $args[0] ne &mt('year') && $args[0] ne &mt('month') && $args[0] ne &mt('week') && $args[0] ne &mt('day'))) { print "\n". &mt('usage: either enter the script name followed by at least one argument')."\n". ' -- 1. '.&mt('the time interval from prior to now, i.e., day, week etc., for which log-in activity is to be checked')."\n\n". ' '.&mt('or enter the script name followed by at least two arguments')."\n". ' -- 1. '.&mt('a specific courseID')."\n". ' -- 2. '.&mt('a specific course domain')."\n\n". ' '.&mt('Both cases support an additional, optional, final argument: a two letter code for the desired language')."\n". ' '.&mt('-- one of: [_1]').$langchoices."\n". ' '.&mt('Accordingly use one of the following:')."\n\n". ' rebuild_lastlogin.pl '.&mt('all').' '."\n". ' rebuild_lastlogin.pl '.&mt('year').' '."\n". ' rebuild_lastlogin.pl '.&mt('month').' '."\n". ' rebuild_lastlogin.pl '.&mt('week').' '."\n". ' rebuild_lastlogin.pl '.&mt('day').' '."\n". ' rebuild_lastlogin.pl [COURSEID] [COURSEDOMAIN] '."\n"; exit; } } # Make sure this process is running from user=www my $wwwid=getpwnam('www'); if ($wwwid!=$<) { my $emailto="$Apache::lonnet::perlvar{'lonAdmEMail'}}"; my $subj="LON: $Apache::lonnet::perlvar{'lonHostID'} User ID mismatch"; my $msg=&mt('User ID mismatch.').' '.&mt('[_1] must be run as user www.','rebuild_lastlogin.pl'); system("echo '$msg' | mail -s '$subj' $emailto > /dev/null"); exit 1; } if ($Apache::lonnet::perlvar{'lonRole'} ne 'library') { print &mt('[_1] only runs on a LON-CAPA library server.','rebuild_lastlogin.pl')."\n"; exit; } # Log script run open(my $fh,'>>'.$Apache::lonnet::perlvar{'lonDaemons'}.'/logs/buildlastlogin.log'); print $fh "==== buildlastlogindb.pl Run ".localtime()."====\n"; my %users; my $timefilter = 1; my $now = time; if ($crsfilter eq '.') { if ($ARGV[0] eq &mt('year')) { $timefilter = $now-31536000; } elsif ($ARGV[0] eq &mt('month')) { $timefilter = $now-2592000; } elsif ($ARGV[0] eq &mt('week')) { $timefilter = $now-604800; } elsif ($ARGV[0] eq &mt('day')) { $timefilter = $now-86400; } } foreach my $dom (@domains) { if ($domfilter) { next if ($dom ne $domfilter); } my %courseshash; my %currhash = &Apache::lonnet::courseiddump($dom,'.',$timefilter,'.','.',$crsfilter,1,\@ids,'.'); foreach my $cid (sort(keys(%currhash))) { my ($cdom,$cnum) = split(/_/,$cid); my $path = &propath($cdom,$cnum); my %advrolehash = &Apache::lonnet::get_my_roles($cnum,$cdom,undef, ['previous','active','future']); my %coursehash = &Apache::lonnet::coursedescription("$cdom/$cnum",{'one_time' => 1}); my %nothidden; if ($coursehash{'nothideprivileged'}) { foreach my $item (split(/\s*\,\s*/,$coursehash{'nothideprivileged'})) { my $user; if ($item =~ /:/) { $user = $item; } else { $user = join(':',split(/[\@]/,$item)); } $nothidden{$user} = 1; } } foreach my $user (keys(%advrolehash)) { my ($uname,$udom,$rest) = split(/:/,$user,3); next if ($users{$uname.':'.$udom}); my @privdoms = ($cdom); if ($udom ne $cdom) { @privdoms = ($udom,$cdom); } if (&Apache::lonnet::privileged($uname,$udom,\@privdoms)) { unless ($nothidden{$uname.':'.$udom}) { next; } } $users{$uname.':'.$udom} = 1; } my $classlist=&Apache::loncoursedata::get_classlist($cdom,$cnum); if (ref($classlist) eq 'HASH') { foreach my $student (keys(%{$classlist})) { next if ($users{$student}); if ($student =~/^($match_username)\:($match_domain)$/) { my ($tuname,$tudom)=($1,$2); my @privdoms = ($cdom); if ($tudom ne $cdom) { @privdoms = ($tudom,$cdom); } if (&Apache::lonnet::privileged($tuname,$tudom,\@privdoms)) { unless ($nothidden{$student}) { next; } } $users{$student} = 1; } } } } } my %lastlogin; my (%numlib,%domservers,%filter); $filter{'action'} = 'Role'; my $possdom; if ($domfilter) { $possdom = $domfilter; } else { $possdom = $possdomstr; } foreach my $dom (@domains) { my %servers = &Apache::lonnet::get_servers($dom,'library'); $numlib{$dom} = scalar(keys(%servers)); if ($numlib{$dom} > 1) { $domservers{$dom} = %servers; } } foreach my $user (sort(keys(%users))) { my ($uname,$udom) = split(/:/,$user); if (grep(/^\Q$udom\E$/,@domains)) { if ($numlib{$udom} == 1) { &readlocalactivitylog($uname,$udom,$possdom,$timefilter,$crsfilter,\%lastlogin); } else { if (ref($domservers{$udom}) eq 'HASH') { my $uhome = &Apache::lonnet::homeserver($uname,$udom); if ($uhome ne 'no_host') { if ($domservers{$udom}{$uhome}) { &readlocalactivitylog($uname,$udom,$possdom,$timefilter,$crsfilter,\%lastlogin); } else { &readremoteactivitylog($uname,$udom,$possdom,$timefilter,$crsfilter,\%filter, \%lastlogin); } } } } } else { my $uhome = &Apache::lonnet::homeserver($uname,$udom); if ($uhome ne 'no_host') { &readremoteactivitylog($uname,$udom,$possdom,$timefilter,$crsfilter,\%filter,\%lastlogin); } } } foreach my $key (sort(keys(%lastlogin))) { if (ref($lastlogin{$key}) eq 'HASH') { if ($key =~ /^($match_domain)_($match_courseid)$/) { my $cdom = $1; my $cnum = $2; my $putresult = &Apache::lonnet::put('nohist_crslastlogin',$lastlogin{$key}, $cdom,$cnum); if ($putresult eq 'ok') { print $fh "stored last login data for $key\n"; } } } } ## Finished! print $fh "==== buildlastlogindb.pl completed ".localtime()." ====\n"; close($fh); exit; sub readlocalactivitylog { my($uname,$udom,$possdomstr,$timefilter,$crsfilter,$lastlogin) = @_; my $path = &propath($udom,$uname); my $posscnum; if ($crsfilter eq '.') { $posscnum = $match_courseid; } else { $posscnum = $crsfilter; } if (-e "$path/activity.log") { if (open(my $fh,"<$path/activity.log")) { my @lines = <$fh>; @lines = reverse(@lines); foreach my $line (@lines) { chomp($line); if ($line =~ m{^(\d+):\w+:Role\s+(cc|in|ta|ep|st|ad)\./($possdomstr)/($posscnum)/?([^/]*)}) { if (($timefilter > 1) && ($1<$timefilter)) { last; } if (ref($lastlogin->{$3.'_'.$4}) eq 'HASH') { if ($lastlogin->{$3.'_'.$4}{$uname.':'.$udom.':'.$5.':'.$2}) { if ($crsfilter ne '.') { last; } else { next; } } } $lastlogin->{$3.'_'.$4}{$uname.':'.$udom.':'.$5.':'.$2} = $1; } } close($fh); } } return; } sub readremoteactivitylog { my ($uname,$udom,$possdomstr,$timefilter,$crsfilter,$filter,$lastlogin) = @_; if (ref($filter) eq 'HASH') { my %filters = %{$filter}; my $result = &Apache::lonnet::userlog_query($uname,$udom,%filters); my $posscnum; if ($crsfilter eq '.') { $posscnum = $match_courseid; } else { $posscnum = $crsfilter; } my $now = time; if (($result ne 'file_error') && ($result ne 'error: reply_file_error') && ($result !~ /^timeout:/)) { my $now = time; foreach my $item (map { &unescape($_); } (split(/&/,$result))) { if ($item =~ m{^(\d+):\w+:Role\s+(cc|in|ta|ep|st|ad)\./($possdomstr)/($posscnum)/?([^/]*)}) { if (($timefilter > 1) && ($1<$timefilter)) { last; } if (ref($lastlogin->{$3.'_'.$4}) eq 'HASH') { if ($lastlogin->{$3.'_'.$4}{$uname.':'.$udom.':'.$5.':'.$2}) { if ($crsfilter ne '.') { last; } else { next; } } } $lastlogin->{$3.'_'.$4}{$uname.':'.$udom.':'.$5.':'.$2} = $1; } } } } return; }