Upgrade to 4.0.8 with mod of ExternalAuth + absolute paths to ticket-menu.
[usit-rt.git] / lib / RT / Queue.pm
... / ...
CommitLineData
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 SignAuto => undef,
398 Encrypt => undef,
399 _RecordTransaction => 1,
400 @_
401 );
402
403 unless ( $self->CurrentUser->HasRight(Right => 'AdminQueue', Object => $RT::System) )
404 { #Check them ACLs
405 return ( 0, $self->loc("No permission to create queues") );
406 }
407
408 {
409 my ($val, $msg) = $self->_ValidateName( $args{'Name'} );
410 return ($val, $msg) unless $val;
411 }
412
413 if ( $args{'Lifecycle'} && $args{'Lifecycle'} ne 'default' ) {
414 return ( 0, $self->loc('Invalid lifecycle name') )
415 unless $self->ValidateLifecycle( $args{'Lifecycle'} );
416 } else {
417 $args{'Lifecycle'} = undef;
418 }
419
420 my %attrs = map {$_ => 1} $self->ReadableAttributes;
421
422 #TODO better input validation
423 $RT::Handle->BeginTransaction();
424 my $id = $self->SUPER::Create( map { $_ => $args{$_} } grep exists $args{$_}, keys %attrs );
425 unless ($id) {
426 $RT::Handle->Rollback();
427 return ( 0, $self->loc('Queue could not be created') );
428 }
429
430 my $create_ret = $self->_CreateQueueGroups();
431 unless ($create_ret) {
432 $RT::Handle->Rollback();
433 return ( 0, $self->loc('Queue could not be created') );
434 }
435 if ( $args{'_RecordTransaction'} ) {
436 $self->_NewTransaction( Type => "Create" );
437 }
438 $RT::Handle->Commit;
439
440 for my $attr (qw/Sign SignAuto Encrypt/) {
441 next unless defined $args{$attr};
442 my $set = "Set" . $attr;
443 my ($status, $msg) = $self->$set( $args{$attr} );
444 $RT::Logger->error("Couldn't set attribute '$attr': $msg")
445 unless $status;
446 }
447
448 RT->System->QueueCacheNeedsUpdate(1);
449
450 return ( $id, $self->loc("Queue created") );
451}
452
453
454
455sub Delete {
456 my $self = shift;
457 return ( 0,
458 $self->loc('Deleting this object would break referential integrity') );
459}
460
461
462
463=head2 SetDisabled
464
465Takes a boolean.
4661 will cause this queue to no longer be available for tickets.
4670 will re-enable this queue.
468
469=cut
470
471sub SetDisabled {
472 my $self = shift;
473 my $val = shift;
474
475 $RT::Handle->BeginTransaction();
476 my $set_err = $self->_Set( Field =>'Disabled', Value => $val);
477 unless ($set_err) {
478 $RT::Handle->Rollback();
479 $RT::Logger->warning("Couldn't ".($val == 1) ? "disable" : "enable"." queue ".$self->PrincipalObj->Id);
480 return (undef);
481 }
482 $self->_NewTransaction( Type => ($val == 1) ? "Disabled" : "Enabled" );
483
484 $RT::Handle->Commit();
485
486 RT->System->QueueCacheNeedsUpdate(1);
487
488 if ( $val == 1 ) {
489 return (1, $self->loc("Queue disabled"));
490 } else {
491 return (1, $self->loc("Queue enabled"));
492 }
493
494}
495
496
497
498=head2 Load
499
500Takes either a numerical id or a textual Name and loads the specified queue.
501
502=cut
503
504sub Load {
505 my $self = shift;
506
507 my $identifier = shift;
508 if ( !$identifier ) {
509 return (undef);
510 }
511
512 if ( $identifier =~ /^(\d+)$/ ) {
513 $self->SUPER::LoadById($identifier);
514 }
515 else {
516 $self->LoadByCols( Name => $identifier );
517 }
518
519 return ( $self->Id );
520
521}
522
523
524
525=head2 ValidateName NAME
526
527Takes a queue name. Returns true if it's an ok name for
528a new queue. Returns undef if there's already a queue by that name.
529
530=cut
531
532sub ValidateName {
533 my $self = shift;
534 my $name = shift;
535
536 my ($ok, $msg) = $self->_ValidateName($name);
537
538 return $ok ? 1 : 0;
539}
540
541sub _ValidateName {
542 my $self = shift;
543 my $name = shift;
544
545 return (undef, "Queue name is required") unless length $name;
546
547 # Validate via the superclass first
548 # Case: short circuit if it's an integer so we don't have
549 # fale negatives when loading a temp queue
550 unless ( my $q = $self->SUPER::ValidateName($name) ) {
551 return ($q, $self->loc("'[_1]' is not a valid name.", $name));
552 }
553
554 my $tempqueue = RT::Queue->new(RT->SystemUser);
555 $tempqueue->Load($name);
556
557 #If this queue exists, return undef
558 if ( $tempqueue->Name() && $tempqueue->id != $self->id) {
559 return (undef, $self->loc("Queue already exists") );
560 }
561
562 return (1);
563}
564
565
566=head2 SetSign
567
568=cut
569
570sub Sign {
571 my $self = shift;
572 my $value = shift;
573
574 return undef unless $self->CurrentUserHasRight('SeeQueue');
575 my $attr = $self->FirstAttribute('Sign') or return 0;
576 return $attr->Content;
577}
578
579sub SetSign {
580 my $self = shift;
581 my $value = shift;
582
583 return ( 0, $self->loc('Permission Denied') )
584 unless $self->CurrentUserHasRight('AdminQueue');
585
586 my ($status, $msg) = $self->SetAttribute(
587 Name => 'Sign',
588 Description => 'Sign outgoing messages by default',
589 Content => $value,
590 );
591 return ($status, $msg) unless $status;
592 return ($status, $self->loc('Signing enabled')) if $value;
593 return ($status, $self->loc('Signing disabled'));
594}
595
596sub SignAuto {
597 my $self = shift;
598 my $value = shift;
599
600 return undef unless $self->CurrentUserHasRight('SeeQueue');
601 my $attr = $self->FirstAttribute('SignAuto') or return 0;
602 return $attr->Content;
603}
604
605sub SetSignAuto {
606 my $self = shift;
607 my $value = shift;
608
609 return ( 0, $self->loc('Permission Denied') )
610 unless $self->CurrentUserHasRight('AdminQueue');
611
612 my ($status, $msg) = $self->SetAttribute(
613 Name => 'SignAuto',
614 Description => 'Sign auto-generated outgoing messages',
615 Content => $value,
616 );
617 return ($status, $msg) unless $status;
618 return ($status, $self->loc('Signing enabled')) if $value;
619 return ($status, $self->loc('Signing disabled'));
620}
621
622sub Encrypt {
623 my $self = shift;
624 my $value = shift;
625
626 return undef unless $self->CurrentUserHasRight('SeeQueue');
627 my $attr = $self->FirstAttribute('Encrypt') or return 0;
628 return $attr->Content;
629}
630
631sub SetEncrypt {
632 my $self = shift;
633 my $value = shift;
634
635 return ( 0, $self->loc('Permission Denied') )
636 unless $self->CurrentUserHasRight('AdminQueue');
637
638 my ($status, $msg) = $self->SetAttribute(
639 Name => 'Encrypt',
640 Description => 'Encrypt outgoing messages by default',
641 Content => $value,
642 );
643 return ($status, $msg) unless $status;
644 return ($status, $self->loc('Encrypting enabled')) if $value;
645 return ($status, $self->loc('Encrypting disabled'));
646}
647
648=head2 Templates
649
650Returns an RT::Templates object of all of this queue's templates.
651
652=cut
653
654sub Templates {
655 my $self = shift;
656
657 my $templates = RT::Templates->new( $self->CurrentUser );
658
659 if ( $self->CurrentUserHasRight('ShowTemplate') ) {
660 $templates->LimitToQueue( $self->id );
661 }
662
663 return ($templates);
664}
665
666
667
668
669=head2 CustomField NAME
670
671Load the queue-specific custom field named NAME
672
673=cut
674
675sub CustomField {
676 my $self = shift;
677 my $name = shift;
678 my $cf = RT::CustomField->new($self->CurrentUser);
679 $cf->LoadByNameAndQueue(Name => $name, Queue => $self->Id);
680 return ($cf);
681}
682
683
684
685=head2 TicketCustomFields
686
687Returns an L<RT::CustomFields> object containing all global and
688queue-specific B<ticket> custom fields.
689
690=cut
691
692sub TicketCustomFields {
693 my $self = shift;
694
695 my $cfs = RT::CustomFields->new( $self->CurrentUser );
696 if ( $self->CurrentUserHasRight('SeeQueue') ) {
697 $cfs->SetContextObject( $self );
698 $cfs->LimitToGlobalOrObjectId( $self->Id );
699 $cfs->LimitToLookupType( 'RT::Queue-RT::Ticket' );
700 $cfs->ApplySortOrder;
701 }
702 return ($cfs);
703}
704
705
706
707=head2 TicketTransactionCustomFields
708
709Returns an L<RT::CustomFields> object containing all global and
710queue-specific B<transaction> custom fields.
711
712=cut
713
714sub TicketTransactionCustomFields {
715 my $self = shift;
716
717 my $cfs = RT::CustomFields->new( $self->CurrentUser );
718 if ( $self->CurrentUserHasRight('SeeQueue') ) {
719 $cfs->SetContextObject( $self );
720 $cfs->LimitToGlobalOrObjectId( $self->Id );
721 $cfs->LimitToLookupType( 'RT::Queue-RT::Ticket-RT::Transaction' );
722 $cfs->ApplySortOrder;
723 }
724 return ($cfs);
725}
726
727
728
729
730
731=head2 AllRoleGroupTypes
732
733Returns a list of the names of the various role group types that this queue
734has, including Requestor and Owner. If you don't want them, see
735L</ManageableRoleGroupTypes>.
736
737=cut
738
739sub AllRoleGroupTypes {
740 my $self = shift;
741 return ($self->ManageableRoleGroupTypes, qw(Requestor Owner));
742}
743
744=head2 IsRoleGroupType
745
746Returns whether the passed-in type is a role group type.
747
748=cut
749
750sub IsRoleGroupType {
751 my $self = shift;
752 my $type = shift;
753
754 for my $valid_type ($self->AllRoleGroupTypes) {
755 return 1 if $type eq $valid_type;
756 }
757
758 return 0;
759}
760
761=head2 ManageableRoleGroupTypes
762
763Returns a list of the names of the various role group types that this queue
764has, excluding Requestor and Owner. If you want them, see L</AllRoleGroupTypes>.
765
766=cut
767
768sub ManageableRoleGroupTypes {
769 return qw(Cc AdminCc);
770}
771
772=head2 IsManageableRoleGroupType
773
774Returns whether the passed-in type is a manageable role group type.
775
776=cut
777
778sub IsManageableRoleGroupType {
779 my $self = shift;
780 my $type = shift;
781
782 for my $valid_type ($self->ManageableRoleGroupTypes) {
783 return 1 if $type eq $valid_type;
784 }
785
786 return 0;
787}
788
789
790=head2 _CreateQueueGroups
791
792Create the ticket groups and links for this ticket.
793This routine expects to be called from Ticket->Create _inside of a transaction_
794
795It will create four groups for this ticket: Requestor, Cc, AdminCc and Owner.
796
797It will return true on success and undef on failure.
798
799
800=cut
801
802sub _CreateQueueGroups {
803 my $self = shift;
804
805 my @types = $self->AllRoleGroupTypes;
806
807 foreach my $type (@types) {
808 my $ok = $self->_CreateQueueRoleGroup($type);
809 return undef if !$ok;
810 }
811
812 return 1;
813}
814
815sub _CreateQueueRoleGroup {
816 my $self = shift;
817 my $type = shift;
818
819 my $type_obj = RT::Group->new($self->CurrentUser);
820 my ($id, $msg) = $type_obj->CreateRoleGroup(Instance => $self->Id,
821 Type => $type,
822 Domain => 'RT::Queue-Role');
823 unless ($id) {
824 $RT::Logger->error("Couldn't create a Queue group of type '$type' for queue ".
825 $self->Id.": ".$msg);
826 return(undef);
827 }
828
829 return $id;
830}
831
832
833
834# _HasModifyWatcherRight {{{
835sub _HasModifyWatcherRight {
836 my $self = shift;
837 my %args = (
838 Type => undef,
839 PrincipalId => undef,
840 Email => undef,
841 @_
842 );
843
844 return 1 if $self->CurrentUserHasRight('ModifyQueueWatchers');
845
846 #If the watcher we're trying to add is for the current user
847 if ( defined $args{'PrincipalId'} && $self->CurrentUser->PrincipalId eq $args{'PrincipalId'}) {
848 if ( $args{'Type'} eq 'AdminCc' ) {
849 return 1 if $self->CurrentUserHasRight('WatchAsAdminCc');
850 }
851 elsif ( $args{'Type'} eq 'Cc' or $args{'Type'} eq 'Requestor' ) {
852 return 1 if $self->CurrentUserHasRight('Watch');
853 }
854 else {
855 $RT::Logger->warning( "$self -> _HasModifyWatcher got passed a bogus type $args{Type}");
856 return ( 0, $self->loc('Invalid queue role group type [_1]', $args{Type}) );
857 }
858 }
859
860 return ( 0, $self->loc("Permission Denied") );
861}
862
863
864=head2 AddWatcher
865
866AddWatcher takes a parameter hash. The keys are as follows:
867
868Type One of Requestor, Cc, AdminCc
869
870PrinicpalId The RT::Principal id of the user or group that's being added as a watcher
871Email The email address of the new watcher. If a user with this
872 email address can't be found, a new nonprivileged user will be created.
873
874If 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.
875
876Returns a tuple of (status/id, message).
877
878=cut
879
880sub AddWatcher {
881 my $self = shift;
882 my %args = (
883 Type => undef,
884 PrincipalId => undef,
885 Email => undef,
886 @_
887 );
888
889 return ( 0, "No principal specified" )
890 unless $args{'Email'} or $args{'PrincipalId'};
891
892 if ( !$args{'PrincipalId'} && $args{'Email'} ) {
893 my $user = RT::User->new( $self->CurrentUser );
894 $user->LoadByEmail( $args{'Email'} );
895 $args{'PrincipalId'} = $user->PrincipalId if $user->id;
896 }
897
898 return ( 0, "Unknown watcher type [_1]", $args{Type} )
899 unless $self->IsRoleGroupType($args{Type});
900
901 my ($ok, $msg) = $self->_HasModifyWatcherRight(%args);
902 return ($ok, $msg) if !$ok;
903
904 return $self->_AddWatcher(%args);
905}
906
907#This contains the meat of AddWatcher. but can be called from a routine like
908# Create, which doesn't need the additional acl check
909sub _AddWatcher {
910 my $self = shift;
911 my %args = (
912 Type => undef,
913 Silent => undef,
914 PrincipalId => undef,
915 Email => undef,
916 @_
917 );
918
919
920 my $principal = RT::Principal->new( $self->CurrentUser );
921 if ( $args{'PrincipalId'} ) {
922 $principal->Load( $args{'PrincipalId'} );
923 if ( $principal->id and $principal->IsUser and my $email = $principal->Object->EmailAddress ) {
924 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'})))
925 if RT::EmailParser->IsRTAddress( $email );
926 }
927 }
928 elsif ( $args{'Email'} ) {
929 if ( RT::EmailParser->IsRTAddress( $args{'Email'} ) ) {
930 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'})));
931 }
932 my $user = RT::User->new($self->CurrentUser);
933 $user->LoadByEmail( $args{'Email'} );
934 $user->Load( $args{'Email'} )
935 unless $user->id;
936
937 if ( $user->Id ) { # If the user exists
938 $principal->Load( $user->PrincipalId );
939 } else {
940 # if the user doesn't exist, we need to create a new user
941 my $new_user = RT::User->new(RT->SystemUser);
942
943 my ( $Address, $Name ) =
944 RT::Interface::Email::ParseAddressFromHeader($args{'Email'});
945
946 my ( $Val, $Message ) = $new_user->Create(
947 Name => $Address,
948 EmailAddress => $Address,
949 RealName => $Name,
950 Privileged => 0,
951 Comments => 'Autocreated when added as a watcher'
952 );
953 unless ($Val) {
954 $RT::Logger->error("Failed to create user ".$args{'Email'} .": " .$Message);
955 # Deal with the race condition of two account creations at once
956 $new_user->LoadByEmail( $args{'Email'} );
957 }
958 $principal->Load( $new_user->PrincipalId );
959 }
960 }
961 # If we can't find this watcher, we need to bail.
962 unless ( $principal->Id ) {
963 return(0, $self->loc("Could not find or create that user"));
964 }
965
966 my $group = RT::Group->new($self->CurrentUser);
967 $group->LoadQueueRoleGroup(Type => $args{'Type'}, Queue => $self->Id);
968 unless ($group->id) {
969 return(0,$self->loc("Group not found"));
970 }
971
972 if ( $group->HasMember( $principal)) {
973
974 return ( 0, $self->loc('That principal is already a [_1] for this queue', $args{'Type'}) );
975 }
976
977
978 my ($m_id, $m_msg) = $group->_AddMember(PrincipalId => $principal->Id);
979 unless ($m_id) {
980 $RT::Logger->error("Failed to add ".$principal->Id." as a member of group ".$group->Id.": ".$m_msg);
981
982 return ( 0, $self->loc('Could not make that principal a [_1] for this queue', $args{'Type'}) );
983 }
984 return ( 1, $self->loc("Added [_1] to members of [_2] for this queue.", $principal->Object->Name, $args{'Type'} ));
985}
986
987
988
989=head2 DeleteWatcher { Type => TYPE, PrincipalId => PRINCIPAL_ID, Email => EMAIL_ADDRESS }
990
991
992Deletes a queue watcher. Takes two arguments:
993
994Type (one of Requestor,Cc,AdminCc)
995
996and one of
997
998PrincipalId (an RT::Principal Id of the watcher you want to remove)
999 OR
1000Email (the email address of an existing wathcer)
1001
1002
1003=cut
1004
1005
1006sub DeleteWatcher {
1007 my $self = shift;
1008
1009 my %args = ( Type => undef,
1010 PrincipalId => undef,
1011 Email => undef,
1012 @_ );
1013
1014 unless ( $args{'PrincipalId'} || $args{'Email'} ) {
1015 return ( 0, $self->loc("No principal specified") );
1016 }
1017
1018 if ( !$args{PrincipalId} and $args{Email} ) {
1019 my $user = RT::User->new( $self->CurrentUser );
1020 my ($rv, $msg) = $user->LoadByEmail( $args{Email} );
1021 $args{PrincipalId} = $user->PrincipalId if $rv;
1022 }
1023
1024 my $principal = RT::Principal->new( $self->CurrentUser );
1025 if ( $args{'PrincipalId'} ) {
1026 $principal->Load( $args{'PrincipalId'} );
1027 }
1028 else {
1029 my $user = RT::User->new( $self->CurrentUser );
1030 $user->LoadByEmail( $args{'Email'} );
1031 $principal->Load( $user->Id );
1032 }
1033
1034 # If we can't find this watcher, we need to bail.
1035 unless ( $principal->Id ) {
1036 return ( 0, $self->loc("Could not find that principal") );
1037 }
1038
1039 my $group = RT::Group->new($self->CurrentUser);
1040 $group->LoadQueueRoleGroup(Type => $args{'Type'}, Queue => $self->Id);
1041 unless ($group->id) {
1042 return(0,$self->loc("Group not found"));
1043 }
1044
1045 return ( 0, $self->loc('Unknown watcher type [_1]', $args{Type}) )
1046 unless $self->IsRoleGroupType($args{Type});
1047
1048 my ($ok, $msg) = $self->_HasModifyWatcherRight(%args);
1049 return ($ok, $msg) if !$ok;
1050
1051 # see if this user is already a watcher.
1052
1053 unless ( $group->HasMember($principal)) {
1054 return ( 0,
1055 $self->loc('That principal is not a [_1] for this queue', $args{'Type'}) );
1056 }
1057
1058 my ($m_id, $m_msg) = $group->_DeleteMember($principal->Id);
1059 unless ($m_id) {
1060 $RT::Logger->error("Failed to delete ".$principal->Id.
1061 " as a member of group ".$group->Id.": ".$m_msg);
1062
1063 return ( 0, $self->loc('Could not remove that principal as a [_1] for this queue', $args{'Type'}) );
1064 }
1065
1066 return ( 1, $self->loc("Removed [_1] from members of [_2] for this queue.", $principal->Object->Name, $args{'Type'} ));
1067}
1068
1069
1070
1071=head2 AdminCcAddresses
1072
1073returns String: All queue AdminCc email addresses as a string
1074
1075=cut
1076
1077sub AdminCcAddresses {
1078 my $self = shift;
1079
1080 unless ( $self->CurrentUserHasRight('SeeQueue') ) {
1081 return undef;
1082 }
1083
1084 return ( $self->AdminCc->MemberEmailAddressesAsString )
1085
1086}
1087
1088
1089
1090=head2 CcAddresses
1091
1092returns String: All queue Ccs as a string of email addresses
1093
1094=cut
1095
1096sub CcAddresses {
1097 my $self = shift;
1098
1099 unless ( $self->CurrentUserHasRight('SeeQueue') ) {
1100 return undef;
1101 }
1102
1103 return ( $self->Cc->MemberEmailAddressesAsString);
1104
1105}
1106
1107
1108
1109=head2 Cc
1110
1111Takes nothing.
1112Returns an RT::Group object which contains this Queue's Ccs.
1113If the user doesn't have "ShowQueue" permission, returns an empty group
1114
1115=cut
1116
1117sub Cc {
1118 my $self = shift;
1119
1120 my $group = RT::Group->new($self->CurrentUser);
1121 if ( $self->CurrentUserHasRight('SeeQueue') ) {
1122 $group->LoadQueueRoleGroup(Type => 'Cc', Queue => $self->Id);
1123 }
1124 return ($group);
1125
1126}
1127
1128
1129
1130=head2 AdminCc
1131
1132Takes nothing.
1133Returns an RT::Group object which contains this Queue's AdminCcs.
1134If the user doesn't have "ShowQueue" permission, returns an empty group
1135
1136=cut
1137
1138sub AdminCc {
1139 my $self = shift;
1140
1141 my $group = RT::Group->new($self->CurrentUser);
1142 if ( $self->CurrentUserHasRight('SeeQueue') ) {
1143 $group->LoadQueueRoleGroup(Type => 'AdminCc', Queue => $self->Id);
1144 }
1145 return ($group);
1146
1147}
1148
1149
1150
1151# a generic routine to be called by IsRequestor, IsCc and IsAdminCc
1152
1153=head2 IsWatcher { Type => TYPE, PrincipalId => PRINCIPAL_ID }
1154
1155Takes a param hash with the attributes Type and PrincipalId
1156
1157Type is one of Requestor, Cc, AdminCc and Owner
1158
1159PrincipalId is an RT::Principal id
1160
1161Returns true if that principal is a member of the group Type for this queue
1162
1163
1164=cut
1165
1166sub IsWatcher {
1167 my $self = shift;
1168
1169 my %args = ( Type => 'Cc',
1170 PrincipalId => undef,
1171 @_
1172 );
1173
1174 # Load the relevant group.
1175 my $group = RT::Group->new($self->CurrentUser);
1176 $group->LoadQueueRoleGroup(Type => $args{'Type'}, Queue => $self->id);
1177 # Ask if it has the member in question
1178
1179 my $principal = RT::Principal->new($self->CurrentUser);
1180 $principal->Load($args{'PrincipalId'});
1181 unless ($principal->Id) {
1182 return (undef);
1183 }
1184
1185 return ($group->HasMemberRecursively($principal));
1186}
1187
1188
1189
1190
1191=head2 IsCc PRINCIPAL_ID
1192
1193Takes an RT::Principal id.
1194Returns true if the principal is a requestor of the current queue.
1195
1196
1197=cut
1198
1199sub IsCc {
1200 my $self = shift;
1201 my $cc = shift;
1202
1203 return ( $self->IsWatcher( Type => 'Cc', PrincipalId => $cc ) );
1204
1205}
1206
1207
1208
1209=head2 IsAdminCc PRINCIPAL_ID
1210
1211Takes an RT::Principal id.
1212Returns true if the principal is a requestor of the current queue.
1213
1214=cut
1215
1216sub IsAdminCc {
1217 my $self = shift;
1218 my $person = shift;
1219
1220 return ( $self->IsWatcher( Type => 'AdminCc', PrincipalId => $person ) );
1221
1222}
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233sub _Set {
1234 my $self = shift;
1235
1236 unless ( $self->CurrentUserHasRight('AdminQueue') ) {
1237 return ( 0, $self->loc('Permission Denied') );
1238 }
1239 return ( $self->SUPER::_Set(@_) );
1240}
1241
1242
1243
1244sub _Value {
1245 my $self = shift;
1246
1247 unless ( $self->CurrentUserHasRight('SeeQueue') ) {
1248 return (undef);
1249 }
1250
1251 return ( $self->__Value(@_) );
1252}
1253
1254
1255
1256=head2 CurrentUserHasRight
1257
1258Takes one argument. A textual string with the name of the right we want to check.
1259Returns true if the current user has that right for this queue.
1260Returns undef otherwise.
1261
1262=cut
1263
1264sub CurrentUserHasRight {
1265 my $self = shift;
1266 my $right = shift;
1267
1268 return (
1269 $self->HasRight(
1270 Principal => $self->CurrentUser,
1271 Right => "$right"
1272 )
1273 );
1274
1275}
1276
1277=head2 CurrentUserCanSee
1278
1279Returns true if the current user can see the queue, using SeeQueue
1280
1281=cut
1282
1283sub CurrentUserCanSee {
1284 my $self = shift;
1285
1286 return $self->CurrentUserHasRight('SeeQueue');
1287}
1288
1289
1290=head2 HasRight
1291
1292Takes a param hash with the fields 'Right' and 'Principal'.
1293Principal defaults to the current user.
1294Returns true if the principal has that right for this queue.
1295Returns undef otherwise.
1296
1297=cut
1298
1299# TAKES: Right and optional "Principal" which defaults to the current user
1300sub HasRight {
1301 my $self = shift;
1302 my %args = (
1303 Right => undef,
1304 Principal => $self->CurrentUser,
1305 @_
1306 );
1307 my $principal = delete $args{'Principal'};
1308 unless ( $principal ) {
1309 $RT::Logger->error("Principal undefined in Queue::HasRight");
1310 return undef;
1311 }
1312
1313 return $principal->HasRight(
1314 %args,
1315 Object => ($self->Id ? $self : $RT::System),
1316 );
1317}
1318
1319
1320
1321
1322=head2 id
1323
1324Returns the current value of id.
1325(In the database, id is stored as int(11).)
1326
1327
1328=cut
1329
1330
1331=head2 Name
1332
1333Returns the current value of Name.
1334(In the database, Name is stored as varchar(200).)
1335
1336
1337
1338=head2 SetName VALUE
1339
1340
1341Set Name to VALUE.
1342Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1343(In the database, Name will be stored as a varchar(200).)
1344
1345
1346=cut
1347
1348
1349=head2 Description
1350
1351Returns the current value of Description.
1352(In the database, Description is stored as varchar(255).)
1353
1354
1355
1356=head2 SetDescription VALUE
1357
1358
1359Set Description to VALUE.
1360Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1361(In the database, Description will be stored as a varchar(255).)
1362
1363
1364=cut
1365
1366
1367=head2 CorrespondAddress
1368
1369Returns the current value of CorrespondAddress.
1370(In the database, CorrespondAddress is stored as varchar(120).)
1371
1372
1373
1374=head2 SetCorrespondAddress VALUE
1375
1376
1377Set CorrespondAddress to VALUE.
1378Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1379(In the database, CorrespondAddress will be stored as a varchar(120).)
1380
1381
1382=cut
1383
1384
1385=head2 CommentAddress
1386
1387Returns the current value of CommentAddress.
1388(In the database, CommentAddress is stored as varchar(120).)
1389
1390
1391
1392=head2 SetCommentAddress VALUE
1393
1394
1395Set CommentAddress to VALUE.
1396Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1397(In the database, CommentAddress will be stored as a varchar(120).)
1398
1399
1400=cut
1401
1402
1403=head2 Lifecycle
1404
1405Returns the current value of Lifecycle.
1406(In the database, Lifecycle is stored as varchar(32).)
1407
1408
1409
1410=head2 SetLifecycle VALUE
1411
1412
1413Set Lifecycle to VALUE.
1414Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1415(In the database, Lifecycle will be stored as a varchar(32).)
1416
1417
1418=cut
1419
1420=head2 SubjectTag
1421
1422Returns the current value of SubjectTag.
1423(In the database, SubjectTag is stored as varchar(120).)
1424
1425
1426
1427=head2 SetSubjectTag VALUE
1428
1429
1430Set SubjectTag to VALUE.
1431Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1432(In the database, SubjectTag will be stored as a varchar(120).)
1433
1434
1435=cut
1436
1437
1438=head2 InitialPriority
1439
1440Returns the current value of InitialPriority.
1441(In the database, InitialPriority is stored as int(11).)
1442
1443
1444
1445=head2 SetInitialPriority VALUE
1446
1447
1448Set InitialPriority to VALUE.
1449Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1450(In the database, InitialPriority will be stored as a int(11).)
1451
1452
1453=cut
1454
1455
1456=head2 FinalPriority
1457
1458Returns the current value of FinalPriority.
1459(In the database, FinalPriority is stored as int(11).)
1460
1461
1462
1463=head2 SetFinalPriority VALUE
1464
1465
1466Set FinalPriority to VALUE.
1467Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1468(In the database, FinalPriority will be stored as a int(11).)
1469
1470
1471=cut
1472
1473
1474=head2 DefaultDueIn
1475
1476Returns the current value of DefaultDueIn.
1477(In the database, DefaultDueIn is stored as int(11).)
1478
1479
1480
1481=head2 SetDefaultDueIn VALUE
1482
1483
1484Set DefaultDueIn to VALUE.
1485Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1486(In the database, DefaultDueIn will be stored as a int(11).)
1487
1488
1489=cut
1490
1491
1492=head2 Creator
1493
1494Returns the current value of Creator.
1495(In the database, Creator is stored as int(11).)
1496
1497
1498=cut
1499
1500
1501=head2 Created
1502
1503Returns the current value of Created.
1504(In the database, Created is stored as datetime.)
1505
1506
1507=cut
1508
1509
1510=head2 LastUpdatedBy
1511
1512Returns the current value of LastUpdatedBy.
1513(In the database, LastUpdatedBy is stored as int(11).)
1514
1515
1516=cut
1517
1518
1519=head2 LastUpdated
1520
1521Returns the current value of LastUpdated.
1522(In the database, LastUpdated is stored as datetime.)
1523
1524
1525=cut
1526
1527
1528=head2 Disabled
1529
1530Returns the current value of Disabled.
1531(In the database, Disabled is stored as smallint(6).)
1532
1533
1534
1535=head2 SetDisabled VALUE
1536
1537
1538Set Disabled to VALUE.
1539Returns (1, 'Status message') on success and (0, 'Error Message') on failure.
1540(In the database, Disabled will be stored as a smallint(6).)
1541
1542
1543=cut
1544
1545
1546
1547sub _CoreAccessible {
1548 {
1549
1550 id =>
1551 {read => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => ''},
1552 Name =>
1553 {read => 1, write => 1, sql_type => 12, length => 200, is_blob => 0, is_numeric => 0, type => 'varchar(200)', default => ''},
1554 Description =>
1555 {read => 1, write => 1, sql_type => 12, length => 255, is_blob => 0, is_numeric => 0, type => 'varchar(255)', default => ''},
1556 CorrespondAddress =>
1557 {read => 1, write => 1, sql_type => 12, length => 120, is_blob => 0, is_numeric => 0, type => 'varchar(120)', default => ''},
1558 CommentAddress =>
1559 {read => 1, write => 1, sql_type => 12, length => 120, is_blob => 0, is_numeric => 0, type => 'varchar(120)', default => ''},
1560 SubjectTag =>
1561 {read => 1, write => 1, sql_type => 12, length => 120, is_blob => 0, is_numeric => 0, type => 'varchar(120)', default => ''},
1562 Lifecycle =>
1563 {read => 1, write => 1, sql_type => 12, length => 32, is_blob => 0, is_numeric => 0, type => 'varchar(32)', default => ''},
1564 InitialPriority =>
1565 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
1566 FinalPriority =>
1567 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
1568 DefaultDueIn =>
1569 {read => 1, write => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
1570 Creator =>
1571 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
1572 Created =>
1573 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
1574 LastUpdatedBy =>
1575 {read => 1, auto => 1, sql_type => 4, length => 11, is_blob => 0, is_numeric => 1, type => 'int(11)', default => '0'},
1576 LastUpdated =>
1577 {read => 1, auto => 1, sql_type => 11, length => 0, is_blob => 0, is_numeric => 0, type => 'datetime', default => ''},
1578 Disabled =>
1579 {read => 1, write => 1, sql_type => 5, length => 6, is_blob => 0, is_numeric => 1, type => 'smallint(6)', default => '0'},
1580
1581 }
1582};
1583
1584
1585
1586RT::Base->_ImportOverlays();
1587
15881;