Putting 4.2.0 on top of 4.0.17
[usit-rt.git] / lib / RT / Shredder / Plugin / Users.pm
CommitLineData
84fb5b46
MKG
1# BEGIN BPS TAGGED BLOCK {{{
2#
3# COPYRIGHT:
4#
403d7b0b 5# This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC
84fb5b46
MKG
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
49package RT::Shredder::Plugin::Users;
50
51use strict;
52use warnings FATAL => 'all';
53use base qw(RT::Shredder::Plugin::Base::Search);
54
55=head1 NAME
56
57RT::Shredder::Plugin::Users - search plugin for wiping users.
58
59=head1 ARGUMENTS
60
61=head2 status - string
62
63Status argument allow you to limit result set to C<disabled>,
64C<enabled> or C<any> users.
65B<< Default value is C<disabled>. >>
66
67=head2 name - mask
68
69User name mask.
70
71=head2 email - mask
72
73Email address mask.
74
75=head2 member_of - group identifier
76
77Using this option users that are members of a particular group can
78be selected for deletion. Identifier is name of user defined group
79or id of a group, as well C<Privileged> or <unprivileged> can used
80to select people from system groups.
81
82=head2 replace_relations - user identifier
83
84When you delete user there are could be minor links to him in RT DB.
85This option allow you to replace this links with link to other user.
86This links are Creator and LastUpdatedBy, but NOT any watcher roles,
87this means that if user is watcher(Requestor, Owner,
88Cc or AdminCc) of the ticket or queue then link would be deleted.
89
90This argument could be user id or name.
91
92=head2 no_tickets - boolean
93
94If true then plugin looks for users who are not watchers (Owners,
95Requestors, Ccs or AdminCcs) of any ticket.
96
97Before RT 3.8.5, users who were watchers of deleted tickets B<will be deleted>
98when this option was enabled. Decision has been made that it's not correct
99and you should either shred these deleted tickets, change watchers or
100explicitly delete user by name or email.
101
102Note that found users still B<may have relations> with other objects,
103for example via Creator or LastUpdatedBy fields, and you most probably
104want to use C<replace_relations> option.
105
106=cut
107
108sub SupportArgs
109{
110 return $_[0]->SUPER::SupportArgs,
111 qw(status name email member_of replace_relations no_tickets);
112}
113
114sub TestArgs
115{
116 my $self = shift;
117 my %args = @_;
118 if( $args{'status'} ) {
119 unless( $args{'status'} =~ /^(disabled|enabled|any)$/i ) {
120 return (0, "Status '$args{'status'}' is unsupported.");
121 }
122 } else {
123 $args{'status'} = 'disabled';
124 }
125 if( $args{'email'} ) {
126 $args{'email'} = $self->ConvertMaskToSQL( $args{'email'} );
127 }
128 if( $args{'name'} ) {
129 $args{'name'} = $self->ConvertMaskToSQL( $args{'name'} );
130 }
131 if( $args{'member_of'} ) {
132 my $group = RT::Group->new( RT->SystemUser );
133 if ( $args{'member_of'} =~ /^(Everyone|Privileged|Unprivileged)$/i ) {
134 $group->LoadSystemInternalGroup( $args{'member_of'} );
135 }
136 else {
137 $group->LoadUserDefinedGroup( $args{'member_of'} );
138 }
139 unless ( $group->id ) {
140 return (0, "Couldn't load group '$args{'member_of'}'" );
141 }
142 $args{'member_of'} = $group->id;
143
144 }
145 if( $args{'replace_relations'} ) {
146 my $uid = $args{'replace_relations'};
147 # XXX: it's possible that SystemUser is not available
148 my $user = RT::User->new( RT->SystemUser );
149 $user->Load( $uid );
150 unless( $user->id ) {
151 return (0, "Couldn't load user '$uid'" );
152 }
153 $args{'replace_relations'} = $user->id;
154 }
155 return $self->SUPER::TestArgs( %args );
156}
157
158sub Run
159{
160 my $self = shift;
161 my %args = ( Shredder => undef, @_ );
162 my $objs = RT::Users->new( RT->SystemUser );
163 # XXX: we want preload only things we need, but later while
164 # logging we need all data, TODO envestigate this
165 # $objs->Columns(qw(id Name EmailAddress Lang Timezone
166 # Creator Created LastUpdated LastUpdatedBy));
167 if( my $s = $self->{'opt'}{'status'} ) {
168 if( $s eq 'any' ) {
169 $objs->FindAllRows;
170 } elsif( $s eq 'disabled' ) {
171 $objs->LimitToDeleted;
172 } else {
173 $objs->LimitToEnabled;
174 }
175 }
176 if( $self->{'opt'}{'email'} ) {
177 $objs->Limit( FIELD => 'EmailAddress',
178 OPERATOR => 'MATCHES',
179 VALUE => $self->{'opt'}{'email'},
180 );
181 }
182 if( $self->{'opt'}{'name'} ) {
183 $objs->Limit( FIELD => 'Name',
184 OPERATOR => 'MATCHES',
185 VALUE => $self->{'opt'}{'name'},
af59614d 186 CASESENSITIVE => 0,
84fb5b46
MKG
187 );
188 }
189 if( $self->{'opt'}{'member_of'} ) {
190 $objs->MemberOfGroup( $self->{'opt'}{'member_of'} );
191 }
192 if( $self->{'opt'}{'no_tickets'} ) {
193 return $self->FilterWithoutTickets(
194 Shredder => $args{'Shredder'},
195 Objects => $objs,
196 );
197 } else {
198 if( $self->{'opt'}{'limit'} ) {
199 $objs->RowsPerPage( $self->{'opt'}{'limit'} );
200 }
201 }
202 return (1, $objs);
203}
204
205sub SetResolvers
206{
207 my $self = shift;
208 my %args = ( Shredder => undef, @_ );
209
210 if( $self->{'opt'}{'replace_relations'} ) {
211 my $uid = $self->{'opt'}{'replace_relations'};
212 my $resolver = sub {
213 my %args = (@_);
214 my $t = $args{'TargetObject'};
215 foreach my $method ( qw(Creator LastUpdatedBy) ) {
216 next unless $t->_Accessible( $method => 'read' );
217 $t->__Set( Field => $method, Value => $uid );
218 }
219 };
220 $args{'Shredder'}->PutResolver( BaseClass => 'RT::User', Code => $resolver );
221 }
222 return (1);
223}
224
225sub FilterWithoutTickets {
226 my $self = shift;
227 my %args = (
228 Shredder => undef,
229 Objects => undef,
230 @_,
231 );
232 my $users = $args{Objects};
233 $self->FetchNext( $users, 'init' );
234
235 my @res;
236 while ( my $user = $self->FetchNext( $users ) ) {
237 push @res, $user if $self->_WithoutTickets( $user );
238 return (1, \@res) if $self->{'opt'}{'limit'} && @res >= $self->{'opt'}{'limit'};
239 }
240 return (1, \@res);
241}
242
243sub _WithoutTickets {
244 my ($self, $user) = @_;
245 my $tickets = RT::Tickets->new( RT->SystemUser );
246 $tickets->{'allow_deleted_search'} = 1;
247 $tickets->FromSQL( 'Watcher.id = '. $user->id );
248 # HACK: we may use Count method which counts all records
249 # that match condtion, but we really want to know only that
250 # at least one record exist, so we fetch first row only
251 $tickets->RowsPerPage(1);
252 return !$tickets->First;
253}
254
2551;