[Bps-public-commit] RT-Extension-MandatoryOnTransition branch, support-coreroles-and-customroles, created. 0.17-4-gc1563d5

Craig Kaiser craig at bestpractical.com
Mon Jan 7 11:06:57 EST 2019


The branch, support-coreroles-and-customroles has been created
        at  c1563d58ecc29aacef42c00849a4858f828fe35b (commit)

- Log -----------------------------------------------------------------
commit c951d4cdf7abf06f03c32944d441c07133199623
Author: Craig Kaiser <craig at bestpractical.com>
Date:   Fri Jan 4 11:11:15 2019 -0500

    Support customrole mandatoryontransition conditions
    
    Use the syntax 'CustomRole.MyRole' to set mandatory conditions for
    customrole values. Pass a group key to specify if one of the current
    users for the customrole must be a member of a specific group.

diff --git a/lib/RT/Extension/MandatoryOnTransition.pm b/lib/RT/Extension/MandatoryOnTransition.pm
index d238c18..45a3733 100644
--- a/lib/RT/Extension/MandatoryOnTransition.pm
+++ b/lib/RT/Extension/MandatoryOnTransition.pm
@@ -127,7 +127,7 @@ config option.  This option takes the generic form of:
 
     Set( %MandatoryOnTransition,
         'QueueName' => {
-            'from -> to' => [ 'BasicField', 'CF.MyField', ],
+            'from -> to' => [ 'BasicField', 'CF.MyField', 'CustomRole.MyRole' ],
         },
     );
 
@@ -146,10 +146,11 @@ Category selection before resolving tickets in every other queue.
 
     Set( %MandatoryOnTransition,
         Helpdesk => {
-            '* -> resolved' => ['TimeWorked', 'CF.Resolution'],
+            '* -> resolved' => ['TimeWorked', 'CF.Resolution', 'CustomRole.Analyst'],
         },
         '*' => {
             '* -> resolved' => ['CF.Category'],
+            'CustomRole.Analyst' => {transition => '* -> open', group => 'Engineering'},
         },
     );
 
@@ -340,6 +341,8 @@ sub RequiredFields {
     my @core = grep { !/^CF\./i && $core_supported{$_} } @$required;
     my @cfs  =  map { /^CF\.(.+)$/i; $1; }
                grep { /^CF\./i } @$required;
+    my @roles = map { /^(:?[CustomRole\.]?.+)$/i; $1; }
+               grep { /^CustomRole\.|^AdminCc|^Cc|^Requestor|^Owner/i } @$required;
 
     # Pull out any must_be or must_not_be rules
     my %cf_must_values = ();
@@ -359,7 +362,25 @@ sub RequiredFields {
             }
         }
     }
-    return (\@core, \@cfs, \%cf_must_values);
+
+    my %role_group_values;
+    foreach my $role (@roles){
+        if ( $config{$role} ){
+            my $transition = $config{$role}->{'transition'};
+            unless ( $transition ){
+                RT->Logger->error("No transition defined in group rules for $role");
+                next;
+            }
+
+            if ( $transition eq "$from -> $to"
+                || $transition eq "* -> $to"
+                || $transition eq "$from -> *" ) {
+
+                $role_group_values{$role} = $config{$role};
+            }
+        }
+    }
+    return (\@core, \@cfs, \@roles, \%cf_must_values, \%role_group_values);
 }
 
 =head3 CheckMandatoryFields
@@ -412,13 +433,15 @@ sub CheckMandatoryFields {
     }
 
     # Some convenience variables set depending on what gets passed
-    my ($CFs, $CurrentUser);
+    my ($CFs, $CRs, $CurrentUser);
     if ( $args{'Ticket'} ){
         $CFs = $args{'Ticket'}->CustomFields;
+        $CRs = $args{'Ticket'}->QueueObj->CustomRoles;
         $CurrentUser = $args{'Ticket'}->CurrentUser();
     }
     elsif ( $args{'Queue'} ){
         $CFs = $args{'Queue'}->TicketCustomFields;
+        $CRs = $args{'Queue'}->CustomRoles;
         $CurrentUser = $args{'Queue'}->CurrentUser();
     }
     else{
@@ -426,15 +449,14 @@ sub CheckMandatoryFields {
         return \@errors;
     }
 
-    my ($core, $cfs, $must_values) = $self->RequiredFields(
+    my ($core, $cfs, $roles, $cf_must_values, $role_group_values) = $self->RequiredFields(
         Ticket  => $args{'Ticket'},
         Queue   => $args{'Queue'} ? $args{'Queue'}->Name : undef,
         From    => $args{'From'},
         To      => $args{'To'},
         NewQueue => $$ARGSRef{'Queue'},
     );
-
-    return \@errors unless @$core or @$cfs;
+    return \@errors unless @$core or @$cfs or $roles;
 
     my $transition =  ($args{'From'} ||'') ne ($args{'To'} || '') ? 'Status' : 'Queue';
 
@@ -500,6 +522,54 @@ sub CheckMandatoryFields {
             $label, $CurrentUser->loc($transition),  $CurrentUser->loc($field_label{$transition}));
     }
 
+    if ( @$roles ) {
+        foreach my $role (@$roles) {
+            my $role_values;
+            my $role_arg = $role;
+            my $role_full = $role;
+
+            if ( $role =~ 'CustomRole.' ) {
+                if ( not $CRs ){
+                    $RT::Logger->error("Custom Roles object required to process mandatory custom roles");
+                    return \@errors;
+                }
+                $role =~ qr/^CustomRole\.(.+)$/i;
+                $role = $1;
+
+                my $role_object = RT::CustomRole->new($args{Ticket}->CurrentUser);
+
+                my ($ret, $msg) = $role_object->Load($role);
+                push @errors, $CurrentUser->loc("Failed to load customrole $role:  $msg") unless $ret;
+                return \@errors unless $ret;
+
+                $role_arg = 'RT::CustomRole-' . $role_object->Id;
+
+                ($ret, $msg) = $role_values = $args{Ticket}->RoleGroup($role_object->GroupType);
+                push @errors, $CurrentUser->loc("$msg") unless $ret;
+                return \@errors unless $ret;
+            } else {
+                $role_values = RT::Group->new($args{Ticket}->CurrentUser);
+                my ($ret, $msg) = $role_values->LoadRoleGroup(Object => $args{Ticket}, Name => $role);
+                push @errors, $CurrentUser->loc("Failed to load role $role for ticket: $msg") unless $ret;
+                return \@errors unless $ret;
+            }
+
+            my @role_values;
+            my @row_input_id = grep $role_arg eq $ARGSRef->{$_}, keys %{$ARGSRef};
+            if ( @row_input_id ) {
+                map {push @role_values, $ARGSRef->{"WatcherAddressEmail$_"} } grep { $_ =~ qr/WatcherTypeEmail(.+)/ } @row_input_id;
+            }
+
+            my @temp_array = $role_values->MemberEmailAddresses;
+            push @role_values, @temp_array if scalar @temp_array;
+
+            if ( not scalar @role_values ) {
+                    push @errors, "Requires value for $role to perform action.";
+                    return \@errors;
+            }
+        return \@errors if scalar @errors;
+    }
+
     return \@errors unless @$cfs;
 
     if ( not $CFs ){
@@ -565,7 +635,7 @@ sub CheckMandatoryFields {
         }
 
         # Check for specific values
-        if ( exists $must_values->{$cf->Name} ){
+        if ( exists $cf_must_values->{$cf->Name} ){
             my $cf_value = $value;
 
             if ( not defined $cf_value and $args{'Ticket'} ){
@@ -574,8 +644,8 @@ sub CheckMandatoryFields {
                 $cf_value = $args{'Ticket'}->FirstCustomFieldValue($cf->Name);
             }
 
-            if ( exists $must_values->{$cf->Name}{'must_be'} ){
-                my @must_be = @{$must_values->{$cf->Name}{'must_be'}};
+            if ( exists $cf_must_values->{$cf->Name}{'must_be'} ){
+                my @must_be = @{$cf_must_values->{$cf->Name}{'must_be'}};
 
                 # OK if it's defined and is one of the specified values
                 next if defined $cf_value and grep { $cf_value eq $_ } @must_be;
@@ -593,8 +663,8 @@ sub CheckMandatoryFields {
                 next;
             }
 
-            if ( exists $must_values->{$cf->Name}{'must_not_be'} ){
-                my @must_not_be = @{$must_values->{$cf->Name}{'must_not_be'}};
+            if ( exists $cf_must_values->{$cf->Name}{'must_not_be'} ){
+                my @must_not_be = @{$cf_must_values->{$cf->Name}{'must_not_be'}};
 
                 # OK if it's defined and _not_ in the list
                 next if defined $cf_value and !grep { $cf_value eq $_ } @must_not_be;

commit e6296cc3cc253a58132efa46263f17320ece175e
Author: Craig Kaiser <craig at bestpractical.com>
Date:   Fri Jan 4 14:33:30 2019 -0500

    Allow group key to be set for roles
    
    If a group key is set for a role it will be attempted
    to be loaded and then check that a value for the
    role is a member of the group.

diff --git a/lib/RT/Extension/MandatoryOnTransition.pm b/lib/RT/Extension/MandatoryOnTransition.pm
index 45a3733..a83b5cb 100644
--- a/lib/RT/Extension/MandatoryOnTransition.pm
+++ b/lib/RT/Extension/MandatoryOnTransition.pm
@@ -566,7 +566,30 @@ sub CheckMandatoryFields {
             if ( not scalar @role_values ) {
                     push @errors, "Requires value for $role to perform action.";
                     return \@errors;
+            } elsif ( $role_group_values->{$role_full}->{group} ) {
+                my $group_name = $role_group_values->{$role_full}->{group};
+                my $group = RT::Group->new($args{Ticket}->CurrentUser);
+
+                my ($ret, $msg) = $group->LoadUserDefinedGroup($group_name);
+                push @errors, $CurrentUser->loc("Failed to load group: '$group_name' $msg") unless $ret;
+                return \@errors unless $ret;
+
+                my $has_valid_member;
+                my $user = RT::User->new($args{Ticket}->CurrentUser);
+
+                foreach my $member (@role_values) {
+                    next if $has_valid_member;
+                    ($ret, $msg) = $user->LoadByEmail($member);
+                    RT::Logger->error($msg) unless $ret;
+                    next unless $ret;
+
+                    $has_valid_member = $group->HasMemberRecursively($user->Id);
+                }
+                push @errors, $CurrentUser->loc("User who is a member of $group_name is required for role: $role")
+                    unless $has_valid_member;
+                return \@errors unless $has_valid_member;
             }
+        }
         return \@errors if scalar @errors;
     }
 

commit e746df73cebdff194a8c505449a5bef881e676b2
Author: Craig Kaiser <craig at bestpractical.com>
Date:   Mon Jan 7 08:37:09 2019 -0500

    Move Owner code into the roles code block
    
    Since owner is a ticket role it makes logical sense to move the code
    handling the owner value on a ticket into the same code block as
    the other role handling code.

diff --git a/lib/RT/Extension/MandatoryOnTransition.pm b/lib/RT/Extension/MandatoryOnTransition.pm
index a83b5cb..bc7488f 100644
--- a/lib/RT/Extension/MandatoryOnTransition.pm
+++ b/lib/RT/Extension/MandatoryOnTransition.pm
@@ -258,18 +258,16 @@ pair to %CORE_FOR_UPDATE and/or %CORE_FOR_CREATE.
 
 =cut
 
-our @CORE_SUPPORTED  = qw(Content TimeWorked TimeTaken Owner);
-our @CORE_TICKET     = qw(TimeWorked Owner);
+our @CORE_SUPPORTED  = qw(Content TimeWorked TimeTaken);
+our @CORE_TICKET     = qw(TimeWorked);
 our %CORE_FOR_UPDATE = (
     TimeWorked  => 'UpdateTimeWorked',
     TimeTaken   => 'UpdateTimeWorked',
-    Content     => 'UpdateContent',
-    Owner       => 'Owner',
+    Content     => 'UpdateContent'
 );
 our %CORE_FOR_CREATE = (
     TimeWorked  => 'TimeWorked',
     Content     => 'Content',
-    Owner       => 'Owner',
 );
 
 =head2 Methods
@@ -479,36 +477,6 @@ sub CheckMandatoryFields {
             : $CORE_FOR_CREATE{$field};
         next unless $arg;
 
-        if ($field eq 'Owner') {
-            my $value;
-
-            # There are 2 Owner fields on Jumbo page, copied the same handling from it.
-            if (ref $ARGSRef->{$arg}) {
-                foreach my $owner (@{$ARGSRef->{$arg}}) {
-                    if (defined($owner) && $owner =~ /\D/) {
-                        $value = $owner unless ($args{'Ticket'}->OwnerObj->Name eq $owner);
-                    }
-                    elsif (length $owner) {
-                        $value = $owner unless ($args{'Ticket'}->OwnerObj->id == $owner);
-                    }
-                }
-            }
-            else {
-                $value = $ARGSRef->{$arg};
-            }
-
-            if (($value || $args{'Ticket'}->$field()) == $RT::Nobody->id) {
-                push @errors,
-                  $CurrentUser->loc(
-                    "[_1] is required when changing [_2] to [_3]",
-                    $field,
-                    $CurrentUser->loc($transition),
-                    $CurrentUser->loc($field_label{$transition})
-                  );
-                next;
-            }
-        }
-
         next if defined $ARGSRef->{$arg} and length $ARGSRef->{$arg};
 
         # Do we have a value currently?
@@ -547,21 +515,49 @@ sub CheckMandatoryFields {
                 ($ret, $msg) = $role_values = $args{Ticket}->RoleGroup($role_object->GroupType);
                 push @errors, $CurrentUser->loc("$msg") unless $ret;
                 return \@errors unless $ret;
+            } elsif ( $role =~ 'Owner' ) {
+                my $value;
+                foreach my $owner (@{$ARGSRef->{$role}}) {
+                    if (defined($owner) && $owner =~ /\D/) {
+                        $value = $owner unless ($args{'Ticket'}->OwnerObj->Name eq $owner);
+                    }
+                    elsif (length $owner) {
+                        $value = $owner unless ($args{'Ticket'}->OwnerObj->id == $owner);
+                    }
+                }
+                if (($value || $args{'Ticket'}->$role()) == $RT::Nobody->id) {
+                    push @errors,
+                    $CurrentUser->loc(
+                        "[_1] is required when changing [_2] to [_3]",
+                        $role,
+                        $CurrentUser->loc($args{From}),
+                        $CurrentUser->loc($args{To})
+                    );
+                }
+                my $user = RT::User->new($args{Ticket}->CurrentUser);
+                my ($ret, $msg) = $user->Load($value);
+                push @errors, $CurrentUser->loc("$msg") unless $ret;
+                return \@errors unless $ret;
+
+                $role_values = $user->EmailAddress;
             } else {
                 $role_values = RT::Group->new($args{Ticket}->CurrentUser);
                 my ($ret, $msg) = $role_values->LoadRoleGroup(Object => $args{Ticket}, Name => $role);
                 push @errors, $CurrentUser->loc("Failed to load role $role for ticket: $msg") unless $ret;
                 return \@errors unless $ret;
             }
-
             my @role_values;
             my @row_input_id = grep $role_arg eq $ARGSRef->{$_}, keys %{$ARGSRef};
             if ( @row_input_id ) {
                 map {push @role_values, $ARGSRef->{"WatcherAddressEmail$_"} } grep { $_ =~ qr/WatcherTypeEmail(.+)/ } @row_input_id;
             }
 
-            my @temp_array = $role_values->MemberEmailAddresses;
-            push @role_values, @temp_array if scalar @temp_array;
+            if ( ref $role_values eq 'RT::Group' ) {
+                my @temp_array = $role_values->MemberEmailAddresses;
+                push @role_values, @temp_array if scalar @temp_array;
+            } else {
+                push @role_values, $role_values;
+            }
 
             if ( not scalar @role_values ) {
                     push @errors, "Requires value for $role to perform action.";

commit c1563d58ecc29aacef42c00849a4858f828fe35b
Author: Craig Kaiser <craig at bestpractical.com>
Date:   Mon Jan 7 08:47:46 2019 -0500

    Update README and inc files

diff --git a/README b/README
index ad7e034..80db750 100644
--- a/README
+++ b/README
@@ -96,7 +96,7 @@ CONFIGURATION
 
         Set( %MandatoryOnTransition,
             'QueueName' => {
-                'from -> to' => [ 'BasicField', 'CF.MyField', ],
+                'from -> to' => [ 'BasicField', 'CF.MyField', 'CustomRole.MyRole' ],
             },
         );
 
@@ -125,6 +125,18 @@ CONFIGURATION
     The transition syntax is similar to that found in RT's Lifecycles. See
     perldoc /opt/rt4/etc/RT_Config.pm.
 
+  Requiring role values
+    You can require any core or custom role on a RT::Ticket object, further
+    more you can require a 'group' category to check that at least one value
+    for that role is a member of the required group. In the below example we
+    require that the custom role 'customer' have a user value who is a
+    member of the group 'Customers' and the owner of the ticket must be a
+    member of the group 'Helpdesk'.
+
+    Set( %MandatoryOnTransition, 'General' => { 'CustomRole.customer' => {
+    transition => 'open -> *', group => 'Customers' }, 'Owner' => {
+    transition => 'open -> *', group => 'Helpdesk' }, }, );
+
   Restrictions on Queue Transitions
     The default behavior for MandatoryOnTransition operates on status
     transitions, so a change from new to open or from open to resolved. It
diff --git a/inc/Module/Install/Can.pm b/inc/Module/Install/Can.pm
deleted file mode 100644
index d65c753..0000000
--- a/inc/Module/Install/Can.pm
+++ /dev/null
@@ -1,163 +0,0 @@
-#line 1
-package Module::Install::Can;
-
-use strict;
-use Config                ();
-use ExtUtils::MakeMaker   ();
-use Module::Install::Base ();
-
-use vars qw{$VERSION @ISA $ISCORE};
-BEGIN {
-	$VERSION = '1.19';
-	@ISA     = 'Module::Install::Base';
-	$ISCORE  = 1;
-}
-
-# check if we can load some module
-### Upgrade this to not have to load the module if possible
-sub can_use {
-	my ($self, $mod, $ver) = @_;
-	$mod =~ s{::|\\}{/}g;
-	$mod .= '.pm' unless $mod =~ /\.pm$/i;
-
-	my $pkg = $mod;
-	$pkg =~ s{/}{::}g;
-	$pkg =~ s{\.pm$}{}i;
-
-	local $@;
-	eval { require $mod; $pkg->VERSION($ver || 0); 1 };
-}
-
-# Check if we can run some command
-sub can_run {
-	my ($self, $cmd) = @_;
-
-	my $_cmd = $cmd;
-	return $_cmd if (-x $_cmd or $_cmd = MM->maybe_command($_cmd));
-
-	for my $dir ((split /$Config::Config{path_sep}/, $ENV{PATH}), '.') {
-		next if $dir eq '';
-		require File::Spec;
-		my $abs = File::Spec->catfile($dir, $cmd);
-		return $abs if (-x $abs or $abs = MM->maybe_command($abs));
-	}
-
-	return;
-}
-
-# Can our C compiler environment build XS files
-sub can_xs {
-	my $self = shift;
-
-	# Ensure we have the CBuilder module
-	$self->configure_requires( 'ExtUtils::CBuilder' => 0.27 );
-
-	# Do we have the configure_requires checker?
-	local $@;
-	eval "require ExtUtils::CBuilder;";
-	if ( $@ ) {
-		# They don't obey configure_requires, so it is
-		# someone old and delicate. Try to avoid hurting
-		# them by falling back to an older simpler test.
-		return $self->can_cc();
-	}
-
-	# Do we have a working C compiler
-	my $builder = ExtUtils::CBuilder->new(
-		quiet => 1,
-	);
-	unless ( $builder->have_compiler ) {
-		# No working C compiler
-		return 0;
-	}
-
-	# Write a C file representative of what XS becomes
-	require File::Temp;
-	my ( $FH, $tmpfile ) = File::Temp::tempfile(
-		"compilexs-XXXXX",
-		SUFFIX => '.c',
-	);
-	binmode $FH;
-	print $FH <<'END_C';
-#include "EXTERN.h"
-#include "perl.h"
-#include "XSUB.h"
-
-int main(int argc, char **argv) {
-    return 0;
-}
-
-int boot_sanexs() {
-    return 1;
-}
-
-END_C
-	close $FH;
-
-	# Can the C compiler access the same headers XS does
-	my @libs   = ();
-	my $object = undef;
-	eval {
-		local $^W = 0;
-		$object = $builder->compile(
-			source => $tmpfile,
-		);
-		@libs = $builder->link(
-			objects     => $object,
-			module_name => 'sanexs',
-		);
-	};
-	my $result = $@ ? 0 : 1;
-
-	# Clean up all the build files
-	foreach ( $tmpfile, $object, @libs ) {
-		next unless defined $_;
-		1 while unlink;
-	}
-
-	return $result;
-}
-
-# Can we locate a (the) C compiler
-sub can_cc {
-	my $self   = shift;
-
-	if ($^O eq 'VMS') {
-		require ExtUtils::CBuilder;
-		my $builder = ExtUtils::CBuilder->new(
-		quiet => 1,
-		);
-		return $builder->have_compiler;
-	}
-
-	my @chunks = split(/ /, $Config::Config{cc}) or return;
-
-	# $Config{cc} may contain args; try to find out the program part
-	while (@chunks) {
-		return $self->can_run("@chunks") || (pop(@chunks), next);
-	}
-
-	return;
-}
-
-# Fix Cygwin bug on maybe_command();
-if ( $^O eq 'cygwin' ) {
-	require ExtUtils::MM_Cygwin;
-	require ExtUtils::MM_Win32;
-	if ( ! defined(&ExtUtils::MM_Cygwin::maybe_command) ) {
-		*ExtUtils::MM_Cygwin::maybe_command = sub {
-			my ($self, $file) = @_;
-			if ($file =~ m{^/cygdrive/}i and ExtUtils::MM_Win32->can('maybe_command')) {
-				ExtUtils::MM_Win32->maybe_command($file);
-			} else {
-				ExtUtils::MM_Unix->maybe_command($file);
-			}
-		}
-	}
-}
-
-1;
-
-__END__
-
-#line 245
diff --git a/inc/Module/Install/Fetch.pm b/inc/Module/Install/Fetch.pm
deleted file mode 100644
index 3072b08..0000000
--- a/inc/Module/Install/Fetch.pm
+++ /dev/null
@@ -1,93 +0,0 @@
-#line 1
-package Module::Install::Fetch;
-
-use strict;
-use Module::Install::Base ();
-
-use vars qw{$VERSION @ISA $ISCORE};
-BEGIN {
-	$VERSION = '1.19';
-	@ISA     = 'Module::Install::Base';
-	$ISCORE  = 1;
-}
-
-sub get_file {
-    my ($self, %args) = @_;
-    my ($scheme, $host, $path, $file) =
-        $args{url} =~ m|^(\w+)://([^/]+)(.+)/(.+)| or return;
-
-    if ( $scheme eq 'http' and ! eval { require LWP::Simple; 1 } ) {
-        $args{url} = $args{ftp_url}
-            or (warn("LWP support unavailable!\n"), return);
-        ($scheme, $host, $path, $file) =
-            $args{url} =~ m|^(\w+)://([^/]+)(.+)/(.+)| or return;
-    }
-
-    $|++;
-    print "Fetching '$file' from $host... ";
-
-    unless (eval { require Socket; Socket::inet_aton($host) }) {
-        warn "'$host' resolve failed!\n";
-        return;
-    }
-
-    return unless $scheme eq 'ftp' or $scheme eq 'http';
-
-    require Cwd;
-    my $dir = Cwd::getcwd();
-    chdir $args{local_dir} or return if exists $args{local_dir};
-
-    if (eval { require LWP::Simple; 1 }) {
-        LWP::Simple::mirror($args{url}, $file);
-    }
-    elsif (eval { require Net::FTP; 1 }) { eval {
-        # use Net::FTP to get past firewall
-        my $ftp = Net::FTP->new($host, Passive => 1, Timeout => 600);
-        $ftp->login("anonymous", 'anonymous at example.com');
-        $ftp->cwd($path);
-        $ftp->binary;
-        $ftp->get($file) or (warn("$!\n"), return);
-        $ftp->quit;
-    } }
-    elsif (my $ftp = $self->can_run('ftp')) { eval {
-        # no Net::FTP, fallback to ftp.exe
-        require FileHandle;
-        my $fh = FileHandle->new;
-
-        local $SIG{CHLD} = 'IGNORE';
-        unless ($fh->open("|$ftp -n")) {
-            warn "Couldn't open ftp: $!\n";
-            chdir $dir; return;
-        }
-
-        my @dialog = split(/\n/, <<"END_FTP");
-open $host
-user anonymous anonymous\@example.com
-cd $path
-binary
-get $file $file
-quit
-END_FTP
-        foreach (@dialog) { $fh->print("$_\n") }
-        $fh->close;
-    } }
-    else {
-        warn "No working 'ftp' program available!\n";
-        chdir $dir; return;
-    }
-
-    unless (-f $file) {
-        warn "Fetching failed: $@\n";
-        chdir $dir; return;
-    }
-
-    return if exists $args{size} and -s $file != $args{size};
-    system($args{run}) if exists $args{run};
-    unlink($file) if $args{remove};
-
-    print(((!exists $args{check_for} or -e $args{check_for})
-        ? "done!" : "failed! ($!)"), "\n");
-    chdir $dir; return !$?;
-}
-
-1;
diff --git a/inc/Module/Install/Substitute.pm b/inc/Module/Install/Substitute.pm
deleted file mode 100644
index 56af7fe..0000000
--- a/inc/Module/Install/Substitute.pm
+++ /dev/null
@@ -1,131 +0,0 @@
-#line 1
-package Module::Install::Substitute;
-
-use strict;
-use warnings;
-use 5.008; # I don't care much about earlier versions
-
-use Module::Install::Base;
-our @ISA = qw(Module::Install::Base);
-
-our $VERSION = '0.03';
-
-require File::Temp;
-require File::Spec;
-require Cwd;
-
-#line 89
-
-sub substitute
-{
-	my $self = shift;
-	$self->{__subst} = shift;
-	$self->{__option} = {};
-	if( UNIVERSAL::isa( $_[0], 'HASH' ) ) {
-		my $opts = shift;
-		while( my ($k,$v) = each( %$opts ) ) {
-			$self->{__option}->{ lc( $k ) } = $v || '';
-		}
-	}
-	$self->_parse_options;
-
-	my @file = @_;
-	foreach my $f (@file) {
-		$self->_rewrite_file( $f );
-	}
-
-	return;
-}
-
-sub _parse_options
-{
-	my $self = shift;
-	my $cwd = Cwd::getcwd();
-	foreach my $t ( qw(from to) ) {
-        $self->{__option}->{$t} = $cwd unless $self->{__option}->{$t};
-		my $d = $self->{__option}->{$t};
-		die "Couldn't read directory '$d'" unless -d $d && -r _;
-	}
-}
-
-sub _rewrite_file
-{
-	my ($self, $file) = @_;
-	my $source = File::Spec->catfile( $self->{__option}{from}, $file );
-	$source .= $self->{__option}{sufix} if $self->{__option}{sufix};
-	unless( -f $source && -r _ ) {
-		print STDERR "Couldn't find file '$source'\n";
-		return;
-	}
-	my $dest = File::Spec->catfile( $self->{__option}{to}, $file );
-	return $self->__rewrite_file( $source, $dest );
-}
-
-sub __rewrite_file
-{
-	my ($self, $source, $dest) = @_;
-
-	my $mode = (stat($source))[2];
-
-	open my $sfh, "<$source" or die "Couldn't open '$source' for read";
-	print "Open input '$source' file for substitution\n";
-
-	my ($tmpfh, $tmpfname) = File::Temp::tempfile('mi-subst-XXXX', UNLINK => 1);
-	$self->__process_streams( $sfh, $tmpfh, ($source eq $dest)? 1: 0 );
-	close $sfh;
-
-	seek $tmpfh, 0, 0 or die "Couldn't seek in tmp file";
-
-	open my $dfh, ">$dest" or die "Couldn't open '$dest' for write";
-	print "Open output '$dest' file for substitution\n";
-
-	while( <$tmpfh> ) {
-		print $dfh $_;
-	}
-	close $dfh;
-	chmod $mode, $dest or "Couldn't change mode on '$dest'";
-}
-
-sub __process_streams
-{
-	my ($self, $in, $out, $replace) = @_;
-	
-	my @queue = ();
-	my $subst = $self->{'__subst'};
-	my $re_subst = join('|', map {"\Q$_"} keys %{ $subst } );
-
-	while( my $str = <$in> ) {
-		if( $str =~ /^###\s*(before|replace|after)\:\s?(.*)$/s ) {
-			my ($action, $nstr) = ($1,$2);
-			$nstr =~ s/\@($re_subst)\@/$subst->{$1}/ge;
-
-			die "Replace action is bad idea for situations when dest is equal to source"
-                if $replace && $action eq 'replace';
-			if( $action eq 'before' ) {
-				die "no line before 'before' action" unless @queue;
-				# overwrite prev line;
-				pop @queue;
-				push @queue, $nstr;
-				push @queue, $str;
-			} elsif( $action eq 'replace' ) {
-				push @queue, $nstr;
-			} elsif( $action eq 'after' ) {
-				push @queue, $str;
-				push @queue, $nstr;
-				# skip one line;
-				<$in>;
-			}
-		} else {
-			push @queue, $str;
-		}
-		while( @queue > 3 ) {
-			print $out shift(@queue);
-		}
-	}
-	while( scalar @queue ) {
-		print $out shift(@queue);
-	}
-}
-
-1;
-
diff --git a/inc/Module/Install/Win32.pm b/inc/Module/Install/Win32.pm
deleted file mode 100644
index f7aa615..0000000
--- a/inc/Module/Install/Win32.pm
+++ /dev/null
@@ -1,64 +0,0 @@
-#line 1
-package Module::Install::Win32;
-
-use strict;
-use Module::Install::Base ();
-
-use vars qw{$VERSION @ISA $ISCORE};
-BEGIN {
-	$VERSION = '1.19';
-	@ISA     = 'Module::Install::Base';
-	$ISCORE  = 1;
-}
-
-# determine if the user needs nmake, and download it if needed
-sub check_nmake {
-	my $self = shift;
-	$self->load('can_run');
-	$self->load('get_file');
-
-	require Config;
-	return unless (
-		$^O eq 'MSWin32'                     and
-		$Config::Config{make}                and
-		$Config::Config{make} =~ /^nmake\b/i and
-		! $self->can_run('nmake')
-	);
-
-	print "The required 'nmake' executable not found, fetching it...\n";
-
-	require File::Basename;
-	my $rv = $self->get_file(
-		url       => 'http://download.microsoft.com/download/vc15/Patch/1.52/W95/EN-US/Nmake15.exe',
-		ftp_url   => 'ftp://ftp.microsoft.com/Softlib/MSLFILES/Nmake15.exe',
-		local_dir => File::Basename::dirname($^X),
-		size      => 51928,
-		run       => 'Nmake15.exe /o > nul',
-		check_for => 'Nmake.exe',
-		remove    => 1,
-	);
-
-	die <<'END_MESSAGE' unless $rv;
-
--------------------------------------------------------------------------------
-
-Since you are using Microsoft Windows, you will need the 'nmake' utility
-before installation. It's available at:
-
-  http://download.microsoft.com/download/vc15/Patch/1.52/W95/EN-US/Nmake15.exe
-      or
-  ftp://ftp.microsoft.com/Softlib/MSLFILES/Nmake15.exe
-
-Please download the file manually, save it to a directory in %PATH% (e.g.
-C:\WINDOWS\COMMAND\), then launch the MS-DOS command line shell, "cd" to
-that directory, and run "Nmake15.exe" from there; that will create the
-'nmake.exe' file needed by this module.
-
-You may then resume the installation process described in README.
-
--------------------------------------------------------------------------------
-END_MESSAGE
-
-}
-
-1;
diff --git a/inc/Module/Install/WriteAll.pm b/inc/Module/Install/WriteAll.pm
deleted file mode 100644
index 2db861a..0000000
--- a/inc/Module/Install/WriteAll.pm
+++ /dev/null
@@ -1,63 +0,0 @@
-#line 1
-package Module::Install::WriteAll;
-
-use strict;
-use Module::Install::Base ();
-
-use vars qw{$VERSION @ISA $ISCORE};
-BEGIN {
-	$VERSION = '1.19';
-	@ISA     = qw{Module::Install::Base};
-	$ISCORE  = 1;
-}
-
-sub WriteAll {
-	my $self = shift;
-	my %args = (
-		meta        => 1,
-		sign        => 0,
-		inline      => 0,
-		check_nmake => 1,
-		@_,
-	);
-
-	$self->sign(1)                if $args{sign};
-	$self->admin->WriteAll(%args) if $self->is_admin;
-
-	$self->check_nmake if $args{check_nmake};
-	unless ( $self->makemaker_args->{PL_FILES} ) {
-		# XXX: This still may be a bit over-defensive...
-		unless ($self->makemaker(6.25)) {
-			$self->makemaker_args( PL_FILES => {} ) if -f 'Build.PL';
-		}
-	}
-
-	# Until ExtUtils::MakeMaker support MYMETA.yml, make sure
-	# we clean it up properly ourself.
-	$self->realclean_files('MYMETA.yml');
-
-	if ( $args{inline} ) {
-		$self->Inline->write;
-	} else {
-		$self->Makefile->write;
-	}
-
-	# The Makefile write process adds a couple of dependencies,
-	# so write the META.yml files after the Makefile.
-	if ( $args{meta} ) {
-		$self->Meta->write;
-	}
-
-	# Experimental support for MYMETA
-	if ( $ENV{X_MYMETA} ) {
-		if ( $ENV{X_MYMETA} eq 'JSON' ) {
-			$self->Meta->write_mymeta_json;
-		} else {
-			$self->Meta->write_mymeta_yaml;
-		}
-	}
-
-	return 1;
-}
-
-1;
diff --git a/lib/RT/Extension/MandatoryOnTransition.pm b/lib/RT/Extension/MandatoryOnTransition.pm
index bc7488f..d75f96a 100644
--- a/lib/RT/Extension/MandatoryOnTransition.pm
+++ b/lib/RT/Extension/MandatoryOnTransition.pm
@@ -146,17 +146,31 @@ Category selection before resolving tickets in every other queue.
 
     Set( %MandatoryOnTransition,
         Helpdesk => {
-            '* -> resolved' => ['TimeWorked', 'CF.Resolution', 'CustomRole.Analyst'],
+            '* -> resolved' => ['TimeWorked', 'CF.Resolution'],
         },
         '*' => {
             '* -> resolved' => ['CF.Category'],
-            'CustomRole.Analyst' => {transition => '* -> open', group => 'Engineering'},
         },
     );
 
 The transition syntax is similar to that found in RT's Lifecycles.  See
 C<perldoc /opt/rt4/etc/RT_Config.pm>.
 
+=head2 Requiring role values
+
+You can require any core or custom role on a RT::Ticket object, further more
+you can require a 'group' category to check that at least one value for that
+role is a member of the required group. In the below example we require that
+the custom role 'customer' have a user value who is a member of the group 'Customers'
+and the owner of the ticket must be a member of the group 'Helpdesk'.
+
+Set( %MandatoryOnTransition,
+    'General' => {
+        'CustomRole.customer' => { transition => 'open -> *', group => 'Customers' },
+        'Owner' => { transition => 'open -> *', group => 'Helpdesk' },
+    },
+);
+
 =head2 Restrictions on Queue Transitions
 
 The default behavior for C<MandatoryOnTransition> operates on status transitions,

-----------------------------------------------------------------------


More information about the Bps-public-commit mailing list