summaryrefslogtreecommitdiff
path: root/rt/lib/RT/Shredder/Plugin
diff options
context:
space:
mode:
Diffstat (limited to 'rt/lib/RT/Shredder/Plugin')
-rw-r--r--rt/lib/RT/Shredder/Plugin/Attachments.pm141
-rw-r--r--rt/lib/RT/Shredder/Plugin/Base.pm188
-rw-r--r--rt/lib/RT/Shredder/Plugin/Base/Dump.pm70
-rw-r--r--rt/lib/RT/Shredder/Plugin/Base/Search.pm142
-rw-r--r--rt/lib/RT/Shredder/Plugin/Objects.pm107
-rw-r--r--rt/lib/RT/Shredder/Plugin/SQLDump.pm96
-rw-r--r--rt/lib/RT/Shredder/Plugin/Summary.pm188
-rw-r--r--rt/lib/RT/Shredder/Plugin/Tickets.pm153
-rw-r--r--rt/lib/RT/Shredder/Plugin/Users.pm260
9 files changed, 1345 insertions, 0 deletions
diff --git a/rt/lib/RT/Shredder/Plugin/Attachments.pm b/rt/lib/RT/Shredder/Plugin/Attachments.pm
new file mode 100644
index 000000000..36dbbcfb8
--- /dev/null
+++ b/rt/lib/RT/Shredder/Plugin/Attachments.pm
@@ -0,0 +1,141 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+# <jesse@bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+package RT::Shredder::Plugin::Attachments;
+
+use strict;
+use warnings FATAL => 'all';
+use base qw(RT::Shredder::Plugin::Base::Search);
+
+=head1 NAME
+
+RT::Shredder::Plugin::Attachments - search plugin for wiping attachments.
+
+=head1 ARGUMENTS
+
+=head2 files_only - boolean value
+
+Search only file attachments.
+
+=head2 file - mask
+
+Search files with specific file name only.
+
+Example: '*.xl?' or '*.gif'
+
+=head2 longer - attachment content size
+
+Search attachments which content is longer than specified.
+You can use trailing 'K' or 'M' character to specify size in
+kilobytes or megabytes.
+
+=cut
+
+sub SupportArgs { return $_[0]->SUPER::SupportArgs, qw(files_only file longer) }
+
+sub TestArgs
+{
+ my $self = shift;
+ my %args = @_;
+ my $queue;
+ if( $args{'file'} ) {
+ unless( $args{'file'} =~ /^[\w\. *?]+$/) {
+ return( 0, "Files mask '$args{file}' has invalid characters" );
+ }
+ $args{'file'} = $self->ConvertMaskToSQL( $args{'file'} );
+ }
+ if( $args{'longer'} ) {
+ unless( $args{'longer'} =~ /^\d+\s*[mk]?$/i ) {
+ return( 0, "Invalid file size argument '$args{longer}'" );
+ }
+ }
+ return $self->SUPER::TestArgs( %args );
+}
+
+sub Run
+{
+ my $self = shift;
+ my @conditions = ();
+ my @values = ();
+ if( $self->{'opt'}{'file'} ) {
+ my $mask = $self->{'opt'}{'file'};
+ push @conditions, "( Filename LIKE ? )";
+ push @values, $mask;
+ }
+ if( $self->{'opt'}{'files_only'} ) {
+ push @conditions, "( LENGTH(Filename) > 0 )";
+ }
+ if( $self->{'opt'}{'longer'} ) {
+ my $size = $self->{'opt'}{'longer'};
+ $size =~ s/([mk])//i;
+ $size *= 1024 if $1 && lc $1 eq 'k';
+ $size *= 1024*1024 if $1 && lc $1 eq 'm';
+ push @conditions, "( LENGTH(Content) > ? )";
+ push @values, $size;
+ }
+ return (0, "At least one condition should be provided" ) unless @conditions;
+ my $query = "SELECT id FROM Attachments WHERE ". join ' AND ', @conditions;
+ if( $self->{'opt'}{'limit'} ) {
+ $RT::Handle->ApplyLimits( \$query, $self->{'opt'}{'limit'} );
+ }
+ my $sth = $RT::Handle->SimpleQuery( $query, @values );
+ return (0, "Internal error: '$sth'. Please send bug report.") unless $sth;
+
+ my @objs;
+ while( my $row = $sth->fetchrow_arrayref ) {
+ push @objs, $row->[0];
+ }
+ return (0, "Internal error: '". $sth->err ."'. Please send bug report.") if $sth->err;
+
+ map { $_ = "RT::Attachment-$_" } @objs;
+
+ return (1, @objs);
+}
+
+1;
+
diff --git a/rt/lib/RT/Shredder/Plugin/Base.pm b/rt/lib/RT/Shredder/Plugin/Base.pm
new file mode 100644
index 000000000..3e3e73b27
--- /dev/null
+++ b/rt/lib/RT/Shredder/Plugin/Base.pm
@@ -0,0 +1,188 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+# <jesse@bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+package RT::Shredder::Plugin::Base;
+
+use strict;
+use warnings FATAL => 'all';
+
+=head1 NAME
+
+RT::Shredder::Plugin::Base - base class for Shredder plugins.
+
+=cut
+
+sub new
+{
+ my $proto = shift;
+ my $self = bless( {}, ref $proto || $proto );
+ $self->_Init( @_ );
+ return $self;
+}
+
+sub _Init
+{
+ my $self = shift;
+ $self->{'opt'} = { @_ };
+}
+
+=head1 USAGE
+
+=head2 masks
+
+If any argument is marked with keyword C<mask> then it means
+that this argument support two special characters:
+
+1) C<*> matches any non empty sequence of the characters.
+For example C<*@example.com> will match any email address in
+C<example.com> domain.
+
+2) C<?> matches exactly one character.
+For example C<????> will match any string four characters long.
+
+=head1 METHODS
+
+=head2 for subclassing in plugins
+
+=head3 Type - is not supported yet
+
+See F<Todo> for more info.
+
+=cut
+
+sub Type { return '' }
+
+=head3 SupportArgs
+
+Takes nothing.
+Returns list of the supported plugin arguments.
+
+Base class returns list of the arguments which all
+classes B<must> support.
+
+=cut
+
+sub SupportArgs { return () }
+
+=head3 HasSupportForArgs
+
+Takes a list of argument names. Returns true if
+all arguments are supported by plugin and returns
+C<(0, $msg)> in other case.
+
+=cut
+
+sub HasSupportForArgs
+{
+ my $self = shift;
+ my @args = @_;
+ my @unsupported = ();
+ foreach my $a( @args ) {
+ push @unsupported, $a unless grep $_ eq $a, $self->SupportArgs;
+ }
+ return( 1 ) unless @unsupported;
+ return( 0, "Plugin doesn't support argument(s): @unsupported" ) if @unsupported;
+}
+
+=head3 TestArgs
+
+Takes hash with arguments and thier values and returns true
+if all values pass testing otherwise returns C<(0, $msg)>.
+
+Stores arguments hash in C<$self->{'opt'}>, you can access this hash
+from C<Run> method.
+
+Method should be subclassed if plugin support non standard arguments.
+
+=cut
+
+sub TestArgs
+{
+ my $self = shift;
+ my %args = @_;
+ if ( $self->{'opt'} ) {
+ $self->{'opt'} = { %{$self->{'opt'}}, %args };
+ } else {
+ $self->{'opt'} = \%args;
+ }
+ return 1;
+}
+
+=head3 Run
+
+Takes no arguments.
+Executes plugin and return C<(1, @objs)> on success or
+C<(0, $msg)> if error had happenned.
+
+Method B<must> be subclassed, this class always returns error.
+
+Method B<must> be called only after C<TestArgs> method in other
+case values of the arguments are not available.
+
+=cut
+
+sub Run { return (0, "This is abstract plugin, you couldn't use it directly") }
+
+=head2 utils
+
+=head3 ConvertMaskToSQL
+
+Takes one argument - mask with C<*> and C<?> chars and
+return mask SQL chars.
+
+=cut
+
+sub ConvertMaskToSQL {
+ my $self = shift;
+ my $mask = shift || '';
+ $mask =~ s/\*/%/g;
+ $mask =~ s/\?/_/g;
+ return $mask;
+}
+
+1;
diff --git a/rt/lib/RT/Shredder/Plugin/Base/Dump.pm b/rt/lib/RT/Shredder/Plugin/Base/Dump.pm
new file mode 100644
index 000000000..ecfc82e5c
--- /dev/null
+++ b/rt/lib/RT/Shredder/Plugin/Base/Dump.pm
@@ -0,0 +1,70 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+# <jesse@bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+package RT::Shredder::Plugin::Base::Dump;
+
+use strict;
+use warnings FATAL => 'all';
+
+use base qw(RT::Shredder::Plugin::Base);
+
+=head1 NAME
+
+RT::Shredder::Plugin::Base - base class for Shredder plugins.
+
+=cut
+
+sub Type { return 'dump' }
+sub AppliesToStates { return () }
+sub SupportArgs { return () }
+
+sub PushMark { return 1 }
+sub PopMark { return 1 }
+sub RollbackTo { return 1 }
+
+1;
diff --git a/rt/lib/RT/Shredder/Plugin/Base/Search.pm b/rt/lib/RT/Shredder/Plugin/Base/Search.pm
new file mode 100644
index 000000000..21de15982
--- /dev/null
+++ b/rt/lib/RT/Shredder/Plugin/Base/Search.pm
@@ -0,0 +1,142 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+# <jesse@bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+package RT::Shredder::Plugin::Base::Search;
+
+use strict;
+use warnings FATAL => 'all';
+
+use base qw(RT::Shredder::Plugin::Base);
+
+=head1 NAME
+
+RT::Shredder::Plugin::Base - base class for Shredder plugins.
+
+=cut
+
+sub Type { return 'search' }
+
+=head1 ARGUMENTS
+
+Arguments which all plugins support.
+
+=head2 limit - unsigned integer
+
+Allow you to limit search results. B<< Default value is C<10> >>.
+
+=head1 METHODS
+
+=cut
+
+sub SupportArgs
+{
+ my %seen;
+ return sort
+ grep $_ && !$seen{$_},
+ shift->SUPER::SupportArgs(@_),
+ qw(limit);
+}
+
+sub TestArgs
+{
+ my $self = shift;
+ my %args = @_;
+ if( defined $args{'limit'} && $args{'limit'} ne '' ) {
+ my $limit = $args{'limit'};
+ $limit =~ s/[^0-9]//g;
+ unless( $args{'limit'} eq $limit ) {
+ return( 0, "'limit' should be an unsigned integer");
+ }
+ $args{'limit'} = $limit;
+ } else {
+ $args{'limit'} = 10;
+ }
+ return $self->SUPER::TestArgs( %args );
+}
+
+sub SetResolvers { return 1 }
+
+
+=head2 FetchNext $collection [, $init]
+
+Returns next object in collection as method L<RT::SearchBuilder/Next>, but
+doesn't stop on page boundaries.
+
+When method is called with true C<$init> arg it enables pages on collection
+and selects first page.
+
+Main purpose of this method is to avoid loading of whole collection into
+memory as RT does by default when pager is not used. This method init paging
+on the collection, but doesn't stop when reach page end.
+
+Example:
+
+ $plugin->FetchNext( $tickets, 'init' );
+ while( my $ticket = $plugin->FetchNext( $tickets ) ) {
+ ...
+ }
+
+=cut
+
+use constant PAGE_SIZE => 100;
+sub FetchNext {
+ my ($self, $objs, $init) = @_;
+ if ( $init ) {
+ $objs->RowsPerPage( PAGE_SIZE );
+ $objs->FirstPage;
+ return;
+ }
+
+ my $obj = $objs->Next;
+ return $obj if $obj;
+ $objs->NextPage;
+ return $objs->Next;
+}
+
+1;
+
diff --git a/rt/lib/RT/Shredder/Plugin/Objects.pm b/rt/lib/RT/Shredder/Plugin/Objects.pm
new file mode 100644
index 000000000..fe2314ae8
--- /dev/null
+++ b/rt/lib/RT/Shredder/Plugin/Objects.pm
@@ -0,0 +1,107 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+# <jesse@bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+package RT::Shredder::Plugin::Objects;
+
+use strict;
+use warnings FATAL => 'all';
+use base qw(RT::Shredder::Plugin::Base::Search);
+
+use RT::Shredder;
+
+=head1 NAME
+
+RT::Shredder::Plugin::Objects - search plugin for wiping any selected object.
+
+=head1 ARGUMENTS
+
+This plugin searches an RT object you want, so you can use
+the object name as argument and id as value, for example if
+you want select ticket #123 then from CLI you write next
+command:
+
+ rt-shredder --plugin 'Objects=Ticket,123'
+
+=cut
+
+sub SupportArgs
+{
+ return $_[0]->SUPER::SupportArgs, @RT::Shredder::SUPPORTED_OBJECTS;
+}
+
+sub TestArgs
+{
+ my $self = shift;
+ my %args = @_;
+
+ my @strings;
+ foreach my $name( @RT::Shredder::SUPPORTED_OBJECTS ) {
+ next unless $args{$name};
+
+ my $list = $args{$name};
+ $list = [$list] unless UNIVERSAL::isa( $list, 'ARRAY' );
+ push @strings, map "RT::$name\-$_", @$list;
+ }
+
+ my @objs = RT::Shredder->CastObjectsToRecords( Objects => \@strings );
+
+ my @res = $self->SUPER::TestArgs( %args );
+
+ $self->{'opt'}->{'objects'} = \@objs;
+
+ return (@res);
+}
+
+sub Run
+{
+ my $self = shift;
+ my %args = ( Shredder => undef, @_ );
+ return (1, @{$self->{'opt'}->{'objects'}});
+}
+
+1;
diff --git a/rt/lib/RT/Shredder/Plugin/SQLDump.pm b/rt/lib/RT/Shredder/Plugin/SQLDump.pm
new file mode 100644
index 000000000..cd3fa33d3
--- /dev/null
+++ b/rt/lib/RT/Shredder/Plugin/SQLDump.pm
@@ -0,0 +1,96 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+# <jesse@bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+package RT::Shredder::Plugin::SQLDump;
+
+use strict;
+use warnings;
+
+use base qw(RT::Shredder::Plugin::Base::Dump);
+use RT::Shredder;
+
+sub AppliesToStates { return 'after wiping dependencies' }
+
+sub SupportArgs
+{
+ my $self = shift;
+ return $self->SUPER::SupportArgs, qw(file_name from_storage);
+}
+
+sub TestArgs
+{
+ my $self = shift;
+ my %args = @_;
+ $args{'from_storage'} = 1 unless defined $args{'from_storage'};
+ my $file = $args{'file_name'} = RT::Shredder->GetFileName(
+ FileName => $args{'file_name'},
+ FromStorage => delete $args{'from_storage'},
+ );
+ open $args{'file_handle'}, ">:raw", $file
+ or return (0, "Couldn't open '$file' for write: $!");
+
+ return $self->SUPER::TestArgs( %args );
+}
+
+sub FileName { return $_[0]->{'opt'}{'file_name'} }
+sub FileHandle { return $_[0]->{'opt'}{'file_handle'} }
+
+sub Run
+{
+ my $self = shift;
+ return (0, 'no handle') unless my $fh = $self->{'opt'}{'file_handle'};
+
+ my %args = ( Object => undef, @_ );
+ my $query = $args{'Object'}->_AsInsertQuery;
+ $query .= "\n" unless $query =~ /\n$/;
+
+ return print $fh $query or return (0, "Couldn't write to filehandle");
+ return 1;
+}
+
+1;
diff --git a/rt/lib/RT/Shredder/Plugin/Summary.pm b/rt/lib/RT/Shredder/Plugin/Summary.pm
new file mode 100644
index 000000000..6ab76ba0b
--- /dev/null
+++ b/rt/lib/RT/Shredder/Plugin/Summary.pm
@@ -0,0 +1,188 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+# <jesse@bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+package RT::Shredder::Plugin::Summary;
+
+use strict;
+use warnings FATAL => 'all';
+
+use base qw(RT::Shredder::Plugin::SQLDump);
+
+sub AppliesToStates { return 'before any action' }
+
+sub TestArgs
+{
+ my $self = shift;
+ my %args = (file_name => '', @_);
+ unless( $args{'file_name'} ) {
+ require POSIX;
+ $args{'file_name'} = POSIX::strftime( "summary-%Y%m%dT%H%M%S.XXXX.txt", gmtime );
+ }
+ return $self->SUPER::TestArgs( %args );
+}
+
+sub Run
+{
+ my $self = shift;
+ my %args = ( Object => undef, @_ );
+ my $class = ref $args{'Object'};
+ $class =~ s/^RT:://;
+ $class =~ s/:://g;
+ my $method = 'WriteDown'. $class;
+ $method = 'WriteDownDefault' unless $self->can($method);
+ return $self->$method( %args );
+ return 1;
+}
+
+my %skip_refs_to = ();
+
+sub WriteDownDefault {
+ my $self = shift;
+ my %args = ( Object => undef, @_ );
+ return $self->_WriteDownHash(
+ $args{'Object'},
+ $self->_MakeHash( $args{'Object'} ),
+ );
+}
+
+# TODO: cover other objects
+# ACE.pm
+# Attachment.pm
+# CustomField.pm
+# CustomFieldValue.pm
+# GroupMember.pm
+# Group.pm
+# Link.pm
+# ObjectCustomFieldValue.pm
+# Principal.pm
+# Queue.pm
+# Ticket.pm
+# User.pm
+
+# ScripAction.pm - works fine with defaults
+# ScripCondition.pm - works fine with defaults
+# Template.pm - works fine with defaults
+
+sub WriteDownCachedGroupMember { return 1 }
+sub WriteDownPrincipal { return 1 }
+
+sub WriteDownGroup {
+ my $self = shift;
+ my %args = ( Object => undef, @_ );
+ if ( $args{'Object'}->Domain =~ /-Role$/ ) {
+ return $skip_refs_to{ $args{'Object'}->_AsString } = 1;
+ }
+ return $self->WriteDownDefault( %args );
+}
+
+sub WriteDownTransaction {
+ my $self = shift;
+ my %args = ( Object => undef, @_ );
+
+ my $props = $self->_MakeHash( $args{'Object'} );
+ $props->{'Object'} = delete $props->{'ObjectType'};
+ $props->{'Object'} .= '-'. delete $props->{'ObjectId'}
+ if $props->{'ObjectId'};
+ return 1 if $skip_refs_to{ $props->{'Object'} };
+
+ delete $props->{$_} foreach grep
+ !defined $props->{$_} || $props->{$_} eq '', keys %$props;
+
+ return $self->_WriteDownHash( $args{'Object'}, $props );
+}
+
+sub WriteDownScrip {
+ my $self = shift;
+ my %args = ( Object => undef, @_ );
+ my $props = $self->_MakeHash( $args{'Object'} );
+ $props->{'Action'} = $args{'Object'}->ActionObj->Name;
+ $props->{'Condition'} = $args{'Object'}->ConditionObj->Name;
+ $props->{'Template'} = $args{'Object'}->TemplateObj->Name;
+ $props->{'Queue'} = $args{'Object'}->QueueObj->Name || 'global';
+
+ return $self->_WriteDownHash( $args{'Object'}, $props );
+}
+
+sub _MakeHash {
+ my ($self, $obj) = @_;
+ my $hash = $self->__MakeHash( $obj );
+ foreach (grep exists $hash->{$_}, qw(Creator LastUpdatedBy)) {
+ my $method = $_ .'Obj';
+ my $u = $obj->$method();
+ $hash->{ $_ } = $u->EmailAddress || $u->Name || $u->_AsString;
+ }
+ return $hash;
+}
+
+sub __MakeHash {
+ my ($self, $obj) = @_;
+ my %hash;
+ $hash{ $_ } = $obj->$_()
+ foreach sort keys %{ $obj->_ClassAccessible };
+ return \%hash;
+}
+
+sub _WriteDownHash {
+ my ($self, $obj, $hash) = @_;
+ return (0, 'no handle') unless my $fh = $self->{'opt'}{'file_handle'};
+
+ print $fh "=== ". $obj->_AsString ." ===\n"
+ or return (0, "Couldn't write to filehandle");
+
+ foreach my $key( sort keys %$hash ) {
+ my $val = $hash->{ $key };
+ next unless defined $val;
+ $val =~ s/\n/\n /g;
+ print $fh $key .': '. $val ."\n"
+ or return (0, "Couldn't write to filehandle");
+ }
+ print $fh "\n" or return (0, "Couldn't write to filehandle");
+ return 1;
+}
+
+1;
diff --git a/rt/lib/RT/Shredder/Plugin/Tickets.pm b/rt/lib/RT/Shredder/Plugin/Tickets.pm
new file mode 100644
index 000000000..ebbfb3e2a
--- /dev/null
+++ b/rt/lib/RT/Shredder/Plugin/Tickets.pm
@@ -0,0 +1,153 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+# <jesse@bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+package RT::Shredder::Plugin::Tickets;
+
+use strict;
+use warnings FATAL => 'all';
+use base qw(RT::Shredder::Plugin::Base::Search);
+
+=head1 NAME
+
+RT::Shredder::Plugin::Tickets - search plugin for wiping tickets.
+
+=head1 ARGUMENTS
+
+=head2 query - query string
+
+Search tickets with query string.
+Examples:
+ Queue = 'my queue' AND ( Status = 'deleted' OR Status = 'rejected' )
+ LastUpdated < '2003-12-31 23:59:59'
+
+B<Hint:> You can construct query with the query builder in RT's web
+interface and then open advanced page and copy query string.
+
+Arguments C<queue>, C<status> and C<updated_before> have been dropped
+as you can easy make the same search with the C<query> option.
+See examples above.
+
+=head2 with_linked - boolen
+
+Deletes all tickets that are linked to tickets that match L<query>.
+
+=head2 apply_query_to_linked - boolean
+
+Delete linked tickets only if those too match L<query>.
+
+See also L<with_linked>.
+
+=cut
+
+sub SupportArgs { return $_[0]->SUPER::SupportArgs, qw(query with_linked apply_query_to_linked) }
+
+sub TestArgs
+{
+ my $self = shift;
+ my %args = @_;
+ my $queue;
+ if( $args{'query'} ) {
+ my $objs = RT::Tickets->new( $RT::SystemUser );
+ $objs->{'allow_deleted_search'} = 1;
+ my ($status, $msg) = $objs->FromSQL( $args{'query'} );
+ return( 0, "Bad query argument, error: $msg" ) unless $status;
+ $self->{'opt'}{'objects'} = $objs;
+ }
+ $args{'with_linked'} = 1 if $args{'apply_query_to_linked'};
+
+ return $self->SUPER::TestArgs( %args );
+}
+
+sub Run
+{
+ my $self = shift;
+ my $objs = $self->{'opt'}{'objects'}
+ or return (1, undef);
+
+ $objs->OrderByCols( { FIELD => 'id', ORDER => 'ASC' } );
+ unless ( $self->{'opt'}{'with_linked'} ) {
+ if( $self->{'opt'}{'limit'} ) {
+ $objs->RowsPerPage( $self->{'opt'}{'limit'} );
+ }
+ return (1, $objs);
+ }
+
+ my (@top, @linked, %seen);
+ $self->FetchNext($objs, 1);
+ while ( my $obj = $self->FetchNext( $objs ) ) {
+ next if $seen{ $obj->id }++;
+ push @linked, $self->GetLinked( Object => $obj, Seen => \%seen );
+ push @top, $obj;
+ last if $self->{'opt'}{'limit'}
+ && @top >= $self->{'opt'}{'limit'};
+ }
+ return (1, @top, @linked);
+}
+
+sub GetLinked
+{
+ my $self = shift;
+ my %arg = @_;
+ my @res = ();
+ my $query = 'Linked = '. $arg{'Object'}->id;
+ if ( $self->{'opt'}{'apply_query_to_linked'} ) {
+ $query .= " AND ( ". $self->{'opt'}{'query'} ." )";
+ }
+ my $objs = RT::Tickets->new( $RT::SystemUser );
+ $objs->{'allow_deleted_search'} = 1;
+ $objs->FromSQL( $query );
+ $self->FetchNext( $objs, 1 );
+ while ( my $linked_obj = $self->FetchNext( $objs ) ) {
+ next if $arg{'Seen'}->{ $linked_obj->id }++;
+ push @res, $self->GetLinked( %arg, Object => $linked_obj );
+ push @res, $linked_obj;
+ }
+ return @res;
+}
+
+1;
diff --git a/rt/lib/RT/Shredder/Plugin/Users.pm b/rt/lib/RT/Shredder/Plugin/Users.pm
new file mode 100644
index 000000000..2565fc546
--- /dev/null
+++ b/rt/lib/RT/Shredder/Plugin/Users.pm
@@ -0,0 +1,260 @@
+# BEGIN BPS TAGGED BLOCK {{{
+#
+# COPYRIGHT:
+#
+# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+# <jesse@bestpractical.com>
+#
+# (Except where explicitly superseded by other copyright notices)
+#
+#
+# LICENSE:
+#
+# This work is made available to you under the terms of Version 2 of
+# the GNU General Public License. A copy of that license should have
+# been provided with this software, but in any event can be snarfed
+# from www.gnu.org.
+#
+# This work is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301 or visit their web page on the internet at
+# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+#
+#
+# CONTRIBUTION SUBMISSION POLICY:
+#
+# (The following paragraph is not intended to limit the rights granted
+# to you to modify and distribute this software under the terms of
+# the GNU General Public License and is only of importance to you if
+# you choose to contribute your changes and enhancements to the
+# community by submitting them to Best Practical Solutions, LLC.)
+#
+# By intentionally submitting any modifications, corrections or
+# derivatives to this work, or any other work intended for use with
+# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+# you are the copyright holder for those contributions and you grant
+# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
+# royalty-free, perpetual, license to use, copy, create derivative
+# works based on those contributions, and sublicense and distribute
+# those contributions and any derivatives thereof.
+#
+# END BPS TAGGED BLOCK }}}
+
+package RT::Shredder::Plugin::Users;
+
+use strict;
+use warnings FATAL => 'all';
+use base qw(RT::Shredder::Plugin::Base::Search);
+
+=head1 NAME
+
+RT::Shredder::Plugin::Users - search plugin for wiping users.
+
+=head1 ARGUMENTS
+
+=head2 status - string
+
+Status argument allow you to limit result set to C<disabled>,
+C<enabled> or C<any> users.
+B<< Default value is C<disabled>. >>
+
+=head2 name - mask
+
+User name mask.
+
+=head2 email - mask
+
+Email address mask.
+
+=head2 member_of - group identifier
+
+Using this option users that are members of a particular group can
+be selected for deletion. Identifier is name of user defined group
+or id of a group, as well C<Privileged> or <unprivileged> can used
+to select people from system groups.
+
+=head2 replace_relations - user identifier
+
+When you delete user there are could be minor links to him in RT DB.
+This option allow you to replace this links with link to other user.
+This links are Creator and LastUpdatedBy, but NOT any watcher roles,
+this means that if user is watcher(Requestor, Owner,
+Cc or AdminCc) of the ticket or queue then link would be deleted.
+
+This argument could be user id or name.
+
+=head2 no_tickets - boolean
+
+If true then plugin looks for users who are not watchers (Owners,
+Requestors, Ccs or AdminCcs) of any ticket.
+
+Before RT 3.8.5, users who were watchers of deleted tickets B<will be deleted>
+when this option was enabled. Decision has been made that it's not correct
+and you should either shred these deleted tickets, change watchers or
+explicitly delete user by name or email.
+
+Note that found users still B<may have relations> with other objects,
+for example via Creator or LastUpdatedBy fields, and you most probably
+want to use C<replace_relations> option.
+
+=cut
+
+sub SupportArgs
+{
+ return $_[0]->SUPER::SupportArgs,
+ qw(status name email member_of replace_relations no_tickets);
+}
+
+sub TestArgs
+{
+ my $self = shift;
+ my %args = @_;
+ if( $args{'status'} ) {
+ unless( $args{'status'} =~ /^(disabled|enabled|any)$/i ) {
+ return (0, "Status '$args{'status'}' is unsupported.");
+ }
+ } else {
+ $args{'status'} = 'disabled';
+ }
+ if( $args{'email'} ) {
+ $args{'email'} = $self->ConvertMaskToSQL( $args{'email'} );
+ }
+ if( $args{'name'} ) {
+ $args{'name'} = $self->ConvertMaskToSQL( $args{'name'} );
+ }
+ if( $args{'member_of'} ) {
+ my $group = RT::Group->new( $RT::SystemUser );
+ if ( $args{'member_of'} =~ /^(Everyone|Privileged|Unprivileged)$/i ) {
+ $group->LoadSystemInternalGroup( $args{'member_of'} );
+ }
+ else {
+ $group->LoadUserDefinedGroup( $args{'member_of'} );
+ }
+ unless ( $group->id ) {
+ return (0, "Couldn't load group '$args{'member_of'}'" );
+ }
+ $args{'member_of'} = $group->id;
+
+ }
+ if( $args{'replace_relations'} ) {
+ my $uid = $args{'replace_relations'};
+ # XXX: it's possible that SystemUser is not available
+ my $user = RT::User->new( $RT::SystemUser );
+ $user->Load( $uid );
+ unless( $user->id ) {
+ return (0, "Couldn't load user '$uid'" );
+ }
+ $args{'replace_relations'} = $user->id;
+ }
+ return $self->SUPER::TestArgs( %args );
+}
+
+sub Run
+{
+ my $self = shift;
+ my %args = ( Shredder => undef, @_ );
+ my $objs = RT::Users->new( $RT::SystemUser );
+ # XXX: we want preload only things we need, but later while
+ # logging we need all data, TODO envestigate this
+ # $objs->Columns(qw(id Name EmailAddress Lang Timezone
+ # Creator Created LastUpdated LastUpdatedBy));
+ if( my $s = $self->{'opt'}{'status'} ) {
+ if( $s eq 'any' ) {
+ $objs->{'find_disabled_rows'} = 1;
+ } elsif( $s eq 'disabled' ) {
+ $objs->{'find_disabled_rows'} = 1;
+ $objs->Limit(
+ ALIAS => $objs->PrincipalsAlias,
+ FIELD => 'Disabled',
+ OPERATOR => '!=',
+ VALUE => '0',
+ );
+ } else {
+ $objs->LimitToEnabled;
+ }
+ }
+ if( $self->{'opt'}{'email'} ) {
+ $objs->Limit( FIELD => 'EmailAddress',
+ OPERATOR => 'MATCHES',
+ VALUE => $self->{'opt'}{'email'},
+ );
+ }
+ if( $self->{'opt'}{'name'} ) {
+ $objs->Limit( FIELD => 'Name',
+ OPERATOR => 'MATCHES',
+ VALUE => $self->{'opt'}{'name'},
+ );
+ }
+ if( $self->{'opt'}{'member_of'} ) {
+ $objs->MemberOfGroup( $self->{'opt'}{'member_of'} );
+ }
+ if( $self->{'opt'}{'no_tickets'} ) {
+ return $self->FilterWithoutTickets(
+ Shredder => $args{'Shredder'},
+ Objects => $objs,
+ );
+ } else {
+ if( $self->{'opt'}{'limit'} ) {
+ $objs->RowsPerPage( $self->{'opt'}{'limit'} );
+ }
+ }
+ return (1, $objs);
+}
+
+sub SetResolvers
+{
+ my $self = shift;
+ my %args = ( Shredder => undef, @_ );
+
+ if( $self->{'opt'}{'replace_relations'} ) {
+ my $uid = $self->{'opt'}{'replace_relations'};
+ my $resolver = sub {
+ my %args = (@_);
+ my $t = $args{'TargetObject'};
+ foreach my $method ( qw(Creator LastUpdatedBy) ) {
+ next unless $t->_Accessible( $method => 'read' );
+ $t->__Set( Field => $method, Value => $uid );
+ }
+ };
+ $args{'Shredder'}->PutResolver( BaseClass => 'RT::User', Code => $resolver );
+ }
+ return (1);
+}
+
+sub FilterWithoutTickets {
+ my $self = shift;
+ my %args = (
+ Shredder => undef,
+ Objects => undef,
+ @_,
+ );
+ my $users = $args{Objects};
+ $self->FetchNext( $users, 'init' );
+
+ my @res;
+ while ( my $user = $self->FetchNext( $users ) ) {
+ push @res, $user if $self->_WithoutTickets( $user );
+ return (1, \@res) if $self->{'opt'}{'limit'} && @res >= $self->{'opt'}{'limit'};
+ }
+ return (1, \@res);
+}
+
+sub _WithoutTickets {
+ my ($self, $user) = @_;
+ my $tickets = RT::Tickets->new( $RT::SystemUser );
+ $tickets->{'allow_deleted_search'} = 1;
+ $tickets->FromSQL( 'Watcher.id = '. $user->id );
+ # HACK: we may use Count method which counts all records
+ # that match condtion, but we really want to know only that
+ # at least one record exist, so we fetch first row only
+ $tickets->RowsPerPage(1);
+ return !$tickets->First;
+}
+
+1;