Initial commit 4.0.5-3
[usit-rt.git] / lib / RT / Queue.pm
CommitLineData
84fb5b46
MKG
1# BEGIN BPS TAGGED BLOCK {{{
2#
3# COPYRIGHT:
4#
5# This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC
6# <sales@bestpractical.com>
7#
8# (Except where explicitly superseded by other copyright notices)
9#
10#
11# LICENSE:
12#
13# This work is made available to you under the terms of Version 2 of
14# the GNU General Public License. A copy of that license should have
15# been provided with this software, but in any event can be snarfed
16# from www.gnu.org.
17#
18# This work is distributed in the hope that it will be useful, but
19# WITHOUT ANY WARRANTY; without even the implied warranty of
20# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21# General Public License for more details.
22#
23# You should have received a copy of the GNU General Public License
24# along with this program; if not, write to the Free Software
25# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26# 02110-1301 or visit their web page on the internet at
27# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
28#
29#
30# CONTRIBUTION SUBMISSION POLICY:
31#
32# (The following paragraph is not intended to limit the rights granted
33# to you to modify and distribute this software under the terms of
34# the GNU General Public License and is only of importance to you if
35# you choose to contribute your changes and enhancements to the
36# community by submitting them to Best Practical Solutions, LLC.)
37#
38# By intentionally submitting any modifications, corrections or
39# derivatives to this work, or any other work intended for use with
40# Request Tracker, to Best Practical Solutions, LLC, you confirm that
41# you are the copyright holder for those contributions and you grant
42# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
43# royalty-free, perpetual, license to use, copy, create derivative
44# works based on those contributions, and sublicense and distribute
45# those contributions and any derivatives thereof.
46#
47# END BPS TAGGED BLOCK }}}
48
49=head1 NAME
50
51 RT::Queue - an RT Queue object
52
53=head1 SYNOPSIS
54
55 use RT::Queue;
56
57=head1 DESCRIPTION
58
59An RT queue object.
60
61=head1 METHODS
62
63=cut
64
65
66package RT::Queue;
67
68use strict;
69use warnings;
70use base 'RT::Record';
71
72sub Table {'Queues'}
73
74
75
76use RT::Groups;
77use RT::ACL;
78use RT::Interface::Email;
79
80our @DEFAULT_ACTIVE_STATUS = qw(new open stalled);
81our @DEFAULT_INACTIVE_STATUS = qw(resolved rejected deleted);
82
83# $self->loc('new'); # For the string extractor to get a string to localize
84# $self->loc('open'); # For the string extractor to get a string to localize
85# $self->loc('stalled'); # For the string extractor to get a string to localize
86# $self->loc('resolved'); # For the string extractor to get a string to localize
87# $self->loc('rejected'); # For the string extractor to get a string to localize
88# $self->loc('deleted'); # For the string extractor to get a string to localize
89
90
91our $RIGHTS = {
92 SeeQueue => 'View queue', # loc_pair
93 AdminQueue => 'Create, modify and delete queue', # loc_pair
94 ShowACL => 'Display Access Control List', # loc_pair
95 ModifyACL => 'Create, modify and delete Access Control List entries', # loc_pair
96 ModifyQueueWatchers => 'Modify queue watchers', # loc_pair
97 SeeCustomField => 'View custom field values', # loc_pair
98 ModifyCustomField => 'Modify custom field values', # loc_pair
99 AssignCustomFields => 'Assign and remove queue custom fields', # loc_pair
100 ModifyTemplate => 'Modify Scrip templates', # loc_pair
101 ShowTemplate => 'View Scrip templates', # loc_pair
102
103 ModifyScrips => 'Modify Scrips', # loc_pair
104 ShowScrips => 'View Scrips', # loc_pair
105
106 ShowTicket => 'View ticket summaries', # loc_pair
107 ShowTicketComments => 'View ticket private commentary', # loc_pair
108 ShowOutgoingEmail => 'View exact outgoing email messages and their recipients', # loc_pair
109
110 Watch => 'Sign up as a ticket Requestor or ticket or queue Cc', # loc_pair
111 WatchAsAdminCc => 'Sign up as a ticket or queue AdminCc', # loc_pair
112 CreateTicket => 'Create tickets', # loc_pair
113 ReplyToTicket => 'Reply to tickets', # loc_pair
114 CommentOnTicket => 'Comment on tickets', # loc_pair
115 OwnTicket => 'Own tickets', # loc_pair
116 ModifyTicket => 'Modify tickets', # loc_pair
117 DeleteTicket => 'Delete tickets', # loc_pair
118 TakeTicket => 'Take tickets', # loc_pair
119 StealTicket => 'Steal tickets', # loc_pair
120
121 ForwardMessage => 'Forward messages outside of RT', # loc_pair
122};
123
124our $RIGHT_CATEGORIES = {
125 SeeQueue => 'General',
126 AdminQueue => 'Admin',
127 ShowACL => 'Admin',
128 ModifyACL => 'Admin',
129 ModifyQueueWatchers => 'Admin',
130 SeeCustomField => 'General',
131 ModifyCustomField => 'Staff',
132 AssignCustomFields => 'Admin',
133 ModifyTemplate => 'Admin',
134 ShowTemplate => 'Admin',
135 ModifyScrips => 'Admin',
136 ShowScrips => 'Admin',
137 ShowTicket => 'General',
138 ShowTicketComments => 'Staff',
139 ShowOutgoingEmail => 'Staff',
140 Watch => 'General',
141 WatchAsAdminCc => 'Staff',
142 CreateTicket => 'General',
143 ReplyToTicket => 'General',
144 CommentOnTicket => 'General',
145 OwnTicket => 'Staff',
146 ModifyTicket => 'Staff',
147 DeleteTicket => 'Staff',
148 TakeTicket => 'Staff',
149 StealTicket => 'Staff',
150 ForwardMessage => 'Staff',
151};
152
153# Tell RT::ACE that this sort of object can get acls granted
154$RT::ACE::OBJECT_TYPES{'RT::Queue'} = 1;
155
156# TODO: This should be refactored out into an RT::ACLedObject or something
157# stuff the rights into a hash of rights that can exist.
158
159__PACKAGE__->AddRights(%$RIGHTS);
160__PACKAGE__->AddRightCategories(%$RIGHT_CATEGORIES);
161require RT::Lifecycle;
162
163=head2 AddRights C<RIGHT>, C<DESCRIPTION> [, ...]
164
165Adds the given rights to the list of possible rights. This method
166should be called during server startup, not at runtime.
167
168=cut
169
170sub AddRights {
171 my $self = shift;
172 my %new = @_;
173 $RIGHTS = { %$RIGHTS, %new };
174 %RT::ACE::LOWERCASERIGHTNAMES = ( %RT::ACE::LOWERCASERIGHTNAMES,
175 map { lc($_) => $_ } keys %new);
176}
177
178=head2 AddRightCategories C<RIGHT>, C<CATEGORY> [, ...]
179
180Adds the given right and category pairs to the list of right categories. This
181method should be called during server startup, not at runtime.
182
183=cut
184
185sub AddRightCategories {
186 my $self = shift if ref $_[0] or $_[0] eq __PACKAGE__;
187 my %new = @_;
188 $RIGHT_CATEGORIES = { %$RIGHT_CATEGORIES, %new };
189}
190
191sub AddLink {
192 my $self = shift;
193 my %args = ( Target => '',
194 Base => '',
195 Type => '',
196 Silent => undef,
197 @_ );
198
199 unless ( $self->CurrentUserHasRight('ModifyQueue') ) {
200 return ( 0, $self->loc("Permission Denied") );
201 }
202
203 return $self->SUPER::_AddLink(%args);
204}
205
206sub DeleteLink {
207 my $self = shift;
208 my %args = (
209 Base => undef,
210 Target => undef,
211 Type => undef,
212 @_
213 );
214
215 #check acls
216 unless ( $self->CurrentUserHasRight('ModifyQueue') ) {
217 $RT::Logger->debug("No permission to delete links");
218 return ( 0, $self->loc('Permission Denied'))
219 }
220
221 return $self->SUPER::_DeleteLink(%args);
222}
223
224=head2 AvailableRights
225
226Returns a hash of available rights for this object. The keys are the right names and the values are a description of what the rights do
227
228=cut
229
230sub AvailableRights {
231 my $self = shift;
232 return($RIGHTS);
233}
234
235=head2 RightCategories
236
237Returns a hashref where the keys are rights for this type of object and the
238values are the category (General, Staff, Admin) the right falls into.
239
240=cut
241
242sub RightCategories {
243 return $RIGHT_CATEGORIES;
244}
245
246
247sub Lifecycle {
248 my $self = shift;
249 unless (ref $self && $self->id) {
250 return RT::Lifecycle->Load('')
251 }
252
253 my $name = $self->_Value( Lifecycle => @_ );
254 $name ||= 'default';
255
256 my $res = RT::Lifecycle->Load( $name );
257 unless ( $res ) {
258 $RT::Logger->error("Lifecycle '$name' for queue '".$self->Name."' doesn't exist");
259 return RT::Lifecycle->Load('default');
260 }
261 return $res;
262}
263
264sub SetLifecycle {
265 my $self = shift;
266 my $value = shift;
267
268 if ( $value && $value ne 'default' ) {
269 return (0, $self->loc('[_1] is not valid lifecycle', $value ))
270 unless $self->ValidateLifecycle( $value );
271 } else {
272 $value = undef;
273 }
274
275 return $self->_Set( Field => 'Lifecycle', Value => $value, @_ );
276}
277
278=head2 ValidateLifecycle NAME
279
280Takes a lifecycle name. Returns true if it's an ok name and such
281lifecycle is configured. Returns undef otherwise.
282
283=cut
284
285sub ValidateLifecycle {
286 my $self = shift;
287 my $value = shift;
288 return undef unless RT::Lifecycle->Load( $value );
289 return 1;
290}
291
292
293=head2 ActiveStatusArray
294
295Returns an array of all ActiveStatuses for this queue
296
297=cut
298
299sub ActiveStatusArray {
300 my $self = shift;
301 return $self->Lifecycle->Valid('initial', 'active');
302}
303
304=head2 InactiveStatusArray
305
306Returns an array of all InactiveStatuses for this queue
307
308=cut
309
310sub InactiveStatusArray {
311 my $self = shift;
312 return $self->Lifecycle->Inactive;
313}
314
315=head2 StatusArray
316
317Returns an array of all statuses for this queue
318
319=cut
320
321sub StatusArray {
322 my $self = shift;
323 return $self->Lifecycle->Valid( @_ );
324}
325
326=head2 IsValidStatus value
327
328Returns true if value is a valid status. Otherwise, returns 0.
329
330=cut
331
332sub IsValidStatus {
333 my $self = shift;
334 return $self->Lifecycle->IsValid( shift );
335}
336
337=head2 IsActiveStatus value
338
339Returns true if value is a Active status. Otherwise, returns 0
340
341=cut
342
343sub IsActiveStatus {
344 my $self = shift;
345 return $self->Lifecycle->IsValid( shift, 'initial', 'active');
346}
347
348
349
350=head2 IsInactiveStatus value
351
352Returns true if value is a Inactive status. Otherwise, returns 0
353
354
355=cut
356
357sub IsInactiveStatus {
358 my $self = shift;
359 return $self->Lifecycle->IsInactive( shift );
360}
361
362
363
364
365
366
367=head2 Create(ARGS)
368
369Arguments: ARGS is a hash of named parameters. Valid parameters are:
370
371 Name (required)
372 Description
373 CorrespondAddress
374 CommentAddress
375 InitialPriority
376 FinalPriority
377 DefaultDueIn
378
379If you pass the ACL check, it creates the queue and returns its queue id.
380
381
382=cut
383
384sub Create {
385 my $self = shift;
386 my %args = (
387 Name => undef,
388 Description => '',
389 CorrespondAddress => '',
390 CommentAddress => '',
391 Lifecycle => 'default',
392 SubjectTag => undef,
393 InitialPriority => 0,
394 FinalPriority => 0,
395 DefaultDueIn => 0,
396 Sign => undef,
397 Encrypt => undef,
398 _RecordTransaction => 1,
399 @_
400 );
401
402 unless ( $self->CurrentUser->HasRight(Right => 'AdminQueue', Object => $RT::System) )
403 { #Check them ACLs
404 return ( 0, $self->loc("No permission to create queues") );
405 }
406
407 {
408 my ($val, $msg) = $self->_ValidateName( $args{'Name'} );
409 return ($val, $msg) unless $val;
410 }
411
412 if ( $args{'Lifecycle'} && $args{'Lifecycle'} ne 'default' ) {
413 return ( 0, $self->loc('Invalid lifecycle name') )
414 unless $self->ValidateLifecycle( $args{'Lifecycle'} );
415 } else {
416 $args{'Lifecycle'} = undef;
417 }
418
419 my %attrs = map {$_ => 1} $self->ReadableAttributes;
420
421 #TODO better input validation
422 $RT::Handle->BeginTransaction();
423 my $id = $self->SUPER::Create( map { $_ => $args{$_} } grep exists $args{$_}, keys %attrs );
424 unless ($id) {
425 $RT::Handle->Rollback();
426 return ( 0, $self->loc('Queue could not be created') );
427 }
428
429 my $create_ret = $self->_CreateQueueGroups();
430 unless ($create_ret) {
431 $RT::Handle->Rollback();
432 return ( 0, $self->loc('Queue could not be created') );
433 }
434 if ( $args{'_RecordTransaction'} ) {
435 $self->_NewTransaction( Type => "Create" );
436 }
437 $RT::Handle->Commit;
438
439 if ( defined $args{'Sign'} ) {
440 my ($status, $msg) = $self->SetSign( $args{'Sign'} );
441 $RT::Logger->error("Couldn't set attribute 'Sign': $msg")
442 unless $status;
443 }
444 if ( defined $args{'Encrypt'} ) {
445 my ($status, $msg) = $self->SetEncrypt( $args{'Encrypt'} );
446 $RT::Logger->error("Couldn't set attribute 'Encrypt': $msg")
447 unless $status;
448 }
449
450 RT->System->QueueCacheNeedsUpdate(1);
451
452 return ( $id, $self->loc("Queue created") );
453}
454
455
456
457sub Delete {
458 my $self = shift;
459 return ( 0,
460 $self->loc('Deleting this object would break referential integrity') );
461}
462
463
464
465=head2 SetDisabled
466
467Takes a boolean.
4681 will cause this queue to no longer be available for tickets.
4690 will re-enable this queue.
470
471=cut
472
473sub SetDisabled {
474 my $self = shift;
475 my $val = shift;
476
477 $RT::Handle->BeginTransaction();
478 my $set_err = $self->_Set( Field =>'Disabled', Value => $val);
479 unless ($set_err) {
480 $RT::Handle->Rollback();
481 $RT::Logger->warning("Couldn't ".($val == 1) ? "disable" : "enable"." queue ".$self->PrincipalObj->Id);
482 return (undef);
483 }
484 $self->_NewTransaction( Type => ($val == 1) ? "Disabled" : "Enabled" );
485
486 $RT::Handle->Commit();
487
488 RT->System->QueueCacheNeedsUpdate(1);
489
490 if ( $val == 1 ) {
491 return (1, $self->loc("Queue disabled"));
492 } else {
493 return (1, $self->loc("Queue enabled"));
494 }
495
496}
497
498
499
500=head2 Load
501
502Takes either a numerical id or a textual Name and loads the specified queue.
503
504=cut
505
506sub Load {
507 my $self = shift;
508
509 my $identifier = shift;
510 if ( !$identifier ) {
511 return (undef);
512 }
513
514 if ( $identifier =~ /^(\d+)$/ ) {
515 $self->SUPER::LoadById($identifier);
516 }
517 else {
518 $self->LoadByCols( Name => $identifier );
519 }
520
521 return ( $self->Id );
522
523}
524
525
526
527=head2 ValidateName NAME
528
529Takes a queue name. Returns true if it's an ok name for
530a new queue. Returns undef if there's already a queue by that name.
531
532=cut
533
534sub ValidateName {
535 my $self = shift;
536 my $name = shift;
537
538 my ($ok, $msg) = $self->_ValidateName($name);
539
540 return $ok ? 1 : 0;
541}
542
543sub _ValidateName {
544 my $self = shift;
545 my $name = shift;
546
547 return (undef, "Queue name is required") unless length $name;
548
549 # Validate via the superclass first
550 # Case: short circuit if it's an integer so we don't have
551 # fale negatives when loading a temp queue
552 unless ( my $q = $self->SUPER::ValidateName($name) ) {
553 return ($q, $self->loc("'[_1]' is not a valid name.", $name));
554 }
555
556 my $tempqueue = RT::Queue->new(RT->SystemUser);
557 $tempqueue->Load($name);
558
559 #If this queue exists, return undef
560 if ( $tempqueue->Name() && $tempqueue->id != $self->id) {
561 return (undef, $self->loc("Queue already exists") );
562 }
563
564 return (1);
565}
566
567
568=head2 SetSign
569
570=cut
571
572sub Sign {
573 my $self = shift;
574 my $value = shift;
575
576 return undef unless $self->CurrentUserHasRight('SeeQueue');
577 my $attr = $self->FirstAttribute('Sign') or return 0;
578 return $attr->Content;
579}
580
581sub SetSign {
582 my $self = shift;
583 my $value = shift;
584
585 return ( 0, $self->loc('Permission Denied') )
586 unless $self->CurrentUserHasRight('AdminQueue');
587
588 my ($status, $msg) = $self->SetAttribute(
589 Name => 'Sign',
590 Description => 'Sign outgoing messages by default',
591 Content => $value,
592 );
593 return ($status, $msg) unless $status;
594 return ($status, $self->loc('Signing enabled')) if $value;
595 return ($status, $self->loc('Signing disabled'));
596}
597
598sub Encrypt {
599 my $self = shift;
600 my $value = shift;
601
602 return undef unless $self->CurrentUserHasRight('SeeQueue');
603 my $attr = $self->FirstAttribute('Encrypt') or return 0;
604 return $attr->Content;
605}
606
607sub SetEncrypt {
608 my $self = shift;
609 my $value = shift;
610
611 return ( 0, $self->loc('Permission Denied') )
612 unless $self->CurrentUserHasRight('AdminQueue');
613
614 my ($status, $msg) = $self->SetAttribute(
615 Name => 'Encrypt',
616 Description => 'Encrypt outgoing messages by default',
617 Content => $value,
618 );
619 return ($status, $msg) unless $status;
620 return ($status, $self->loc('Encrypting enabled')) if $value;
621 return ($status, $self->loc('Encrypting disabled'));
622}
623
624=head2 Templates
625
626Returns an RT::Templates object of all of this queue's templates.
627
628=cut
629
630sub Templates {
631 my $self = shift;
632
633 my $templates = RT::Templates->new( $self->CurrentUser );
634
635 if ( $self->CurrentUserHasRight('ShowTemplate') ) {
636 $templates->LimitToQueue( $self->id );
637 }
638
639 return ($templates);
640}
641
642
643
644
645=head2 CustomField NAME
646
647Load the queue-specific custom field named NAME
648
649=cut
650
651sub CustomField {
652 my $self = shift;
653 my $name = shift;
654 my $cf = RT::CustomField->new($self->CurrentUser);
655 $cf->LoadByNameAndQueue(Name => $name, Queue => $self->Id);
656 return ($cf);
657}
658
659
660
661=head2 TicketCustomFields
662
663Returns an L<RT::CustomFields> object containing all global and
664queue-specific B<ticket> custom fields.
665
666=cut
667
668sub TicketCustomFields {
669 my $self = shift;
670
671 my $cfs = RT::CustomFields->new( $self->CurrentUser );
672 if ( $self->CurrentUserHasRight('SeeQueue') ) {
673 $cfs->SetContextObject( $self );
674 $cfs->LimitToGlobalOrObjectId( $self->Id );
675 $cfs->LimitToLookupType( 'RT::Queue-RT::Ticket' );
676 $cfs->ApplySortOrder;
677 }
678 return ($cfs);
679}
680
681
682
683=head2 TicketTransactionCustomFields
684
685Returns an L<RT::CustomFields> object containing all global and
686queue-specific B<transaction> custom fields.
687
688=cut
689
690sub TicketTransactionCustomFields {
691 my $self = shift;
692
693 my $cfs = RT::CustomFields->new( $self->CurrentUser );
694 if ( $self->CurrentUserHasRight('SeeQueue') ) {
695 $cfs->SetContextObject( $self );
696 $cfs->LimitToGlobalOrObjectId( $self->Id );
697 $cfs->LimitToLookupType( 'RT::Queue-RT::Ticket-RT::Transaction' );
698 $cfs->ApplySortOrder;
699 }
700 return ($cfs);
701}
702
703
704
705
706
707=head2 AllRoleGroupTypes
708
709Returns a list of the names of the various role group types that this queue
710has, including Requestor and Owner. If you don't want them, see
711L</ManageableRoleGroupTypes>.
712
713=cut
714
715sub AllRoleGroupTypes {
716 my $self = shift;
717 return ($self->ManageableRoleGroupTypes, qw(Requestor Owner));
718}
719
720=head2 IsRoleGroupType
721
722Returns whether the passed-in type is a role group type.
723
724=cut
725
726sub IsRoleGroupType {
727 my $self = shift;
728 my $type = shift;
729
730 for my $valid_type ($self->AllRoleGroupTypes) {
731 return 1 if $type eq $valid_type;
732 }
733
734 return 0;
735}
736
737=head2 ManageableRoleGroupTypes
738
739Returns a list of the names of the various role group types that this queue
740has, excluding Requestor and Owner. If you want them, see L</AllRoleGroupTypes>.
741
742=cut
743
744sub ManageableRoleGroupTypes {
745 return qw(Cc AdminCc);
746}
747
748=head2 IsManageableRoleGroupType
749
750Returns whether the passed-in type is a manageable role group type.
751
752=cut
753
754sub IsManageableRoleGroupType {
755 my $self = shift;
756 my $type = shift;
757
758 for my $valid_type ($self->ManageableRoleGroupTypes) {
759 return 1 if $type eq $valid_type;
760 }
761
762 return 0;
763}
764
765
766=head2 _CreateQueueGroups
767
768Create the ticket groups and links for this ticket.
769This routine expects to be called from Ticket->Create _inside of a transaction_
770
771It will create four groups for this ticket: Requestor, Cc, AdminCc and Owner.
772
773It will return true on success and undef on failure.
774
775
776=cut
777
778sub _CreateQueueGroups {
779 my $self = shift;
780
781 my @types = $self->AllRoleGroupTypes;
782
783 foreach my $type (@types) {
784 my $ok = $self->_CreateQueueRoleGroup($type);
785 return undef if !$ok;
786 }
787
788 return 1;
789}
790
791sub _CreateQueueRoleGroup {
792 my $self = shift;
793 my $type = shift;
794
795 my $type_obj = RT::Group->new($self->CurrentUser);
796 my ($id, $msg) = $type_obj->CreateRoleGroup(Instance => $self->Id,
797 Type => $type,
798 Domain => 'RT::Queue-Role');
799 unless ($id) {
800 $RT::Logger->error("Couldn't create a Queue group of type '$type' for queue ".
801 $self->Id.": ".$msg);
802 return(undef);
803 }
804
805 return $id;
806}
807
808
809
810# _HasModifyWatcherRight {{{
811sub _HasModifyWatcherRight {
812 my $self = shift;
813 my %args = (
814 Type => undef,
815 PrincipalId => undef,
816 Email => undef,
817 @_
818 );
819
820 return 1 if $self->CurrentUserHasRight('ModifyQueueWatchers');
821
822 #If the watcher we're trying to add is for the current user
823 if ( defined $args{'PrincipalId'} && $self->CurrentUser->PrincipalId eq $args{'PrincipalId'}) {
824 if ( $args{'Type'} eq 'AdminCc' ) {
825 return 1 if $self->CurrentUserHasRight('WatchAsAdminCc');
826 }
827 elsif ( $args{'Type'} eq 'Cc' or $args{'Type'} eq 'Requestor' ) {
828 return 1 if $self->CurrentUserHasRight('Watch');
829 }
830 else {
831 $RT::Logger->warning( "$self -> _HasModifyWatcher got passed a bogus type $args{Type}");
832 return ( 0, $self->loc('Invalid queue role group type [_1]', $args{Type}) );
833 }
834 }
835
836 return ( 0, $self->loc("Permission Denied") );
837}
838
839
840=head2 AddWatcher
841
842AddWatcher takes a parameter hash. The keys are as follows:
843
844Type One of Requestor, Cc, AdminCc
845
846PrinicpalId The RT::Principal id of the user or group that's being added as a watcher
847Email The email address of the new watcher. If a user with this
848 email address can't be found, a new nonprivileged user will be created.
849
850If the watcher you\'re trying to set has an RT account, set the Owner parameter to their User Id. Otherwise, set the Email parameter to their Email address.
851
852Returns a tuple of (status/id, message).
853
854=cut
855
856sub AddWatcher {
857 my $self = shift;
858 my %args = (
859 Type => undef,
860 PrincipalId => undef,
861 Email => undef,
862 @_
863 );
864
865 return ( 0, "No principal specified" )
866 unless $args{'Email'} or $args{'PrincipalId'};
867
868 if ( !$args{'PrincipalId'} && $args{'Email'} ) {
869 my $user = RT::User->new( $self->CurrentUser );
870 $user->LoadByEmail( $args{'Email'} );
871 $args{'PrincipalId'} = $user->PrincipalId if $user->id;
872 }
873
874 return ( 0, "Unknown watcher type [_1]", $args{Type} )
875 unless $self->IsRoleGroupType($args{Type});
876
877 my ($ok, $msg) = $self->_HasModifyWatcherRight(%args);
878 return ($ok, $msg) if !$ok;
879
880 return $self->_AddWatcher(%args);
881}
882
883#This contains the meat of AddWatcher. but can be called from a routine like
884# Create, which doesn't need the additional acl check
885sub _AddWatcher {
886 my $self = shift;
887 my %args = (
888 Type => undef,
889 Silent => undef,
890 PrincipalId => undef,
891 Email => undef,
892 @_
893 );
894
895
896 my $principal = RT::Principal->new( $self->CurrentUser );
897 if ( $args{'PrincipalId'} ) {
898 $principal->Load( $args{'PrincipalId'} );
899 if ( $principal->id and $principal->IsUser and my $email = $principal->Object->EmailAddress ) {
900 return (0, $self->loc("[_1] is an address RT receives mail at. Adding it as a '[_2]' would create a mail loop", $email, $self->loc($args{'Type'})))
901 if RT::EmailParser->IsRTAddress( $email );
902 }
903 }
904 elsif ( $args{'Email'} ) {
905 if ( RT::EmailParser->IsRTAddress( $args{'Email'} ) ) {
906 return (0, $self->loc("[_1] is an address RT receives mail at. Adding it as a '[_2]' would create a mail loop", $args{'Email'}, $self->loc($args{'Type'})));
907 }
908 my $user = RT::User->new($self->CurrentUser);
909 $user->LoadByEmail( $args{'Email'} );
910 $user->Load( $args{'Email'} )
911 unless $user->id;
912
913 if ( $user->Id ) { # If the user exists
914 $principal->Load( $user->PrincipalId );
915 } else {
916 # if the user doesn't exist, we need to create a new user
917 my $new_user = RT::User->new(RT->SystemUser);
918
919 my ( $Address, $Name ) =
920 RT::Interface::Email::ParseAddressFromHeader($args{'Email'});
921
922 my ( $Val, $Message ) = $new_user->Create(
923 Name => $Address,
924 EmailAddress => $Address,
925 RealName => $Name,
926 Privileged => 0,
927 Comments => 'Autocreated when added as a watcher'
928 );
929 unless ($Val) {
930 $RT::Logger->error("Failed to create user ".$args{'Email'} .": " .$Message);
931 # Deal with the race condition of two account creations at once
932 $new_user->LoadByEmail( $args{'Email'} );
933 }
934 $principal->Load( $new_user->PrincipalId );
935 }
936 }
937 # If we can't find this watcher, we need to bail.
938 unless ( $principal->Id ) {
939 return(0, $self->loc("Could not find or create that user"));
940 }
941
942 my $group = RT::Group->new($self->CurrentUser);
943 $group->LoadQueueRoleGroup(Type => $args{'Type'}, Queue => $self->Id);
944 unless ($group->id) {
945 return(0,$self->loc("Group not found"));
946 }
947
948 if ( $group->HasMember( $principal)) {
949
950 return ( 0, $self->loc('That principal is already a [_1] for this queue', $args{'Type'}) );
951 }
952
953
954 my ($m_id, $m_msg) = $group->_AddMember(PrincipalId => $principal->Id);
955 unless ($m_id) {
956 $RT::Logger->error("Failed to add ".$principal->Id." as a member of group ".$group->Id.": ".$m_msg);
957
958 return ( 0, $self->loc('Could not make that principal a [_1] for this queue', $args{'Type'}) );
959 }
960 return ( 1, $self->loc("Added [_1] to members of [_2] for this queue.", $principal->Object->Name, $args{'Type'} ));
961}
962
963
964
965=head2 DeleteWatcher { Type => TYPE, PrincipalId => PRINCIPAL_ID, Email => EMAIL_ADDRESS }
966
967
968Deletes a queue watcher. Takes two arguments:
969
970Type (one of Requestor,Cc,AdminCc)
971
972and one of
973
974PrincipalId (an RT::Principal Id of the watcher you want to remove)
975 OR
976Email (the email address of an existing wathcer)
977
978
979=cut
980
981
982sub DeleteWatcher {
983 my $self = shift;
984
985 my %args = ( Type => undef,
986 PrincipalId => undef,
987 Email => undef,
988 @_ );
989
990 unless ( $args{'PrincipalId'} || $args{'Email'} ) {
991 return ( 0, $self->loc("No principal specified") );
992 }
993
994 if ( !$args{PrincipalId} and $args{Email} ) {
995 my $user = RT::User->new( $self->CurrentUser );
996 my ($rv, $msg) = $user->LoadByEmail( $args{Email} );
997 $args{PrincipalId} = $user->PrincipalId if $rv;
998 }
999
1000 my $principal = RT::Principal->new( $self->CurrentUser );
1001 if ( $args{'PrincipalId'} ) {
1002 $principal->Load( $args{'PrincipalId'} );
1003 }
1004 else {
1005 my $user = RT::User->new( $self->CurrentUser );
1006 $user->LoadByEmail( $args{'Email'} );
1007 $principal->Load( $user->Id );
1008 }
1009
1010 # If we can't find this watcher, we need to bail.
1011 unless ( $principal->Id ) {
1012 return ( 0, $self->loc("Could not find that principal") );
1013 }
1014
1015 my $group = RT::Group->new($self->CurrentUser);
1016 $group->LoadQueueRoleGroup(Type => $args{'Type'}, Queue => $self->Id);
1017 unless ($group->id) {
1018 return(0,$self->loc("Group not found"));
1019 }
1020
1021 return ( 0, $self->loc('Unknown watcher type [_1]', $args{Type}) )
1022 unless $self->IsRoleGroupType($args{Type});
1023
1024 my ($ok, $msg) = $self->_HasModifyWatcherRight(%args);
1025 return ($ok, $msg) if !$ok;
1026
1027 # see if this user is already a watcher.
1028
1029 unless ( $group->HasMember($principal)) {
1030 return ( 0,
1031 $self->loc('That principal is not a [_1] for this queue', $args{'Type'}) );
1032 }
1033
1034 my ($m_id, $m_msg) = $group->_DeleteMember($principal->Id);
1035 unless ($m_id) {
1036 $RT::Logger->error("Failed to delete ".$principal->Id.
1037 " as a member of group ".$group->Id.": ".$m_msg);
1038
1039 return ( 0, $self->loc('Could not remove that principal as a [_1] for this queue', $args{'Type'}) );
1040 }
1041
1042 return ( 1, $self->loc("Removed [_1] from members of [_2] for this queue.", $principal->Object->Name, $args{'Type'} ));
1043}
1044
1045
1046
1047=head2 AdminCcAddresses
1048
1049returns String: All queue AdminCc email addresses as a string
1050
1051=cut
1052
1053sub AdminCcAddresses {
1054 my $self = shift;
1055
1056 unless ( $self->CurrentUserHasRight('SeeQueue') ) {
1057 return undef;
1058 }
1059
1060 return ( $self->AdminCc->MemberEmailAddressesAsString )
1061
1062}
1063
1064
1065
1066=head2 CcAddresses
1067
1068returns String: All queue Ccs as a string of email addresses
1069
1070=cut
1071
1072sub CcAddresses {
1073 my $self = shift;
1074
1075 unless ( $self->CurrentUserHasRight('SeeQueue') ) {
1076 return undef;
1077 }
1078
1079 return ( $self->Cc->MemberEmailAddressesAsString);
1080
1081}
1082
1083
1084
1085=head2 Cc
1086
1087Takes nothing.
1088Returns an RT::Group object which contains this Queue's Ccs.
1089If the user doesn't have "ShowQueue" permission, returns an empty group
1090
1091=cut
1092
1093sub Cc {
1094 my $self = shift;
1095
1096 my $group = RT::Group->new($self->CurrentUser);
1097 if ( $self->CurrentUserHasRight('SeeQueue') ) {
1098 $group->LoadQueueRoleGroup(Type => 'Cc', Queue => $self->Id);
1099 }
1100 return ($group);
1101
1102}
1103
1104
1105
1106=head2 AdminCc
1107
1108Takes nothing.
1109Returns an RT::Group object which contains this Queue's AdminCcs.
1110If the user doesn't have "ShowQueue" permission, returns an empty group
1111
1112=cut
1113
1114sub AdminCc {
1115 my $self = shift;
1116
1117 my $group = RT::Group->new($self->CurrentUser);
1118 if ( $self->CurrentUserHasRight('SeeQueue') ) {
1119 $group->LoadQueueRoleGroup(Type => 'AdminCc', Queue => $self->Id);
1120 }
1121 return ($group);
1122
1123}
1124
1125
1126
1127# a generic routine to be called by IsRequestor, IsCc and IsAdminCc
1128
1129=head2 IsWatcher { Type => TYPE, PrincipalId => PRINCIPAL_ID }
1130
1131Takes a param hash with the attributes Type and PrincipalId
1132
1133Type is one of Requestor, Cc, AdminCc and Owner
1134
1135PrincipalId is an RT::Principal id
1136
1137Returns true if that principal is a member of the group Type for this queue
1138
1139
1140=cut
1141
1142sub IsWatcher {
1143 my $self = shift;
1144
1145 my %args = ( Type => 'Cc',
1146 PrincipalId => undef,
1147 @_
1148 );
1149
1150 # Load the relevant group.
1151 my $group = RT::Group->new($self->CurrentUser);
1152 $group->LoadQueueRoleGroup(Type => $args{'Type'}, Queue => $self->id);
1153 # Ask if it has the member in question
1154
1155 my $principal = RT::Principal->new($self->CurrentUser);
1156 $principal->Load($args{'PrincipalId'});
1157 unless ($principal->Id) {
1158 return (undef);
1159 }
1160
1161 return ($group->HasMemberRecursively($principal));
1162}
1163
1164
1165
1166
1167=head2 IsCc PRINCIPAL_ID
1168
1169Takes an RT::Principal id.
1170Returns true if the principal is a requestor of the current queue.
1171
1172
1173=cut
1174
1175sub IsCc {
1176 my $self = shift;
1177 my $cc = shift;
1178
1179 return ( $self->IsWatcher( Type => 'Cc', PrincipalId => $cc ) );
1180
1181}
1182
1183
1184
1185=head2 IsAdminCc PRINCIPAL_ID
1186
1187Takes an RT::Principal id.
1188Returns true if the principal is a requestor of the current queue.
1189
1190=cut
1191
1192sub IsAdminCc {
1193 my $self = shift;
1194 my $person = shift;
1195
1196 return ( $self->IsWatcher( Type => 'AdminCc', PrincipalId => $person ) );
1197
1198}
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209sub _Set {
1210 my $self = shift;
1211
1212 unless ( $self->CurrentUserHasRight('AdminQueue') ) {
1213 return ( 0, $self->loc('Permission Denied') );
1214 }
1215 return ( $self->SUPER::_Set(@_) );
1216}
1217
1218
1219
1220sub _Value {
1221 my $self = shift;
1222
1223 unless ( $self->CurrentUserHasRight('SeeQueue') ) {
1224 return (undef);
1225 }
1226
1227 return ( $self->__Value(@_) );
1228}
1229
1230
1231
1232=head2 CurrentUserHasRight
1233
1234Takes one argument. A textual string with the name of the right we want to check.
1235Returns true if the current user has that right for this queue.
1236Returns undef otherwise.
1237
1238=cut
1239
1240sub CurrentUserHasRight {
1241 my $self = shift;
1242 my $right = shift;
1243
1244 return (
1245 $self->HasRight(
1246 Principal => $self->CurrentUser,
1247 Right => "$right"
1248 )
1249 );
1250
1251}
1252
1253=head2 CurrentUserCanSee
1254
1255Returns true if the current user can see the queue, using SeeQueue
1256
1257=cut
1258
1259sub CurrentUserCanSee {
1260 my $self = shift;
1261
1262 return $self->CurrentUserHasRight('SeeQueue');
1263}
1264
1265
1266=head2 HasRight
1267
1268Takes a param hash with the fields 'Right' and 'Principal'.
1269Principal defaults to the current user.
1270Returns true if the principal has that right for this queue.
1271Returns undef otherwise.
1272
1273=cut
1274
1275# TAKES: Right and optional "Principal" which defaults to the current user
1276sub HasRight {
1277 my $self = shift;
1278 my %args = (
1279 Right => undef,
1280 Principal => $self->CurrentUser,
1281 @_
1282 );
1283 my $principal = delete $args{'Principal'};
1284 unless ( $principal ) {
1285 $RT::Logger->error("Principal undefined in Queue::HasRight");
1286 return undef;
1287 }
1288
1289 return $principal->HasRight(
1290 %args,
1291 Object => ($self->Id ? $self : $RT::System),
1292 );
1293}
1294
1295
1296
1297
1298=head2 id
1299
1300Returns the current value of id.
1301(In the database, id is stored as int(11).)
1302
1303
1304=cut
1305
1306
1307=head2 Name
1308
1309Returns the current value of Name.
1310(In the database, Name is stored as varchar(200).)
1311
1312
1313
1314=head2 SetName VALUE
1315
1316
1317Set Name to VALUE.
1318Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1319(In the database, Name will be stored as a varchar(200).)
1320
1321
1322=cut
1323
1324
1325=head2 Description
1326
1327Returns the current value of Description.
1328(In the database, Description is stored as varchar(255).)
1329
1330
1331
1332=head2 SetDescription VALUE
1333
1334
1335Set Description to VALUE.
1336Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1337(In the database, Description will be stored as a varchar(255).)
1338
1339
1340=cut
1341
1342
1343=head2 CorrespondAddress
1344
1345Returns the current value of CorrespondAddress.
1346(In the database, CorrespondAddress is stored as varchar(120).)
1347
1348
1349
1350=head2 SetCorrespondAddress VALUE
1351
1352
1353Set CorrespondAddress to VALUE.
1354Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1355(In the database, CorrespondAddress will be stored as a varchar(120).)
1356
1357
1358=cut
1359
1360
1361=head2 CommentAddress
1362
1363Returns the current value of CommentAddress.
1364(In the database, CommentAddress is stored as varchar(120).)
1365
1366
1367
1368=head2 SetCommentAddress VALUE
1369
1370
1371Set CommentAddress to VALUE.
1372Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1373(In the database, CommentAddress will be stored as a varchar(120).)
1374
1375
1376=cut
1377
1378
1379=head2 Lifecycle
1380
1381Returns the current value of Lifecycle.
1382(In the database, Lifecycle is stored as varchar(32).)
1383
1384
1385
1386=head2 SetLifecycle VALUE
1387
1388
1389Set Lifecycle to VALUE.
1390Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1391(In the database, Lifecycle will be stored as a varchar(32).)
1392
1393
1394=cut
1395
1396=head2 SubjectTag
1397
1398Returns the current value of SubjectTag.
1399(In the database, SubjectTag is stored as varchar(120).)
1400
1401
1402
1403=head2 SetSubjectTag VALUE
1404
1405
1406Set SubjectTag to VALUE.
1407Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1408(In the database, SubjectTag will be stored as a varchar(120).)
1409
1410
1411=cut
1412
1413
1414=head2 InitialPriority
1415
1416Returns the current value of InitialPriority.
1417(In the database, InitialPriority is stored as int(11).)
1418
1419
1420
1421=head2 SetInitialPriority VALUE
1422
1423
1424Set InitialPriority to VALUE.
1425Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1426(In the database, InitialPriority will be stored as a int(11).)
1427
1428
1429=cut
1430
1431
1432=head2 FinalPriority
1433
1434Returns the current value of FinalPriority.
1435(In the database, FinalPriority is stored as int(11).)
1436
1437
1438
1439=head2 SetFinalPriority VALUE
1440
1441
1442Set FinalPriority to VALUE.
1443Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1444(In the database, FinalPriority will be stored as a int(11).)
1445
1446
1447=cut
1448
1449
1450=head2 DefaultDueIn
1451
1452Returns the current value of DefaultDueIn.
1453(In the database, DefaultDueIn is stored as int(11).)
1454
1455
1456
1457=head2 SetDefaultDueIn VALUE
1458
1459
1460Set DefaultDueIn to VALUE.
1461Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1462(In the database, DefaultDueIn will be stored as a int(11).)
1463
1464
1465=cut
1466
1467
1468=head2 Creator
1469
1470Returns the current value of Creator.
1471(In the database, Creator is stored as int(11).)
1472
1473
1474=cut
1475
1476
1477=head2 Created
1478
1479Returns the current value of Created.
1480(In the database, Created is stored as datetime.)
1481
1482
1483=cut
1484
1485
1486=head2 LastUpdatedBy
1487
1488Returns the current value of LastUpdatedBy.
1489(In the database, LastUpdatedBy is stored as int(11).)
1490
1491
1492=cut
1493
1494
1495=head2 LastUpdated
1496
1497Returns the current value of LastUpdated.
1498(In the database, LastUpdated is stored as datetime.)
1499
1500
1501=cut
1502
1503
1504=head2 Disabled
1505
1506Returns the current value of Disabled.
1507(In the database, Disabled is stored as smallint(6).)
1508
1509
1510
1511=head2 SetDisabled VALUE
1512
1513
1514Set Disabled to VALUE.
1515Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1516(In the database, Disabled will be stored as a smallint(6).)
1517
1518
1519=cut
1520
1521
1522
1523sub _CoreAccessible {
1524 {
1525
1526 id =>
1527 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
1528 Name =>
1529 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
1530 Description =>
1531 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
1532 CorrespondAddress =>
1533 {read => 1, write => 1, sql_type => 12, length => 120, is_blob => 0, is_numeric => 0, type => 'varchar(120)', default => ''},
1534 CommentAddress =>
1535 {read => 1, write => 1, sql_type => 12, length => 120, is_blob => 0, is_numeric => 0, type => 'varchar(120)', default => ''},
1536 SubjectTag =>
1537 {read => 1, write => 1, sql_type => 12, length => 120, is_blob => 0, is_numeric => 0, type => 'varchar(120)', default => ''},
1538 Lifecycle =>
1539 {read => 1, write => 1, sql_type => 12, length => 32, is_blob => 0, is_numeric => 0, type => 'varchar(32)', default => ''},
1540 InitialPriority =>
1541 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
1542 FinalPriority =>
1543 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
1544 DefaultDueIn =>
1545 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
1546 Creator =>
1547 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
1548 Created =>
1549 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
1550 LastUpdatedBy =>
1551 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
1552 LastUpdated =>
1553 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
1554 Disabled =>
1555 {read => 1, write => 1, sql_type => 5, length => 6, is_blob => 0, is_numeric => 1, type => 'smallint(6)', default => '0'},
1556
1557 }
1558};
1559
1560
1561
1562RT::Base->_ImportOverlays();
1563
15641;