[Bps-public-commit] rt-extension-assetsql branch, 4.4.1/custom-roles-test, created. 0.04-2-g7760037

Craig Kaiser craig at bestpractical.com
Thu Nov 2 17:10:09 EDT 2017


The branch, 4.4.1/custom-roles-test has been created
        at  7760037f1af9f45f27c92861616e8be75bc8b2f2 (commit)

- Log -----------------------------------------------------------------
commit 5db7a29657a0ddd2b97bf95d269ef08237bd5567
Author: craig <craig at bestpractical.com>
Date:   Fri Oct 20 15:07:59 2017 -0400

    Add support for custom roles in asset query builder

diff --git a/html/Asset/Search/Build.html b/html/Asset/Search/Build.html
index 5272dc0..6914aa4 100644
--- a/html/Asset/Search/Build.html
+++ b/html/Asset/Search/Build.html
@@ -147,8 +147,8 @@ my $cf_field_names =
 
 # Try to find if we're adding a clause
 foreach my $arg ( keys %ARGS ) {
-    next unless $arg =~ m/^ValueOf(\w+|($cf_field_names).\{.*?\})$/
-                && ( ref $ARGS{$arg} eq "ARRAY"
+    next unless $arg =~ m/^ValueOf(\w+|($cf_field_names).\{.*?\}|CustomRole.\{.*?\})$/
+                        && ( ref $ARGS{$arg} eq "ARRAY"
                      ? grep $_ ne '', @{ $ARGS{$arg} }
                      : $ARGS{$arg} ne '' );
 
@@ -201,7 +201,7 @@ push @actions, $m->comp('/Search/Elements/EditQuery:Process',
 my $optionlist_arrayref;
 ($query{'Query'}, $optionlist_arrayref) = $tree->GetQueryAndOptionList(\@current_values);
 
-my $optionlist = join "\n", map { qq(<option value="$_->{INDEX}" $_->{SELECTED}>) 
+my $optionlist = join "\n", map { qq(<option value="$_->{INDEX}" $_->{SELECTED}>)
                                   . (" " x (5 * $_->{DEPTH}))
                                   . $m->interp->apply_escapes($_->{TEXT}, 'h') . qq(</option>) } @$optionlist_arrayref;
 
diff --git a/html/Asset/Search/Elements/BuildFormatString b/html/Asset/Search/Elements/BuildFormatString
index 4e65800..427ca5e 100644
--- a/html/Asset/Search/Elements/BuildFormatString
+++ b/html/Asset/Search/Elements/BuildFormatString
@@ -40,24 +40,21 @@ my @fields = qw(
     NBSP
 ); # loc_qw
 
-my $CustomFields = RT::CustomFields->new( $session{'CurrentUser'});
+
+#my %ranges = %{ RT->Config->Get('CustomDateRanges')->{'RT::Asset'} || {} };
+#push @fields, sort keys %ranges;
+
+my $CustomRoles = RT::CustomRoles->new( $session{'CurrentUser'});
 foreach my $id (keys %catalogs) {
-    # Gotta load up the $catalog object, since catalogs get stored by name now.
     my $catalog = RT::Catalog->new($session{'CurrentUser'});
     $catalog->Load($id);
     next unless $catalog->Id;
-    $CustomFields->LimitToCatalog($catalog->Id);
-    $CustomFields->SetContextObject( $catalog ) if keys %catalogs == 1;
+    $CustomRoles->LimitToObjectId($catalog->Id);
 }
-$CustomFields->LimitToCatalog(0);
-
-while ( my $CustomField = $CustomFields->Next ) {
-    push @fields, "CustomField.{" . $CustomField->Name . "}";
+while ( my $Role = $CustomRoles->Next ) {
+    push @fields, "CustomRole.{" . $Role->Name . "}";
 }
 
-my %ranges = %{ RT->Config->Get('CustomDateRanges')->{'RT::Asset'} || {} };
-push @fields, sort keys %ranges;
-
 $m->callback( Fields => \@fields, ARGSRef => \%ARGS );
 
 my ( @seen);
@@ -73,7 +70,7 @@ foreach my $field (@format) {
 
 if ( $RemoveCol ) {
     # we do this regex match to avoid a non-numeric warning
-    my ($index) = ($CurrentDisplayColumns // '') =~ /^(\d+)/;
+    my ($index) = ($CurrentDisplayColumns // '') =~ /^(\d)/;
     if ( defined($index) ) {
         delete $seen[$index];
         my @temp = @seen;
@@ -122,7 +119,7 @@ elsif ( $AddCol ) {
     }
 }
 elsif ( $ColUp ) {
-    my ($index) = ($CurrentDisplayColumns // '') =~ /^(\d+)/;
+    my ($index) = ($CurrentDisplayColumns // '') =~ /^(\d)/;
     if ( defined $index && ( $index - 1 ) >= 0 ) {
         my $column = $seen[$index];
         $seen[$index]       = $seen[ $index - 1 ];
@@ -131,7 +128,7 @@ elsif ( $ColUp ) {
     }
 }
 elsif ( $ColDown ) {
-    my ($index) = ($CurrentDisplayColumns // '') =~ /^(\d+)/;
+    my ($index) = ($CurrentDisplayColumns // '') =~ /^(\d)/;
     if ( defined $index && ( $index + 1 ) < scalar @seen ) {
         my $column = $seen[$index];
         $seen[$index]       = $seen[ $index + 1 ];
diff --git a/html/Asset/Search/Elements/ConditionRow b/html/Asset/Search/Elements/ConditionRow
new file mode 100644
index 0000000..5b9ea48
--- /dev/null
+++ b/html/Asset/Search/Elements/ConditionRow
@@ -0,0 +1,99 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2016 Best Practical Solutions, LLC
+%#                                          <sales at 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 }}}
+<tr>
+<td class="label"><% $handle_block->( $Condition->{'Field'}, $Condition->{'Name'} .'Field' ) |n %></td>
+<td class="operator"><% $handle_block->( $Condition->{'Op'}, $Condition->{'Name'} .'Op') |n %></td>
+<td class="value"><% $handle_block->( $Condition->{'Value'}, 'ValueOf'. $Condition->{'Name'} ) |n %></td>
+</tr>
+<%INIT>
+return unless $Condition && $Condition->{'Name'};
+
+$m->callback( Condition => \$Condition );
+return unless $Condition;
+
+my $handle_block;
+$handle_block = sub {
+    my $box = shift;
+    return $box unless ref $box;
+
+    my $name = shift;
+    if ( UNIVERSAL::isa($box, 'ARRAY') ) {
+        my $res = '';
+        $res .= $handle_block->( $_, $name ) foreach @$box;
+        return $res;
+    }
+
+    return undef unless UNIVERSAL::isa($box, 'HASH');
+    if ( $box->{'Type'} eq 'component' ) {
+        $box->{'Arguments'} ||= {},
+        return $m->scomp( $box->{'Path'}, %{ $box->{'Arguments'} }, Name => $name );
+    }
+    if ( $box->{'Type'} eq 'text' ) {
+        $box->{id} ||= $box->{name} ||= $name;
+        $box->{value} ||= delete($box->{Default}) || '';
+        return "<input ".join(" ", map{$m->interp->apply_escapes(lc($_),'h')
+                                      .q{="}.$m->interp->apply_escapes($box->{$_},'h').q{"}}
+                                   sort keys %$box)." />";
+    }
+    if ( $box->{'Type'} eq 'select' ) {
+        my $res = '';
+        $res .= qq{<select id="$name" name="$name">};
+        my @options = @{ $box->{'Options'} };
+        while( my $k = shift @options ) {
+            my $v = shift @options;
+            $res .= qq{<option value="$k">$v</option>};
+        }
+        $res .= qq{</select>};
+        return $res;
+    }
+};
+
+</%INIT>
+<%ARGS>
+$Condition => {}
+</%ARGS>
diff --git a/html/Asset/Search/Elements/EditSort b/html/Asset/Search/Elements/EditSort
index ec60707..0600777 100644
--- a/html/Asset/Search/Elements/EditSort
+++ b/html/Asset/Search/Elements/EditSort
@@ -43,8 +43,8 @@ selected="selected"
 <td class="label">
 <&|/l&>Rows per page</&>:
 </td><td class="value">
-<& /Elements/SelectResultsPerPage, 
-    Name => "RowsPerPage", 
+<& /Elements/SelectResultsPerPage,
+    Name => "RowsPerPage",
     Default => $RowsPerPage &>
 </td>
 </tr>
@@ -69,6 +69,15 @@ $fields{'Contact'} = 'Contact';
 my @cfs = grep /^CustomField/, @{$ARGS{AvailableColumns}};
 $fields{$_} = $_ for @cfs;
 
+# Add all available CustomRoles to the list of sortable columns.
+my @roles = grep /^CustomRole/, @{$ARGS{AvailableColumns}};
+for my $role (@roles) {
+    my ($label) = $role =~ /^CustomRole.{(.*)}$/;
+    my $value = $role;
+    $fields{$label . '.EmailAddress' } = $value . '.EmailAddress';
+}
+
+
 # Add PAW sort
 $fields{'Custom.Ownership'} = 'Custom.Ownership';
 
diff --git a/html/Asset/Search/Elements/PickCriteria b/html/Asset/Search/Elements/PickCriteria
index 6a3cc34..6a4d084 100644
--- a/html/Asset/Search/Elements/PickCriteria
+++ b/html/Asset/Search/Elements/PickCriteria
@@ -5,6 +5,7 @@
 
 % $m->callback( %ARGS, CallbackName => "BeforeBasics" );
 <& PickBasics, catalogs => \%catalogs &>
+<& PickCustomRoles, catalogs => \%catalogs &>
 <& PickAssetCFs, catalogs => \%catalogs &>
 % $m->callback( %ARGS, CallbackName => "AfterCFs" );
 
diff --git a/html/Asset/Search/Elements/PickCustomRoles b/html/Asset/Search/Elements/PickCustomRoles
new file mode 100644
index 0000000..5165900
--- /dev/null
+++ b/html/Asset/Search/Elements/PickCustomRoles
@@ -0,0 +1,89 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2017 Best Practical Solutions, LLC
+%#                                          <sales at 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 }}}
+<%ARGS>
+%catalogs => ()
+</%ARGS>
+<%INIT>
+my $CustomRoles = RT::CustomRoles->new( $session{'CurrentUser'});
+foreach my $id (keys %catalogs) {
+    my $catalog = RT::Catalog->new($session{'CurrentUser'});
+    $catalog->Load($id);
+    next unless $catalog->Id;
+    $CustomRoles->LimitToObjectId($catalog->Id);
+}
+$m->callback(
+    CallbackName => 'MassageCustomRoles',
+    CustomRoles  => $CustomRoles,
+);
+
+my @lines;
+while ( my $Role = $CustomRoles->Next ) {
+    my $name = "CustomRole.{" . $Role->Name . "}";
+    my %line = (
+        Name => $name,
+        Field => {
+            Type => 'component',
+            Path => 'SelectPersonType',
+            Arguments => { Role => $Role, Default => $name },
+        },
+        Op => {
+            Type => 'component',
+            Path => '/Elements/SelectMatch',
+        },
+        Value => { Type => 'text', Size => 20 },
+    );
+
+    push @lines, \%line;
+}
+
+$m->callback( Conditions => \@lines, Catalogs => \%catalogs );
+
+</%INIT>
+% foreach( @lines ) {
+<& ConditionRow, Condition => $_ &>
+% }
diff --git a/html/Asset/Search/Elements/SelectPersonType b/html/Asset/Search/Elements/SelectPersonType
index 0bb34d9..b8e1ca4 100644
--- a/html/Asset/Search/Elements/SelectPersonType
+++ b/html/Asset/Search/Elements/SelectPersonType
@@ -3,26 +3,45 @@
 <option value="">-</option>
 % }
 % for my $option (@types) {
+% my ($value, $label) = ($option, $option);
+% if (ref($option)) {
+%     ($value, $label) = @$option;
+% }
 %  if ($Suffix) {
-<option value="<% $option %><% $Suffix %>"<%$option eq $Default && qq[ selected="selected"] |n %> ><% loc($option) %> <% loc('Group') %></option>
+<option value="<% $value %><% $Suffix %>"<%$value eq $Default && qq[ selected="selected"] |n %> ><% loc($label) %> <% loc('Group') %></option>
 %   next;
 %  }
 %  foreach my $subtype (@subtypes) {
-<option value="<%"$option.$subtype"%>"<%$option eq $Default && $subtype eq 'EmailAddress' && qq[ selected="selected"] |n %> ><% loc($option) %> <% loc($subtype) %></option>
+<option value="<%"$value.$subtype"%>"<%$value eq $Default && $subtype eq 'EmailAddress' && qq[ selected="selected"] |n %> ><% loc($label) %> <% loc($subtype) %></option>
 %  }
 % }
 </select>
 
 <%INIT>
-my @types = qw(Owner HeldBy Contact);
-my @subtypes = @{ $RT::Assets::SEARCHABLE_SUBFIELDS{'User'} };
+my @types;
+if ($Role) {
+   @types = (
+      [ "CustomRole.{" . $Role->Name . "}", $Role->Name ],
+   );
+}
+elsif ($Scope =~ /queue/) {
+   @types = qw(Cc AdminCc);
+}
+elsif ($Suffix eq 'Group') {
+   @types = qw(Owner Requestor Cc AdminCc Watcher);
+}
+else {
+   @types = qw(Requestor Cc AdminCc Watcher Owner QueueCc QueueAdminCc QueueWatcher);
+}
+
+my @subtypes = @{ $RT::Tickets::SEARCHABLE_SUBFIELDS{'User'} };
 
-$m->callback(Types => \@types, Subtypes => \@subtypes);
 </%INIT>
 <%ARGS>
 $AllowNull => 1
 $Suffix => ''
-$Default =>undef
-$Scope => 'asset'
+$Default=>undef
+$Scope => 'ticket'
 $Name => 'WatcherType'
+$Role => undef
 </%ARGS>
diff --git a/lib/RT/Extension/AssetSQL/Assets.pm b/lib/RT/Extension/AssetSQL/Assets.pm
index bf20558..95ddd4e 100644
--- a/lib/RT/Extension/AssetSQL/Assets.pm
+++ b/lib/RT/Extension/AssetSQL/Assets.pm
@@ -1,6 +1,7 @@
 use strict;
 use warnings;
 use 5.010;
+# use RT::CustomRoles;
 
 package RT::Assets;
 
@@ -69,7 +70,7 @@ sub ItemsArrayRefWindow {
     my $self = shift;
     my $window = shift;
 
-    my @old = ($self->_ItemsCounter, $self->RowsPerPage, $self->FirstRow+1);
+    my @old = ($self->_ItemsCounter, $self->RowsPerPage, $self->FirstRow1);
 
     $self->RowsPerPage( $window );
     $self->FirstRow(1);
@@ -462,6 +463,9 @@ our %FIELD_METADATA = (
     Contact         => [ 'WATCHERFIELD' => 'Contact', ], #loc_left_pair
     ContactGroup    => [ 'MEMBERSHIPFIELD' => 'Contact', ], #loc_left_pair
 
+    CustomRole       => [ 'WATCHERFIELD' ], # loc_left_pair
+    CustomRole       => [ 'WATCHERFIELD' ], # loc_left_pair
+
     CustomFieldValue => [ 'CUSTOMFIELD' => 'Asset' ], #loc_left_pair
     CustomField      => [ 'CUSTOMFIELD' => 'Asset' ], #loc_left_pair
     CF               => [ 'CUSTOMFIELD' => 'Asset' ], #loc_left_pair
@@ -608,7 +612,7 @@ sub _EnumLimit {
         or $op     eq "!=";
 
     my $meta = $FIELD_METADATA{$field};
-    if ( defined $meta->[1] && defined $value && $value !~ /^\d+$/ ) {
+    if ( defined $meta->[1] && defined $value && $value !~ /^\d$/ ) {
         my $class = "RT::" . $meta->[1];
         my $o     = $class->new( $sb->CurrentUser );
         $o->Load($value);
@@ -792,13 +796,13 @@ sub _DateLimit {
     my ( $sb, $field, $op, $value, %rest ) = @_;
 
     die "Invalid Date Op: $op"
-        unless $op =~ /^(=|>|<|>=|<=|IS(\s+NOT)?)$/i;
+        unless $op =~ /^(=|>|<|>=|<=|IS(\sNOT)?)$/i;
 
     my $meta = $FIELD_METADATA{$field};
     die "Incorrect Meta Data for $field"
         unless ( defined $meta->[1] );
 
-    if ( $op =~ /^(IS(\s+NOT)?)$/i) {
+    if ( $op =~ /^(IS(\sNOT)?)$/i) {
         return $sb->Limit(
             FUNCTION => $sb->NotSetDateToNullFunction,
             FIELD    => $meta->[1],
@@ -810,12 +814,12 @@ sub _DateLimit {
 
     if ( my $subkey = $rest{SUBKEY} ) {
         if ( $subkey eq 'DayOfWeek' && $op !~ /IS/i && $value =~ /[^0-9]/ ) {
-            for ( my $i = 0; $i < @RT::Date::DAYS_OF_WEEK; $i++ ) {
+            for ( my $i = 0; $i < @RT::Date::DAYS_OF_WEEK; $i ) {
                 # Use a case-insensitive regex for better matching across
                 # locales since we don't have fc() and lc() is worse.  Really
                 # we should be doing Unicode normalization too, but we don't do
                 # that elsewhere in RT.
-                # 
+                #
                 # XXX I18N: Replace the regex with fc() once we're guaranteed 5.16.
                 next unless lc $RT::Date::DAYS_OF_WEEK[ $i ] eq lc $value
                          or $sb->CurrentUser->loc($RT::Date::DAYS_OF_WEEK[ $i ]) =~ /^\Q$value\E$/i;
@@ -826,12 +830,12 @@ sub _DateLimit {
                 if $value =~ /[^0-9]/;
         }
         elsif ( $subkey eq 'Month' && $op !~ /IS/i && $value =~ /[^0-9]/ ) {
-            for ( my $i = 0; $i < @RT::Date::MONTHS; $i++ ) {
+            for ( my $i = 0; $i < @RT::Date::MONTHS; $i ) {
                 # Use a case-insensitive regex for better matching across
                 # locales since we don't have fc() and lc() is worse.  Really
                 # we should be doing Unicode normalization too, but we don't do
                 # that elsewhere in RT.
-                # 
+                #
                 # XXX I18N: Replace the regex with fc() once we're guaranteed 5.16.
                 next unless lc $RT::Date::MONTHS[ $i ] eq lc $value
                          or $sb->CurrentUser->loc($RT::Date::MONTHS[ $i ]) =~ /^\Q$value\E$/i;
@@ -972,7 +976,13 @@ sub _WatcherLimit {
     my $meta = $FIELD_METADATA{ $field };
     my $type = $meta->[1] || '';
     my $class = $meta->[2] || 'Asset';
+    my $column = $rest{"SUBKEY"};
 
+    if ($field eq 'CustomRole') {
+        my ($role, $col, $original_name) = $self->_CustomRoleDecipher( $column );
+        $column = $col || 'id';
+        $type = $role ? $role->GroupType : $original_name;
+    }
     # Bail if the subfield is not allowed
     if (    $rest{SUBKEY}
         and not grep { $_ eq $rest{SUBKEY} } @{$SEARCHABLE_SUBFIELDS{'User'}})
@@ -1008,7 +1018,7 @@ sub _WatcherMembershipLimit {
     die "Invalid $field Op: $op"
         unless $op =~ /^=$/;
 
-    unless ( $value =~ /^\d+$/ ) {
+    unless ( $value =~ /^\d$/ ) {
         my $group = RT::Group->new( $self->CurrentUser );
         $group->LoadUserDefinedGroup( $value );
         $value = $group->id || 0;
@@ -1042,83 +1052,40 @@ sub _WatcherMembershipLimit {
     );
 }
 
-=head2 _CustomFieldDecipher
-
-Try and turn a CF descriptor into (cfid, cfname) object pair.
+=head2 _CustomRoleDecipher
 
-Takes an optional second parameter of the CF LookupType, defaults to Asset CFs.
+Try and turn a custom role descriptor (e.g. C<CustomRole.{Engineer}>) into
+(role, column, original name).
 
 =cut
 
-sub _CustomFieldDecipher {
-    my ($self, $string, $lookuptype) = @_;
-    $lookuptype ||= $self->_SingularClass->CustomFieldLookupType;
-
-    my ($object, $field, $column) = ($string =~ /^(?:(.+?)\.)?\{(.+)\}(?:\.(Content|LargeContent))?$/);
-    $field ||= ($string =~ /^\{(.*?)\}$/)[0] || $string;
-
-    my ($cf, $applied_to);
+sub _CustomRoleDecipher {
+    my ($self, $string) = @_;
 
-    if ( $object ) {
-        my $record_class = RT::CustomField->RecordClassFromLookupType($lookuptype);
-        $applied_to = $record_class->new( $self->CurrentUser );
-        $applied_to->Load( $object );
+    my ($field, $column) = ($string =~ /^\{(.)\}(?:\.(\w))?$/);
 
-        if ( $applied_to->id ) {
-            RT->Logger->debug("Limiting to CFs identified by '$field' applied to $record_class #@{[$applied_to->id]} (loaded via '$object')");
-        }
-        else {
-            RT->Logger->warning("$record_class '$object' doesn't exist, parsed from '$string'");
-            $object = 0;
-            undef $applied_to;
-        }
-    }
+    my $role;
 
     if ( $field =~ /\D/ ) {
-        $object ||= '';
-        my $cfs = RT::CustomFields->new( $self->CurrentUser );
-        $cfs->Limit( FIELD => 'Name', VALUE => $field, CASESENSITIVE => 0 );
-        $cfs->LimitToLookupType($lookuptype);
-
-        if ($applied_to) {
-            $cfs->SetContextObject($applied_to);
-            $cfs->LimitToObjectId($applied_to->id);
+        my $roles = RT::CustomRoles->new( $self->CurrentUser );
+        $roles->Limit( FIELD => 'Name', VALUE => $field, CASESENSITIVE => 0 );
+
+        # custom roles are named uniquely, but just in case there are
+        # multiple matches, bail out as we don't know which one to use
+        $role = $roles->First;
+        if ( $role ) {
+            $role = undef if $roles->Next;
         }
-
-        # if there is more then one field the current user can
-        # see with the same name then we shouldn't return cf object
-        # as we don't know which one to use
-        $cf = $cfs->First;
-        if ( $cf ) {
-            $cf = undef if $cfs->Next;
-        }
-        else {
-            # find the cf without ACL
-            # this is because current _CustomFieldJoinByName has a bug that
-            # can't search correctly with negative cf ops :/
-            my $cfs = RT::CustomFields->new( RT->SystemUser );
-            $cfs->Limit( FIELD => 'Name', VALUE => $field, CASESENSITIVE => 0 );
-            $cfs->LimitToLookupType( $lookuptype );
-
-            if ( $applied_to ) {
-                $cfs->SetContextObject( $applied_to );
-                $cfs->LimitToObjectId( $applied_to->id );
-            }
-
-            $cf = $cfs->First unless $cfs->Count > 1;
-        }
-
     }
     else {
-        $cf = RT::CustomField->new( $self->CurrentUser );
-        $cf->Load( $field );
-        $cf->SetContextObject($applied_to)
-            if $cf->id and $applied_to;
+        $role = RT::CustomRole->new( $self->CurrentUser );
+        $role->Load( $field );
     }
 
-    return ($object, $field, $cf, $column);
+    return ($role, $column, $field);
 }
 
+
 =head2 _CustomFieldLimit
 
 Limit based on CustomFields
@@ -1465,7 +1432,7 @@ sub _RestrictionsToClauses {
 
         # Two special case
         # Handle subkey fields with a different real field
-        if ( $field =~ /^(\w+)\./ ) {
+        if ( $field =~ /^(\w)\./ ) {
             $realfield = $1;
         }
 
@@ -1586,4 +1553,3 @@ sub _ProcessRestrictions {
 }
 
 1;
-

commit 7760037f1af9f45f27c92861616e8be75bc8b2f2
Author: craig <craig at bestpractical.com>
Date:   Mon Oct 23 14:07:10 2017 -0400

    Support for custom role query building for Assets

diff --git a/CurrentUser- b/CurrentUser-
new file mode 100644
index 0000000..e69de29
diff --git a/GotoFirstItem b/GotoFirstItem
new file mode 100644
index 0000000..e69de29
diff --git a/Next b/Next
new file mode 100644
index 0000000..e69de29
diff --git a/S b/S
new file mode 100644
index 0000000..e69de29
diff --git a/_ItemsCounter b/_ItemsCounter
new file mode 100644
index 0000000..e69de29
diff --git a/html/Asset/Search/Elements/BuildFormatString b/html/Asset/Search/Elements/BuildFormatString
index 427ca5e..2bf09b1 100644
--- a/html/Asset/Search/Elements/BuildFormatString
+++ b/html/Asset/Search/Elements/BuildFormatString
@@ -1,3 +1,50 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2016 Best Practical Solutions, LLC
+%#                                          <sales at 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 }}}
 <%ARGS>
 $Format => RT->Config->Get('AssetSearchFormat')
 
@@ -41,8 +88,8 @@ my @fields = qw(
 ); # loc_qw
 
 
-#my %ranges = %{ RT->Config->Get('CustomDateRanges')->{'RT::Asset'} || {} };
-#push @fields, sort keys %ranges;
+my %ranges = %{ RT->Config->Get('CustomDateRanges')->{'RT::Asset'} || {} };
+push @fields, sort keys %ranges;
 
 my $CustomRoles = RT::CustomRoles->new( $session{'CurrentUser'});
 foreach my $id (keys %catalogs) {
@@ -70,7 +117,7 @@ foreach my $field (@format) {
 
 if ( $RemoveCol ) {
     # we do this regex match to avoid a non-numeric warning
-    my ($index) = ($CurrentDisplayColumns // '') =~ /^(\d)/;
+    my ($index) = ($CurrentDisplayColumns // '') =~ /^(\d+)/;
     if ( defined($index) ) {
         delete $seen[$index];
         my @temp = @seen;
@@ -119,7 +166,7 @@ elsif ( $AddCol ) {
     }
 }
 elsif ( $ColUp ) {
-    my ($index) = ($CurrentDisplayColumns // '') =~ /^(\d)/;
+    my ($index) = ($CurrentDisplayColumns // '') =~ /^(\d+)/;
     if ( defined $index && ( $index - 1 ) >= 0 ) {
         my $column = $seen[$index];
         $seen[$index]       = $seen[ $index - 1 ];
@@ -128,7 +175,7 @@ elsif ( $ColUp ) {
     }
 }
 elsif ( $ColDown ) {
-    my ($index) = ($CurrentDisplayColumns // '') =~ /^(\d)/;
+    my ($index) = ($CurrentDisplayColumns // '') =~ /^(\d+)/;
     if ( defined $index && ( $index + 1 ) < scalar @seen ) {
         my $column = $seen[$index];
         $seen[$index]       = $seen[ $index + 1 ];
diff --git a/html/Asset/Search/Elements/SelectPersonType b/html/Asset/Search/Elements/SelectPersonType
index b8e1ca4..463ec05 100644
--- a/html/Asset/Search/Elements/SelectPersonType
+++ b/html/Asset/Search/Elements/SelectPersonType
@@ -24,24 +24,24 @@ if ($Role) {
       [ "CustomRole.{" . $Role->Name . "}", $Role->Name ],
    );
 }
-elsif ($Scope =~ /queue/) {
+elsif ($Scope =~ /catalog/) {
    @types = qw(Cc AdminCc);
 }
 elsif ($Suffix eq 'Group') {
    @types = qw(Owner Requestor Cc AdminCc Watcher);
 }
 else {
-   @types = qw(Requestor Cc AdminCc Watcher Owner QueueCc QueueAdminCc QueueWatcher);
+   @types = qw(Requestor Cc AdminCc Watcher Owner CatalogCc CatalogAdminCc CatalogWatcher);
 }
 
-my @subtypes = @{ $RT::Tickets::SEARCHABLE_SUBFIELDS{'User'} };
+my @subtypes = @{ $RT::Assets::SEARCHABLE_SUBFIELDS{'User'} };
 
 </%INIT>
 <%ARGS>
 $AllowNull => 1
 $Suffix => ''
 $Default=>undef
-$Scope => 'ticket'
+$Scope => 'asset'
 $Name => 'WatcherType'
 $Role => undef
 </%ARGS>
diff --git a/id b/id
new file mode 100644
index 0000000..e69de29
diff --git a/id, b/id,
new file mode 100644
index 0000000..e69de29
diff --git a/lib/RT/Extension/AssetSQL/Assets.pm b/lib/RT/Extension/AssetSQL/Assets.pm
index 95ddd4e..5bad77d 100644
--- a/lib/RT/Extension/AssetSQL/Assets.pm
+++ b/lib/RT/Extension/AssetSQL/Assets.pm
@@ -464,7 +464,6 @@ our %FIELD_METADATA = (
     ContactGroup    => [ 'MEMBERSHIPFIELD' => 'Contact', ], #loc_left_pair
 
     CustomRole       => [ 'WATCHERFIELD' ], # loc_left_pair
-    CustomRole       => [ 'WATCHERFIELD' ], # loc_left_pair
 
     CustomFieldValue => [ 'CUSTOMFIELD' => 'Asset' ], #loc_left_pair
     CustomField      => [ 'CUSTOMFIELD' => 'Asset' ], #loc_left_pair
@@ -612,7 +611,7 @@ sub _EnumLimit {
         or $op     eq "!=";
 
     my $meta = $FIELD_METADATA{$field};
-    if ( defined $meta->[1] && defined $value && $value !~ /^\d$/ ) {
+    if ( defined $meta->[1] && defined $value && $value !~ /^\d+$/ ) {
         my $class = "RT::" . $meta->[1];
         my $o     = $class->new( $sb->CurrentUser );
         $o->Load($value);
@@ -796,13 +795,13 @@ sub _DateLimit {
     my ( $sb, $field, $op, $value, %rest ) = @_;
 
     die "Invalid Date Op: $op"
-        unless $op =~ /^(=|>|<|>=|<=|IS(\sNOT)?)$/i;
+        unless $op =~ /^(=|>|<|>=|<=|IS(\s+NOT)?)$/i;
 
     my $meta = $FIELD_METADATA{$field};
     die "Incorrect Meta Data for $field"
         unless ( defined $meta->[1] );
 
-    if ( $op =~ /^(IS(\sNOT)?)$/i) {
+    if ( $op =~ /^(IS(\s+NOT)?)$/i) {
         return $sb->Limit(
             FUNCTION => $sb->NotSetDateToNullFunction,
             FIELD    => $meta->[1],
@@ -814,7 +813,7 @@ sub _DateLimit {
 
     if ( my $subkey = $rest{SUBKEY} ) {
         if ( $subkey eq 'DayOfWeek' && $op !~ /IS/i && $value =~ /[^0-9]/ ) {
-            for ( my $i = 0; $i < @RT::Date::DAYS_OF_WEEK; $i ) {
+            for ( my $i = 0; $i < @RT::Date::DAYS_OF_WEEK; $i++ ) {
                 # Use a case-insensitive regex for better matching across
                 # locales since we don't have fc() and lc() is worse.  Really
                 # we should be doing Unicode normalization too, but we don't do
@@ -830,7 +829,7 @@ sub _DateLimit {
                 if $value =~ /[^0-9]/;
         }
         elsif ( $subkey eq 'Month' && $op !~ /IS/i && $value =~ /[^0-9]/ ) {
-            for ( my $i = 0; $i < @RT::Date::MONTHS; $i ) {
+            for ( my $i = 0; $i < @RT::Date::MONTHS; $i++ ) {
                 # Use a case-insensitive regex for better matching across
                 # locales since we don't have fc() and lc() is worse.  Really
                 # we should be doing Unicode normalization too, but we don't do
@@ -976,7 +975,7 @@ sub _WatcherLimit {
     my $meta = $FIELD_METADATA{ $field };
     my $type = $meta->[1] || '';
     my $class = $meta->[2] || 'Asset';
-    my $column = $rest{"SUBKEY"};
+    my $column = $rest{SUBKEY};
 
     if ($field eq 'CustomRole') {
         my ($role, $col, $original_name) = $self->_CustomRoleDecipher( $column );
@@ -984,16 +983,16 @@ sub _WatcherLimit {
         $type = $role ? $role->GroupType : $original_name;
     }
     # Bail if the subfield is not allowed
-    if (    $rest{SUBKEY}
-        and not grep { $_ eq $rest{SUBKEY} } @{$SEARCHABLE_SUBFIELDS{'User'}})
+    if (    $column
+        and not grep { $_ eq $column } @{$SEARCHABLE_SUBFIELDS{'User'}})
     {
-        die "Invalid watcher subfield: '$rest{SUBKEY}'";
+        die "Invalid watcher subfield: '$column'";
     }
 
     $self->RoleLimit(
         TYPE      => $type,
         CLASS     => "RT::$class",
-        FIELD     => $rest{SUBKEY},
+        FIELD     => $column,
         OPERATOR  => $op,
         VALUE     => $value,
         SUBCLAUSE => "assetsql",
@@ -1018,7 +1017,7 @@ sub _WatcherMembershipLimit {
     die "Invalid $field Op: $op"
         unless $op =~ /^=$/;
 
-    unless ( $value =~ /^\d$/ ) {
+    unless ( $value =~ /^\d+$/ ) {
         my $group = RT::Group->new( $self->CurrentUser );
         $group->LoadUserDefinedGroup( $value );
         $value = $group->id || 0;
@@ -1052,6 +1051,68 @@ sub _WatcherMembershipLimit {
     );
 }
 
+=head2 _CustomFieldDecipher
+
+Try and turn a CF descriptor into (cfid, cfname) object pair.
+
+Takes an optional second parameter of the CF LookupType, defaults to Ticket CFs.
+
+=cut
+
+sub _CustomFieldDecipher {
+    my ($self, $string, $lookuptype) = @_;
+    $lookuptype ||= $self->_SingularClass->CustomFieldLookupType;
+
+    my ($object, $field, $column) = ($string =~ /^(?:(.+?)\.)?\{(.+)\}(?:\.(Content|LargeContent))?$/);
+    $field ||= ($string =~ /^\{(.*?)\}$/)[0] || $string;
+
+    my ($cf, $applied_to);
+
+    if ( $object ) {
+        my $record_class = RT::CustomField->RecordClassFromLookupType($lookuptype);
+        $applied_to = $record_class->new( $self->CurrentUser );
+        $applied_to->Load( $object );
+
+        if ( $applied_to->id ) {
+            RT->Logger->debug("Limiting to CFs identified by '$field' applied to $record_class #@{[$applied_to->id]} (loaded via '$object')");
+        }
+        else {
+            RT->Logger->warning("$record_class '$object' doesn't exist, parsed from '$string'");
+            $object = 0;
+            undef $applied_to;
+        }
+    }
+
+    if ( $field =~ /\D/ ) {
+        $object ||= '';
+        my $cfs = RT::CustomFields->new( $self->CurrentUser );
+        $cfs->Limit( FIELD => 'Name', VALUE => $field, CASESENSITIVE => 0 );
+        $cfs->LimitToLookupType($lookuptype);
+
+        if ($applied_to) {
+            $cfs->SetContextObject($applied_to);
+            $cfs->LimitToObjectId($applied_to->id);
+        }
+
+        # if there is more then one field the current user can
+        # see with the same name then we shouldn't return cf object
+        # as we don't know which one to use
+        $cf = $cfs->First;
+        if ( $cf ) {
+            $cf = undef if $cfs->Next;
+        }
+    }
+    else {
+        $cf = RT::CustomField->new( $self->CurrentUser );
+        $cf->Load( $field );
+        $cf->SetContextObject($applied_to)
+            if $cf->id and $applied_to;
+    }
+
+    return ($object, $field, $cf, $column);
+}
+
+
 =head2 _CustomRoleDecipher
 
 Try and turn a custom role descriptor (e.g. C<CustomRole.{Engineer}>) into
@@ -1062,7 +1123,7 @@ Try and turn a custom role descriptor (e.g. C<CustomRole.{Engineer}>) into
 sub _CustomRoleDecipher {
     my ($self, $string) = @_;
 
-    my ($field, $column) = ($string =~ /^\{(.)\}(?:\.(\w))?$/);
+    my ($field, $column) = ($string =~ /^\{(.+)\}(?:\.(\w+))?$/);
 
     my $role;
 
diff --git a/{ b/{
new file mode 100644
index 0000000..e69de29
diff --git a/{RecalcAssetLimits} b/{RecalcAssetLimits}
new file mode 100644
index 0000000..e69de29
diff --git a/{_sql_cf_alias} b/{_sql_cf_alias}
new file mode 100644
index 0000000..e69de29
diff --git a/{_sql_looking_at} b/{_sql_looking_at}
new file mode 100644
index 0000000..e69de29
diff --git a/{_sql_object_cfv_alias} b/{_sql_object_cfv_alias}
new file mode 100644
index 0000000..e69de29
diff --git a/{_sql_query} b/{_sql_query}
new file mode 100644
index 0000000..e69de29
diff --git a/{_sql_watcher_join_users_alias} b/{_sql_watcher_join_users_alias}
new file mode 100644
index 0000000..e69de29
diff --git a/{columns_to_display} b/{columns_to_display}
new file mode 100644
index 0000000..e69de29
diff --git a/{item_map} b/{item_map}
new file mode 100644
index 0000000..e69de29
diff --git a/{items_array} b/{items_array}
new file mode 100644
index 0000000..e69de29
diff --git a/{primary_key} b/{primary_key}
new file mode 100644
index 0000000..e69de29
diff --git a/{restriction_index} b/{restriction_index}
new file mode 100644
index 0000000..e69de29
diff --git a/{table} b/{table}
new file mode 100644
index 0000000..e69de29

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


More information about the Bps-public-commit mailing list