1 #$Header: /home/cvs/cvsroot/freeside/rt/lib/RT/Tickets.pm,v 1.1 2002-08-12 06:17:07 ivan Exp $
5 RT::Tickets - A collection of Ticket objects
11 my $tickets = new RT::Tickets($CurrentUser);
15 A collection of RT::Tickets.
21 ok (require RT::TestHarness);
22 ok (require RT::Tickets);
31 @ISA= qw(RT::EasySearch);
33 use vars qw(%TYPES @SORTFIELDS);
37 %TYPES = ( Status => 'ENUM',
41 LastUpdatedBy => 'ENUM',
45 InitialPriority => 'INT',
46 FinalPriority => 'INT',
53 HasDepender => 'LINK',
60 LastUpdated => 'DATE',
64 Content => 'TRANSFIELD',
65 ContentType => 'TRANSFIELD',
66 TransactionDate => 'TRANSDATE',
67 Watcher => 'WATCHERFIELD',
68 LinkedTo => 'LINKFIELD',
69 Keyword => 'KEYWORDFIELD'
78 @SORTFIELDS = qw(id Status Owner Created Due Starts Started
79 Queue Subject Told Started
80 Resolved LastUpdated Priority TimeWorked TimeLeft);
84 Returns the list of fields that lists of tickets can easily be sorted by
97 # {{{ Limit the result set based on content
103 Takes a paramhash with the fields FIELD, OPERATOR, VALUE and DESCRIPTION
104 Generally best called from LimitFoo methods
109 my %args = ( FIELD => undef,
112 DESCRIPTION => undef,
115 $args{'DESCRIPTION'} = "Autodescribed: ".$args{'FIELD'} . $args{'OPERATOR'} . $args{'VALUE'},
116 if (!defined $args{'DESCRIPTION'}) ;
118 my $index = $self->_NextIndex;
120 #make the TicketRestrictions hash the equivalent of whatever we just passed in;
122 %{$self->{'TicketRestrictions'}{$index}} = %args;
124 $self->{'RecalcTicketLimits'} = 1;
126 # If we're looking at the effective id, we don't want to append the other clause
127 # which limits us to tickets where id = effective id
128 if ($args{'FIELD'} eq 'EffectiveId') {
129 $self->{'looking_at_effective_id'} = 1;
142 Returns a frozen string suitable for handing back to ThawLimits.
145 # {{{ sub FreezeLimits
150 return (FreezeThaw::freeze($self->{'TicketRestrictions'},
151 $self->{'restriction_index'}
159 Take a frozen Limits string generated by FreezeLimits and make this tickets
160 object have that set of limits.
169 #if we don't have $in, get outta here.
170 return undef unless ($in);
172 $self->{'RecalcTicketLimits'} = 1;
176 #We don't need to die if the thaw fails.
179 ($self->{'TicketRestrictions'},
180 $self->{'restriction_index'}
181 ) = FreezeThaw::thaw($in);
188 # {{{ Limit by enum or foreign key
194 LimitQueue takes a paramhash with the fields OPERATOR and VALUE.
195 OPERATOR is one of = or !=. (It defaults to =).
202 my %args = (VALUE => undef,
206 #TODO VALUE should also take queue names and queue objects
207 my $queue = new RT::Queue($self->CurrentUser);
208 $queue->Load($args{'VALUE'});
210 #TODO check for a valid queue here
212 $self->Limit (FIELD => 'Queue',
213 VALUE => $queue->id(),
214 OPERATOR => $args{'OPERATOR'},
215 DESCRIPTION => 'Queue ' . $args{'OPERATOR'}. " ". $queue->Name
221 # {{{ sub LimitStatus
225 Takes a paramhash with the fields OPERATOR and VALUE.
226 OPERATOR is one of = or !=.
233 my %args = ( OPERATOR => '=',
235 $self->Limit (FIELD => 'Status',
236 VALUE => $args{'VALUE'},
237 OPERATOR => $args{'OPERATOR'},
238 DESCRIPTION => 'Status ' . $args{'OPERATOR'}. " ". $args{'VALUE'},
248 Takes a paramhash with the fields OPERATOR and VALUE.
249 OPERATOR is one of = or !=, it defaults to "=".
250 VALUE is a string to search for in the type of the ticket.
256 my %args = (OPERATOR => '=',
259 $self->Limit (FIELD => 'Type',
260 VALUE => $args{'VALUE'},
261 OPERATOR => $args{'OPERATOR'},
262 DESCRIPTION => 'Type ' . $args{'OPERATOR'}. " ". $args{'Limit'},
270 # {{{ Limit by string field
272 # {{{ sub LimitSubject
276 Takes a paramhash with the fields OPERATOR and VALUE.
277 OPERATOR is one of = or !=.
278 VALUE is a string to search for in the subject of the ticket.
285 $self->Limit (FIELD => 'Subject',
286 VALUE => $args{'VALUE'},
287 OPERATOR => $args{'OPERATOR'},
288 DESCRIPTION => 'Subject ' . $args{'OPERATOR'}. " ". $args{'VALUE'},
296 # {{{ Limit based on ticket numerical attributes
297 # Things that can be > < = !=
303 Takes a paramhash with the fields OPERATOR and VALUE.
304 OPERATOR is one of =, >, < or !=.
305 VALUE is a ticket Id to search for
311 my %args = (OPERATOR => '=',
314 $self->Limit (FIELD => 'id',
315 VALUE => $args{'VALUE'},
316 OPERATOR => $args{'OPERATOR'},
317 DESCRIPTION => 'Id ' . $args{'OPERATOR'}. " ". $args{'VALUE'},
323 # {{{ sub LimitPriority
327 Takes a paramhash with the fields OPERATOR and VALUE.
328 OPERATOR is one of =, >, < or !=.
329 VALUE is a value to match the ticket\'s priority against
336 $self->Limit (FIELD => 'Priority',
337 VALUE => $args{'VALUE'},
338 OPERATOR => $args{'OPERATOR'},
339 DESCRIPTION => 'Priority ' . $args{'OPERATOR'}. " ". $args{'VALUE'},
345 # {{{ sub LimitInitialPriority
347 =head2 LimitInitialPriority
349 Takes a paramhash with the fields OPERATOR and VALUE.
350 OPERATOR is one of =, >, < or !=.
351 VALUE is a value to match the ticket\'s initial priority against
356 sub LimitInitialPriority {
359 $self->Limit (FIELD => 'InitialPriority',
360 VALUE => $args{'VALUE'},
361 OPERATOR => $args{'OPERATOR'},
362 DESCRIPTION => 'Initial Priority ' . $args{'OPERATOR'}. " ". $args{'VALUE'},
368 # {{{ sub LimitFinalPriority
370 =head2 LimitFinalPriority
372 Takes a paramhash with the fields OPERATOR and VALUE.
373 OPERATOR is one of =, >, < or !=.
374 VALUE is a value to match the ticket\'s final priority against
378 sub LimitFinalPriority {
381 $self->Limit (FIELD => 'FinalPriority',
382 VALUE => $args{'VALUE'},
383 OPERATOR => $args{'OPERATOR'},
384 DESCRIPTION => 'Final Priority ' . $args{'OPERATOR'}. " ". $args{'VALUE'},
390 # {{{ sub LimitTimeWorked
392 =head2 LimitTimeWorked
394 Takes a paramhash with the fields OPERATOR and VALUE.
395 OPERATOR is one of =, >, < or !=.
396 VALUE is a value to match the ticket's TimeWorked attribute
400 sub LimitTimeWorked {
403 $self->Limit (FIELD => 'TimeWorked',
404 VALUE => $args{'VALUE'},
405 OPERATOR => $args{'OPERATOR'},
406 DESCRIPTION => 'Time worked ' . $args{'OPERATOR'}. " ". $args{'VALUE'},
412 # {{{ sub LimitTimeLeft
416 Takes a paramhash with the fields OPERATOR and VALUE.
417 OPERATOR is one of =, >, < or !=.
418 VALUE is a value to match the ticket's TimeLeft attribute
425 $self->Limit (FIELD => 'TimeLeft',
426 VALUE => $args{'VALUE'},
427 OPERATOR => $args{'OPERATOR'},
428 DESCRIPTION => 'Time left ' . $args{'OPERATOR'}. " ". $args{'VALUE'},
436 # {{{ Limiting based on attachment attributes
438 # {{{ sub LimitContent
442 Takes a paramhash with the fields OPERATOR and VALUE.
443 OPERATOR is one of =, LIKE, NOT LIKE or !=.
444 VALUE is a string to search for in the body of the ticket
450 $self->Limit (FIELD => 'Content',
451 VALUE => $args{'VALUE'},
452 OPERATOR => $args{'OPERATOR'},
453 DESCRIPTION => 'Ticket content ' . $args{'OPERATOR'}. " ". $args{'VALUE'},
458 # {{{ sub LimitContentType
460 =head2 LimitContentType
462 Takes a paramhash with the fields OPERATOR and VALUE.
463 OPERATOR is one of =, LIKE, NOT LIKE or !=.
464 VALUE is a content type to search ticket attachments for
468 sub LimitContentType {
471 $self->Limit (FIELD => 'ContentType',
472 VALUE => $args{'VALUE'},
473 OPERATOR => $args{'OPERATOR'},
474 DESCRIPTION => 'Ticket content type ' . $args{'OPERATOR'}. " ". $args{'VALUE'},
481 # {{{ Limiting based on people
487 Takes a paramhash with the fields OPERATOR and VALUE.
488 OPERATOR is one of = or !=.
495 my %args = ( OPERATOR => '=',
498 my $owner = new RT::User($self->CurrentUser);
499 $owner->Load($args{'VALUE'});
500 $self->Limit (FIELD => 'Owner',
502 OPERATOR => $args{'OPERATOR'},
503 DESCRIPTION => 'Owner ' . $args{'OPERATOR'}. " ". $owner->Name()
510 # {{{ Limiting watchers
512 # {{{ sub LimitWatcher
517 Takes a paramhash with the fields OPERATOR, TYPE and VALUE.
518 OPERATOR is one of =, LIKE, NOT LIKE or !=.
519 VALUE is a value to match the ticket\'s watcher email addresses against
520 TYPE is the sort of watchers you want to match against. Leave it undef if you want to search all of them
526 my %args = ( OPERATOR => '=',
532 #build us up a description
533 my ($watcher_type, $desc);
535 $watcher_type = $args{'TYPE'};
538 $watcher_type = "Watcher";
540 $desc = "$watcher_type ".$args{'OPERATOR'}." ".$args{'VALUE'};
543 $self->Limit (FIELD => 'Watcher',
544 VALUE => $args{'VALUE'},
545 OPERATOR => $args{'OPERATOR'},
546 TYPE => $args{'TYPE'},
547 DESCRIPTION => "$desc"
553 # {{{ sub LimitRequestor
555 =head2 LimitRequestor
557 It\'s like LimitWatcher, but it presets TYPE to Requestor
564 $self->LimitWatcher(TYPE=> 'Requestor', @_);
573 It\'s like LimitWatcher, but it presets TYPE to Cc
579 $self->LimitWatcher(TYPE=> 'Cc', @_);
584 # {{{ sub LimitAdminCc
588 It\'s like LimitWatcher, but it presets TYPE to AdminCc
594 $self->LimitWatcher(TYPE=> 'AdminCc', @_);
603 # {{{ Limiting based on links
609 LimitLinkedTo takes a paramhash with two fields: TYPE and TARGET
610 TYPE limits the sort of relationship we want to search on
612 TARGET is the id or URI of the TARGET of the link
613 (TARGET used to be 'TICKET'. 'TICKET' is deprecated, but will be treated as TARGET
626 $self->Limit( FIELD => 'LinkedTo',
628 TARGET => ($args{'TARGET'} || $args{'TICKET'}),
629 TYPE => $args{'TYPE'},
630 DESCRIPTION => "Tickets ".$args{'TYPE'}." by ".($args{'TARGET'} || $args{'TICKET'})
637 # {{{ LimitLinkedFrom
639 =head2 LimitLinkedFrom
641 LimitLinkedFrom takes a paramhash with two fields: TYPE and BASE
642 TYPE limits the sort of relationship we want to search on
645 BASE is the id or URI of the BASE of the link
646 (BASE used to be 'TICKET'. 'TICKET' is deprecated, but will be treated as BASE
651 sub LimitLinkedFrom {
653 my %args = ( BASE => undef,
659 $self->Limit( FIELD => 'LinkedTo',
661 BASE => ($args{'BASE'} || $args{'TICKET'}),
662 TYPE => $args{'TYPE'},
663 DESCRIPTION => "Tickets " .($args{'BASE'} || $args{'TICKET'}) ." ".$args{'TYPE'}
673 my $ticket_id = shift;
674 $self->LimitLinkedTo ( TARGET=> "$ticket_id",
684 my $ticket_id =shift;
685 $self->LimitLinkedFrom ( BASE => "$ticket_id",
696 my $ticket_id = shift;
697 $self->LimitLinkedTo ( TARGET => "$ticket_id",
705 # {{{ LimitDependedOnBy
707 sub LimitDependedOnBy {
709 my $ticket_id = shift;
710 $self->LimitLinkedFrom ( BASE => "$ticket_id",
723 my $ticket_id = shift;
724 $self->LimitLinkedTo ( TARGET => "$ticket_id",
732 # {{{ LimitReferredToBy
734 sub LimitReferredToBy {
736 my $ticket_id = shift;
737 $self->LimitLinkedFrom ( BASE=> "$ticket_id",
747 # {{{ limit based on ticket date attribtes
751 =head2 LimitDate (FIELD => 'DateField', OPERATOR => $oper, VALUE => $ISODate)
753 Takes a paramhash with the fields FIELD OPERATOR and VALUE.
755 OPERATOR is one of > or <
756 VALUE is a date and time in ISO format in GMT
757 FIELD is one of Starts, Started, Told, Created, Resolved, LastUpdated
759 There are also helper functions of the form LimitFIELD that eliminate
760 the need to pass in a FIELD argument.
768 VALUE => $args{'VALUE'},
769 OPERATOR => $args{'OPERATOR'},
773 #Set the description if we didn't get handed it above
774 unless ($args{'DESCRIPTION'} ) {
775 $args{'DESCRIPTION'} = $args{'FIELD'} . " " .$args{'OPERATOR'}. " ". $args{'VALUE'} . " GMT"
778 $self->Limit (%args);
789 $self->LimitDate( FIELD => 'Created', @_);
793 $self->LimitDate( FIELD => 'Due', @_);
798 $self->LimitDate( FIELD => 'Starts', @_);
803 $self->LimitDate( FIELD => 'Started', @_);
807 $self->LimitDate( FIELD => 'Resolved', @_);
811 $self->LimitDate( FIELD => 'Told', @_);
813 sub LimitLastUpdated {
815 $self->LimitDate( FIELD => 'LastUpdated', @_);
818 # {{{ sub LimitTransactionDate
820 =head2 LimitTransactionDate (OPERATOR => $oper, VALUE => $ISODate)
822 Takes a paramhash with the fields FIELD OPERATOR and VALUE.
824 OPERATOR is one of > or <
825 VALUE is a date and time in ISO format in GMT
830 sub LimitTransactionDate {
833 FIELD => 'TransactionDate',
834 VALUE => $args{'VALUE'},
835 OPERATOR => $args{'OPERATOR'},
839 #Set the description if we didn't get handed it above
840 unless ($args{'DESCRIPTION'} ) {
841 $args{'DESCRIPTION'} = $args{'FIELD'} . " " .$args{'OPERATOR'}. " ". $args{'VALUE'} . " GMT"
844 $self->Limit (%args);
852 # {{{ sub LimitKeyword
856 Takes a paramhash of key/value pairs with the following keys:
860 =item KEYWORDSELECT - KeywordSelect id
862 =item OPERATOR - (for KEYWORD only - KEYWORDSELECT operator is always `=')
864 =item KEYWORD - Keyword id
872 my %args = ( KEYWORD => undef,
873 KEYWORDSELECT => undef,
875 DESCRIPTION => undef,
881 use RT::KeywordSelect;
882 my $KeywordSelect = RT::KeywordSelect->new($self->CurrentUser);
883 $KeywordSelect->Load($args{KEYWORDSELECT});
886 # Below, We're checking to see whether the keyword we're searching for
888 # This could probably be rewritten to be easier to read and understand
891 #If we are looking to compare with a null value.
892 if ($args{'OPERATOR'} =~ /is/i) {
893 if ($args{'OPERATOR'} =~ /^is$/i) {
894 $args{'DESCRIPTION'} ||= "Keyword Selection ". $KeywordSelect->Name . " has no value";
896 elsif ($args{'OPERATOR'} =~ /^is not$/i) {
897 $args{'DESCRIPTION'} ||= "Keyword Selection ". $KeywordSelect->Name . " has a value";
900 # if we're not looking to compare with a null value
903 my $Keyword = RT::Keyword->new($self->CurrentUser);
904 $Keyword->Load($args{KEYWORD});
905 $args{'DESCRIPTION'} ||= "Keyword Selection " . $KeywordSelect->Name. " $args{OPERATOR} ". $Keyword->Name;
908 $args{SingleValued} = $KeywordSelect->Single();
911 my $index = $self->_NextIndex;
912 %{$self->{'TicketRestrictions'}{$index}} = %args;
914 $self->{'RecalcTicketLimits'} = 1;
924 Keep track of the counter for the array of restrictions
930 return ($self->{'restriction_index'}++);
936 # {{{ Core bits to make this a DBIx::SearchBuilder object
941 $self->{'table'} = "Tickets";
942 $self->{'RecalcTicketLimits'} = 1;
943 $self->{'looking_at_effective_id'} = 0;
944 $self->{'restriction_index'} =1;
945 $self->{'primary_key'} = "id";
946 $self->SUPER::_Init(@_);
954 return(RT::Ticket->new($self->CurrentUser));
962 $self->_ProcessRestrictions if ($self->{'RecalcTicketLimits'} == 1 );
963 return($self->SUPER::Count());
967 # {{{ sub ItemsArrayRef
971 Returns a reference to the set of all items found in this search
979 my $placeholder = $self->_ItemsCounter;
980 $self->GotoFirstItem();
981 while (my $item = $self->Next) {
982 push (@items, $item);
985 $self->GotoItem($placeholder);
994 $self->_ProcessRestrictions if ($self->{'RecalcTicketLimits'} == 1 );
996 my $Ticket = $self->SUPER::Next();
997 if ((defined($Ticket)) and (ref($Ticket))) {
999 #Make sure we _never_ show dead tickets
1000 #TODO we should be doing this in the where clause.
1001 #but you can't do multiple clauses on the same field just yet :/
1003 if ($Ticket->Status eq 'dead') {
1004 return($self->Next());
1006 elsif ($Ticket->CurrentUserHasRight('ShowTicket')) {
1010 #If the user doesn't have the right to show this ticket
1012 return($self->Next());
1015 #if there never was any ticket
1025 # {{{ Deal with storing and restoring restrictions
1027 # {{{ sub LoadRestrictions
1029 =head2 LoadRestrictions
1031 LoadRestrictions takes a string which can fully populate the TicketRestrictons hash.
1032 TODO It is not yet implemented
1038 # {{{ sub DescribeRestrictions
1040 =head2 DescribeRestrictions
1043 Returns a hash keyed by restriction id.
1044 Each element of the hash is currently a one element hash that contains DESCRIPTION which
1045 is a description of the purpose of that TicketRestriction
1049 sub DescribeRestrictions {
1052 my ($row, %listing);
1054 foreach $row (keys %{$self->{'TicketRestrictions'}}) {
1055 $listing{$row} = $self->{'TicketRestrictions'}{$row}{'DESCRIPTION'};
1061 # {{{ sub RestrictionValues
1063 =head2 RestrictionValues FIELD
1065 Takes a restriction field and returns a list of values this field is restricted
1070 sub RestrictionValues {
1073 map $self->{'TicketRestrictions'}{$_}{'VALUE'},
1075 $self->{'TicketRestrictions'}{$_}{'FIELD'} eq $field
1076 && $self->{'TicketRestrictions'}{$_}{'OPERATOR'} eq "="
1078 keys %{$self->{'TicketRestrictions'}};
1083 # {{{ sub ClearRestrictions
1085 =head2 ClearRestrictions
1087 Removes all restrictions irretrievably
1091 sub ClearRestrictions {
1093 delete $self->{'TicketRestrictions'};
1094 $self->{'looking_at_effective_id'} = 0;
1095 $self->{'RecalcTicketLimits'} =1;
1100 # {{{ sub DeleteRestriction
1102 =head2 DeleteRestriction
1104 Takes the row Id of a restriction (From DescribeRestrictions' output, for example.
1105 Removes that restriction from the session's limits.
1110 sub DeleteRestriction {
1113 delete $self->{'TicketRestrictions'}{$row};
1115 $self->{'RecalcTicketLimits'} = 1;
1116 #make the underlying easysearch object forget all its preconceptions
1121 # {{{ sub _ProcessRestrictions
1123 sub _ProcessRestrictions {
1126 #Need to clean the EasySearch slate because it makes things too sticky
1127 $self->CleanSlate();
1129 #Blow away ticket aliases since we'll need to regenerate them for a new search
1130 delete $self->{'TicketAliases'};
1131 delete $self->{KeywordsAliases};
1135 foreach $row (keys %{$self->{'TicketRestrictions'}}) {
1136 my $restriction = $self->{'TicketRestrictions'}{$row};
1137 # {{{ if it's an int
1139 if ($TYPES{$restriction->{'FIELD'}} eq 'INT' ) {
1140 if ($restriction->{'OPERATOR'} =~ /^(=|!=|>|<|>=|<=)$/) {
1141 $self->SUPER::Limit( FIELD => $restriction->{'FIELD'},
1142 ENTRYAGGREGATOR => 'AND',
1143 OPERATOR => $restriction->{'OPERATOR'},
1144 VALUE => $restriction->{'VALUE'},
1149 # {{{ if it's an enum
1150 elsif ($TYPES{$restriction->{'FIELD'}} eq 'ENUM') {
1152 if ($restriction->{'OPERATOR'} eq '=') {
1153 $self->SUPER::Limit( FIELD => $restriction->{'FIELD'},
1154 ENTRYAGGREGATOR => 'OR',
1156 VALUE => $restriction->{'VALUE'},
1159 elsif ($restriction->{'OPERATOR'} eq '!=') {
1160 $self->SUPER::Limit( FIELD => $restriction->{'FIELD'},
1161 ENTRYAGGREGATOR => 'AND',
1163 VALUE => $restriction->{'VALUE'},
1169 # {{{ if it's a date
1171 elsif ($TYPES{$restriction->{'FIELD'}} eq 'DATE') {
1172 $self->SUPER::Limit( FIELD => $restriction->{'FIELD'},
1173 ENTRYAGGREGATOR => 'AND',
1174 OPERATOR => $restriction->{'OPERATOR'},
1175 VALUE => $restriction->{'VALUE'},
1179 # {{{ if it's a string
1181 elsif ($TYPES{$restriction->{'FIELD'}} eq 'STRING') {
1183 if ($restriction->{'OPERATOR'} eq '=') {
1184 $self->SUPER::Limit( FIELD => $restriction->{'FIELD'},
1185 ENTRYAGGREGATOR => 'OR',
1187 VALUE => $restriction->{'VALUE'},
1191 elsif ($restriction->{'OPERATOR'} eq '!=') {
1192 $self->SUPER::Limit( FIELD => $restriction->{'FIELD'},
1193 ENTRYAGGREGATOR => 'AND',
1195 VALUE => $restriction->{'VALUE'},
1199 elsif ($restriction->{'OPERATOR'} eq 'LIKE') {
1200 $self->SUPER::Limit( FIELD => $restriction->{'FIELD'},
1201 ENTRYAGGREGATOR => 'AND',
1203 VALUE => $restriction->{'VALUE'},
1207 elsif ($restriction->{'OPERATOR'} eq 'NOT LIKE') {
1208 $self->SUPER::Limit( FIELD => $restriction->{'FIELD'},
1209 ENTRYAGGREGATOR => 'AND',
1210 OPERATOR => 'NOT LIKE',
1211 VALUE => $restriction->{'VALUE'},
1218 # {{{ if it's Transaction content that we're hunting for
1219 elsif ($TYPES{$restriction->{'FIELD'}} eq 'TRANSFIELD') {
1221 #Basically, we want to make sure that the limits apply to the same attachment,
1222 #rather than just another attachment for the same ticket, no matter how many
1223 #clauses we lump on.
1224 #We put them in TicketAliases so that they get nuked when we redo the join.
1226 unless (defined $self->{'TicketAliases'}{'TransFieldAlias'}) {
1227 $self->{'TicketAliases'}{'TransFieldAlias'} = $self->NewAlias ('Transactions');
1229 unless (defined $self->{'TicketAliases'}{'TransFieldAttachAlias'}){
1230 $self->{'TicketAliases'}{'TransFieldAttachAlias'} = $self->NewAlias('Attachments');
1233 #Join transactions to attachments
1234 $self->Join( ALIAS1 => $self->{'TicketAliases'}{'TransFieldAttachAlias'},
1235 FIELD1 => 'TransactionId',
1236 ALIAS2 => $self->{'TicketAliases'}{'TransFieldAlias'}, FIELD2=> 'id');
1238 #Join transactions to tickets
1239 $self->Join( ALIAS1 => 'main', FIELD1 => $self->{'primary_key'},
1240 ALIAS2 =>$self->{'TicketAliases'}{'TransFieldAlias'}, FIELD2 => 'Ticket');
1242 #Search for the right field
1243 $self->SUPER::Limit(ALIAS => $self->{'TicketAliases'}{'TransFieldAttachAlias'},
1244 ENTRYAGGREGATOR => 'AND',
1245 FIELD => $restriction->{'FIELD'},
1246 OPERATOR => $restriction->{'OPERATOR'} ,
1247 VALUE => $restriction->{'VALUE'},
1255 # {{{ if it's a Transaction date that we're hunting for
1256 elsif ($TYPES{$restriction->{'FIELD'}} eq 'TRANSDATE') {
1258 #Basically, we want to make sure that the limits apply to the same attachment,
1259 #rather than just another attachment for the same ticket, no matter how many
1260 #clauses we lump on.
1261 #We put them in TicketAliases so that they get nuked when we redo the join.
1263 unless (defined $self->{'TicketAliases'}{'TransFieldAlias'}) {
1264 $self->{'TicketAliases'}{'TransFieldAlias'} = $self->NewAlias ('Transactions');
1267 #Join transactions to tickets
1268 $self->Join( ALIAS1 => 'main', FIELD1 => $self->{'primary_key'},
1269 ALIAS2 =>$self->{'TicketAliases'}{'TransFieldAlias'}, FIELD2 => 'Ticket');
1271 #Search for the right field
1272 $self->SUPER::Limit(ALIAS => $self->{'TicketAliases'}{'TransFieldAlias'},
1273 ENTRYAGGREGATOR => 'AND',
1275 OPERATOR => $restriction->{'OPERATOR'} ,
1276 VALUE => $restriction->{'VALUE'} );
1280 # {{{ if it's a relationship that we're hunting for
1282 # Takes FIELD: which is something like "LinkedTo"
1283 # takes TARGET or BASE which is the TARGET or BASE id that we're searching for
1284 # takes TYPE which is the type of link we're looking for.
1286 elsif ($TYPES{$restriction->{'FIELD'}} eq 'LINKFIELD') {
1289 my $LinkAlias = $self->NewAlias ('Links');
1292 #Make sure we get the right type of link, if we're restricting it
1293 if ($restriction->{'TYPE'}) {
1294 $self->SUPER::Limit(ALIAS => $LinkAlias,
1295 ENTRYAGGREGATOR => 'AND',
1298 VALUE => $restriction->{'TYPE'} );
1301 #If we're trying to limit it to things that are target of
1302 if ($restriction->{'TARGET'}) {
1305 # If the TARGET is an integer that means that we want to look at the LocalTarget
1306 # field. otherwise, we want to look at the "Target" field
1309 if ($restriction->{'TARGET'} =~/^(\d+)$/) {
1310 $matchfield = "LocalTarget";
1313 $matchfield = "Target";
1316 $self->SUPER::Limit(ALIAS => $LinkAlias,
1317 ENTRYAGGREGATOR => 'AND',
1318 FIELD => $matchfield,
1320 VALUE => $restriction->{'TARGET'} );
1323 #If we're searching on target, join the base to ticket.id
1324 $self->Join( ALIAS1 => 'main', FIELD1 => $self->{'primary_key'},
1325 ALIAS2 => $LinkAlias,
1326 FIELD2 => 'LocalBase');
1332 #If we're trying to limit it to things that are base of
1333 elsif ($restriction->{'BASE'}) {
1336 # If we're trying to match a numeric link, we want to look at LocalBase,
1337 # otherwise we want to look at "Base"
1340 if ($restriction->{'BASE'} =~/^(\d+)$/) {
1341 $matchfield = "LocalBase";
1344 $matchfield = "Base";
1348 $self->SUPER::Limit(ALIAS => $LinkAlias,
1349 ENTRYAGGREGATOR => 'AND',
1350 FIELD => $matchfield,
1352 VALUE => $restriction->{'BASE'} );
1354 #If we're searching on base, join the target to ticket.id
1355 $self->Join( ALIAS1 => 'main', FIELD1 => $self->{'primary_key'},
1356 ALIAS2 => $LinkAlias,
1357 FIELD2 => 'LocalTarget');
1364 # {{{ if it's a watcher that we're hunting for
1365 elsif ($TYPES{$restriction->{'FIELD'}} eq 'WATCHERFIELD') {
1367 my $Watch = $self->NewAlias('Watchers');
1369 #Join watchers to users
1370 my $User = $self->Join( TYPE => 'left',
1377 #Join Ticket to watchers
1378 $self->Join( ALIAS1 => 'main', FIELD1 => 'id',
1379 ALIAS2 => $Watch, FIELD2 => 'Value');
1382 #Make sure we're only talking about ticket watchers
1383 $self->SUPER::Limit( ALIAS => $Watch,
1389 # Find email address watchers
1390 $self->SUPER::Limit( SUBCLAUSE => 'WatcherEmailAddress',
1393 ENTRYAGGREGATOR => 'OR',
1394 VALUE => $restriction->{'VALUE'},
1395 OPERATOR => $restriction->{'OPERATOR'},
1402 $self->SUPER::Limit(
1403 SUBCLAUSE => 'WatcherEmailAddress',
1405 FIELD => 'EmailAddress',
1406 ENTRYAGGREGATOR => 'OR',
1407 VALUE => $restriction->{'VALUE'},
1408 OPERATOR => $restriction->{'OPERATOR'},
1413 #If we only want a specific type of watchers, then limit it to that
1414 if ($restriction->{'TYPE'}) {
1415 $self->SUPER::Limit( ALIAS => $Watch,
1417 ENTRYAGGREGATOR => 'OR',
1418 VALUE => $restriction->{'TYPE'},
1424 # {{{ if it's a keyword
1425 elsif ($TYPES{$restriction->{'FIELD'}} eq 'KEYWORDFIELD') {
1427 my $null_columns_ok;
1429 my $ObjKeywordsAlias;
1430 $ObjKeywordsAlias = $self->{KeywordsAliases}{$restriction->{'KEYWORDSELECT'}}
1431 if $restriction->{SingleValued};
1432 unless (defined $ObjKeywordsAlias) {
1433 $ObjKeywordsAlias = $self->Join(
1437 TABLE2 => 'ObjectKeywords',
1438 FIELD2 => 'ObjectId'
1440 if ($restriction->{'SingleValued'}) {
1441 $self->{KeywordsAliases}{$restriction->{'KEYWORDSELECT'}}
1442 = $ObjKeywordsAlias;
1447 $self->SUPER::Limit(
1448 ALIAS => $ObjKeywordsAlias,
1450 OPERATOR => $restriction->{'OPERATOR'},
1451 VALUE => $restriction->{'KEYWORD'},
1452 QUOTEVALUE => $restriction->{'QUOTEVALUE'},
1453 ENTRYAGGREGATOR => 'OR',
1456 if ( ($restriction->{'OPERATOR'} =~ /^IS$/i) or
1457 ($restriction->{'OPERATOR'} eq '!=') ) {
1463 #If we're trying to find tickets where the keyword isn't somethng, also check ones where it _IS_ null
1464 if ( $restriction->{'OPERATOR'} eq '!=') {
1465 $self->SUPER::Limit(
1466 ALIAS => $ObjKeywordsAlias,
1471 ENTRYAGGREGATOR => 'OR',
1476 $self->SUPER::Limit(LEFTJOIN => $ObjKeywordsAlias,
1477 FIELD => 'KeywordSelect',
1478 VALUE => $restriction->{'KEYWORDSELECT'},
1479 ENTRYAGGREGATOR => 'OR');
1483 $self->SUPER::Limit( ALIAS => $ObjKeywordsAlias,
1484 FIELD => 'ObjectType',
1486 ENTRYAGGREGATOR => 'AND');
1488 if ($null_columns_ok) {
1489 $self->SUPER::Limit(ALIAS => $ObjKeywordsAlias,
1490 FIELD => 'ObjectType',
1494 ENTRYAGGREGATOR => 'OR');
1504 # here, we make sure we don't get any tickets that have been merged into other tickets
1505 # (Ticket Id == Ticket EffectiveId
1506 # note that we _really_ don't want to do this if we're already looking at the effectiveid
1507 if ($self->_isLimited && (! $self->{'looking_at_effective_id'})) {
1508 $self->SUPER::Limit( FIELD => 'EffectiveId',
1511 VALUE => 'main.id'); #TODO, we shouldn't be hard coding the tablename to main.
1513 $self->{'RecalcTicketLimits'} = 0;
1520 # {{{ Deal with displaying rows of the listing
1523 # Everything in this section is stub code for 2.2
1524 # It's not part of the API. It's not for your use
1525 # It's not for our use.
1529 # {{{ sub SetListingFormat
1531 =head2 SetListingFormat
1533 Takes a single Format string as specified below. parses that format string and makes the various listing output
1536 =item Format strings
1538 Format strings are made up of a chain of Elements delimited with vertical pipes (|).
1539 Elements of a Format string
1542 FormatString: Element[::FormatString]
1544 Element: AttributeName[;HREF=<URL>][;TITLE=<TITLE>]
1546 AttributeName Id | Subject | Status | Owner | Priority | InitialPriority | TimeWorked | TimeLeft |
1548 Keywords[;SELECT=<KeywordSelect>] |
1550 <Created|Starts|Started|Contacted|Due|Resolved>Date<AsString|AsISO|AsAge>
1558 #accept a format string
1562 sub SetListingFormat {
1564 my $listing_format = shift;
1566 my ($element, $attribs);
1568 foreach $element (split (/::/,$listing_format)) {
1569 if ($element =~ /^(.*?);(.*)$/) {
1573 $self->{'format_string'}->[$i]->{'Element'} = $element;
1574 foreach $attrib (split (/;/, $attribs)) {
1576 if ($attrib =~ /^(.*?)=(.*)$/) {
1580 $self->{'format_string'}->[$i]->{"$attrib"} = $val;
1590 # {{{ sub HeaderAsHTML
1595 foreach $col ( @{[ $self->{'format_string'} ]}) {
1596 $header .= "<TH>" . $self->_ColumnTitle($self->{'format_string'}->[$col]) . "</TH>";
1603 # {{{ sub HeaderAsText
1613 # {{{ sub TicketAsHTMLRow
1615 sub TicketAsHTMLRow {
1619 foreach $col (@{[$self->{'format_string'}]}) {
1620 $row .= "<TD>" . $self->_TicketColumnValue($ticket,$self->{'format_string'}->[$col]) . "</TD>";
1627 # {{{ sub TicketAsTextRow
1629 sub TicketAsTextRow {
1639 # {{{ _ColumnTitle {
1647 # return either attrib->{'TITLE'} or..
1648 if ($attrib->{'TITLE'}) {
1649 return($attrib->{'TITLE'});
1651 # failing that, Look up the title in a hash
1653 #TODO create $self->{'ColumnTitles'};
1654 return ($self->{'ColumnTitles'}->{$attrib->{'Element'}});
1661 # {{{ _TicketColumnValue
1662 sub _TicketColumnValue {
1687 /^finalprio/i && do {
1690 $Ticket->FinalPriority
1692 /^initialprio/i && do {
1695 $Ticket->InitialPriority;
1700 $Ticket->TimeWorked;
1708 /^(.*?)date(.*)$/i && do {
1713 $obj = $Ticket->DueObj if $o =~ /due/i;
1714 $obj = $Ticket->CreatedObj if $o =~ /created/i;
1715 $obj = $Ticket->StartsObj if $o =~ /starts/i;
1716 $obj = $Ticket->StartedObj if $o =~ /started/i;
1717 $obj = $Ticket->ToldObj if $o =~ /told/i;
1718 $obj = $Ticket->LastUpdatedObj if $o =~ /lastu/i;
1720 $method = 'ISO' if $m =~ /iso/i;
1722 $method = 'AsString' if $m =~ /asstring/i;
1723 $method = 'AgeAsString' if $m =~ /age/i;
1731 $Ticket->WatchersAsString();
1734 /^requestor/i && do {
1736 $Ticket->RequestorsAsString();
1740 $Ticket->CCAsString();
1746 $Ticket->AdminCcAsString();
1749 /^keywords/i && do {
1751 #Limit it to the keyword select we're talking about, if we've got one.
1752 my $objkeys =$Ticket->KeywordsObj($attrib->{'SELECT'});
1753 $objkeys->KeywordRelativePathsAsString();
1766 "Enum" Things that get Is, IsNot
1769 "Int" Things that get Is LessThan and GreaterThan
1777 "Text" Things that get Is, Like
1785 "Date" OPERATORs Is, Before, After