package TTXFileTickets; # # This module is a part of Trouble Ticket Express package # http://www.troubleticketexpress.com # # COPYRIGHT: 2002-2005, United Web Coders # http://www.unitedwebcoders.com # # $Revision: 290 $ # $Date: 2006-10-19 14:09:29 +0400 (Thu, 19 Oct 2006) $ # $TTXFileTickets::VERSION='2.22'; BEGIN { $TTXFileTickets::REVISION = '$Revision: 290 $'; if ($TTXFileTickets::REVISION =~ /(\d+)/) { $TTXFileTickets::REVISION = $1; } }; use strict; use vars qw(@ISA); require TTXTickets; @ISA = qw(TTXTickets); use TTXData; require TTXSearch; require TTXMarkup; my %errmsgs = ( NOUSER => 'User does not exist', OPENERR => 'Error reading tickets database', WRERR => 'Error writing file', LOCKWRERR => 'Error acquiring lock (failed to create lock file)', LOCKOPNERR => 'Error acquiring lock (failed to open lock file)', LOCKERR => 'Error acquiring lock (flock() failed on lock file)', ); my $boundary = '-----------asdjhfKJS12869nmboiu7826318---'; # ========================================================================== new sub new { my $class = shift; my $self = $class->SUPER::new(@_); $self->{_LOCK_CNT} = 0; $self->load(); return $self; } # ====================================================================== DESTROY sub DESTROY { my $self = shift; if ($self->{_LOCK_CNT}) { $self->{_LOCK_CNT} = 1; $self->unlockdb(); } } # ========================================================================= load sub load { my $self = shift; return 0 if !$self->lockdb(); my $fn = TTXData::get('CONFIG')->get('ticketdb'); return 1 if ! -f $fn; if (!open(DB, $fn)) { $self->{_ERROR_CODE} = 'OPENERR'; $self->unlockdb(); return 0; } my @buff = ; close DB; chomp @buff; foreach my $line (@buff) { my @fields = split(/\|/, $line); my $tik; foreach my $fld ($self->_fields()) { $tik->{$fld} = shift @fields; } $self->{TICKETS}->{$tik->{id}} = $tik; } return 1; } # ======================================================================= lockdb sub lockdb { my $self = shift; if ($self->{_LOCK_CNT}) { ++$self->{_LOCK_CNT}; return 1; } my $lockfile = TTXData::get('CONFIG')->get('basedir')."/lockdb.ttx"; if (! -f $lockfile) { if (!open(LOCK, ">$lockfile")) { $self->{_ERROR_CODE} = 'LOCKWRERR'; return 0; } } elsif (!open(LOCK,"+<$lockfile")) { $self->{_ERROR_CODE} = 'LOCKOPNERR'; return 0; } flock(LOCK, 2); $self->{_LOCK_CNT} = 1; return 1; } # ===================================================================== unlockdb sub unlockdb { my $self = shift; return if !$self->{_LOCK_CNT}; --$self->{_LOCK_CNT}; if (!$self->{_LOCK_CNT}) { flock(LOCK, 8); close LOCK; } } # ========================================================================= save sub save { my $self = shift; return 0 if !$self->lockdb(); # # Build temporary file # my $tmpfn = TTXData::get('CONFIG')->get('ticketdb').'.tmp'; if (!open(TMPDB, ">$tmpfn")) { $self->{_ERROR_CODE} = 'WRERR'; $self->unlockdb(); return 0; } flock(TMPDB, 2); foreach my $id (keys %{$self->{TICKETS}}) { my $line; my $tik = $self->{TICKETS}->{$id}; foreach my $fld ($self->_fields()) { my $val = $tik->{$fld}; $val =~ s/\|/!/g; $val =~ s/\r//g; $val =~ s/\n/ /g; $line .= "$val|"; } print TMPDB "$line\n"; if ($tik->{messages} ne undef) { my $fn = TTXData::get('CONFIG')->get('basedir')."/tickets/$id.cgi"; if (open(MSG, ">$fn")) { flock(MSG, 2); foreach my $msg (@{$tik->{messages}}) { print MSG "$msg\n$boundary\n"; } truncate(MSG, tell(MSG)); close MSG; umask(0); chmod(0777, $fn); } } } close(TMPDB); umask(0); chmod(0777, $tmpfn); # # Check if the ticket database exists, create if none. # my $fn = TTXData::get('CONFIG')->get('ticketdb'); if (! -f $fn) { if (!open(DB, ">$fn")) { $self->{_ERROR_CODE} = 'WRERR'; $self->unlockdb(); return 0; } close DB; } # # Check if it's time for backup # my $backupfq = TTXData::get('CONFIG')->get('backupfq'); $backupfq = 7 * 24 * 60 * 60; # default is weekly my $tmext = time(); $tmext = $tmext - ($tmext % $backupfq); my $backupdir = TTXData::get('CONFIG')->get('basedir').'/backup'; my $backupfn = $backupdir."/tickets$tmext.cgi"; if (! -f $backupfn) { if (! -d $backupdir) { umask(0); if (!mkdir($backupdir, 0777)) { warn "Failed to create directory '$backupdir'"; } } if (-d $backupdir) { rename $fn, $backupfn; } } # # Replace ticket DB # if (!rename($tmpfn,$fn)) { umask 0; chmod(0777, $fn); if (!rename($tmpfn,$fn)) { warn "Failed to 'rename($tmpfn,$fn), make sure the $fn is NOT read-only."; $self->{_ERROR_CODE} = 'WRERR'; $self->unlockdb(); return 0; } } $self->unlockdb(); if (TTXData::get('_USECACHE')) { eval "use TTXCache"; TTXCache::purge() if $@ eq undef; } return 1; } # ======================================================================= deltik sub deltik { my $self = shift; my $id = shift; my $fn = TTXData::get('CONFIG')->get('basedir')."/tickets/$id.cgi"; $fn =~ /(.*)/; $fn = $1; unlink $fn if -f $fn; if (TTXData::get('ISPRO')) { TTXFile::delfiles($id); } delete $self->{TICKETS}->{$id}; } # ================================================================== ticketbykey sub ticketbykey { my $self = shift; my $key = shift; my $id = $key; $id =~ s/Z.*$//; if (TTXData::get('CONFIG')->get('shortkey') || $self->{TICKETS}->{$id}->{key} eq $key) { return $self->ticket($id); } return undef; } # ======================================================================= ticket sub ticket { my ($self, $id, $nomsg) = @_; my $tik = $self->{TICKETS}->{$id}; if ($tik ne undef && $tik->{message} eq undef && !$nomsg) { my $fn = TTXData::get('CONFIG')->get('basedir')."/tickets/$id.cgi"; if (-f $fn && open(MSG, $fn)) { my $rawmsg; read(MSG, $rawmsg, 64*1024); close MSG; my @messages = split(/\n$boundary\n/, $rawmsg); $tik->{messages} = \@messages; } } return $tik; } # ==================================================================== addticket sub addticket { my $self = shift; my $tik = shift; my $fn = TTXData::get('CONFIG')->get('ticketid'); if (! -f $fn) { if (!open(ID, ">$fn")) { $self->{_ERROR_CODE} = 'WRERR'; return 0; } print ID "0\n"; close ID; } if (!open(ID,"+<$fn")) { $self->{_ERROR_CODE} = 'WRERR'; return 0; } flock(ID, 2); my $id = ; chomp $id; ++$id; seek(ID, 0, 0); print ID "$id\n"; close ID; umask(0); chmod(0777, $fn); $tik->{open} = time() if $tik->{open} eq undef; $tik->{updated} = $tik->{open}; $tik->{status} = 'PND'; $tik->{id} = $id; $tik->{key} = "$id"; if (!TTXData::get('CONFIG')->get('shortkey')) { $tik->{key} .= "Z$$".int(1000000 + rand(9000000)).int(1000000 + rand(9000000)); } $self->{TICKETS}->{$id} = $tik; return $id; } # ========================================================================= list sub list { my ($self, $off, $win, $col, $ord, $filter, $drange) = @_; my @list = keys %{$self->{TICKETS}}; my $tickets = $self->{TICKETS}; my $byid; my $ftxt; my $abstract = {}; if ($filter ne undef) { foreach my $f (@{$filter}) { my $expr = $f->{expr}; my $c = $f->{col}; if ($c eq 'text') { $ftxt = $f; } elsif ($c eq '-grp-') { my $oper = $f->{oper}; my $grps = join('|', @{$f->{groups}}); if ($grps ne undef) { @list = grep {$tickets->{$_}->{oper} eq $oper || $tickets->{$_}->{grp} =~ /^$grps$/} @list; } else { @list = grep {$tickets->{$_}->{oper} eq $oper} @list; } } else { $byid = 1 if $c eq 'id'; @list = grep {$tickets->{$_}->{$c} =~ /$expr/i} @list; } } } if (!$byid && TTXData::get('CONFIG')->get('hideoldsolved')) { my $oldis = int(TTXData::get('CONFIG')->get('hideoldsolved')); $oldis = time() - $oldis * 3600 * 24; @list = grep {$tickets->{$_}->{status} ne 'CLS' || $tickets->{$_}->{closed} > $oldis} @list; } if ($drange->{fld} ne undef) { my %fldmap = ( created => 'open', updated => 'updated', solved => 'closed' ); my $fld = $fldmap{$drange->{fld}}; my $stm = $drange->{stm}; my $etm = $drange->{etm}; @list = grep {$tickets->{$_}->{$fld} >= $stm && $tickets->{$_}->{$fld} <= $etm} @list; } if ($ftxt ne undef) { my @l1; my $tokens = TTXSearch::tokenize($ftxt->{expr}); my $match; my $body; my $token; foreach my $id (@list) { my $t = $self->ticket($id); next if $t eq undef; foreach my $msg (@{$t->{messages}}) { $msg =~ s/^.+\n\n//s; $body = TTXMarkup::strip($msg); $match = 1; foreach $token (@{$tokens}) { if ($body !~ /\b$token\b/i) { $match = 0; last; } } if ($match) { $abstract->{$id} = TTXSearch::abstract($body, $tokens); push @l1, $id; last; } } } @list = @l1; } if (grep(/^$col$/, ('open', 'closed', 'updated', 'id'))) { if ($ord eq 'A') { @list = sort {$tickets->{$a}->{$col} <=> $tickets->{$b}->{$col}} @list; } else { @list = sort {$tickets->{$b}->{$col} <=> $tickets->{$a}->{$col}} @list; } } else { if ($ord eq 'A') { @list = sort {$tickets->{$a}->{$col} cmp $tickets->{$b}->{$col}} @list; } else { @list = sort {$tickets->{$b}->{$col} cmp $tickets->{$a}->{$col}} @list; } } $off = 0 if $off < 0 || $off > @list; $win = 20 if $win < 1; $win = @list - $off if ($off + $win) > @list; my $browser; $browser->{abstract} = $abstract; $browser->{total} = int(@list); @{$browser->{list}} = splice @list, $off, $win; $browser->{first} = $off; $browser->{last} = $off + @{$browser->{list}}; return $browser; } # ==================================================================== errortext sub errortext { my $self = shift; return $errmsgs{$self->{_ERROR_CODE}}; } 1; #