This commit was generated by cvs2svn to compensate for changes in r2523,
[freeside.git] / rt / lib / RT / Queue.pm
1 # $Header: /home/cvs/cvsroot/freeside/rt/lib/RT/Queue.pm,v 1.1 2002-08-12 06:17:07 ivan Exp $
2
3 =head1 NAME
4
5   RT::Queue - an RT Queue object
6
7 =head1 SYNOPSIS
8
9   use RT::Queue;
10
11 =head1 DESCRIPTION
12
13
14 =head1 METHODS
15
16 =begin testing 
17 use RT::TestHarness;
18
19 use RT::Queue;
20
21 =end testing
22
23 =cut
24
25
26
27 package RT::Queue;
28 use RT::Record;
29
30 @ISA= qw(RT::Record);
31
32 use vars (@STATUS);
33
34 @STATUS = qw(new open stalled resolved dead); 
35
36 =head2 StatusArray
37
38 Returns an array of all statuses for this queue
39
40 =cut
41
42 sub StatusArray {
43     my $self = shift;
44     return (@STATUS);
45 }
46
47
48 =head2 IsValidStatus VALUE
49
50 Returns true if VALUE is a valid status.  Otherwise, returns 0
51
52 =for testing
53 my $q = new RT::Queue($RT::SystemUser);
54 ok($q->IsValidStatus('new')== 1, 'New is a valid status');
55 ok($q->IsValidStatus('f00')== 0, 'f00 is not a valid status');
56
57 =cut
58
59 sub IsValidStatus {
60         my $self = shift;
61         my $value = shift;
62
63         my $retval = grep (/^$value$/, $self->StatusArray);
64         return ($retval);       
65
66 }
67         
68
69
70
71 # {{{  sub _Init 
72 sub _Init  {
73     my $self = shift;
74     $self->{'table'} = "Queues";
75     return ($self->SUPER::_Init(@_));
76 }
77 # }}}
78
79 # {{{ sub _Accessible 
80
81 sub _Accessible  {
82     my $self = shift;
83     my %Cols = ( Name => 'read/write',
84                  CorrespondAddress => 'read/write',
85                  Description => 'read/write',
86                  CommentAddress =>  'read/write',
87                  InitialPriority =>  'read/write',
88                  FinalPriority =>  'read/write',
89                  DefaultDueIn =>  'read/write',
90                  Creator => 'read/auto',
91                  Created => 'read/auto',
92                  LastUpdatedBy => 'read/auto',
93                  LastUpdated => 'read/auto',
94                  Disabled => 'read/write',
95                  
96                );
97     return($self->SUPER::_Accessible(@_, %Cols));
98 }
99
100 # }}}
101
102 # {{{ sub Create
103
104 =head2 Create
105
106 Create takes the name of the new queue 
107 If you pass the ACL check, it creates the queue and returns its queue id.
108
109 =cut
110
111 sub Create  {
112     my $self = shift;
113     my %args = ( Name => undef,
114                  CorrespondAddress => '',
115                  Description => '',
116                  CommentAddress => '',
117                  InitialPriority => "0",
118                  FinalPriority =>  "0",
119                  DefaultDueIn =>  "0",
120                  @_); 
121     
122     unless ($self->CurrentUser->HasSystemRight('AdminQueue')) {    #Check them ACLs
123         return (0, "No permission to create queues") 
124     }
125
126     unless ($self->ValidateName($args{'Name'})) {
127         return(0, 'Queue already exists');
128     }
129     #TODO better input validation
130     
131     my $id = $self->SUPER::Create(%args);
132     unless ($id) {
133         return (0, 'Queue could not be created');
134     }
135
136     return ($id, "Queue $id created");
137 }
138
139 # }}}
140
141 # {{{ sub Delete 
142
143 sub Delete {
144     my $self = shift;
145     return (0, 'Deleting this object would break referential integrity');
146 }
147
148 # }}}
149
150 # {{{ sub SetDisabled
151
152 =head2 SetDisabled
153
154 Takes a boolean.
155 1 will cause this queue to no longer be avaialble for tickets.
156 0 will re-enable this queue
157
158 =cut
159
160 # }}}
161
162 # {{{ sub Load 
163
164 =head2 Load
165
166 Takes either a numerical id or a textual Name and loads the specified queue.
167   
168 =cut
169
170 sub Load  {
171     my $self = shift;
172     
173     my $identifier = shift;
174     if (!$identifier) {
175         return (undef);
176     }       
177     
178     if ($identifier !~ /\D/) {
179         $self->SUPER::LoadById($identifier);
180     }
181     else {
182         $self->LoadByCol("Name", $identifier);
183     }
184
185     return ($self->Id);
186
187
188 }
189 # }}}
190
191 # {{{ sub ValidateName
192
193 =head2 ValidateName NAME
194
195 Takes a queue name. Returns true if it's an ok name for
196 a new queue. Returns undef if there's already a queue by that name.
197
198 =cut
199
200 sub ValidateName {
201     my $self = shift;
202     my $name = shift;
203    
204     my $tempqueue = new RT::Queue($RT::SystemUser);
205     $tempqueue->Load($name);
206
207     #If we couldn't load it :)
208     unless ($tempqueue->id()) {
209         return(1);
210     }
211
212     #If this queue exists, return undef
213     #Avoid the ACL check.
214     if ($tempqueue->Name()){
215         return(undef);
216     }
217
218     #If the queue doesn't exist, return 1
219     else {
220         return(1);
221     }
222
223 }
224
225
226 # }}}
227
228 # {{{ sub Templates
229
230 =head2 Templates
231
232 Returns an RT::Templates object of all of this queue's templates.
233
234 =cut
235
236 sub Templates {
237     my $self = shift;
238     
239
240     my $templates = RT::Templates->new($self->CurrentUser);
241
242     if ($self->CurrentUserHasRight('ShowTemplate')) {
243         $templates->LimitToQueue($self->id);
244     }
245     
246     return ($templates); 
247 }
248
249 # }}}
250
251 # {{{ Dealing with watchers
252
253 # {{{ sub Watchers
254
255 =head2 Watchers
256
257 Watchers returns a Watchers object preloaded with this queue\'s watchers.
258
259 =cut
260
261 sub Watchers {
262     my $self = shift;
263     
264     require RT::Watchers;
265     my $watchers =RT::Watchers->new($self->CurrentUser);
266     
267     if ($self->CurrentUserHasRight('SeeQueue')) {
268         $watchers->LimitToQueue($self->id);     
269     }   
270     
271     return($watchers);
272 }
273
274 # }}}
275
276 # {{{ sub WatchersAsString
277 =head2 WatchersAsString
278
279 Returns a string of all queue watchers email addresses concatenated with ','s.
280
281 =cut
282
283 sub WatchersAsString {
284     my $self=shift;
285     return($self->Watchers->EmailsAsString());
286 }
287
288 # }}}
289
290 # {{{ sub AdminCcAsString 
291
292 =head2 AdminCcAsString
293
294 Takes nothing. returns a string: All Ticket/Queue AdminCcs.
295
296 =cut
297
298
299 sub AdminCcAsString {
300     my $self=shift;
301     
302     return($self->AdminCc->EmailsAsString());
303   }
304
305 # }}}
306
307 # {{{ sub CcAsString
308
309 =head2 CcAsString
310
311 B<Returns> String: All Queue Ccs as a comma delimited set of email addresses.
312
313 =cut
314
315 sub CcAsString {
316     my $self=shift;
317     
318     return ($self->Cc->EmailsAsString());
319 }
320
321 # }}}
322
323 # {{{ sub Cc
324
325 =head2 Cc
326
327 Takes nothing.
328 Returns a watchers object which contains this queue\'s Cc watchers
329
330 =cut
331
332 sub Cc {
333     my $self = shift;
334     my $cc = $self->Watchers();
335     if ($self->CurrentUserHasRight('SeeQueue')) {
336         $cc->LimitToCc();
337     }
338     return ($cc);
339 }
340
341 # A helper function for Cc, so that we can call it from the ACL checks 
342 # without going through acl checks.
343
344 sub _Cc {
345     my $self = shift;
346     my $cc = $self->Watchers();
347     $cc->LimitToCc();
348     return($cc);
349     
350 }
351
352 # }}}
353
354 # {{{ sub AdminCc
355
356 =head2 AdminCc
357
358 Takes nothing.
359 Returns this queue's administrative Ccs as an RT::Watchers object
360
361 =cut
362
363 sub AdminCc {
364     my $self = shift;
365     my $admin_cc = $self->Watchers();
366     if ($self->CurrentUserHasRight('SeeQueue')) {
367         $admin_cc->LimitToAdminCc();
368     }
369     return($admin_cc);
370 }
371
372 #helper function for AdminCc so we can call it without ACLs
373 sub _AdminCc {
374     my $self = shift;
375     my $admin_cc = $self->Watchers();
376     $admin_cc->LimitToAdminCc();
377     return($admin_cc);
378 }
379
380 # }}}
381
382 # {{{ IsWatcher, IsCc, IsAdminCc
383
384 # {{{ sub IsWatcher
385
386 # a generic routine to be called by IsRequestor, IsCc and IsAdminCc
387
388 =head2 IsWatcher
389
390 Takes a param hash with the attributes Type and User. User is either a user object or string containing an email address. Returns true if that user or string
391 is a queue watcher. Returns undef otherwise
392
393 =cut
394
395 sub IsWatcher {
396     my $self = shift;
397     
398     my %args = ( Type => 'Requestor',
399                  Id => undef,
400                  Email => undef,
401                  @_
402                );
403     #ACL check - can't do it. we need this method for ACL checks
404     #    unless ($self->CurrentUserHasRight('SeeQueue')) {
405     #   return(undef);
406     #    }
407
408
409     my %cols = ('Type' => $args{'Type'},
410                 'Scope' => 'Queue',
411                 'Value' => $self->Id
412                );
413     if (defined ($args{'Id'})) {
414         if (ref($args{'Id'})){ #If it's a ref, assume it's an RT::User object;
415             #Dangerous but ok for now
416             $cols{'Owner'} = $args{'Id'}->Id;
417         }
418         elsif ($args{'Id'} =~ /^\d+$/) { # if it's an integer, it's an RT::User obj
419             $cols{'Owner'} = $args{'Id'};
420         }
421         else {
422             $cols{'Email'} = $args{'Id'};
423         }       
424     }   
425     
426     if (defined $args{'Email'}) {
427         $cols{'Email'} = $args{'Email'};
428     }
429
430     my ($description);
431     $description = join(":",%cols);
432     
433     #If we've cached a positive match...
434     if (defined $self->{'watchers_cache'}->{"$description"}) {
435         if ($self->{'watchers_cache'}->{"$description"} == 1) {
436             return(1);
437         }
438         #If we've cached a negative match...
439         else {
440             return(undef);
441         }
442     }
443
444     require RT::Watcher;
445     my $watcher = new RT::Watcher($self->CurrentUser);
446     $watcher->LoadByCols(%cols);
447     
448     
449     if ($watcher->id) {
450         $self->{'watchers_cache'}->{"$description"} = 1;
451         return(1);
452     }   
453     else {
454         $self->{'watchers_cache'}->{"$description"} = 0;
455         return(undef);
456     }
457     
458 }
459
460 # }}}
461
462 # {{{ sub IsCc
463
464 =head2 IsCc
465
466 Takes a string. Returns true if the string is a Cc watcher of the current queue
467
468 =item Bugs
469
470 Should also be able to handle an RT::User object
471
472 =cut
473
474
475 sub IsCc {
476   my $self = shift;
477   my $cc = shift;
478   
479   return ($self->IsWatcher( Type => 'Cc', Id => $cc ));
480   
481 }
482
483 # }}}
484
485 # {{{ sub IsAdminCc
486
487 =head2 IsAdminCc
488
489 Takes a string. Returns true if the string is an AdminCc watcher of the current queue
490
491 =item Bugs
492
493 Should also be able to handle an RT::User object
494
495 =cut
496
497 sub IsAdminCc {
498   my $self = shift;
499   my $admincc = shift;
500   
501   return ($self->IsWatcher( Type => 'AdminCc', Id => $admincc ));
502   
503 }
504
505 # }}}
506
507 # }}}
508
509 # {{{ sub AddWatcher
510
511 =head2 AddWatcher
512
513 Takes a paramhash of Email, Owner and Type. Type is one of 'Cc' or 'AdminCc',
514 We need either an Email Address in Email or a userid in Owner
515
516 =cut
517
518 sub AddWatcher {
519     my $self = shift;
520     my %args = ( Email => undef,
521                  Type => undef,
522                  Owner => 0,
523                  @_
524                );
525     
526     # {{{ Check ACLS
527     #If the watcher we're trying to add is for the current user
528     if ( ( ( defined $args{'Email'})  && 
529            ( $args{'Email'} eq $self->CurrentUser->EmailAddress) ) or 
530          ($args{'Owner'} eq $self->CurrentUser->Id)) {
531         
532         #  If it's an AdminCc and they don't have 
533         #   'WatchAsAdminCc' or 'ModifyQueueWatchers', bail
534         if ($args{'Type'} eq 'AdminCc') {
535             unless ($self->CurrentUserHasRight('ModifyQueueWatchers') or 
536                     $self->CurrentUserHasRight('WatchAsAdminCc')) {
537                 return(0, 'Permission Denied');
538             }
539         }
540
541         #  If it's a Requestor or Cc and they don't have
542         #   'Watch' or 'ModifyQueueWatchers', bail
543         elsif ($args{'Type'} eq 'Cc') {
544             unless ($self->CurrentUserHasRight('ModifyQueueWatchers') or 
545                     $self->CurrentUserHasRight('Watch')) {
546                 return(0, 'Permission Denied');
547             }
548         }
549         else {
550             $RT::Logger->warn("$self -> AddWatcher hit code".
551                               " it never should. We got passed ".
552                               " a type of ". $args{'Type'});
553             return (0,'Error in parameters to $self AddWatcher');
554         }
555     }
556     # If the watcher isn't the current user 
557     # and the current user  doesn't have 'ModifyQueueWatchers'
558     # bail
559     else {
560         unless ($self->CurrentUserHasRight('ModifyQueueWatchers')) {
561             return (0, "Permission Denied");
562         }
563     }
564     # }}}
565         
566     require RT::Watcher;
567     my $Watcher = new RT::Watcher ($self->CurrentUser);
568     return ($Watcher->Create(Scope => 'Queue', 
569                              Value => $self->Id,
570                              Email => $args{'Email'},
571                              Type => $args{'Type'},
572                              Owner => $args{'Owner'}
573                             ));
574 }
575
576 # }}}
577
578 # {{{ sub AddCc
579
580 =head2 AddCc
581
582 Add a Cc to this queue.
583 Takes a paramhash of Email and Owner. 
584 We need either an Email Address in Email or a userid in Owner
585
586 =cut
587
588
589 sub AddCc {
590     my $self = shift;
591     return ($self->AddWatcher( Type => 'Cc', @_));
592 }
593 # }}}
594
595 # {{{ sub AddAdminCc
596
597 =head2 AddAdminCc
598
599 Add an Administrative Cc to this queue.
600 Takes a paramhash of Email and Owner. 
601 We need either an Email Address in Email or a userid in Owner
602
603 =cut
604
605 sub AddAdminCc {
606     my $self = shift;
607     return ($self->AddWatcher( Type => 'AdminCc', @_));
608 }
609 # }}}
610
611 # {{{ sub DeleteWatcher
612
613 =head2 DeleteWatcher id [type]
614
615 DeleteWatcher takes a single argument which is either an email address 
616 or a watcher id.  
617 If the first argument is an email address, you need to specify the watcher type you're talking
618 about as the second argument. Valid values are 'Cc' or 'AdminCc'.
619 It removes that watcher from this Queue\'s list of watchers.
620
621
622 =cut
623
624
625 sub DeleteWatcher {
626     my $self = shift;
627     my $id = shift;
628     
629     my $type;
630     
631     $type = shift if (@_);
632     
633
634     require RT::Watcher;
635     my $Watcher = new RT::Watcher($self->CurrentUser);
636     
637     #If it\'s a numeric watcherid
638     if ($id =~ /^(\d*)$/) {
639         $Watcher->Load($id);
640     }
641     
642     #Otherwise, we'll assume it's an email address
643     elsif ($type) {
644         my ($result, $msg) = 
645           $Watcher->LoadByValue( Email => $id,
646                                  Scope => 'Queue',
647                                  Value => $self->id,
648                                  Type => $type);
649         return (0,$msg) unless ($result);
650     }
651     
652     else {
653         return(0,"Can\'t delete a watcher by email address without specifying a type");
654     }
655     
656     # {{{ Check ACLS 
657
658     #If the watcher we're trying to delete is for the current user
659     if ($Watcher->Email eq $self->CurrentUser->EmailAddress) {
660                 
661         #  If it's an AdminCc and they don't have 
662         #   'WatchAsAdminCc' or 'ModifyQueueWatchers', bail
663         if ($Watcher->Type eq 'AdminCc') {
664             unless ($self->CurrentUserHasRight('ModifyQueueWatchers') or 
665                     $self->CurrentUserHasRight('WatchAsAdminCc')) {
666                 return(0, 'Permission Denied');
667             }
668         }
669
670         #  If it's a  Cc and they don't have
671         #   'Watch' or 'ModifyQueueWatchers', bail
672         elsif ($Watcher->Type eq 'Cc') {
673             unless ($self->CurrentUserHasRight('ModifyQueueWatchers') or 
674                     $self->CurrentUserHasRight('Watch')) {
675                 return(0, 'Permission Denied');
676             }
677         }
678         else {
679             $RT::Logger->warn("$self -> DeleteWatcher hit code".
680                               " it never should. We got passed ".
681                               " a type of ". $args{'Type'});
682             return (0,'Error in parameters to $self DeleteWatcher');
683         }
684     }
685     # If the watcher isn't the current user 
686     # and the current user  doesn't have 'ModifyQueueWatchers'
687     # bail
688     else {
689         unless ($self->CurrentUserHasRight('ModifyQueueWatchers')) {
690             return (0, "Permission Denied");
691         }
692     }
693
694     # }}}
695     
696     unless (($Watcher->Scope eq 'Queue') and
697             ($Watcher->Value == $self->id) ) {
698         return (0, "Not a watcher for this queue");
699     }
700     
701
702     #Clear out the watchers hash.
703     $self->{'watchers'} = undef;
704     
705     my $retval = $Watcher->Delete();
706     
707     unless ($retval) {
708         return(0,"Watcher could not be deleted.");
709     }
710     
711     return(1, "Watcher deleted");
712 }
713
714 # {{{ sub DeleteCc
715
716 =head2 DeleteCc EMAIL
717
718 Takes an email address. It calls DeleteWatcher with a preset 
719 type of 'Cc'
720
721
722 =cut
723
724 sub DeleteCc {
725    my $self = shift;
726    my $id = shift;
727    return ($self->DeleteWatcher ($id, 'Cc'))
728 }
729
730 # }}}
731
732 # {{{ sub DeleteAdminCc
733
734 =head2 DeleteAdminCc EMAIL
735
736 Takes an email address. It calls DeleteWatcher with a preset 
737 type of 'AdminCc'
738
739
740 =cut
741
742 sub DeleteAdminCc {
743    my $self = shift;
744    my $id = shift;
745    return ($self->DeleteWatcher ($id, 'AdminCc'))
746 }
747
748 # }}}
749
750
751 # }}}
752
753 # }}}
754
755 # {{{ Dealing with keyword selects
756
757 # {{{ sub AddKeywordSelect
758
759 =head2 AddKeywordSelect
760
761 Takes a paramhash of Name, Keyword, Depth and Single.  Adds a new KeywordSelect for 
762 this queue with those attributes.
763
764 =cut
765
766
767 sub AddKeywordSelect {
768     my $self = shift;
769     my %args = ( Keyword => undef,
770                  Depth => undef,
771                  Single => undef,
772                  Name => undef,
773                  @_);
774     
775     #ACLS get handled in KeywordSelect
776     my $NewKeywordSelect = new RT::KeywordSelect($self->CurrentUser);
777     
778     return ($NewKeywordSelect->Create (Keyword => $args{'Keyword'},
779                                Depth => $args{'Depth'},
780                                Name => $args{'Name'},
781                                Single => $args{'Single'},
782                                ObjectType => 'Ticket',
783                                ObjectField => 'Queue',
784                                ObjectValue => $self->Id()
785                               ) );
786 }
787
788 # }}}
789
790 # {{{ sub KeywordSelect
791
792 =head2 KeywordSelect([NAME])
793
794 Takes the name of a keyword select for this queue or that's global.
795 Returns the relevant KeywordSelect object.  Prefers a keywordselect that's 
796 specific to this queue over a global one.  If it can't find the proper
797 Keword select or the user doesn't have permission, returns an empty 
798 KeywordSelect object
799
800 =cut
801
802 sub KeywordSelect {
803     my $self = shift;
804     my $name = shift;
805     
806     require RT::KeywordSelect;
807
808     my $select = RT::KeywordSelect->new($self->CurrentUser);
809     if ($self->CurrentUserHasRight('SeeQueue')) {
810         $select->LoadByName( Name => $name, Queue => $self->Id);
811     }
812     return ($select);
813 }
814
815
816 # }}}
817
818 # {{{ sub KeywordSelects
819
820 =head2 KeywordSelects
821
822 Returns an B<RT::KeywordSelects> object containing the collection of
823 B<RT::KeywordSelect> objects which apply to this queue. (Both queue specific keyword selects
824 and global keyword selects.
825
826 =cut
827
828 sub KeywordSelects {
829   my $self = shift;
830
831
832   use RT::KeywordSelects;
833   my $KeywordSelects = new RT::KeywordSelects($self->CurrentUser);
834
835   if ($self->CurrentUserHasRight('SeeQueue')) {
836       $KeywordSelects->LimitToQueue($self->id);
837       $KeywordSelects->IncludeGlobals();
838   }
839   return ($KeywordSelects);
840 }
841 # }}}
842
843 # }}}
844
845 # {{{ ACCESS CONTROL
846
847 # {{{ sub ACL 
848
849 =head2 ACL
850
851 #Returns an RT::ACL object of ACEs everyone who has anything to do with this queue.
852
853 =cut
854
855 sub ACL  {
856     my $self = shift;
857     
858     use RT::ACL;
859     my $acl = new RT::ACL($self->CurrentUser);
860     
861     if ($self->CurrentUserHasRight('ShowACL')) {
862         $acl->LimitToQueue($self->Id);
863     }
864     
865     return ($acl);
866 }
867
868 # }}}
869
870 # {{{ sub _Set
871 sub _Set {
872     my $self = shift;
873
874     unless ($self->CurrentUserHasRight('AdminQueue')) {
875         return(0, 'Permission Denied');
876     }   
877     return ($self->SUPER::_Set(@_));
878 }
879 # }}}
880
881 # {{{ sub _Value
882
883 sub _Value {
884     my $self = shift;
885
886     unless ($self->CurrentUserHasRight('SeeQueue')) {
887         return (undef);
888     }
889
890     return ($self->__Value(@_));
891 }
892
893 # }}}
894
895 # {{{ sub CurrentUserHasRight
896
897 =head2 CurrentUserHasRight
898
899 Takes one argument. A textual string with the name of the right we want to check.
900 Returns true if the current user has that right for this queue.
901 Returns undef otherwise.
902
903 =cut
904
905 sub CurrentUserHasRight {
906   my $self = shift;
907   my $right = shift;
908
909   return ($self->HasRight( Principal=> $self->CurrentUser,
910                             Right => "$right"));
911
912 }
913
914 # }}}
915
916 # {{{ sub HasRight
917
918 =head2 HasRight
919
920 Takes a param hash with the fields 'Right' and 'Principal'.
921 Principal defaults to the current user.
922 Returns true if the principal has that right for this queue.
923 Returns undef otherwise.
924
925 =cut
926
927 # TAKES: Right and optional "Principal" which defaults to the current user
928 sub HasRight {
929     my $self = shift;
930         my %args = ( Right => undef,
931                      Principal => $self->CurrentUser,
932                      @_);
933         unless(defined $args{'Principal'}) {
934                 $RT::Logger->debug("Principal undefined in Queue::HasRight");
935
936         }
937         return($args{'Principal'}->HasQueueRight(QueueObj => $self,
938           Right => $args{'Right'}));
939 }
940 # }}}
941
942 # }}}
943
944 1;