1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2015 Best Practical Solutions, LLC
6 # <sales@bestpractical.com>
8 # (Except where explicitly superseded by other copyright notices)
13 # This work is made available to you under the terms of Version 2 of
14 # the GNU General Public License. A copy of that license should have
15 # been provided with this software, but in any event can be snarfed
18 # This work is distributed in the hope that it will be useful, but
19 # WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 # General Public License for more details.
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 # 02110-1301 or visit their web page on the internet at
27 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
30 # CONTRIBUTION SUBMISSION POLICY:
32 # (The following paragraph is not intended to limit the rights granted
33 # to you to modify and distribute this software under the terms of
34 # the GNU General Public License and is only of importance to you if
35 # you choose to contribute your changes and enhancements to the
36 # community by submitting them to Best Practical Solutions, LLC.)
38 # By intentionally submitting any modifications, corrections or
39 # derivatives to this work, or any other work intended for use with
40 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
41 # you are the copyright holder for those contributions and you grant
42 # Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
43 # royalty-free, perpetual, license to use, copy, create derivative
44 # works based on those contributions, and sublicense and distribute
45 # those contributions and any derivatives thereof.
47 # END BPS TAGGED BLOCK }}}
52 package RT::SearchBuilder::Role::Roles;
54 use Scalar::Util qw(blessed);
58 RT::Record::Role::Roles - Common methods for records which "watchers" or "roles"
62 =head2 L<RT::SearchBuilder::Role>
66 with 'RT::SearchBuilder::Role';
69 require RT::Principal;
73 require RT::EmailParser;
77 =head2 _RoleGroupClass
79 Returns the class name on which role searches should be based. This relates to
80 the internal L<RT::Group/Domain> and distinguishes between roles on the objects
81 being searched and their counterpart roles on containing classes. For example,
82 limiting on L<RT::Queue> roles while searching for L<RT::Ticket>s.
84 The default implementation is:
88 which is the class that this collection object searches and instatiates objects
89 for. If you're doing something hinky, you may need to override this method.
95 return $self->RecordClass;
100 my %args = (New => 0, Class => '', Name => '', @_);
102 $args{'Class'} ||= $self->_RoleGroupClass;
104 my $name = $args{'Name'};
105 if ( exists $args{'Type'} ) {
106 RT->Deprecated( Arguments => 'Type', Instead => 'Name', Remove => '4.4' );
107 $name = $args{'Type'};
110 return $self->{'_sql_role_group_aliases'}{ $args{'Class'} .'-'. $name }
111 if $self->{'_sql_role_group_aliases'}{ $args{'Class'} .'-'. $name }
114 # If we're looking at a role group on a class that "contains" this record
115 # (i.e. roles on queues for tickets), then we assume that the current
116 # record has a column named after the containing class (i.e.
118 my $instance = $self->_RoleGroupClass eq $args{Class} ? "id" : $args{Class};
119 $instance =~ s/^RT:://;
121 # Watcher groups are always created for each record, so we use INNER join.
122 my $groups = $self->Join(
126 FIELD2 => 'Instance',
127 ENTRYAGGREGATOR => 'AND',
128 DISTINCT => !!$args{'Type'},
134 VALUE => $args{'Class'} .'-Role',
145 $self->{'_sql_role_group_aliases'}{ $args{'Class'} .'-'. $name } = $groups
151 sub _GroupMembersJoin {
153 my %args = (New => 1, GroupsAlias => undef, Left => 1, @_);
155 return $self->{'_sql_group_members_aliases'}{ $args{'GroupsAlias'} }
156 if $self->{'_sql_group_members_aliases'}{ $args{'GroupsAlias'} }
159 my $alias = $self->Join(
160 $args{'Left'} ? (TYPE => 'LEFT') : (),
161 ALIAS1 => $args{'GroupsAlias'},
163 TABLE2 => 'CachedGroupMembers',
165 ENTRYAGGREGATOR => 'AND',
174 $self->{'_sql_group_members_aliases'}{ $args{'GroupsAlias'} } = $alias
182 Helper function which provides joins to a watchers table both for limits
190 my $groups = $self->_RoleGroupsJoin(@_);
191 my $group_members = $self->_GroupMembersJoin( GroupsAlias => $groups );
192 # XXX: work around, we must hide groups that
193 # are members of the role group we search in,
194 # otherwise them result in wrong NULLs in Users
195 # table and break ordering. Now, we know that
196 # RT doesn't allow to add groups as members of the
197 # ticket roles, so we just hide entries in CGM table
198 # with MemberId == GroupId from results
200 LEFTJOIN => $group_members,
203 VALUE => "$group_members.MemberId",
206 my $users = $self->Join(
208 ALIAS1 => $group_members,
209 FIELD1 => 'MemberId',
213 return ($groups, $group_members, $users);
228 my $class = $args{CLASS} || $self->_RoleGroupClass;
230 $args{FIELD} ||= 'id' if $args{VALUE} =~ /^\d+$/;
232 my $type = delete $args{TYPE};
233 if ($type and not $class->HasRole($type)) {
234 RT->Logger->warn("RoleLimit called with invalid role $type for $class");
238 my $column = $type ? $class->Role($type)->{Column} : undef;
240 # if it's equality op and search by Email or Name then we can preload user
241 # we do it to help some DBs better estimate number of rows and get better plans
242 if ( $args{OPERATOR} =~ /^!?=$/
243 && (!$args{FIELD} || $args{FIELD} eq 'Name' || $args{FIELD} eq 'EmailAddress') ) {
244 my $o = RT::User->new( $self->CurrentUser );
247 ? ($column ? 'Load' : 'LoadByEmail')
248 : $args{FIELD} eq 'EmailAddress' ? 'LoadByEmail': 'Load';
249 $o->$method( $args{VALUE} );
251 $args{VALUE} = $o->id || 0;
254 if ( $column and $args{FIELD} and $args{FIELD} eq 'id' ) {
262 $args{FIELD} ||= 'EmailAddress';
264 my ($groups, $group_members, $users);
265 if ( $args{'BUNDLE'} ) {
266 ($groups, $group_members, $users) = @{ $args{'BUNDLE'} };
268 $groups = $self->_RoleGroupsJoin( Name => $type, Class => $class, New => !$type );
271 $self->_OpenParen( $args{SUBCLAUSE} ) if $args{SUBCLAUSE};
272 if ( $args{OPERATOR} =~ /^IS(?: NOT)?$/i ) {
273 # is [not] empty case
275 $group_members ||= $self->_GroupMembersJoin( GroupsAlias => $groups );
276 # to avoid joining the table Users into the query, we just join GM
277 # and make sure we don't match records where group is member of itself
279 LEFTJOIN => $group_members,
282 VALUE => "$group_members.MemberId",
287 ALIAS => $group_members,
289 OPERATOR => $args{OPERATOR},
290 VALUE => $args{VALUE},
293 elsif ( $args{OPERATOR} =~ /^!=$|^NOT\s+/i ) {
294 # negative condition case
297 $args{OPERATOR} =~ s/!|NOT\s+//i;
299 # XXX: we have no way to build correct "Watcher.X != 'Y'" when condition
300 # "X = 'Y'" matches more then one user so we try to fetch two records and
301 # do the right thing when there is only one exist and semi-working solution
303 my $users_obj = RT::Users->new( $self->CurrentUser );
305 FIELD => $args{FIELD},
306 OPERATOR => $args{OPERATOR},
307 VALUE => $args{VALUE},
310 $users_obj->RowsPerPage(2);
311 my @users = @{ $users_obj->ItemsArrayRef };
313 $group_members ||= $self->_GroupMembersJoin( GroupsAlias => $groups );
316 $uid = $users[0]->id if @users;
318 LEFTJOIN => $group_members,
319 ALIAS => $group_members,
325 ALIAS => $group_members,
332 LEFTJOIN => $group_members,
335 VALUE => "$group_members.MemberId",
338 $users ||= $self->Join(
340 ALIAS1 => $group_members,
341 FIELD1 => 'MemberId',
348 FIELD => $args{FIELD},
349 OPERATOR => $args{OPERATOR},
350 VALUE => $args{VALUE},
362 # positive condition case
364 $group_members ||= $self->_GroupMembersJoin(
365 GroupsAlias => $groups, New => 1, Left => 0
367 if ($args{FIELD} eq "id") {
368 # Save a left join to Users, if possible
371 ALIAS => $group_members,
373 OPERATOR => $args{OPERATOR},
374 VALUE => $args{VALUE},
378 $users ||= $self->Join(
380 ALIAS1 => $group_members,
381 FIELD1 => 'MemberId',
388 FIELD => $args{FIELD},
389 OPERATOR => $args{OPERATOR},
390 VALUE => $args{VALUE},
395 $self->_CloseParen( $args{SUBCLAUSE} ) if $args{SUBCLAUSE};
396 return ($groups, $group_members, $users);