import rt 3.8.10
[freeside.git] / rt / sbin / rt-email-group-admin
1 #!/usr/bin/perl
2 # BEGIN BPS TAGGED BLOCK {{{
3 #
4 # COPYRIGHT:
5 #
6 # This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC
7 #                                          <sales@bestpractical.com>
8 #
9 # (Except where explicitly superseded by other copyright notices)
10 #
11 #
12 # LICENSE:
13 #
14 # This work is made available to you under the terms of Version 2 of
15 # the GNU General Public License. A copy of that license should have
16 # been provided with this software, but in any event can be snarfed
17 # from www.gnu.org.
18 #
19 # This work is distributed in the hope that it will be useful, but
20 # WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
22 # General Public License for more details.
23 #
24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
27 # 02110-1301 or visit their web page on the internet at
28 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
29 #
30 #
31 # CONTRIBUTION SUBMISSION POLICY:
32 #
33 # (The following paragraph is not intended to limit the rights granted
34 # to you to modify and distribute this software under the terms of
35 # the GNU General Public License and is only of importance to you if
36 # you choose to contribute your changes and enhancements to the
37 # community by submitting them to Best Practical Solutions, LLC.)
38 #
39 # By intentionally submitting any modifications, corrections or
40 # derivatives to this work, or any other work intended for use with
41 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
42 # you are the copyright holder for those contributions and you grant
43 # Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
44 # royalty-free, perpetual, license to use, copy, create derivative
45 # works based on those contributions, and sublicense and distribute
46 # those contributions and any derivatives thereof.
47 #
48 # END BPS TAGGED BLOCK }}}
49 =head1 NAME
50
51 rt-email-group-admin - Command line tool for administrating NotifyGroup actions
52
53 =head1 SYNOPSIS
54
55     rt-email-group-admin --list
56     rt-email-group-admin --create 'Notify foo team' --group Foo
57     rt-email-group-admin --create 'Notify foo team as comment' --comment --group Foo
58     rt-email-group-admin --create 'Notify group Foo and Bar' --group Foo --group Bar
59     rt-email-group-admin --create 'Notify user foo@bar.com' --user foo@bar.com
60     rt-email-group-admin --create 'Notify VIPs' --user vip1@bar.com
61     rt-email-group-admin --add 'Notify VIPs' --user vip2@bar.com --group vip1 --user vip3@foo.com
62     rt-email-group-admin --rename 'Notify VIPs' --newname 'Inform VIPs'
63     rt-email-group-admin --switch 'Notify VIPs'
64     rt-email-group-admin --delete 'Notify user foo@bar.com'
65
66 =head1 DESCRIPTION
67
68 This script list, create, modify or delete scrip actions in the RT DB. Once
69 you've created an action you can use it in a scrip.
70
71 For example you can create the following action using this script:
72
73     rt-email-group-admin --create 'Notify developers' --group 'Development Team'
74
75 Then you can add the followoing scrip to your Bugs queue:
76     
77     Condition: On Create
78     Action:    Notify developers
79     Template:  Transaction
80     Stage:     TransactionCreate
81
82 Your development team will be notified on every new ticket in the queue.
83
84 =cut
85
86 use warnings;
87 use strict;
88
89 # fix lib paths, some may be relative
90 BEGIN {
91     require File::Spec;
92     my @libs = ("lib", "local/lib");
93     my $bin_path;
94
95     for my $lib (@libs) {
96         unless ( File::Spec->file_name_is_absolute($lib) ) {
97             unless ($bin_path) {
98                 if ( File::Spec->file_name_is_absolute(__FILE__) ) {
99                     $bin_path = ( File::Spec->splitpath(__FILE__) )[1];
100                 }
101                 else {
102                     require FindBin;
103                     no warnings "once";
104                     $bin_path = $FindBin::Bin;
105                 }
106             }
107             $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
108         }
109         unshift @INC, $lib;
110     }
111
112 }
113
114 use RT;
115 RT::LoadConfig;
116 RT::Init;
117
118 require RT::Principal;
119 require RT::User;
120 require RT::Group;
121 require RT::ScripActions;
122
123 use Getopt::Long qw(GetOptions);
124
125 our $cmd = 'usage';
126 our $opts = {};
127
128 sub parse_args {
129     my $tmp;
130     Getopt::Long::Configure( "pass_through" );
131     if ( GetOptions( 'list' => \$tmp ) && $tmp ) {
132         $cmd = 'list';
133     }
134     elsif ( GetOptions( 'create=s' => \$tmp ) && $tmp ) {
135         $cmd = 'create';
136         $opts->{'name'} = $tmp;
137         $opts->{'groups'} = [];
138         $opts->{'users'} = [];
139         GetOptions( 'comment' => \$opts->{'comment'} );
140         GetOptions( 'group:s@' => $opts->{'groups'} );
141         GetOptions( 'user:s@' => $opts->{'users'} );
142         unless ( @{ $opts->{'users'} } + @{ $opts->{'groups'} } ) {
143             usage();
144             exit(-1);
145         }
146     }
147     elsif ( GetOptions( 'add=s' => \$tmp ) && $tmp ) {
148         $cmd = 'add';
149         $opts->{'name'} = $tmp;
150         $opts->{'groups'} = [];
151         $opts->{'users'} = [];
152         GetOptions( 'group:s@' => $opts->{'groups'} );
153         GetOptions( 'user:s@' => $opts->{'users'} );
154         unless ( @{ $opts->{'users'} } + @{ $opts->{'groups'} } ) {
155             usage();
156             exit(-1);
157         }
158     }
159     elsif ( GetOptions( 'switch=s' => \$tmp ) && $tmp ) {
160         $cmd = 'switch';
161         $opts->{'name'} = $tmp;
162     }
163     elsif ( GetOptions( 'rename=s' => \$tmp ) && $tmp ) {
164         $cmd = 'rename';
165         $opts->{'name'} = $tmp;
166         GetOptions( 'newname=s' => \$opts->{'newname'} );
167         unless ( $opts->{'newname'} ) {
168             usage();
169             exit(-1);
170         }
171     }
172     elsif ( GetOptions( 'delete=s' => \$tmp ) && $tmp) {
173         $cmd = 'delete';
174         $opts->{'name'} = $tmp;
175     } else {
176         $cmd = 'usage';
177     }
178     
179     return;
180 }
181
182 sub usage {
183     local $@;
184     eval "require Pod::PlainText;";
185     if ( $@ ) {
186         print "see `perldoc $0`\n";
187     } else {
188         my $parser = Pod::PlainText->new( sentence => 0, width => 78 );
189         $parser->parse_from_file( $0 );
190     }
191 }
192
193 parse_args();
194
195 {
196     eval "main::$cmd()";
197     if ( $@ ) {
198         print STDERR $@ ."\n";
199     }
200 }
201
202 exit(0);
203
204 =head1 USAGE
205
206 rt-email-group-admin --COMMAND ARGS
207
208 =head1 COMMANDS
209
210 =head2 list
211
212 Lists actions and its descriptions.
213
214 =cut
215
216 sub list {
217     my $actions = _get_our_actions();
218     while( my $a = $actions->Next ) {
219         _list( $a );
220     }
221     return;
222 }
223
224 sub _list {
225     my $action = shift;
226
227     print "Name: ". $action->Name() ."\n";
228     print "Module: ". $action->ExecModule() ."\n";
229
230     my @princ = argument_to_list( $action );
231
232     print "Members: \n";
233     foreach( @princ ) {
234         my $obj = RT::Principal->new( $RT::SystemUser );
235         $obj->Load( $_ );
236         next unless $obj->id;
237
238         print "\t". $obj->PrincipalType;
239         print "\t=> ". $obj->Object->Name;
240         print "(Disabled!!!)" if $obj->Disabled;
241         print "\n";
242     }
243     print "\n";
244     return;
245 }
246
247 =head2 create NAME [--comment] [--group GNAME] [--user UNAME]
248
249 Creates new action with NAME and adds users and/or groups to its
250 recipient list. Would be notify as comment if --comment specified.
251
252 =cut
253
254 sub create {
255     my $actions = RT::ScripActions->new( $RT::SystemUser );
256     $actions->Limit(
257         FIELD => 'Name',
258         VALUE => $opts->{'name'},
259     );
260     if ( $actions->Count ) {
261         print STDERR "ScripAction '". $opts->{'name'} ."' allready exists\n";
262         exit(-1);
263     }
264
265     my @groups = _check_groups( @{ $opts->{'groups'} } );
266     my @users  = _check_users( @{ $opts->{'users'} } );    
267     unless ( @users + @groups ) {
268         print STDERR "List of groups and users is empty\n";
269         exit(-1);
270     }
271
272     my $action = __create_empty( $opts->{'name'}, $opts->{'comment'} );
273
274     __add( $action, $_ ) foreach( @users );
275     __add( $action, $_ ) foreach( @groups );
276
277     return;
278 }
279
280 sub __create_empty {
281     my $name = shift;
282     my $as_comment = shift || 0;
283     require RT::ScripAction;
284     my $action = RT::ScripAction->new( $RT::SystemUser );
285     $action->Create(
286         Name => $name,
287         Description => "Created with rt-email-group-admin script",
288         ExecModule => $as_comment? 'NotifyGroupAsComment': 'NotifyGroup',
289         Argument => '',
290     );
291
292     return $action;
293 }
294
295 sub _check_groups
296 {
297     return grep { $_ ? 1: do { print STDERR "Group '$_' skipped, doesn't exist\n"; 0; } }
298         map { __check_group($_) } @_;
299 }
300
301 sub __check_group
302 {
303     my $instance = shift;
304     require RT::Group;
305     my $obj = RT::Group->new( $RT::SystemUser );
306     $obj->LoadUserDefinedGroup( $instance );
307     return $obj->id ? $obj : undef;
308 }
309
310 sub _check_users
311 {
312     return grep { $_ ? 1: do { print STDERR "User '$_' skipped, doesn't exist\n"; 0; } }
313         map { __check_user($_) } @_;
314 }
315
316 sub __check_user
317 {
318     my $instance = shift;
319     require RT::User;
320     my $obj = RT::User->new( $RT::SystemUser );
321     $obj->Load( $instance );
322     return $obj->id ? $obj : undef;
323 }
324
325 =head2 add NAME [--group GNAME] [--user UNAME]
326
327 Adds groups and/or users to recipients of the action NAME.
328
329 =cut
330
331 sub add {
332     my $action = _get_action_by_name( $opts->{'name'} );
333     unless ( $action ) {
334         print STDERR "ScripAction '". $opts->{'name'} ."' doesn't exist\n";
335         exit(-1);
336     }
337
338     my @groups = _check_groups( @{ $opts->{'groups'} } );
339     my @users = _check_users( @{ $opts->{'users'} } );
340     
341     unless ( @users + @groups ) {
342         print STDERR "List of groups and users is empty\n";
343         exit(-1);
344     }
345
346     __add( $action, $_ ) foreach @users;
347     __add( $action, $_ ) foreach @groups;
348
349     return;
350 }
351
352 sub __add
353 {
354     my $action = shift;
355     my $obj = shift;
356
357     my @cur = argument_to_list( $action );
358
359     my $id = $obj->id;
360     return if grep $_ == $id, @cur;
361
362     push @cur, $id;
363
364     return $action->__Set( Field => 'Argument', Value => join(',', @cur) );
365 }
366
367 =head2 delete NAME
368
369 Deletes action NAME if scrips doesn't use it.
370
371 =cut
372
373 sub delete {
374     my $action = _get_action_by_name( $opts->{'name'} );
375     unless ( $action ) {
376         print STDERR "ScripAction '". $opts->{'name'} ."' doesn't exist\n";
377         exit(-1);
378     }
379
380     require RT::Scrips;
381     my $scrips = RT::Scrips->new( $RT::SystemUser );
382     $scrips->Limit( FIELD => 'ScripAction', VALUE => $action->id );
383     if ( $scrips->Count ) {
384         my @sid;
385         while( my $s = $scrips->Next ) {
386             push @sid, $s->id;
387         }
388         print STDERR "ScripAction '". $opts->{'name'} ."'"
389             . " is in use by Scrip(s) ". join( ", ", map "#$_", @sid )
390             . "\n";
391         exit(-1);
392     }
393
394     return __delete( $action );
395 }
396
397 sub __delete {
398     require DBIx::SearchBuilder::Record;
399     return DBIx::SearchBuilder::Record::Delete( shift );
400 }
401
402 sub _get_action_by_name {
403     my $name = shift;
404     my $actions = _get_our_actions();
405     $actions->Limit(
406         FIELD => 'Name',
407         VALUE => $name
408     );
409
410     if ( $actions->Count > 1 ) {
411         print STDERR "More then one ScripAction with name '$name'\n";
412     }
413
414     return $actions->First;
415 }
416
417 =head2 switch NAME
418
419 Switch action NAME from notify as correspondence to comment and back.
420
421 =cut
422
423 sub switch {
424     my $action = _get_action_by_name( $opts->{'name'} );
425     unless ( $action ) {
426         print STDERR "ScripAction '". $opts->{'name'} ."' doesn't exist\n";
427         exit(-1);
428     }
429
430     my %h = (
431         NotifyGroup => 'NotifyGroupAsComment',
432         NotifyGroupAsComment => 'NotifyGroup'
433     );
434
435     return $action->__Set(
436         Field => 'ExecModule',
437         Value => $h{ $action->ExecModule }
438     );
439 }
440
441 =head2 rename NAME --newname NEWNAME
442
443 Renames action NAME to NEWNAME.
444
445 =cut
446
447 sub rename {
448     my $action = _get_action_by_name( $opts->{'name'} );
449     unless ( $action ) {
450         print STDERR "ScripAction '". $opts->{'name'} ."' doesn't exist\n";
451         exit(-1);
452     }
453
454     my $actions = RT::ScripActions->new( $RT::SystemUser );
455     $actions->Limit( FIELD => 'Name', VALUE => $opts->{'newname'} );
456     if ( $actions->Count ) {
457         print STDERR "ScripAction '". $opts->{'newname'} ."' allready exists\n";
458         exit(-1);
459     }
460
461     return $action->__Set(
462         Field => 'Name',
463         Value => $opts->{'newname'},
464     );
465 }
466
467 =head2 NOTES
468
469 If command has option --group or --user then you can use it more then once,
470 if other is not specified.
471
472 =cut
473
474 ###############
475 #### Utils ####
476 ###############
477
478 sub argument_to_list {
479     my $action = shift;
480     require RT::Action::NotifyGroup;
481     return RT::Action::NotifyGroup->__SplitArg( $action->Argument );
482 }
483
484 sub _get_our_actions {
485     my $actions = RT::ScripActions->new( $RT::SystemUser );
486     $actions->Limit(
487         FIELD => 'ExecModule',
488         VALUE => 'NotifyGroup',
489         ENTRYAGGREGATOR => 'OR',
490     );
491     $actions->Limit(
492         FIELD => 'ExecModule',
493         VALUE => 'NotifyGroupAsComment',
494         ENTRYAGGREGATOR => 'OR',
495     );
496
497     return $actions;
498 }
499
500 =head1 AUTHOR
501
502 Ruslan U. Zakirov E<lt>ruz@bestpractical.comE<gt>
503
504 =head1 SEE ALSO
505
506 L<RT::Action::NotifyGroup>, L<RT::Action::NotifyGroupAsComment>
507
508 =cut