#!/usr/bin/perl # TmNetWiki version 0.4 (September 11, 2002) # Copyright (C) 2002 Adam Brate # # Based on the GPLed UseModWiki 0.92 # Copyright (C) 2000-2001 Clifford A. Adams # or # Based on the GPLed AtisWiki 0.3 (C) 1998 Markus Denker # # ...which was based on # the LGPLed CVWiki CVS-patches (C) 1997 Peter Merel # and The Original WikiWikiWeb (C) Ward Cunningham # (code reused with permission) # Email and ThinLine options by Jim Mahoney # # This program 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. # # This program 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 this program; if not, write to the # Free Software Foundation, Inc. # 59 Temple Place, Suite 330 # Boston, MA 02111-1307 USA package Wiki::rc; use strict; # needs subs Wiki::utility: GetParam, QuoteHtml, # Wiki::translate: Ts, T, # Wiki::file: ReadFile, # Wiki::template: ScriptLink, GetAuthorLink, GetHistoryLink, # GetPageLink, ScriptLinkDiff # Wiki::calc: TimeToText, CalcTime, CalcDay # Wiki::page: PageIsLocked # Wiki::area: GetArea # # needs consts $Now $RcDefault, $RcFile, $RcOldFile, @RcDays, $RCName, $UseDiff, # $RecentTop, $FS2, $FS3 # needs vars $UserID use base 'Exporter'; use Wiki::consts; use Wiki::utility; use Wiki::translate; use Wiki::file; use Wiki::template; use Wiki::calc; use Wiki::page; use Wiki::user; use Wiki::area; #if ($^V and $^V gt v5.6.0) { # our @EXPORT; #else { use vars qw(@EXPORT); #} @EXPORT = qw( DoRc GetRc WriteRcLog GetAuthors ); sub GetRcHtml; sub WriteRcLog; sub DoRc { print &GetRc(); } # Recent Changes page sub GetRc { my ($options) = (@_); my ($fileData, $rcline, $i, $daysago, $lastTs, $ts); my ($areaOnly,$idOnly,$userOnly,%uid); my (@fullrc, $status, $oldFileData, $firstTs, $errorText,$rcText); my ($rcHeader,$rcDaysHeader,$rcHtml,$noUpdates); my $starttime = 0; my $lastnum = 0; my $showbar = 0; $rcText = ''; if ($lastnum = &GetParam("lastnum",defined($options->{lastnum})? $options->{lastnum}:0)) { $rcHeader = Ts('Last %s update'.(($lastnum !=1)?'s':''),$lastnum); } elsif ($starttime = &GetParam("from", defined($options->{from})? $options->{from}:0)) { $rcHeader = Ts('Updates since %s', &TimeToText($starttime)); } else { $daysago = &GetParam("days", defined($options->{days})? $options->{days}:0); $daysago = &GetParam("rcdays", 0) if ($daysago == 0); if ($daysago) { $starttime = $Consts->{Now} - ((24*60*60)*$daysago); $rcHeader = Ts('Updates in the last %s day' . (($daysago != 1)?"s":""), $daysago); # Note: must have two translations (for "day" and "days") # Following comment line is for translation helper script # Ts('Updates in the last %s days', ''); } } if ($starttime == 0 && $lastnum == 0) { $starttime = $Consts->{Now} - ((24*60*60)*$Consts->{RcDefault}); $rcHeader = Ts('Updates in the last %s day' . (($Consts->{RcDefault} != 1)?"s":""), $Consts->{RcDefault}); # Translation of above line is identical to previous version } # Get rid of rcHeader if so desired if ($options->{noheader}) { $options->{rcheader} = 0; } if (!&GetParam("rcheader",defined($options->{rcheader})? $options->{rcheader}:1)) { $rcHeader = ""; } # Read rclog data (and oldrclog data if needed) ($status,$errorText,$firstTs,$lastTs,@fullrc) = &ReadRcLog($starttime); unless ($status) { $rcText = &GetTemplate('rc',{ rcHeader => $rcHeader, errorText => $errorText, }); return $rcText; } $idOnly = &GetParam("rcidonly", $options->{rcidonly}); $userOnly = &GetParam("rcuseronly",$options->{rcuseronly}); if ($Consts->{FreeLinks}) { $userOnly =~ s/_/ /g; } # This isn't really good. if ($options->{area}) { $options->{rcareaonly} = $options->{area}; } $areaOnly = &GetParam("rcareaonly",$options->{rcareaonly}); if ($options->{nodaysheader}) { $options->{rcdaysheader} = 0; } if (&GetParam("rcdaysheader",defined($options->{rcdaysheader})? $options->{rcdaysheader}:1)) { my $action='action=rc'; $action .= "&rcidonly=$idOnly" if $idOnly ne ""; $action .= "&rcuseronly=$userOnly" if $userOnly ne ""; $action .= "&rcareaonly=$areaOnly" if $areaOnly ne ""; if ($Consts->{FreeLinks}) { $action =~ s/ /_/g; } $rcDaysHeader = join(" | ",map(&ScriptLink("$action&days=$_", Ts('%s day'.(($_ != 1)? 's':''),$_)), @{$Consts->{RcDays}})); } # Note: must have two translations (for "day" and "days") # Following comment line is for translation helper script # Ts('%s days', ''); unless ($lastnum) { # Later consider a binary search? $i = 0; while ($i < @fullrc) { # Optimization: skip old entries quickly ($ts) = split(/$Consts->{FS3}/, $fullrc[$i]); if ($ts >= $starttime) { $i -= 1000 if ($i > 0); last; } $i += 1000; } $i -= 1000 if (($i > 0) && ($i >= @fullrc)); for (; $i < @fullrc ; $i++) { ($ts) = split(/$Consts->{FS3}/, $fullrc[$i]); last if ($ts >= $starttime); } if ($i == @fullrc) { $noUpdates = 1; } splice(@fullrc, 0, $i); # Remove items before index $i } my ($showedit,$showlocked, @temprc,$ts,$pagename,$summary,$isEdit,$host,$kind,$extra,$rcline); $showedit = &GetParam("rcshowedit", $Consts->{ShowEdits}); $showedit = &GetParam("showedit", defined($options->{showedit}) ? $options->{showedit} : $showedit); if ($options->{nolocked}) {$options->{showlocked} = 0; } $showlocked = &GetParam("rcshowlocked",&UserIsAdmin()); $showlocked = &GetParam("showlocked",defined($options->{showlocked}) ? $options->{showlocked} : $showlocked); if ($showedit != 1 || $idOnly ne "" || $userOnly ne "" || $areaOnly ne "") { my @temprc = (); foreach $rcline (@fullrc) { ($ts, $pagename, $summary, $isEdit, $host,$kind,$extra) = split(/$Consts->{FS3}/, $rcline); next if (($idOnly ne "") && ($idOnly ne $pagename)); if ($userOnly ne "") { my %extra = split(/$Consts->{FS2}/,$extra); next if (!defined $extra{name} || $userOnly ne $extra{name}); $uid{$extra{id}}++; } next if (!$showlocked && &PageIsLocked($pagename)); if ($areaOnly ne "") { my $pageid = $pagename; if ($Consts->{FreeLinks}) { $pageid =~ s/ /_/g; } next if $areaOnly ne &GetArea($pageid); } if ($showedit == 0) { # 0 = No edits push(@temprc, $rcline) if (!$isEdit); } elsif ($showedit == 2) { # 2 = Only edits push(@temprc, $rcline) if ($isEdit); } else { push(@temprc, $rcline); } } @fullrc = @temprc; } if ($lastnum <= $#fullrc) { @fullrc = splice(@fullrc,-1*$lastnum); } # Later consider an end-time limit (items older than X) if (defined($options->{style}) && $options->{style} eq 'min') { $options->{minrc} = 1; } if (&GetParam("minrc",defined($options->{minrc})? $options->{minrc}:0)) { $rcHtml = &GetMinRcHtml(@fullrc); $rcText = &GetTemplate('rc',{ rcHeader => $rcHeader, rcDaysHeader => $rcDaysHeader, errorText => $errorText, noUpdates => $noUpdates, Ts => \&Ts, idOnly => $idOnly, userOnly => $userOnly, areaOnly => $areaOnly, uid => join(', ',keys %uid), getAuthorLink => \&GetAuthorLink, getPageLink => \&GetPageLink, rcHtml => $rcHtml, },'min'); } else { $rcHtml = &GetRcHtml(@fullrc); $rcText = &GetTemplate('rc',{ rcHeader => $rcHeader, rcDaysHeader => $rcDaysHeader, errorText => $errorText, noUpdates => $noUpdates, timeToText => \&TimeToText, T => \&T, Ts => \&Ts, lastTs => $lastTs, starttime => $starttime, idOnly => $idOnly, userOnly => $userOnly, uid => join(', ',keys %uid), getScriptLink => \&ScriptLink, getAuthorLink => \&GetAuthorLink, getPageLink => \&GetPageLink, Consts => $Consts, rcHtml => $rcHtml, }); } return $rcText; } # this repeats a lot of GetRcHtml. sub GetMinRcHtml { my @outrc = @_; my ($rcline, $newtop, $html); my %changetime = (); my %pagecount = (); foreach $rcline (@outrc) { my ($ts, $pagename) = split(/$Consts->{FS3}/, $rcline); $pagecount{$pagename}++; $changetime{$pagename} = $ts; } $newtop = &GetParam("rcnewtop", $Consts->{RecentTop}); $newtop = &GetParam("newtop", $newtop); @outrc = reverse @outrc if ($newtop); $html = &GetTemplate('rc', { outrc => \@outrc, Consts => $Consts, changetime => \%changetime, pagecount => \%pagecount, getPageLink => \&GetPageLink, },'minHtml'); return $html; } sub GetRcHtml { my @outrc = @_; my ($rcline, $newtop, $html, $all); my %changetime = (); my %pagecount = (); # Later consider folding into loop above? # Later add lines to assoc. pagename array (for new RC display) foreach $rcline (@outrc) { my ($ts, $pagename) = split(/$Consts->{FS3}/, $rcline); $pagecount{$pagename}++; $changetime{$pagename} = $ts; } $all = &GetParam("rcall", 0); $all = &GetParam("all", $all); $newtop = &GetParam("rcnewtop", $Consts->{RecentTop}); $newtop = &GetParam("newtop", $newtop); @outrc = reverse @outrc if ($newtop); $html = &GetTemplate('rc', { T => \&T, outrc => \@outrc, Consts => $Consts, all => $all, changetime => \%changetime, pagecount => \%pagecount, calcDay => \&CalcDay, diffrclink => &GetParam('diffrclink',1), scriptLinkDiff => \&ScriptLinkDiff, getPageLink => \&GetPageLink, calcTime => \&CalcTime, rcchangehist => &GetParam('rcchangehist',1), getHistoryLink => \&GetHistoryLink, rcsummary => &GetParam('rcsummary',1), quoteHtml => \&QuoteHtml, getAuthorLink => \&GetAuthorLink, },'html'); return $html; } # Note: all diff and recent-list operations should be done within locks. sub WriteRcLog { my ($id, $summary, $isEdit, $editTime, $name, $rhost) = @_; my ($extraTemp, %extra); %extra = (); $extra{'id'} = $UserID if ($UserID > 0); $extra{'name'} = $name if ($name ne ""); $extraTemp = join($Consts->{FS2}, %extra); # The two fields at the end of a line are kind and extension-hash my $rc_line = join($Consts->{FS3}, $editTime, $id, $summary, $isEdit, $rhost, "0", $extraTemp); if (!open(OUT, ">>$RcFile")) { die(Ts('%s log error:', $RCName) . " $!"); } print OUT $rc_line . "\n"; close(OUT); } sub ReadRcLog { my ($starttime) = @_; my ($status,$fileData,$errorText,@fullrc,$firstTs,$lastTs,$oldFileData); # Read rclog data (and oldrclog data if needed) ($status, $fileData) = &ReadFile($Consts->{RcFile}); $errorText = ""; if (!$status) { # Save error text if needed. $errorText = '

' . Ts('Could not open %s log file', $Consts->{RCName}) . ": $Consts->{RcFile}

" . T('Error was') . ":\n

$!
\n" . '

' . T('Note: This error is normal if no changes have been made.') . "\n"; } @fullrc = split(/\n/, $fileData); $firstTs = 0; if (@fullrc > 0) { # Only false if no lines in file ($firstTs) = split(/$Consts->{FS3}/, $fullrc[0]); } if (($firstTs == 0) || ($starttime <= $firstTs)) { ($status, $oldFileData) = &ReadFile($Consts->{RcOldFile}); if ($status) { @fullrc = split(/\n/, $oldFileData . $fileData); } else { if ($errorText ne "") { # could not open either rclog file $errorText .= "

" . Ts('Could not open old %s log file', $Consts->{RCName}) . ": $Consts->{RcOldFile}

" . T('Error was') . ":\n

$!
\n"; return $status,$errorText; } else { $status = 1; # Not really a problem if no old log file } } } $lastTs = 0; if (@fullrc > 0) { # Only false if no lines in file ($lastTs) = split(/$Consts->{FS3}/, $fullrc[$#fullrc]); } $lastTs++ if (($Consts->{Now} - $lastTs) > 5); # Skip last unless very recent return $status,$errorText,$firstTs,$lastTs,@fullrc; } sub GetAuthors { my ($authors,$rcText,$rcline); my ($status,$errorText,$firstTs,$lastTs,@fullrc); my $starttime = $Consts->{Now} - ((24*60*60)*90); ($status,$errorText,$firstTs,$lastTs,@fullrc) = &ReadRcLog($starttime); unless ($status) { $rcText = &GetTemplate('rc',{ errorText => $errorText, }); return $rcText; } foreach $rcline (@fullrc) { my ($ts,$pagename,$summary,$isEdit,$host,$kind,$extra) = split(/$Consts->{FS3}/,$rcline); my %extra = split(/$Consts->{FS2}/,$extra); if (defined $extra{name} && defined $extra{id}) { $authors->{$extra{name}}->{$extra{id}}++; } } for my $author (sort {$a cmp $b} keys %{$authors}) { my $id = join(', ',keys %{$authors->{$author}}); $rcText .= '
  • '. &GetAuthorLink('',$author,$id)."
    \n"; } return "
      \n".$rcText."
    \n"; }