diff options
author | ivan <ivan> | 2002-08-12 06:17:09 +0000 |
---|---|---|
committer | ivan <ivan> | 2002-08-12 06:17:09 +0000 |
commit | 3ef62a0570055da710328937e7f65dbb2c027c62 (patch) | |
tree | d549158b172fd499b4f81a2981b62aabbde4f99b /rt/lib/RT/Tickets.pm | |
parent | 030438c9cb1c12ccb79130979ef0922097b4311a (diff) |
import rt 2.0.14
Diffstat (limited to 'rt/lib/RT/Tickets.pm')
-rwxr-xr-x | rt/lib/RT/Tickets.pm | 1789 |
1 files changed, 1789 insertions, 0 deletions
diff --git a/rt/lib/RT/Tickets.pm b/rt/lib/RT/Tickets.pm new file mode 100755 index 000000000..dd91126c4 --- /dev/null +++ b/rt/lib/RT/Tickets.pm @@ -0,0 +1,1789 @@ +#$Header: /home/cvs/cvsroot/freeside/rt/lib/RT/Tickets.pm,v 1.1 2002-08-12 06:17:07 ivan Exp $ + +=head1 NAME + + RT::Tickets - A collection of Ticket objects + + +=head1 SYNOPSIS + + use RT::Tickets; + my $tickets = new RT::Tickets($CurrentUser); + +=head1 DESCRIPTION + + A collection of RT::Tickets. + +=head1 METHODS + +=begin testing + +ok (require RT::TestHarness); +ok (require RT::Tickets); + +=end testing + +=cut + +package RT::Tickets; +use RT::EasySearch; +use RT::Ticket; +@ISA= qw(RT::EasySearch); + +use vars qw(%TYPES @SORTFIELDS); + +# {{{ TYPES + +%TYPES = ( Status => 'ENUM', + Queue => 'ENUM', + Type => 'ENUM', + Creator => 'ENUM', + LastUpdatedBy => 'ENUM', + Owner => 'ENUM', + EffectiveId => 'INT', + id => 'INT', + InitialPriority => 'INT', + FinalPriority => 'INT', + Priority => 'INT', + TimeLeft => 'INT', + TimeWorked => 'INT', + MemberOf => 'LINK', + DependsOn => 'LINK', + HasMember => 'LINK', + HasDepender => 'LINK', + RelatedTo => 'LINK', + Told => 'DATE', + StartsBy => 'DATE', + Started => 'DATE', + Due => 'DATE', + Resolved => 'DATE', + LastUpdated => 'DATE', + Created => 'DATE', + Subject => 'STRING', + Type => 'STRING', + Content => 'TRANSFIELD', + ContentType => 'TRANSFIELD', + TransactionDate => 'TRANSDATE', + Watcher => 'WATCHERFIELD', + LinkedTo => 'LINKFIELD', + Keyword => 'KEYWORDFIELD' + + ); + + +# }}} + +# {{{ sub SortFields + +@SORTFIELDS = qw(id Status Owner Created Due Starts Started + Queue Subject Told Started + Resolved LastUpdated Priority TimeWorked TimeLeft); + +=head2 SortFields + +Returns the list of fields that lists of tickets can easily be sorted by + +=cut + + +sub SortFields { + my $self = shift; + return(@SORTFIELDS); +} + + +# }}} + +# {{{ Limit the result set based on content + +# {{{ sub Limit + +=head2 Limit + +Takes a paramhash with the fields FIELD, OPERATOR, VALUE and DESCRIPTION +Generally best called from LimitFoo methods + +=cut +sub Limit { + my $self = shift; + my %args = ( FIELD => undef, + OPERATOR => '=', + VALUE => undef, + DESCRIPTION => undef, + @_ + ); + $args{'DESCRIPTION'} = "Autodescribed: ".$args{'FIELD'} . $args{'OPERATOR'} . $args{'VALUE'}, + if (!defined $args{'DESCRIPTION'}) ; + + my $index = $self->_NextIndex; + + #make the TicketRestrictions hash the equivalent of whatever we just passed in; + + %{$self->{'TicketRestrictions'}{$index}} = %args; + + $self->{'RecalcTicketLimits'} = 1; + + # If we're looking at the effective id, we don't want to append the other clause + # which limits us to tickets where id = effective id + if ($args{'FIELD'} eq 'EffectiveId') { + $self->{'looking_at_effective_id'} = 1; + } + + return ($index); +} + +# }}} + + + + +=head2 FreezeLimits + +Returns a frozen string suitable for handing back to ThawLimits. + +=cut +# {{{ sub FreezeLimits + +sub FreezeLimits { + my $self = shift; + require FreezeThaw; + return (FreezeThaw::freeze($self->{'TicketRestrictions'}, + $self->{'restriction_index'} + )); +} + +# }}} + +=head2 ThawLimits + +Take a frozen Limits string generated by FreezeLimits and make this tickets +object have that set of limits. + +=cut +# {{{ sub ThawLimits + +sub ThawLimits { + my $self = shift; + my $in = shift; + + #if we don't have $in, get outta here. + return undef unless ($in); + + $self->{'RecalcTicketLimits'} = 1; + + require FreezeThaw; + + #We don't need to die if the thaw fails. + + eval { + ($self->{'TicketRestrictions'}, + $self->{'restriction_index'} + ) = FreezeThaw::thaw($in); + } + +} + +# }}} + +# {{{ Limit by enum or foreign key + +# {{{ sub LimitQueue + +=head2 LimitQueue + +LimitQueue takes a paramhash with the fields OPERATOR and VALUE. +OPERATOR is one of = or !=. (It defaults to =). +VALUE is a queue id. + +=cut + +sub LimitQueue { + my $self = shift; + my %args = (VALUE => undef, + OPERATOR => '=', + @_); + + #TODO VALUE should also take queue names and queue objects + my $queue = new RT::Queue($self->CurrentUser); + $queue->Load($args{'VALUE'}); + + #TODO check for a valid queue here + + $self->Limit (FIELD => 'Queue', + VALUE => $queue->id(), + OPERATOR => $args{'OPERATOR'}, + DESCRIPTION => 'Queue ' . $args{'OPERATOR'}. " ". $queue->Name + ); + +} +# }}} + +# {{{ sub LimitStatus + +=head2 LimitStatus + +Takes a paramhash with the fields OPERATOR and VALUE. +OPERATOR is one of = or !=. +VALUE is a status. + +=cut + +sub LimitStatus { + my $self = shift; + my %args = ( OPERATOR => '=', + @_); + $self->Limit (FIELD => 'Status', + VALUE => $args{'VALUE'}, + OPERATOR => $args{'OPERATOR'}, + DESCRIPTION => 'Status ' . $args{'OPERATOR'}. " ". $args{'VALUE'}, + ); +} + +# }}} + +# {{{ sub LimitType + +=head2 LimitType + +Takes a paramhash with the fields OPERATOR and VALUE. +OPERATOR is one of = or !=, it defaults to "=". +VALUE is a string to search for in the type of the ticket. + +=cut + +sub LimitType { + my $self = shift; + my %args = (OPERATOR => '=', + VALUE => undef, + @_); + $self->Limit (FIELD => 'Type', + VALUE => $args{'VALUE'}, + OPERATOR => $args{'OPERATOR'}, + DESCRIPTION => 'Type ' . $args{'OPERATOR'}. " ". $args{'Limit'}, + ); +} + +# }}} + +# }}} + +# {{{ Limit by string field + +# {{{ sub LimitSubject + +=head2 LimitSubject + +Takes a paramhash with the fields OPERATOR and VALUE. +OPERATOR is one of = or !=. +VALUE is a string to search for in the subject of the ticket. + +=cut + +sub LimitSubject { + my $self = shift; + my %args = (@_); + $self->Limit (FIELD => 'Subject', + VALUE => $args{'VALUE'}, + OPERATOR => $args{'OPERATOR'}, + DESCRIPTION => 'Subject ' . $args{'OPERATOR'}. " ". $args{'VALUE'}, + ); +} + +# }}} + +# }}} + +# {{{ Limit based on ticket numerical attributes +# Things that can be > < = != + +# {{{ sub LimitId + +=head2 LimitId + +Takes a paramhash with the fields OPERATOR and VALUE. +OPERATOR is one of =, >, < or !=. +VALUE is a ticket Id to search for + +=cut + +sub LimitId { + my $self = shift; + my %args = (OPERATOR => '=', + @_); + + $self->Limit (FIELD => 'id', + VALUE => $args{'VALUE'}, + OPERATOR => $args{'OPERATOR'}, + DESCRIPTION => 'Id ' . $args{'OPERATOR'}. " ". $args{'VALUE'}, + ); +} + +# }}} + +# {{{ sub LimitPriority + +=head2 LimitPriority + +Takes a paramhash with the fields OPERATOR and VALUE. +OPERATOR is one of =, >, < or !=. +VALUE is a value to match the ticket\'s priority against + +=cut + +sub LimitPriority { + my $self = shift; + my %args = (@_); + $self->Limit (FIELD => 'Priority', + VALUE => $args{'VALUE'}, + OPERATOR => $args{'OPERATOR'}, + DESCRIPTION => 'Priority ' . $args{'OPERATOR'}. " ". $args{'VALUE'}, + ); +} + +# }}} + +# {{{ sub LimitInitialPriority + +=head2 LimitInitialPriority + +Takes a paramhash with the fields OPERATOR and VALUE. +OPERATOR is one of =, >, < or !=. +VALUE is a value to match the ticket\'s initial priority against + + +=cut + +sub LimitInitialPriority { + my $self = shift; + my %args = (@_); + $self->Limit (FIELD => 'InitialPriority', + VALUE => $args{'VALUE'}, + OPERATOR => $args{'OPERATOR'}, + DESCRIPTION => 'Initial Priority ' . $args{'OPERATOR'}. " ". $args{'VALUE'}, + ); +} + +# }}} + +# {{{ sub LimitFinalPriority + +=head2 LimitFinalPriority + +Takes a paramhash with the fields OPERATOR and VALUE. +OPERATOR is one of =, >, < or !=. +VALUE is a value to match the ticket\'s final priority against + +=cut + +sub LimitFinalPriority { + my $self = shift; + my %args = (@_); + $self->Limit (FIELD => 'FinalPriority', + VALUE => $args{'VALUE'}, + OPERATOR => $args{'OPERATOR'}, + DESCRIPTION => 'Final Priority ' . $args{'OPERATOR'}. " ". $args{'VALUE'}, + ); +} + +# }}} + +# {{{ sub LimitTimeWorked + +=head2 LimitTimeWorked + +Takes a paramhash with the fields OPERATOR and VALUE. +OPERATOR is one of =, >, < or !=. +VALUE is a value to match the ticket's TimeWorked attribute + +=cut + +sub LimitTimeWorked { + my $self = shift; + my %args = (@_); + $self->Limit (FIELD => 'TimeWorked', + VALUE => $args{'VALUE'}, + OPERATOR => $args{'OPERATOR'}, + DESCRIPTION => 'Time worked ' . $args{'OPERATOR'}. " ". $args{'VALUE'}, + ); +} + +# }}} + +# {{{ sub LimitTimeLeft + +=head2 LimitTimeLeft + +Takes a paramhash with the fields OPERATOR and VALUE. +OPERATOR is one of =, >, < or !=. +VALUE is a value to match the ticket's TimeLeft attribute + +=cut + +sub LimitTimeLeft { + my $self = shift; + my %args = (@_); + $self->Limit (FIELD => 'TimeLeft', + VALUE => $args{'VALUE'}, + OPERATOR => $args{'OPERATOR'}, + DESCRIPTION => 'Time left ' . $args{'OPERATOR'}. " ". $args{'VALUE'}, + ); +} + +# }}} + +# }}} + +# {{{ Limiting based on attachment attributes + +# {{{ sub LimitContent + +=head2 LimitContent + +Takes a paramhash with the fields OPERATOR and VALUE. +OPERATOR is one of =, LIKE, NOT LIKE or !=. +VALUE is a string to search for in the body of the ticket + +=cut +sub LimitContent { + my $self = shift; + my %args = (@_); + $self->Limit (FIELD => 'Content', + VALUE => $args{'VALUE'}, + OPERATOR => $args{'OPERATOR'}, + DESCRIPTION => 'Ticket content ' . $args{'OPERATOR'}. " ". $args{'VALUE'}, + ); +} + +# }}} +# {{{ sub LimitContentType + +=head2 LimitContentType + +Takes a paramhash with the fields OPERATOR and VALUE. +OPERATOR is one of =, LIKE, NOT LIKE or !=. +VALUE is a content type to search ticket attachments for + +=cut + +sub LimitContentType { + my $self = shift; + my %args = (@_); + $self->Limit (FIELD => 'ContentType', + VALUE => $args{'VALUE'}, + OPERATOR => $args{'OPERATOR'}, + DESCRIPTION => 'Ticket content type ' . $args{'OPERATOR'}. " ". $args{'VALUE'}, + ); +} +# }}} + +# }}} + +# {{{ Limiting based on people + +# {{{ sub LimitOwner + +=head2 LimitOwner + +Takes a paramhash with the fields OPERATOR and VALUE. +OPERATOR is one of = or !=. +VALUE is a user id. + +=cut + +sub LimitOwner { + my $self = shift; + my %args = ( OPERATOR => '=', + @_); + + my $owner = new RT::User($self->CurrentUser); + $owner->Load($args{'VALUE'}); + $self->Limit (FIELD => 'Owner', + VALUE => $owner->Id, + OPERATOR => $args{'OPERATOR'}, + DESCRIPTION => 'Owner ' . $args{'OPERATOR'}. " ". $owner->Name() + ); + +} + +# }}} + +# {{{ Limiting watchers + +# {{{ sub LimitWatcher + + +=head2 LimitWatcher + + Takes a paramhash with the fields OPERATOR, TYPE and VALUE. + OPERATOR is one of =, LIKE, NOT LIKE or !=. + VALUE is a value to match the ticket\'s watcher email addresses against + TYPE is the sort of watchers you want to match against. Leave it undef if you want to search all of them + +=cut + +sub LimitWatcher { + my $self = shift; + my %args = ( OPERATOR => '=', + VALUE => undef, + TYPE => undef, + @_); + + + #build us up a description + my ($watcher_type, $desc); + if ($args{'TYPE'}) { + $watcher_type = $args{'TYPE'}; + } + else { + $watcher_type = "Watcher"; + } + $desc = "$watcher_type ".$args{'OPERATOR'}." ".$args{'VALUE'}; + + + $self->Limit (FIELD => 'Watcher', + VALUE => $args{'VALUE'}, + OPERATOR => $args{'OPERATOR'}, + TYPE => $args{'TYPE'}, + DESCRIPTION => "$desc" + ); +} + +# }}} + +# {{{ sub LimitRequestor + +=head2 LimitRequestor + +It\'s like LimitWatcher, but it presets TYPE to Requestor + +=cut + + +sub LimitRequestor { + my $self = shift; + $self->LimitWatcher(TYPE=> 'Requestor', @_); +} + +# }}} + +# {{{ sub LimitCc + +=head2 LimitCC + +It\'s like LimitWatcher, but it presets TYPE to Cc + +=cut + +sub LimitCc { + my $self = shift; + $self->LimitWatcher(TYPE=> 'Cc', @_); +} + +# }}} + +# {{{ sub LimitAdminCc + +=head2 LimitAdminCc + +It\'s like LimitWatcher, but it presets TYPE to AdminCc + +=cut + +sub LimitAdminCc { + my $self = shift; + $self->LimitWatcher(TYPE=> 'AdminCc', @_); +} + +# }}} + +# }}} + +# }}} + +# {{{ Limiting based on links + +# {{{ LimitLinkedTo + +=head2 LimitLinkedTo + +LimitLinkedTo takes a paramhash with two fields: TYPE and TARGET +TYPE limits the sort of relationship we want to search on + +TARGET is the id or URI of the TARGET of the link +(TARGET used to be 'TICKET'. 'TICKET' is deprecated, but will be treated as TARGET + +=cut + +sub LimitLinkedTo { + my $self = shift; + my %args = ( + TICKET => undef, + TARGET => undef, + TYPE => undef, + @_); + + + $self->Limit( FIELD => 'LinkedTo', + BASE => undef, + TARGET => ($args{'TARGET'} || $args{'TICKET'}), + TYPE => $args{'TYPE'}, + DESCRIPTION => "Tickets ".$args{'TYPE'}." by ".($args{'TARGET'} || $args{'TICKET'}) + ); +} + + +# }}} + +# {{{ LimitLinkedFrom + +=head2 LimitLinkedFrom + +LimitLinkedFrom takes a paramhash with two fields: TYPE and BASE +TYPE limits the sort of relationship we want to search on + + +BASE is the id or URI of the BASE of the link +(BASE used to be 'TICKET'. 'TICKET' is deprecated, but will be treated as BASE + + +=cut + +sub LimitLinkedFrom { + my $self = shift; + my %args = ( BASE => undef, + TICKET => undef, + TYPE => undef, + @_); + + + $self->Limit( FIELD => 'LinkedTo', + TARGET => undef, + BASE => ($args{'BASE'} || $args{'TICKET'}), + TYPE => $args{'TYPE'}, + DESCRIPTION => "Tickets " .($args{'BASE'} || $args{'TICKET'}) ." ".$args{'TYPE'} + ); +} + + +# }}} + +# {{{ LimitMemberOf +sub LimitMemberOf { + my $self = shift; + my $ticket_id = shift; + $self->LimitLinkedTo ( TARGET=> "$ticket_id", + TYPE => 'MemberOf', + ); + +} +# }}} + +# {{{ LimitHasMember +sub LimitHasMember { + my $self = shift; + my $ticket_id =shift; + $self->LimitLinkedFrom ( BASE => "$ticket_id", + TYPE => 'MemberOf', + ); + +} +# }}} + +# {{{ LimitDependsOn + +sub LimitDependsOn { + my $self = shift; + my $ticket_id = shift; + $self->LimitLinkedTo ( TARGET => "$ticket_id", + TYPE => 'DependsOn', + ); + +} + +# }}} + +# {{{ LimitDependedOnBy + +sub LimitDependedOnBy { + my $self = shift; + my $ticket_id = shift; + $self->LimitLinkedFrom ( BASE => "$ticket_id", + TYPE => 'DependsOn', + ); + +} + +# }}} + + +# {{{ LimitRefersTo + +sub LimitRefersTo { + my $self = shift; + my $ticket_id = shift; + $self->LimitLinkedTo ( TARGET => "$ticket_id", + TYPE => 'RefersTo', + ); + +} + +# }}} + +# {{{ LimitReferredToBy + +sub LimitReferredToBy { + my $self = shift; + my $ticket_id = shift; + $self->LimitLinkedFrom ( BASE=> "$ticket_id", + TYPE => 'RefersTo', + ); + +} + +# }}} + +# }}} + +# {{{ limit based on ticket date attribtes + +# {{{ sub LimitDate + +=head2 LimitDate (FIELD => 'DateField', OPERATOR => $oper, VALUE => $ISODate) + +Takes a paramhash with the fields FIELD OPERATOR and VALUE. + +OPERATOR is one of > or < +VALUE is a date and time in ISO format in GMT +FIELD is one of Starts, Started, Told, Created, Resolved, LastUpdated + +There are also helper functions of the form LimitFIELD that eliminate +the need to pass in a FIELD argument. + +=cut + +sub LimitDate { + my $self = shift; + my %args = ( + FIELD => undef, + VALUE => $args{'VALUE'}, + OPERATOR => $args{'OPERATOR'}, + + @_); + + #Set the description if we didn't get handed it above + unless ($args{'DESCRIPTION'} ) { + $args{'DESCRIPTION'} = $args{'FIELD'} . " " .$args{'OPERATOR'}. " ". $args{'VALUE'} . " GMT" + } + + $self->Limit (%args); + +} + +# }}} + + + + +sub LimitCreated { + my $self = shift; + $self->LimitDate( FIELD => 'Created', @_); +} +sub LimitDue { + my $self = shift; + $self->LimitDate( FIELD => 'Due', @_); + +} +sub LimitStarts { + my $self = shift; + $self->LimitDate( FIELD => 'Starts', @_); + +} +sub LimitStarted { + my $self = shift; + $self->LimitDate( FIELD => 'Started', @_); +} +sub LimitResolved { + my $self = shift; + $self->LimitDate( FIELD => 'Resolved', @_); +} +sub LimitTold { + my $self = shift; + $self->LimitDate( FIELD => 'Told', @_); +} +sub LimitLastUpdated { + my $self = shift; + $self->LimitDate( FIELD => 'LastUpdated', @_); +} +# +# {{{ sub LimitTransactionDate + +=head2 LimitTransactionDate (OPERATOR => $oper, VALUE => $ISODate) + +Takes a paramhash with the fields FIELD OPERATOR and VALUE. + +OPERATOR is one of > or < +VALUE is a date and time in ISO format in GMT + + +=cut + +sub LimitTransactionDate { + my $self = shift; + my %args = ( + FIELD => 'TransactionDate', + VALUE => $args{'VALUE'}, + OPERATOR => $args{'OPERATOR'}, + + @_); + + #Set the description if we didn't get handed it above + unless ($args{'DESCRIPTION'} ) { + $args{'DESCRIPTION'} = $args{'FIELD'} . " " .$args{'OPERATOR'}. " ". $args{'VALUE'} . " GMT" + } + + $self->Limit (%args); + +} + +# }}} + +# }}} + +# {{{ sub LimitKeyword + +=head2 LimitKeyword + +Takes a paramhash of key/value pairs with the following keys: + +=over 4 + +=item KEYWORDSELECT - KeywordSelect id + +=item OPERATOR - (for KEYWORD only - KEYWORDSELECT operator is always `=') + +=item KEYWORD - Keyword id + +=back + +=cut + +sub LimitKeyword { + my $self = shift; + my %args = ( KEYWORD => undef, + KEYWORDSELECT => undef, + OPERATOR => '=', + DESCRIPTION => undef, + FIELD => 'Keyword', + QUOTEVALUE => 1, + @_ + ); + + use RT::KeywordSelect; + my $KeywordSelect = RT::KeywordSelect->new($self->CurrentUser); + $KeywordSelect->Load($args{KEYWORDSELECT}); + + + # Below, We're checking to see whether the keyword we're searching for + # is null or not. + # This could probably be rewritten to be easier to read and understand + + + #If we are looking to compare with a null value. + if ($args{'OPERATOR'} =~ /is/i) { + if ($args{'OPERATOR'} =~ /^is$/i) { + $args{'DESCRIPTION'} ||= "Keyword Selection ". $KeywordSelect->Name . " has no value"; + } + elsif ($args{'OPERATOR'} =~ /^is not$/i) { + $args{'DESCRIPTION'} ||= "Keyword Selection ". $KeywordSelect->Name . " has a value"; + } + } + # if we're not looking to compare with a null value + else { + use RT::Keyword; + my $Keyword = RT::Keyword->new($self->CurrentUser); + $Keyword->Load($args{KEYWORD}); + $args{'DESCRIPTION'} ||= "Keyword Selection " . $KeywordSelect->Name. " $args{OPERATOR} ". $Keyword->Name; + } + + $args{SingleValued} = $KeywordSelect->Single(); + + + my $index = $self->_NextIndex; + %{$self->{'TicketRestrictions'}{$index}} = %args; + + $self->{'RecalcTicketLimits'} = 1; + return ($index); +} + +# }}} + +# {{{ sub _NextIndex + +=head2 _NextIndex + +Keep track of the counter for the array of restrictions + +=cut + +sub _NextIndex { + my $self = shift; + return ($self->{'restriction_index'}++); +} +# }}} + +# }}} + +# {{{ Core bits to make this a DBIx::SearchBuilder object + +# {{{ sub _Init +sub _Init { + my $self = shift; + $self->{'table'} = "Tickets"; + $self->{'RecalcTicketLimits'} = 1; + $self->{'looking_at_effective_id'} = 0; + $self->{'restriction_index'} =1; + $self->{'primary_key'} = "id"; + $self->SUPER::_Init(@_); + +} +# }}} + +# {{{ sub NewItem +sub NewItem { + my $self = shift; + return(RT::Ticket->new($self->CurrentUser)); + +} +# }}} + +# {{{ sub Count +sub Count { + my $self = shift; + $self->_ProcessRestrictions if ($self->{'RecalcTicketLimits'} == 1 ); + return($self->SUPER::Count()); +} +# }}} + +# {{{ sub ItemsArrayRef + +=head2 ItemsArrayRef + +Returns a reference to the set of all items found in this search + +=cut + +sub ItemsArrayRef { + my $self = shift; + my @items; + + my $placeholder = $self->_ItemsCounter; + $self->GotoFirstItem(); + while (my $item = $self->Next) { + push (@items, $item); + } + + $self->GotoItem($placeholder); + return(\@items); +} +# }}} + +# {{{ sub Next +sub Next { + my $self = shift; + + $self->_ProcessRestrictions if ($self->{'RecalcTicketLimits'} == 1 ); + + my $Ticket = $self->SUPER::Next(); + if ((defined($Ticket)) and (ref($Ticket))) { + + #Make sure we _never_ show dead tickets + #TODO we should be doing this in the where clause. + #but you can't do multiple clauses on the same field just yet :/ + + if ($Ticket->Status eq 'dead') { + return($self->Next()); + } + elsif ($Ticket->CurrentUserHasRight('ShowTicket')) { + return($Ticket); + } + + #If the user doesn't have the right to show this ticket + else { + return($self->Next()); + } + } + #if there never was any ticket + else { + return(undef); + } + +} +# }}} + +# }}} + +# {{{ Deal with storing and restoring restrictions + +# {{{ sub LoadRestrictions + +=head2 LoadRestrictions + +LoadRestrictions takes a string which can fully populate the TicketRestrictons hash. +TODO It is not yet implemented + +=cut + +# }}} + +# {{{ sub DescribeRestrictions + +=head2 DescribeRestrictions + +takes nothing. +Returns a hash keyed by restriction id. +Each element of the hash is currently a one element hash that contains DESCRIPTION which +is a description of the purpose of that TicketRestriction + +=cut + +sub DescribeRestrictions { + my $self = shift; + + my ($row, %listing); + + foreach $row (keys %{$self->{'TicketRestrictions'}}) { + $listing{$row} = $self->{'TicketRestrictions'}{$row}{'DESCRIPTION'}; + } + return (%listing); +} +# }}} + +# {{{ sub RestrictionValues + +=head2 RestrictionValues FIELD + +Takes a restriction field and returns a list of values this field is restricted +to. + +=cut + +sub RestrictionValues { + my $self = shift; + my $field = shift; + map $self->{'TicketRestrictions'}{$_}{'VALUE'}, + grep { + $self->{'TicketRestrictions'}{$_}{'FIELD'} eq $field + && $self->{'TicketRestrictions'}{$_}{'OPERATOR'} eq "=" + } + keys %{$self->{'TicketRestrictions'}}; +} + +# }}} + +# {{{ sub ClearRestrictions + +=head2 ClearRestrictions + +Removes all restrictions irretrievably + +=cut + +sub ClearRestrictions { + my $self = shift; + delete $self->{'TicketRestrictions'}; + $self->{'looking_at_effective_id'} = 0; + $self->{'RecalcTicketLimits'} =1; +} + +# }}} + +# {{{ sub DeleteRestriction + +=head2 DeleteRestriction + +Takes the row Id of a restriction (From DescribeRestrictions' output, for example. +Removes that restriction from the session's limits. + +=cut + + +sub DeleteRestriction { + my $self = shift; + my $row = shift; + delete $self->{'TicketRestrictions'}{$row}; + + $self->{'RecalcTicketLimits'} = 1; + #make the underlying easysearch object forget all its preconceptions +} + +# }}} + +# {{{ sub _ProcessRestrictions + +sub _ProcessRestrictions { + my $self = shift; + + #Need to clean the EasySearch slate because it makes things too sticky + $self->CleanSlate(); + + #Blow away ticket aliases since we'll need to regenerate them for a new search + delete $self->{'TicketAliases'}; + delete $self->{KeywordsAliases}; + + my $row; + + foreach $row (keys %{$self->{'TicketRestrictions'}}) { + my $restriction = $self->{'TicketRestrictions'}{$row}; + # {{{ if it's an int + + if ($TYPES{$restriction->{'FIELD'}} eq 'INT' ) { + if ($restriction->{'OPERATOR'} =~ /^(=|!=|>|<|>=|<=)$/) { + $self->SUPER::Limit( FIELD => $restriction->{'FIELD'}, + ENTRYAGGREGATOR => 'AND', + OPERATOR => $restriction->{'OPERATOR'}, + VALUE => $restriction->{'VALUE'}, + ); + } + } + # }}} + # {{{ if it's an enum + elsif ($TYPES{$restriction->{'FIELD'}} eq 'ENUM') { + + if ($restriction->{'OPERATOR'} eq '=') { + $self->SUPER::Limit( FIELD => $restriction->{'FIELD'}, + ENTRYAGGREGATOR => 'OR', + OPERATOR => '=', + VALUE => $restriction->{'VALUE'}, + ); + } + elsif ($restriction->{'OPERATOR'} eq '!=') { + $self->SUPER::Limit( FIELD => $restriction->{'FIELD'}, + ENTRYAGGREGATOR => 'AND', + OPERATOR => '!=', + VALUE => $restriction->{'VALUE'}, + ); + } + + } + # }}} + # {{{ if it's a date + + elsif ($TYPES{$restriction->{'FIELD'}} eq 'DATE') { + $self->SUPER::Limit( FIELD => $restriction->{'FIELD'}, + ENTRYAGGREGATOR => 'AND', + OPERATOR => $restriction->{'OPERATOR'}, + VALUE => $restriction->{'VALUE'}, + ); + } + # }}} + # {{{ if it's a string + + elsif ($TYPES{$restriction->{'FIELD'}} eq 'STRING') { + + if ($restriction->{'OPERATOR'} eq '=') { + $self->SUPER::Limit( FIELD => $restriction->{'FIELD'}, + ENTRYAGGREGATOR => 'OR', + OPERATOR => '=', + VALUE => $restriction->{'VALUE'}, + CASESENSITIVE => 0 + ); + } + elsif ($restriction->{'OPERATOR'} eq '!=') { + $self->SUPER::Limit( FIELD => $restriction->{'FIELD'}, + ENTRYAGGREGATOR => 'AND', + OPERATOR => '!=', + VALUE => $restriction->{'VALUE'}, + CASESENSITIVE => 0 + ); + } + elsif ($restriction->{'OPERATOR'} eq 'LIKE') { + $self->SUPER::Limit( FIELD => $restriction->{'FIELD'}, + ENTRYAGGREGATOR => 'AND', + OPERATOR => 'LIKE', + VALUE => $restriction->{'VALUE'}, + CASESENSITIVE => 0 + ); + } + elsif ($restriction->{'OPERATOR'} eq 'NOT LIKE') { + $self->SUPER::Limit( FIELD => $restriction->{'FIELD'}, + ENTRYAGGREGATOR => 'AND', + OPERATOR => 'NOT LIKE', + VALUE => $restriction->{'VALUE'}, + CASESENSITIVE => 0 + ); + } + } + + # }}} + # {{{ if it's Transaction content that we're hunting for + elsif ($TYPES{$restriction->{'FIELD'}} eq 'TRANSFIELD') { + + #Basically, we want to make sure that the limits apply to the same attachment, + #rather than just another attachment for the same ticket, no matter how many + #clauses we lump on. + #We put them in TicketAliases so that they get nuked when we redo the join. + + unless (defined $self->{'TicketAliases'}{'TransFieldAlias'}) { + $self->{'TicketAliases'}{'TransFieldAlias'} = $self->NewAlias ('Transactions'); + } + unless (defined $self->{'TicketAliases'}{'TransFieldAttachAlias'}){ + $self->{'TicketAliases'}{'TransFieldAttachAlias'} = $self->NewAlias('Attachments'); + + } + #Join transactions to attachments + $self->Join( ALIAS1 => $self->{'TicketAliases'}{'TransFieldAttachAlias'}, + FIELD1 => 'TransactionId', + ALIAS2 => $self->{'TicketAliases'}{'TransFieldAlias'}, FIELD2=> 'id'); + + #Join transactions to tickets + $self->Join( ALIAS1 => 'main', FIELD1 => $self->{'primary_key'}, + ALIAS2 =>$self->{'TicketAliases'}{'TransFieldAlias'}, FIELD2 => 'Ticket'); + + #Search for the right field + $self->SUPER::Limit(ALIAS => $self->{'TicketAliases'}{'TransFieldAttachAlias'}, + ENTRYAGGREGATOR => 'AND', + FIELD => $restriction->{'FIELD'}, + OPERATOR => $restriction->{'OPERATOR'} , + VALUE => $restriction->{'VALUE'}, + CASESENSITIVE => 0 + ); + + + } + + # }}} + # {{{ if it's a Transaction date that we're hunting for + elsif ($TYPES{$restriction->{'FIELD'}} eq 'TRANSDATE') { + + #Basically, we want to make sure that the limits apply to the same attachment, + #rather than just another attachment for the same ticket, no matter how many + #clauses we lump on. + #We put them in TicketAliases so that they get nuked when we redo the join. + + unless (defined $self->{'TicketAliases'}{'TransFieldAlias'}) { + $self->{'TicketAliases'}{'TransFieldAlias'} = $self->NewAlias ('Transactions'); + } + + #Join transactions to tickets + $self->Join( ALIAS1 => 'main', FIELD1 => $self->{'primary_key'}, + ALIAS2 =>$self->{'TicketAliases'}{'TransFieldAlias'}, FIELD2 => 'Ticket'); + + #Search for the right field + $self->SUPER::Limit(ALIAS => $self->{'TicketAliases'}{'TransFieldAlias'}, + ENTRYAGGREGATOR => 'AND', + FIELD => 'Created', + OPERATOR => $restriction->{'OPERATOR'} , + VALUE => $restriction->{'VALUE'} ); + } + + # }}} + # {{{ if it's a relationship that we're hunting for + + # Takes FIELD: which is something like "LinkedTo" + # takes TARGET or BASE which is the TARGET or BASE id that we're searching for + # takes TYPE which is the type of link we're looking for. + + elsif ($TYPES{$restriction->{'FIELD'}} eq 'LINKFIELD') { + + + my $LinkAlias = $self->NewAlias ('Links'); + + + #Make sure we get the right type of link, if we're restricting it + if ($restriction->{'TYPE'}) { + $self->SUPER::Limit(ALIAS => $LinkAlias, + ENTRYAGGREGATOR => 'AND', + FIELD => 'Type', + OPERATOR => '=', + VALUE => $restriction->{'TYPE'} ); + } + + #If we're trying to limit it to things that are target of + if ($restriction->{'TARGET'}) { + + + # If the TARGET is an integer that means that we want to look at the LocalTarget + # field. otherwise, we want to look at the "Target" field + + my ($matchfield); + if ($restriction->{'TARGET'} =~/^(\d+)$/) { + $matchfield = "LocalTarget"; + } + else { + $matchfield = "Target"; + } + + $self->SUPER::Limit(ALIAS => $LinkAlias, + ENTRYAGGREGATOR => 'AND', + FIELD => $matchfield, + OPERATOR => '=', + VALUE => $restriction->{'TARGET'} ); + + + #If we're searching on target, join the base to ticket.id + $self->Join( ALIAS1 => 'main', FIELD1 => $self->{'primary_key'}, + ALIAS2 => $LinkAlias, + FIELD2 => 'LocalBase'); + + + + + } + #If we're trying to limit it to things that are base of + elsif ($restriction->{'BASE'}) { + + + # If we're trying to match a numeric link, we want to look at LocalBase, + # otherwise we want to look at "Base" + + my ($matchfield); + if ($restriction->{'BASE'} =~/^(\d+)$/) { + $matchfield = "LocalBase"; + } + else { + $matchfield = "Base"; + } + + + $self->SUPER::Limit(ALIAS => $LinkAlias, + ENTRYAGGREGATOR => 'AND', + FIELD => $matchfield, + OPERATOR => '=', + VALUE => $restriction->{'BASE'} ); + + #If we're searching on base, join the target to ticket.id + $self->Join( ALIAS1 => 'main', FIELD1 => $self->{'primary_key'}, + ALIAS2 => $LinkAlias, + FIELD2 => 'LocalTarget'); + + } + + } + + # }}} + # {{{ if it's a watcher that we're hunting for + elsif ($TYPES{$restriction->{'FIELD'}} eq 'WATCHERFIELD') { + + my $Watch = $self->NewAlias('Watchers'); + + #Join watchers to users + my $User = $self->Join( TYPE => 'left', + ALIAS1 => $Watch, + FIELD1 => 'Owner', + TABLE2 => 'Users', + FIELD2 => 'id', + ); + + #Join Ticket to watchers + $self->Join( ALIAS1 => 'main', FIELD1 => 'id', + ALIAS2 => $Watch, FIELD2 => 'Value'); + + + #Make sure we're only talking about ticket watchers + $self->SUPER::Limit( ALIAS => $Watch, + FIELD => 'Scope', + VALUE => 'Ticket', + OPERATOR => '='); + + + # Find email address watchers + $self->SUPER::Limit( SUBCLAUSE => 'WatcherEmailAddress', + ALIAS => $Watch, + FIELD => 'Email', + ENTRYAGGREGATOR => 'OR', + VALUE => $restriction->{'VALUE'}, + OPERATOR => $restriction->{'OPERATOR'}, + CASESENSITIVE => 0 + ); + + + + #Find user watchers + $self->SUPER::Limit( + SUBCLAUSE => 'WatcherEmailAddress', + ALIAS => $User, + FIELD => 'EmailAddress', + ENTRYAGGREGATOR => 'OR', + VALUE => $restriction->{'VALUE'}, + OPERATOR => $restriction->{'OPERATOR'}, + CASESENSITIVE => 0 + ); + + + #If we only want a specific type of watchers, then limit it to that + if ($restriction->{'TYPE'}) { + $self->SUPER::Limit( ALIAS => $Watch, + FIELD => 'Type', + ENTRYAGGREGATOR => 'OR', + VALUE => $restriction->{'TYPE'}, + OPERATOR => '='); + } + } + + # }}} + # {{{ if it's a keyword + elsif ($TYPES{$restriction->{'FIELD'}} eq 'KEYWORDFIELD') { + + my $null_columns_ok; + + my $ObjKeywordsAlias; + $ObjKeywordsAlias = $self->{KeywordsAliases}{$restriction->{'KEYWORDSELECT'}} + if $restriction->{SingleValued}; + unless (defined $ObjKeywordsAlias) { + $ObjKeywordsAlias = $self->Join( + TYPE => 'left', + ALIAS1 => 'main', + FIELD1 => 'id', + TABLE2 => 'ObjectKeywords', + FIELD2 => 'ObjectId' + ); + if ($restriction->{'SingleValued'}) { + $self->{KeywordsAliases}{$restriction->{'KEYWORDSELECT'}} + = $ObjKeywordsAlias; + } + } + + + $self->SUPER::Limit( + ALIAS => $ObjKeywordsAlias, + FIELD => 'Keyword', + OPERATOR => $restriction->{'OPERATOR'}, + VALUE => $restriction->{'KEYWORD'}, + QUOTEVALUE => $restriction->{'QUOTEVALUE'}, + ENTRYAGGREGATOR => 'OR', + ); + + if ( ($restriction->{'OPERATOR'} =~ /^IS$/i) or + ($restriction->{'OPERATOR'} eq '!=') ) { + + $null_columns_ok=1; + + } + + #If we're trying to find tickets where the keyword isn't somethng, also check ones where it _IS_ null + if ( $restriction->{'OPERATOR'} eq '!=') { + $self->SUPER::Limit( + ALIAS => $ObjKeywordsAlias, + FIELD => 'Keyword', + OPERATOR => 'IS', + VALUE => 'NULL', + QUOTEVALUE => 0, + ENTRYAGGREGATOR => 'OR', + ); + } + + + $self->SUPER::Limit(LEFTJOIN => $ObjKeywordsAlias, + FIELD => 'KeywordSelect', + VALUE => $restriction->{'KEYWORDSELECT'}, + ENTRYAGGREGATOR => 'OR'); + + + + $self->SUPER::Limit( ALIAS => $ObjKeywordsAlias, + FIELD => 'ObjectType', + VALUE => 'Ticket', + ENTRYAGGREGATOR => 'AND'); + + if ($null_columns_ok) { + $self->SUPER::Limit(ALIAS => $ObjKeywordsAlias, + FIELD => 'ObjectType', + OPERATOR => 'IS', + VALUE => 'NULL', + QUOTEVALUE => 0, + ENTRYAGGREGATOR => 'OR'); + } + + } + # }}} + + + } + + + # here, we make sure we don't get any tickets that have been merged into other tickets + # (Ticket Id == Ticket EffectiveId + # note that we _really_ don't want to do this if we're already looking at the effectiveid + if ($self->_isLimited && (! $self->{'looking_at_effective_id'})) { + $self->SUPER::Limit( FIELD => 'EffectiveId', + OPERATOR => '=', + QUOTEVALUE => 0, + VALUE => 'main.id'); #TODO, we shouldn't be hard coding the tablename to main. + } + $self->{'RecalcTicketLimits'} = 0; +} + +# }}} + +# }}} + +# {{{ Deal with displaying rows of the listing + +# +# Everything in this section is stub code for 2.2 +# It's not part of the API. It's not for your use +# It's not for our use. +# + + +# {{{ sub SetListingFormat + +=head2 SetListingFormat + +Takes a single Format string as specified below. parses that format string and makes the various listing output +things DTRT. + +=item Format strings + +Format strings are made up of a chain of Elements delimited with vertical pipes (|). +Elements of a Format string + + +FormatString: Element[::FormatString] + +Element: AttributeName[;HREF=<URL>][;TITLE=<TITLE>] + +AttributeName Id | Subject | Status | Owner | Priority | InitialPriority | TimeWorked | TimeLeft | + + Keywords[;SELECT=<KeywordSelect>] | + + <Created|Starts|Started|Contacted|Due|Resolved>Date<AsString|AsISO|AsAge> + + +=cut + + + + +#accept a format string + + + +sub SetListingFormat { + my $self = shift; + my $listing_format = shift; + + my ($element, $attribs); + my $i = 0; + foreach $element (split (/::/,$listing_format)) { + if ($element =~ /^(.*?);(.*)$/) { + $element = $1; + $attribs = $2; + } + $self->{'format_string'}->[$i]->{'Element'} = $element; + foreach $attrib (split (/;/, $attribs)) { + my $value = ""; + if ($attrib =~ /^(.*?)=(.*)$/) { + $attrib = $1; + $value = $2; + } + $self->{'format_string'}->[$i]->{"$attrib"} = $val; + + } + + } + return(1); +} + +# }}} + +# {{{ sub HeaderAsHTML +sub HeaderAsHTML { + my $self = shift; + my $header = ""; + my $col; + foreach $col ( @{[ $self->{'format_string'} ]}) { + $header .= "<TH>" . $self->_ColumnTitle($self->{'format_string'}->[$col]) . "</TH>"; + + } + return ($header); +} +# }}} + +# {{{ sub HeaderAsText +#Print text header +sub HeaderAsText { + my $self = shift; + my ($header); + + return ($header); +} +# }}} + +# {{{ sub TicketAsHTMLRow +#Print HTML row +sub TicketAsHTMLRow { + my $self = shift; + my $Ticket = shift; + my ($row, $col); + foreach $col (@{[$self->{'format_string'}]}) { + $row .= "<TD>" . $self->_TicketColumnValue($ticket,$self->{'format_string'}->[$col]) . "</TD>"; + + } + return ($row); +} +# }}} + +# {{{ sub TicketAsTextRow +#Print text row +sub TicketAsTextRow { + my $self = shift; + my ($row); + + #TODO implement + + return ($row); +} +# }}} + +# {{{ _ColumnTitle { + +sub _ColumnTitle { + my $self = shift; + + # Attrib is a hash + my $attrib = shift; + + # return either attrib->{'TITLE'} or.. + if ($attrib->{'TITLE'}) { + return($attrib->{'TITLE'}); + } + # failing that, Look up the title in a hash + else { + #TODO create $self->{'ColumnTitles'}; + return ($self->{'ColumnTitles'}->{$attrib->{'Element'}}); + } + +} + +# }}} + +# {{{ _TicketColumnValue +sub _TicketColumnValue { + my $self = shift; + my $Ticket = shift; + my $attrib = shift; + + + my $out; + + SWITCH: { + /^id/i && do { + $out = $Ticket->id; + last SWITCH; + }; + /^subj/i && do { + last SWITCH; + $Ticket->Subject; + }; + /^status/i && do { + last SWITCH; + $Ticket->Status; + }; + /^prio/i && do { + last SWITCH; + $Ticket->Priority; + }; + /^finalprio/i && do { + + last SWITCH; + $Ticket->FinalPriority + }; + /^initialprio/i && do { + + last SWITCH; + $Ticket->InitialPriority; + }; + /^timel/i && do { + + last SWITCH; + $Ticket->TimeWorked; + }; + /^timew/i && do { + + last SWITCH; + $Ticket->TimeLeft; + }; + + /^(.*?)date(.*)$/i && do { + my $o = $1; + my $m = $2; + my ($obj); + #TODO: optimize + $obj = $Ticket->DueObj if $o =~ /due/i; + $obj = $Ticket->CreatedObj if $o =~ /created/i; + $obj = $Ticket->StartsObj if $o =~ /starts/i; + $obj = $Ticket->StartedObj if $o =~ /started/i; + $obj = $Ticket->ToldObj if $o =~ /told/i; + $obj = $Ticket->LastUpdatedObj if $o =~ /lastu/i; + + $method = 'ISO' if $m =~ /iso/i; + + $method = 'AsString' if $m =~ /asstring/i; + $method = 'AgeAsString' if $m =~ /age/i; + last SWITCH; + $obj->$method(); + + }; + + /^watcher/i && do { + last SWITCH; + $Ticket->WatchersAsString(); + }; + + /^requestor/i && do { + last SWITCH; + $Ticket->RequestorsAsString(); + }; + /^cc/i && do { + last SWITCH; + $Ticket->CCAsString(); + }; + + + /^admincc/i && do { + last SWITCH; + $Ticket->AdminCcAsString(); + }; + + /^keywords/i && do { + last SWITCH; + #Limit it to the keyword select we're talking about, if we've got one. + my $objkeys =$Ticket->KeywordsObj($attrib->{'SELECT'}); + $objkeys->KeywordRelativePathsAsString(); + }; + + } + +} + +# }}} + +# }}} + +# {{{ POD +=head2 notes +"Enum" Things that get Is, IsNot + + +"Int" Things that get Is LessThan and GreaterThan +id +InitialPriority +FinalPriority +Priority +TimeLeft +TimeWorked + +"Text" Things that get Is, Like +Subject +TransactionContent + + +"Link" OPERATORs + + +"Date" OPERATORs Is, Before, After + + =cut +# }}} +1; |