diff options
Diffstat (limited to 'rt/lib/RT/Shredder/Plugin')
-rw-r--r-- | rt/lib/RT/Shredder/Plugin/Attachments.pm | 141 | ||||
-rw-r--r-- | rt/lib/RT/Shredder/Plugin/Base.pm | 188 | ||||
-rw-r--r-- | rt/lib/RT/Shredder/Plugin/Base/Dump.pm | 70 | ||||
-rw-r--r-- | rt/lib/RT/Shredder/Plugin/Base/Search.pm | 142 | ||||
-rw-r--r-- | rt/lib/RT/Shredder/Plugin/Objects.pm | 107 | ||||
-rw-r--r-- | rt/lib/RT/Shredder/Plugin/SQLDump.pm | 96 | ||||
-rw-r--r-- | rt/lib/RT/Shredder/Plugin/Summary.pm | 188 | ||||
-rw-r--r-- | rt/lib/RT/Shredder/Plugin/Tickets.pm | 153 | ||||
-rw-r--r-- | rt/lib/RT/Shredder/Plugin/Users.pm | 260 |
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; |