import rt 2.0.14
[freeside.git] / rt / lib / RT / Tickets.pm
1 #$Header: /home/cvs/cvsroot/freeside/rt/lib/RT/Tickets.pm,v 1.1 2002-08-12 06:17:07 ivan Exp $
2
3 =head1 NAME
4
5   RT::Tickets - A collection of Ticket objects
6
7
8 =head1 SYNOPSIS
9
10   use RT::Tickets;
11   my $tickets = new RT::Tickets($CurrentUser);
12
13 =head1 DESCRIPTION
14
15    A collection of RT::Tickets.
16
17 =head1 METHODS
18
19 =begin testing
20
21 ok (require RT::TestHarness);
22 ok (require RT::Tickets);
23
24 =end testing
25
26 =cut
27
28 package RT::Tickets;
29 use RT::EasySearch;
30 use RT::Ticket;
31 @ISA= qw(RT::EasySearch);
32
33 use vars qw(%TYPES @SORTFIELDS);
34
35 # {{{ TYPES
36
37 %TYPES =    ( Status => 'ENUM',
38               Queue  => 'ENUM',
39               Type => 'ENUM',
40               Creator => 'ENUM',
41               LastUpdatedBy => 'ENUM',
42               Owner => 'ENUM',
43               EffectiveId => 'INT',
44               id => 'INT',
45               InitialPriority => 'INT',
46               FinalPriority => 'INT',
47               Priority => 'INT',
48               TimeLeft => 'INT',
49               TimeWorked => 'INT',
50               MemberOf => 'LINK',
51               DependsOn => 'LINK',
52               HasMember => 'LINK',
53               HasDepender => 'LINK',
54               RelatedTo => 'LINK',
55               Told => 'DATE',
56               StartsBy => 'DATE',
57               Started => 'DATE',
58               Due  => 'DATE',
59               Resolved => 'DATE',
60               LastUpdated => 'DATE',
61               Created => 'DATE',
62               Subject => 'STRING',
63               Type => 'STRING',
64               Content => 'TRANSFIELD',
65               ContentType => 'TRANSFIELD',
66               TransactionDate => 'TRANSDATE',
67               Watcher => 'WATCHERFIELD',
68               LinkedTo => 'LINKFIELD',
69               Keyword => 'KEYWORDFIELD'
70
71             );
72
73
74 # }}}
75
76 # {{{ sub SortFields
77
78 @SORTFIELDS = qw(id Status Owner Created Due Starts Started
79                  Queue Subject Told Started 
80                     Resolved LastUpdated Priority TimeWorked TimeLeft);
81
82 =head2 SortFields
83
84 Returns the list of fields that lists of tickets can easily be sorted by
85
86 =cut
87
88
89 sub SortFields {
90         my $self = shift;
91         return(@SORTFIELDS);
92 }
93
94
95 # }}}
96
97 # {{{ Limit the result set based on content
98
99 # {{{ sub Limit 
100
101 =head2 Limit
102
103 Takes a paramhash with the fields FIELD, OPERATOR, VALUE and DESCRIPTION
104 Generally best called from LimitFoo methods
105
106 =cut
107 sub Limit {
108     my $self = shift;
109     my %args = ( FIELD => undef,
110                  OPERATOR => '=',
111                  VALUE => undef,
112                  DESCRIPTION => undef,
113                  @_
114                );
115    $args{'DESCRIPTION'} = "Autodescribed: ".$args{'FIELD'} . $args{'OPERATOR'} . $args{'VALUE'},
116     if (!defined $args{'DESCRIPTION'}) ;
117
118     my $index = $self->_NextIndex;
119     
120     #make the TicketRestrictions hash the equivalent of whatever we just passed in;
121     
122     %{$self->{'TicketRestrictions'}{$index}} = %args;
123
124     $self->{'RecalcTicketLimits'} = 1;
125
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;
130     }
131
132     return ($index);
133 }
134
135 # }}}
136
137
138
139
140 =head2 FreezeLimits
141
142 Returns a frozen string suitable for handing back to ThawLimits.
143
144 =cut
145 # {{{ sub FreezeLimits
146
147 sub FreezeLimits {
148         my $self = shift;
149         require FreezeThaw;
150         return (FreezeThaw::freeze($self->{'TicketRestrictions'},
151                                    $self->{'restriction_index'}
152                                   ));
153 }
154
155 # }}}
156
157 =head2 ThawLimits
158
159 Take a frozen Limits string generated by FreezeLimits and make this tickets
160 object have that set of limits.
161
162 =cut
163 # {{{ sub ThawLimits
164
165 sub ThawLimits {
166         my $self = shift;
167         my $in = shift;
168         
169         #if we don't have $in, get outta here.
170         return undef unless ($in);
171
172         $self->{'RecalcTicketLimits'} = 1;
173
174         require FreezeThaw;
175         
176         #We don't need to die if the thaw fails.
177         
178         eval {
179                 ($self->{'TicketRestrictions'},
180                 $self->{'restriction_index'}
181                 ) = FreezeThaw::thaw($in);
182         }
183
184 }
185
186 # }}}
187
188 # {{{ Limit by enum or foreign key
189
190 # {{{ sub LimitQueue
191
192 =head2 LimitQueue
193
194 LimitQueue takes a paramhash with the fields OPERATOR and VALUE.
195 OPERATOR is one of = or !=. (It defaults to =).
196 VALUE is a queue id. 
197
198 =cut
199
200 sub LimitQueue {
201     my $self = shift;
202     my %args = (VALUE => undef,
203                 OPERATOR => '=',
204                 @_);
205
206     #TODO  VALUE should also take queue names and queue objects
207     my $queue = new RT::Queue($self->CurrentUser);
208     $queue->Load($args{'VALUE'});
209     
210     #TODO check for a valid queue here
211
212     $self->Limit (FIELD => 'Queue',
213                   VALUE => $queue->id(),
214                   OPERATOR => $args{'OPERATOR'},
215                   DESCRIPTION => 'Queue ' .  $args{'OPERATOR'}. " ". $queue->Name
216                  );
217     
218 }
219 # }}}
220
221 # {{{ sub LimitStatus
222
223 =head2 LimitStatus
224
225 Takes a paramhash with the fields OPERATOR and VALUE.
226 OPERATOR is one of = or !=.
227 VALUE is a status.
228
229 =cut
230
231 sub LimitStatus {
232     my $self = shift;
233     my %args = ( OPERATOR => '=',
234                   @_);
235     $self->Limit (FIELD => 'Status',
236                   VALUE => $args{'VALUE'},
237                   OPERATOR => $args{'OPERATOR'},
238                   DESCRIPTION => 'Status ' .  $args{'OPERATOR'}. " ". $args{'VALUE'},
239                  );
240 }
241
242 # }}}
243
244 # {{{ sub LimitType
245
246 =head2 LimitType
247
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.
251
252 =cut
253
254 sub LimitType {
255     my $self = shift;
256     my %args = (OPERATOR => '=',
257                 VALUE => undef,
258                 @_);
259     $self->Limit (FIELD => 'Type',
260                   VALUE => $args{'VALUE'},
261                   OPERATOR => $args{'OPERATOR'},
262                   DESCRIPTION => 'Type ' .  $args{'OPERATOR'}. " ". $args{'Limit'},
263                  );
264 }
265
266 # }}}
267
268 # }}}
269
270 # {{{ Limit by string field
271
272 # {{{ sub LimitSubject
273
274 =head2 LimitSubject
275
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.
279
280 =cut
281
282 sub LimitSubject {
283     my $self = shift;
284     my %args = (@_);
285     $self->Limit (FIELD => 'Subject',
286                   VALUE => $args{'VALUE'},
287                   OPERATOR => $args{'OPERATOR'},
288                   DESCRIPTION => 'Subject ' .  $args{'OPERATOR'}. " ". $args{'VALUE'},
289                  );
290 }
291
292 # }}}
293
294 # }}}
295
296 # {{{ Limit based on ticket numerical attributes
297 # Things that can be > < = !=
298
299 # {{{ sub LimitId
300
301 =head2 LimitId
302
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
306
307 =cut
308
309 sub LimitId {
310     my $self = shift;
311     my %args = (OPERATOR => '=',
312                 @_);
313     
314     $self->Limit (FIELD => 'id',
315                   VALUE => $args{'VALUE'},
316                   OPERATOR => $args{'OPERATOR'},
317                   DESCRIPTION => 'Id ' .  $args{'OPERATOR'}. " ". $args{'VALUE'},
318                  );
319 }
320
321 # }}}
322
323 # {{{ sub LimitPriority
324
325 =head2 LimitPriority
326
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
330
331 =cut
332
333 sub LimitPriority {
334     my $self = shift;
335     my %args = (@_);
336     $self->Limit (FIELD => 'Priority',
337                   VALUE => $args{'VALUE'},
338                   OPERATOR => $args{'OPERATOR'},
339                   DESCRIPTION => 'Priority ' .  $args{'OPERATOR'}. " ". $args{'VALUE'},
340                  );
341 }
342
343 # }}}
344
345 # {{{ sub LimitInitialPriority
346
347 =head2 LimitInitialPriority
348
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
352
353
354 =cut
355
356 sub LimitInitialPriority {
357     my $self = shift;
358     my %args = (@_);
359     $self->Limit (FIELD => 'InitialPriority',
360                   VALUE => $args{'VALUE'},
361                   OPERATOR => $args{'OPERATOR'},
362                   DESCRIPTION => 'Initial Priority ' .  $args{'OPERATOR'}. " ". $args{'VALUE'},
363                  );
364 }
365
366 # }}}
367
368 # {{{ sub LimitFinalPriority
369
370 =head2 LimitFinalPriority
371
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
375
376 =cut
377
378 sub LimitFinalPriority {
379     my $self = shift;
380     my %args = (@_);
381     $self->Limit (FIELD => 'FinalPriority',
382                   VALUE => $args{'VALUE'},
383                   OPERATOR => $args{'OPERATOR'},
384                   DESCRIPTION => 'Final Priority ' .  $args{'OPERATOR'}. " ". $args{'VALUE'},
385                  );
386 }
387
388 # }}}
389
390 # {{{ sub LimitTimeWorked
391
392 =head2 LimitTimeWorked
393
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
397
398 =cut
399
400 sub LimitTimeWorked {
401     my $self = shift;
402     my %args = (@_);
403     $self->Limit (FIELD => 'TimeWorked',
404                   VALUE => $args{'VALUE'},
405                   OPERATOR => $args{'OPERATOR'},
406                   DESCRIPTION => 'Time worked ' .  $args{'OPERATOR'}. " ". $args{'VALUE'},
407                  );
408 }
409
410 # }}}
411
412 # {{{ sub LimitTimeLeft
413
414 =head2 LimitTimeLeft
415
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
419
420 =cut
421
422 sub LimitTimeLeft {
423     my $self = shift;
424     my %args = (@_);
425     $self->Limit (FIELD => 'TimeLeft',
426                   VALUE => $args{'VALUE'},
427                   OPERATOR => $args{'OPERATOR'},
428                   DESCRIPTION => 'Time left ' .  $args{'OPERATOR'}. " ". $args{'VALUE'},
429                  );
430 }
431
432 # }}}
433
434 # }}}
435
436 # {{{ Limiting based on attachment attributes
437
438 # {{{ sub LimitContent
439
440 =head2 LimitContent
441
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
445
446 =cut
447 sub LimitContent {
448     my $self = shift;
449     my %args = (@_);
450     $self->Limit (FIELD => 'Content',
451                   VALUE => $args{'VALUE'},
452                   OPERATOR => $args{'OPERATOR'},
453                   DESCRIPTION => 'Ticket content ' .  $args{'OPERATOR'}. " ". $args{'VALUE'},
454                  );
455 }
456
457 # }}}
458 # {{{ sub LimitContentType
459
460 =head2 LimitContentType
461
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
465
466 =cut
467   
468 sub LimitContentType {
469     my $self = shift;
470     my %args = (@_);
471     $self->Limit (FIELD => 'ContentType',
472                   VALUE => $args{'VALUE'},
473                   OPERATOR => $args{'OPERATOR'},
474                   DESCRIPTION => 'Ticket content type ' .  $args{'OPERATOR'}. " ". $args{'VALUE'},
475                  );
476 }
477 # }}}
478
479 # }}}
480
481 # {{{ Limiting based on people
482
483 # {{{ sub LimitOwner
484
485 =head2 LimitOwner
486
487 Takes a paramhash with the fields OPERATOR and VALUE.
488 OPERATOR is one of = or !=.
489 VALUE is a user id.
490
491 =cut
492
493 sub LimitOwner {
494     my $self = shift;
495     my %args = ( OPERATOR => '=',
496                  @_);
497     
498     my $owner = new RT::User($self->CurrentUser);
499     $owner->Load($args{'VALUE'});
500     $self->Limit (FIELD => 'Owner',
501                   VALUE => $owner->Id,
502                   OPERATOR => $args{'OPERATOR'},
503                   DESCRIPTION => 'Owner ' .  $args{'OPERATOR'}. " ". $owner->Name()
504                  );
505     
506 }
507
508 # }}}
509
510 # {{{ Limiting watchers
511
512 # {{{ sub LimitWatcher
513
514
515 =head2 LimitWatcher
516   
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
521
522 =cut
523    
524 sub LimitWatcher {
525     my $self = shift;
526     my %args = ( OPERATOR => '=',
527                  VALUE => undef,
528                  TYPE => undef,
529                 @_);
530
531
532     #build us up a description
533     my ($watcher_type, $desc);
534     if ($args{'TYPE'}) {
535         $watcher_type = $args{'TYPE'};
536     }
537     else {
538         $watcher_type = "Watcher";
539     }
540     $desc = "$watcher_type ".$args{'OPERATOR'}." ".$args{'VALUE'};
541
542
543     $self->Limit (FIELD => 'Watcher',
544                   VALUE => $args{'VALUE'},
545                   OPERATOR => $args{'OPERATOR'},
546                   TYPE => $args{'TYPE'},
547                   DESCRIPTION => "$desc"
548                  );
549 }
550
551 # }}}
552
553 # {{{ sub LimitRequestor
554
555 =head2 LimitRequestor
556
557 It\'s like LimitWatcher, but it presets TYPE to Requestor
558
559 =cut
560
561
562 sub LimitRequestor {
563     my $self = shift;
564     $self->LimitWatcher(TYPE=> 'Requestor', @_);
565 }
566
567 # }}}
568
569 # {{{ sub LimitCc
570
571 =head2 LimitCC
572
573 It\'s like LimitWatcher, but it presets TYPE to Cc
574
575 =cut
576
577 sub LimitCc {
578     my $self = shift;
579     $self->LimitWatcher(TYPE=> 'Cc', @_);
580 }
581
582 # }}}
583
584 # {{{ sub LimitAdminCc
585
586 =head2 LimitAdminCc
587
588 It\'s like LimitWatcher, but it presets TYPE to AdminCc
589
590 =cut
591   
592 sub LimitAdminCc {
593     my $self = shift;
594     $self->LimitWatcher(TYPE=> 'AdminCc', @_);
595 }
596
597 # }}}
598
599 # }}}
600
601 # }}}
602
603 # {{{ Limiting based on links
604
605 # {{{ LimitLinkedTo
606
607 =head2 LimitLinkedTo
608
609 LimitLinkedTo takes a paramhash with two fields: TYPE and TARGET
610 TYPE limits the sort of relationship we want to search on
611
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
614
615 =cut
616
617 sub LimitLinkedTo {
618     my $self = shift;
619     my %args = ( 
620                 TICKET => undef,
621                 TARGET => undef,
622                 TYPE => undef,
623                  @_);
624
625
626     $self->Limit( FIELD => 'LinkedTo',
627                   BASE => undef,
628                   TARGET => ($args{'TARGET'} || $args{'TICKET'}),
629                   TYPE => $args{'TYPE'},
630                   DESCRIPTION => "Tickets ".$args{'TYPE'}." by ".($args{'TARGET'} || $args{'TICKET'})
631                 );
632 }
633
634
635 # }}}
636
637 # {{{ LimitLinkedFrom
638
639 =head2 LimitLinkedFrom
640
641 LimitLinkedFrom takes a paramhash with two fields: TYPE and BASE
642 TYPE limits the sort of relationship we want to search on
643
644
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
647
648
649 =cut
650
651 sub LimitLinkedFrom {
652     my $self = shift;
653     my %args = ( BASE => undef,
654                  TICKET => undef,
655                  TYPE => undef,
656                  @_);
657
658     
659     $self->Limit( FIELD => 'LinkedTo',
660                   TARGET => undef,
661                   BASE => ($args{'BASE'} || $args{'TICKET'}),
662                   TYPE => $args{'TYPE'},
663                   DESCRIPTION => "Tickets " .($args{'BASE'} || $args{'TICKET'}) ." ".$args{'TYPE'}
664                 );
665 }
666
667
668 # }}}
669
670 # {{{ LimitMemberOf 
671 sub LimitMemberOf {
672     my $self = shift;
673     my $ticket_id = shift;
674     $self->LimitLinkedTo ( TARGET=> "$ticket_id",
675                            TYPE => 'MemberOf',
676                           );
677     
678 }
679 # }}}
680
681 # {{{ LimitHasMember
682 sub LimitHasMember {
683     my $self = shift;
684     my $ticket_id =shift;
685     $self->LimitLinkedFrom ( BASE => "$ticket_id",
686                              TYPE => 'MemberOf',
687                              );
688     
689 }
690 # }}}
691
692 # {{{ LimitDependsOn
693
694 sub LimitDependsOn {
695     my $self = shift;
696     my $ticket_id = shift;
697     $self->LimitLinkedTo ( TARGET => "$ticket_id",
698                            TYPE => 'DependsOn',
699                            );
700     
701 }
702
703 # }}}
704
705 # {{{ LimitDependedOnBy
706
707 sub LimitDependedOnBy {
708     my $self = shift;
709     my $ticket_id = shift;
710     $self->LimitLinkedFrom (  BASE => "$ticket_id",
711                                TYPE => 'DependsOn',
712                              );
713     
714 }
715
716 # }}}
717
718
719 # {{{ LimitRefersTo
720
721 sub LimitRefersTo {
722     my $self = shift;
723     my $ticket_id = shift;
724     $self->LimitLinkedTo ( TARGET => "$ticket_id",
725                            TYPE => 'RefersTo',
726                            );
727     
728 }
729
730 # }}}
731
732 # {{{ LimitReferredToBy
733
734 sub LimitReferredToBy {
735     my $self = shift;
736     my $ticket_id = shift;
737     $self->LimitLinkedFrom (  BASE=> "$ticket_id",
738                                TYPE => 'RefersTo',
739                              );
740     
741 }
742
743 # }}}
744
745 # }}}
746
747 # {{{ limit based on ticket date attribtes
748
749 # {{{ sub LimitDate
750
751 =head2 LimitDate (FIELD => 'DateField', OPERATOR => $oper, VALUE => $ISODate)
752
753 Takes a paramhash with the fields FIELD OPERATOR and VALUE.
754
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
758
759 There are also helper functions of the form LimitFIELD that eliminate
760 the need to pass in a FIELD argument.
761
762 =cut
763
764 sub LimitDate {
765     my $self = shift;
766     my %args = (
767                   FIELD => undef,
768                   VALUE => $args{'VALUE'},
769                   OPERATOR => $args{'OPERATOR'},
770
771                   @_);
772
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"
776     }
777
778     $self->Limit (%args);
779
780 }
781
782 # }}}
783
784
785
786
787 sub LimitCreated {
788     my $self = shift;
789     $self->LimitDate( FIELD => 'Created', @_);
790 }
791 sub LimitDue {
792     my $self = shift;
793     $self->LimitDate( FIELD => 'Due', @_);
794
795 }
796 sub LimitStarts {
797     my $self = shift;
798     $self->LimitDate( FIELD => 'Starts', @_);
799
800 }
801 sub LimitStarted {
802     my $self = shift;
803     $self->LimitDate( FIELD => 'Started', @_);
804 }
805 sub LimitResolved { 
806     my $self = shift;
807     $self->LimitDate( FIELD => 'Resolved', @_);
808 }
809 sub LimitTold {
810     my $self = shift;
811     $self->LimitDate( FIELD => 'Told', @_);
812 }
813 sub LimitLastUpdated {
814     my $self = shift;
815     $self->LimitDate( FIELD => 'LastUpdated', @_);
816 }
817 #
818 # {{{ sub LimitTransactionDate
819
820 =head2 LimitTransactionDate (OPERATOR => $oper, VALUE => $ISODate)
821
822 Takes a paramhash with the fields FIELD OPERATOR and VALUE.
823
824 OPERATOR is one of > or < 
825 VALUE is a date and time in ISO format in GMT
826
827
828 =cut
829
830 sub LimitTransactionDate {
831     my $self = shift;
832     my %args = (
833                   FIELD => 'TransactionDate',
834                   VALUE => $args{'VALUE'},
835                   OPERATOR => $args{'OPERATOR'},
836
837                   @_);
838
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"
842     }
843
844     $self->Limit (%args);
845
846 }
847
848 # }}}
849
850 # }}}
851
852 # {{{ sub LimitKeyword
853
854 =head2 LimitKeyword 
855
856 Takes a paramhash of key/value pairs with the following keys:
857
858 =over 4
859
860 =item KEYWORDSELECT - KeywordSelect id
861
862 =item OPERATOR - (for KEYWORD only - KEYWORDSELECT operator is always `=')
863
864 =item KEYWORD - Keyword id
865
866 =back
867
868 =cut
869
870 sub LimitKeyword {
871     my $self = shift;
872     my %args = ( KEYWORD => undef,
873                  KEYWORDSELECT => undef,
874                  OPERATOR => '=',
875                  DESCRIPTION => undef,
876                  FIELD => 'Keyword',
877                  QUOTEVALUE => 1,
878                  @_
879                );
880
881     use RT::KeywordSelect;
882     my $KeywordSelect = RT::KeywordSelect->new($self->CurrentUser);
883     $KeywordSelect->Load($args{KEYWORDSELECT});
884     
885
886     # Below, We're checking to see whether the keyword we're searching for
887     # is null or not.
888     # This could probably be rewritten to be easier to read and  understand
889
890     
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";
895         }
896         elsif ($args{'OPERATOR'} =~ /^is not$/i) {
897             $args{'DESCRIPTION'} ||= "Keyword Selection ". $KeywordSelect->Name . " has a value";
898         }
899     }
900         # if we're not looking to compare with a null value
901     else {      
902         use RT::Keyword;
903         my $Keyword = RT::Keyword->new($self->CurrentUser);
904         $Keyword->Load($args{KEYWORD});
905         $args{'DESCRIPTION'} ||= "Keyword Selection " . $KeywordSelect->Name.  " $args{OPERATOR} ". $Keyword->Name;
906     }
907     
908     $args{SingleValued} = $KeywordSelect->Single();
909  
910     
911     my $index = $self->_NextIndex;
912     %{$self->{'TicketRestrictions'}{$index}} = %args;
913     
914     $self->{'RecalcTicketLimits'} = 1;
915     return ($index);
916 }
917
918 # }}}
919
920 # {{{ sub _NextIndex
921
922 =head2 _NextIndex
923
924 Keep track of the counter for the array of restrictions
925
926 =cut
927
928 sub _NextIndex {
929     my $self = shift;
930     return ($self->{'restriction_index'}++);
931 }
932 # }}}
933
934 # }}} 
935
936 # {{{ Core bits to make this a DBIx::SearchBuilder object
937
938 # {{{ sub _Init 
939 sub _Init  {
940     my $self = shift;
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(@_);
947
948 }
949 # }}}
950
951 # {{{ sub NewItem 
952 sub NewItem  {
953   my $self = shift;
954   return(RT::Ticket->new($self->CurrentUser));
955
956 }
957 # }}}
958
959 # {{{ sub Count
960 sub Count {
961   my $self = shift;
962   $self->_ProcessRestrictions if ($self->{'RecalcTicketLimits'} == 1 );
963   return($self->SUPER::Count());
964 }
965 # }}}
966
967 # {{{ sub ItemsArrayRef
968
969 =head2 ItemsArrayRef
970
971 Returns a reference to the set of all items found in this search
972
973 =cut
974
975 sub ItemsArrayRef {
976     my $self = shift;
977     my @items;
978     
979     my $placeholder = $self->_ItemsCounter;
980     $self->GotoFirstItem();
981     while (my $item = $self->Next) { 
982         push (@items, $item);
983     }
984     
985     $self->GotoItem($placeholder);
986     return(\@items);
987 }
988 # }}}
989
990 # {{{ sub Next 
991 sub Next {
992         my $self = shift;
993         
994         $self->_ProcessRestrictions if ($self->{'RecalcTicketLimits'} == 1 );
995
996         my $Ticket = $self->SUPER::Next();
997         if ((defined($Ticket)) and (ref($Ticket))) {
998
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 :/
1002
1003             if ($Ticket->Status eq 'dead') {
1004                 return($self->Next());
1005             }
1006             elsif ($Ticket->CurrentUserHasRight('ShowTicket')) {
1007                 return($Ticket);
1008             }
1009
1010             #If the user doesn't have the right to show this ticket
1011             else {      
1012                 return($self->Next());
1013             }
1014         }
1015         #if there never was any ticket
1016         else {
1017                 return(undef);
1018         }       
1019
1020 }
1021 # }}}
1022
1023 # }}}
1024
1025 # {{{ Deal with storing and restoring restrictions
1026
1027 # {{{ sub LoadRestrictions
1028
1029 =head2 LoadRestrictions
1030
1031 LoadRestrictions takes a string which can fully populate the TicketRestrictons hash.
1032 TODO It is not yet implemented
1033
1034 =cut
1035
1036 # }}}
1037
1038 # {{{ sub DescribeRestrictions
1039
1040 =head2 DescribeRestrictions
1041
1042 takes nothing.
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
1046
1047 =cut
1048
1049 sub DescribeRestrictions  {
1050     my $self = shift;
1051     
1052     my ($row, %listing);
1053     
1054     foreach $row (keys %{$self->{'TicketRestrictions'}}) {
1055         $listing{$row} = $self->{'TicketRestrictions'}{$row}{'DESCRIPTION'};
1056     }
1057     return (%listing);
1058 }
1059 # }}}
1060
1061 # {{{ sub RestrictionValues
1062
1063 =head2 RestrictionValues FIELD
1064
1065 Takes a restriction field and returns a list of values this field is restricted
1066 to.
1067
1068 =cut
1069
1070 sub RestrictionValues {
1071     my $self = shift;
1072     my $field = shift;
1073     map $self->{'TicketRestrictions'}{$_}{'VALUE'},
1074       grep {
1075              $self->{'TicketRestrictions'}{$_}{'FIELD'} eq $field
1076              && $self->{'TicketRestrictions'}{$_}{'OPERATOR'} eq "="
1077            }
1078         keys %{$self->{'TicketRestrictions'}};
1079 }
1080
1081 # }}}
1082
1083 # {{{ sub ClearRestrictions
1084
1085 =head2 ClearRestrictions
1086
1087 Removes all restrictions irretrievably
1088
1089 =cut
1090   
1091 sub ClearRestrictions {
1092     my $self = shift;
1093     delete $self->{'TicketRestrictions'};
1094     $self->{'looking_at_effective_id'} = 0;
1095     $self->{'RecalcTicketLimits'} =1;
1096 }
1097
1098 # }}}
1099
1100 # {{{ sub DeleteRestriction
1101
1102 =head2 DeleteRestriction
1103
1104 Takes the row Id of a restriction (From DescribeRestrictions' output, for example.
1105 Removes that restriction from the session's limits.
1106
1107 =cut
1108
1109
1110 sub DeleteRestriction {
1111     my $self = shift;
1112     my $row = shift;
1113     delete $self->{'TicketRestrictions'}{$row};
1114     
1115     $self->{'RecalcTicketLimits'} = 1;
1116     #make the underlying easysearch object forget all its preconceptions
1117 }
1118
1119 # }}}
1120
1121 # {{{ sub _ProcessRestrictions 
1122
1123 sub _ProcessRestrictions {
1124     my $self = shift;
1125
1126     #Need to clean the EasySearch slate because it makes things too sticky
1127     $self->CleanSlate();
1128
1129     #Blow away ticket aliases since we'll need to regenerate them for a new search
1130     delete $self->{'TicketAliases'};
1131     delete $self->{KeywordsAliases};
1132
1133     my $row;
1134     
1135     foreach $row (keys %{$self->{'TicketRestrictions'}}) {
1136         my $restriction = $self->{'TicketRestrictions'}{$row};
1137         # {{{ if it's an int
1138         
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'},
1145                               );
1146             }
1147         }
1148         # }}}
1149         # {{{ if it's an enum
1150         elsif ($TYPES{$restriction->{'FIELD'}} eq 'ENUM') {
1151             
1152             if ($restriction->{'OPERATOR'} eq '=') {
1153                 $self->SUPER::Limit( FIELD => $restriction->{'FIELD'},
1154                               ENTRYAGGREGATOR => 'OR',
1155                               OPERATOR => '=',
1156                               VALUE => $restriction->{'VALUE'},
1157                             );
1158             }
1159             elsif ($restriction->{'OPERATOR'} eq '!=') {
1160                 $self->SUPER::Limit( FIELD => $restriction->{'FIELD'},
1161                               ENTRYAGGREGATOR => 'AND',
1162                               OPERATOR => '!=',
1163                               VALUE => $restriction->{'VALUE'},
1164                             );
1165             }
1166             
1167         }
1168         # }}}
1169         # {{{ if it's a date
1170
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'},
1176                                );
1177         }
1178         # }}}
1179         # {{{ if it's a string
1180
1181         elsif ($TYPES{$restriction->{'FIELD'}} eq 'STRING') {
1182             
1183             if ($restriction->{'OPERATOR'} eq '=') {
1184                 $self->SUPER::Limit( FIELD => $restriction->{'FIELD'},
1185                               ENTRYAGGREGATOR => 'OR',
1186                               OPERATOR => '=',
1187                               VALUE => $restriction->{'VALUE'},
1188                               CASESENSITIVE => 0
1189                             );
1190             }
1191             elsif ($restriction->{'OPERATOR'} eq '!=') {
1192                 $self->SUPER::Limit( FIELD => $restriction->{'FIELD'},
1193                               ENTRYAGGREGATOR => 'AND',
1194                               OPERATOR => '!=',
1195                               VALUE => $restriction->{'VALUE'},
1196                               CASESENSITIVE => 0
1197                             );
1198             }
1199             elsif ($restriction->{'OPERATOR'} eq 'LIKE') {
1200                 $self->SUPER::Limit( FIELD => $restriction->{'FIELD'},
1201                               ENTRYAGGREGATOR => 'AND',
1202                               OPERATOR => 'LIKE',
1203                               VALUE => $restriction->{'VALUE'},
1204                               CASESENSITIVE => 0
1205                             );
1206             }
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'},
1212                               CASESENSITIVE => 0
1213                             );
1214             }
1215         }
1216
1217         # }}}
1218         # {{{ if it's Transaction content that we're hunting for
1219         elsif ($TYPES{$restriction->{'FIELD'}} eq 'TRANSFIELD') {
1220
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.
1225             
1226             unless (defined $self->{'TicketAliases'}{'TransFieldAlias'}) {
1227                 $self->{'TicketAliases'}{'TransFieldAlias'} = $self->NewAlias ('Transactions');
1228             }
1229             unless (defined $self->{'TicketAliases'}{'TransFieldAttachAlias'}){
1230                 $self->{'TicketAliases'}{'TransFieldAttachAlias'} = $self->NewAlias('Attachments');
1231                 
1232             }
1233             #Join transactions to attachments
1234             $self->Join( ALIAS1 => $self->{'TicketAliases'}{'TransFieldAttachAlias'},  
1235                          FIELD1 => 'TransactionId',
1236                          ALIAS2 => $self->{'TicketAliases'}{'TransFieldAlias'}, FIELD2=> 'id');
1237             
1238             #Join transactions to tickets
1239             $self->Join( ALIAS1 => 'main', FIELD1 => $self->{'primary_key'},
1240                          ALIAS2 =>$self->{'TicketAliases'}{'TransFieldAlias'}, FIELD2 => 'Ticket');
1241             
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'},
1248                                   CASESENSITIVE => 0
1249                                 );
1250             
1251
1252         }
1253
1254         # }}}
1255         # {{{ if it's a Transaction date that we're hunting for
1256         elsif ($TYPES{$restriction->{'FIELD'}} eq 'TRANSDATE') {
1257
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.
1262             
1263             unless (defined $self->{'TicketAliases'}{'TransFieldAlias'}) {
1264                 $self->{'TicketAliases'}{'TransFieldAlias'} = $self->NewAlias ('Transactions');
1265             }
1266
1267             #Join transactions to tickets
1268             $self->Join( ALIAS1 => 'main', FIELD1 => $self->{'primary_key'},
1269                          ALIAS2 =>$self->{'TicketAliases'}{'TransFieldAlias'}, FIELD2 => 'Ticket');
1270             
1271             #Search for the right field
1272             $self->SUPER::Limit(ALIAS => $self->{'TicketAliases'}{'TransFieldAlias'},
1273                                 ENTRYAGGREGATOR => 'AND',
1274                                 FIELD =>    'Created',
1275                                 OPERATOR => $restriction->{'OPERATOR'} ,
1276                                 VALUE =>    $restriction->{'VALUE'} );
1277         }
1278
1279         # }}}
1280         # {{{ if it's a relationship that we're hunting for
1281         
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.
1285
1286         elsif ($TYPES{$restriction->{'FIELD'}} eq 'LINKFIELD') {
1287
1288             
1289             my $LinkAlias = $self->NewAlias ('Links');
1290
1291             
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',
1296                                     FIELD =>   'Type',
1297                                     OPERATOR => '=',
1298                                     VALUE =>    $restriction->{'TYPE'} );
1299             }
1300             
1301             #If we're trying to limit it to things that are target of
1302             if ($restriction->{'TARGET'}) {
1303                 
1304
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
1307
1308                 my ($matchfield);
1309                 if ($restriction->{'TARGET'} =~/^(\d+)$/) {
1310                     $matchfield = "LocalTarget";
1311                 }       
1312                 else {
1313                     $matchfield = "Target";
1314                 }       
1315
1316                 $self->SUPER::Limit(ALIAS => $LinkAlias,
1317                                     ENTRYAGGREGATOR => 'AND',
1318                                     FIELD =>   $matchfield,
1319                                     OPERATOR => '=',
1320                                     VALUE =>    $restriction->{'TARGET'} );
1321
1322                 
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');
1327
1328             
1329
1330
1331             }
1332             #If we're trying to limit it to things that are base of
1333             elsif ($restriction->{'BASE'}) {
1334
1335
1336                 # If we're trying to match a numeric link, we want to look at LocalBase,
1337                 # otherwise we want to look at "Base"
1338
1339                 my ($matchfield);
1340                 if ($restriction->{'BASE'} =~/^(\d+)$/) {
1341                     $matchfield = "LocalBase";
1342                 }       
1343                 else {
1344                     $matchfield = "Base";
1345                 }       
1346
1347
1348                 $self->SUPER::Limit(ALIAS => $LinkAlias,
1349                                     ENTRYAGGREGATOR => 'AND',
1350                                     FIELD => $matchfield,
1351                                     OPERATOR => '=',
1352                                     VALUE =>    $restriction->{'BASE'} );
1353                 
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');
1358                 
1359             }
1360
1361         }
1362                 
1363         # }}}
1364         # {{{ if it's a watcher that we're hunting for
1365         elsif ($TYPES{$restriction->{'FIELD'}} eq 'WATCHERFIELD') {
1366
1367             my $Watch = $self->NewAlias('Watchers');
1368
1369             #Join watchers to users
1370             my $User = $self->Join( TYPE => 'left',
1371                                      ALIAS1 => $Watch, 
1372                                      FIELD1 => 'Owner',
1373                                      TABLE2 => 'Users', 
1374                                      FIELD2 => 'id',
1375                                    );
1376
1377             #Join Ticket to watchers
1378             $self->Join( ALIAS1 => 'main', FIELD1 => 'id',
1379                          ALIAS2 => $Watch, FIELD2 => 'Value');
1380
1381
1382             #Make sure we're only talking about ticket watchers
1383             $self->SUPER::Limit( ALIAS => $Watch,
1384                                  FIELD => 'Scope',
1385                                  VALUE => 'Ticket',
1386                                  OPERATOR => '=');
1387
1388
1389             # Find email address watchers
1390             $self->SUPER::Limit( SUBCLAUSE => 'WatcherEmailAddress',
1391                                  ALIAS => $Watch,
1392                                  FIELD => 'Email',
1393                                  ENTRYAGGREGATOR => 'OR',
1394                                  VALUE => $restriction->{'VALUE'},
1395                                  OPERATOR => $restriction->{'OPERATOR'},
1396                                  CASESENSITIVE => 0
1397                         );
1398
1399
1400
1401             #Find user watchers
1402             $self->SUPER::Limit(
1403                                 SUBCLAUSE => 'WatcherEmailAddress',
1404                                 ALIAS => $User,
1405                                 FIELD => 'EmailAddress',
1406                                 ENTRYAGGREGATOR => 'OR',
1407                                 VALUE => $restriction->{'VALUE'},
1408                                 OPERATOR => $restriction->{'OPERATOR'},
1409                                 CASESENSITIVE => 0
1410                                );
1411
1412             
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,
1416                                      FIELD => 'Type',
1417                                      ENTRYAGGREGATOR => 'OR',
1418                                      VALUE => $restriction->{'TYPE'},
1419                                      OPERATOR => '=');
1420             }
1421         }
1422
1423         # }}}
1424         # {{{ if it's a keyword
1425         elsif ($TYPES{$restriction->{'FIELD'}} eq 'KEYWORDFIELD') {
1426  
1427             my $null_columns_ok;
1428
1429             my $ObjKeywordsAlias;
1430             $ObjKeywordsAlias = $self->{KeywordsAliases}{$restriction->{'KEYWORDSELECT'}}
1431               if $restriction->{SingleValued};
1432             unless (defined $ObjKeywordsAlias) {
1433               $ObjKeywordsAlias = $self->Join(
1434                                                TYPE => 'left',
1435                                                ALIAS1 => 'main',
1436                                                FIELD1 => 'id',
1437                                                TABLE2 => 'ObjectKeywords',
1438                                                FIELD2 => 'ObjectId'
1439                                               );
1440               if ($restriction->{'SingleValued'}) {
1441                 $self->{KeywordsAliases}{$restriction->{'KEYWORDSELECT'}} 
1442                   = $ObjKeywordsAlias;
1443               }
1444             }
1445
1446           
1447             $self->SUPER::Limit(
1448                                 ALIAS => $ObjKeywordsAlias,
1449                                 FIELD => 'Keyword',
1450                                 OPERATOR => $restriction->{'OPERATOR'},
1451                                 VALUE => $restriction->{'KEYWORD'},
1452                                 QUOTEVALUE => $restriction->{'QUOTEVALUE'},
1453                                 ENTRYAGGREGATOR => 'OR',
1454                                );
1455             
1456             if  ( ($restriction->{'OPERATOR'} =~ /^IS$/i) or 
1457                   ($restriction->{'OPERATOR'} eq '!=') ) {
1458                 
1459                 $null_columns_ok=1;
1460
1461             } 
1462
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,
1467                                     FIELD => 'Keyword',
1468                                     OPERATOR => 'IS',
1469                                     VALUE => 'NULL',
1470                                     QUOTEVALUE => 0,
1471                                     ENTRYAGGREGATOR => 'OR',
1472                                    );
1473               }
1474
1475
1476             $self->SUPER::Limit(LEFTJOIN => $ObjKeywordsAlias,
1477                                 FIELD => 'KeywordSelect',
1478                                 VALUE => $restriction->{'KEYWORDSELECT'},
1479                                 ENTRYAGGREGATOR => 'OR');
1480
1481
1482  
1483             $self->SUPER::Limit( ALIAS => $ObjKeywordsAlias,
1484                                  FIELD => 'ObjectType',
1485                                  VALUE => 'Ticket',
1486                                  ENTRYAGGREGATOR => 'AND');
1487             
1488             if ($null_columns_ok) {
1489                  $self->SUPER::Limit(ALIAS => $ObjKeywordsAlias,
1490                                     FIELD => 'ObjectType',
1491                                     OPERATOR => 'IS',
1492                                     VALUE => 'NULL',
1493                                     QUOTEVALUE => 0,
1494                                     ENTRYAGGREGATOR => 'OR');
1495             }
1496            
1497         }
1498         # }}}
1499
1500     
1501      }
1502
1503      
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', 
1509               OPERATOR => '=',
1510               QUOTEVALUE => 0,
1511               VALUE => 'main.id');   #TODO, we shouldn't be hard coding the tablename to main.
1512       } 
1513     $self->{'RecalcTicketLimits'} = 0;
1514 }
1515
1516 # }}}
1517
1518 # }}}
1519
1520 # {{{ Deal with displaying rows of the listing 
1521
1522 #
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.
1526 #
1527
1528
1529 # {{{ sub SetListingFormat
1530
1531 =head2 SetListingFormat
1532
1533 Takes a single Format string as specified below. parses that format string and makes the various listing output
1534 things DTRT.
1535
1536 =item Format strings
1537
1538 Format strings are made up of a chain of Elements delimited with vertical pipes (|).
1539 Elements of a Format string 
1540
1541
1542 FormatString:    Element[::FormatString]
1543
1544 Element:         AttributeName[;HREF=<URL>][;TITLE=<TITLE>]
1545
1546 AttributeName    Id | Subject | Status | Owner | Priority | InitialPriority | TimeWorked | TimeLeft |
1547   
1548                  Keywords[;SELECT=<KeywordSelect>] | 
1549         
1550                 <Created|Starts|Started|Contacted|Due|Resolved>Date<AsString|AsISO|AsAge>
1551
1552
1553 =cut
1554
1555
1556
1557
1558 #accept a format string
1559
1560
1561
1562 sub SetListingFormat {
1563     my $self = shift;
1564     my $listing_format = shift;
1565     
1566     my ($element, $attribs);
1567     my $i = 0;
1568     foreach $element (split (/::/,$listing_format)) {
1569         if ($element =~ /^(.*?);(.*)$/) {
1570             $element = $1;
1571             $attribs = $2;
1572         }       
1573         $self->{'format_string'}->[$i]->{'Element'} = $element;
1574         foreach $attrib (split (/;/, $attribs)) {
1575             my $value = "";
1576             if ($attrib =~ /^(.*?)=(.*)$/) {
1577                 $attrib = $1;
1578                 $value = $2;
1579             }   
1580             $self->{'format_string'}->[$i]->{"$attrib"} = $val;
1581             
1582         }
1583     
1584     }
1585     return(1);
1586 }
1587
1588 # }}}
1589
1590 # {{{ sub HeaderAsHTML
1591 sub HeaderAsHTML {
1592     my $self = shift;
1593     my $header = "";
1594     my $col;
1595     foreach $col ( @{[ $self->{'format_string'} ]}) {
1596         $header .= "<TH>" . $self->_ColumnTitle($self->{'format_string'}->[$col]) . "</TH>";
1597         
1598     }
1599     return ($header);
1600 }
1601 # }}}
1602
1603 # {{{ sub HeaderAsText
1604 #Print text header
1605 sub HeaderAsText {
1606     my $self = shift;
1607     my ($header);
1608     
1609     return ($header);
1610 }
1611 # }}}
1612
1613 # {{{ sub TicketAsHTMLRow
1614 #Print HTML row
1615 sub TicketAsHTMLRow {
1616     my $self = shift;
1617     my $Ticket = shift;
1618     my ($row, $col);
1619     foreach $col (@{[$self->{'format_string'}]}) {
1620         $row .= "<TD>" . $self->_TicketColumnValue($ticket,$self->{'format_string'}->[$col]) . "</TD>";
1621         
1622     }
1623     return ($row);
1624 }
1625 # }}}
1626
1627 # {{{ sub TicketAsTextRow
1628 #Print text row
1629 sub TicketAsTextRow {
1630     my $self = shift;
1631     my ($row);
1632
1633     #TODO implement
1634     
1635     return ($row);
1636 }
1637 # }}}
1638
1639 # {{{ _ColumnTitle {
1640
1641 sub _ColumnTitle {
1642     my $self = shift;
1643     
1644     # Attrib is a hash 
1645     my $attrib = shift;
1646     
1647     # return either attrib->{'TITLE'} or..
1648     if ($attrib->{'TITLE'}) {
1649         return($attrib->{'TITLE'});
1650     }   
1651     # failing that, Look up the title in a hash
1652     else {
1653         #TODO create $self->{'ColumnTitles'};
1654         return ($self->{'ColumnTitles'}->{$attrib->{'Element'}});
1655     }   
1656     
1657 }
1658
1659 # }}}
1660
1661 # {{{ _TicketColumnValue
1662 sub _TicketColumnValue {
1663     my $self = shift;
1664     my $Ticket = shift;
1665     my $attrib = shift;
1666
1667     
1668     my $out;
1669
1670   SWITCH: {
1671         /^id/i && do {
1672             $out = $Ticket->id;
1673             last SWITCH; 
1674         };
1675         /^subj/i && do {
1676             last SWITCH; 
1677             $Ticket->Subject;
1678                    };   
1679         /^status/i && do {
1680             last SWITCH; 
1681             $Ticket->Status;
1682         };
1683         /^prio/i && do {
1684             last SWITCH; 
1685             $Ticket->Priority;
1686         };
1687         /^finalprio/i && do {
1688             
1689             last SWITCH; 
1690             $Ticket->FinalPriority
1691         };
1692         /^initialprio/i && do {
1693             
1694             last SWITCH; 
1695             $Ticket->InitialPriority;
1696         };      
1697         /^timel/i && do {
1698             
1699             last SWITCH; 
1700             $Ticket->TimeWorked;
1701         };
1702         /^timew/i && do {
1703             
1704             last SWITCH; 
1705             $Ticket->TimeLeft;
1706         };
1707         
1708         /^(.*?)date(.*)$/i && do {
1709             my $o = $1;
1710             my $m = $2;
1711             my ($obj);
1712             #TODO: optimize
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;
1719             
1720             $method = 'ISO' if $m =~ /iso/i;
1721             
1722             $method = 'AsString' if $m =~ /asstring/i;
1723             $method = 'AgeAsString' if $m =~ /age/i;
1724             last SWITCH;
1725             $obj->$method();
1726               
1727         };
1728           
1729           /^watcher/i && do {
1730               last SWITCH; 
1731               $Ticket->WatchersAsString();
1732           };    
1733         
1734         /^requestor/i && do {
1735             last SWITCH; 
1736             $Ticket->RequestorsAsString();
1737         };      
1738         /^cc/i && do {
1739             last SWITCH; 
1740             $Ticket->CCAsString();
1741         };      
1742         
1743         
1744         /^admincc/i && do {
1745             last SWITCH; 
1746             $Ticket->AdminCcAsString();
1747         };
1748         
1749         /^keywords/i && do {
1750             last SWITCH; 
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();
1754         };
1755         
1756     }
1757       
1758 }
1759
1760 # }}}
1761
1762 # }}}
1763
1764 # {{{ POD
1765 =head2 notes
1766 "Enum" Things that get Is, IsNot
1767
1768
1769 "Int" Things that get Is LessThan and GreaterThan
1770 id
1771 InitialPriority
1772 FinalPriority
1773 Priority
1774 TimeLeft
1775 TimeWorked
1776
1777 "Text" Things that get Is, Like
1778 Subject
1779 TransactionContent
1780
1781
1782 "Link" OPERATORs
1783
1784
1785 "Date" OPERATORs Is, Before, After
1786
1787   =cut
1788 # }}}
1789 1;