WatcherGroup => [ 'MEMBERSHIPFIELD', ], #loc_left_pair
HasAttribute => [ 'HASATTRIBUTE', 1 ],
HasNoAttribute => [ 'HASATTRIBUTE', 0 ],
- Agentnum => [ 'FREESIDEFIELD', ],
- Classnum => [ 'FREESIDEFIELD', ],
- Tagnum => [ 'FREESIDEFIELD', 'cust_tag' ],
+ #freeside
+ Customer => [ 'FREESIDEFIELD' => 'Customer' ],
+ Service => [ 'FREESIDEFIELD' => 'Service' ],
+ WillResolve => [ 'DATE' => 'WillResolve', ], #loc_left_pair
+);
+
+our %SEARCHABLE_SUBFIELDS = (
+ User => [qw(
+ EmailAddress Name RealName Nickname Organization Address1 Address2
+ WorkPhone HomePhone MobilePhone PagerPhone id
+ )],
);
# Mapping of Field Type to Function
$date->SetToNow;
$date->SetToStart('month', Timezone => 'server');
$daystart = $date->ISO;
- $date->AddMonth;
+ $date->AddMonth(Timezone => 'server');
$dayend = $date->ISO;
}
elsif ( lc($value) eq 'last month' ) {
my $type = $meta->[1] || '';
my $class = $meta->[2] || 'Ticket';
+ # Bail if the subfield is not allowed
+ if ( $rest{SUBKEY}
+ and not grep { $_ eq $rest{SUBKEY} } @{$SEARCHABLE_SUBFIELDS{'User'}})
+ {
+ die "Invalid watcher subfield: '$rest{SUBKEY}'";
+ }
+
# Owner was ENUM field, so "Owner = 'xxx'" allowed user to
# search by id and Name at the same time, this is workaround
# to preserve backward compatibility
sub _CustomFieldDecipher {
my ($self, $string) = @_;
- my ($queue, $field, $column) = ($string =~ /^(?:(.+?)\.)?{(.+)}(?:\.(.+))?$/);
+ my ($queue, $field, $column) = ($string =~ /^(?:(.+?)\.)?{(.+)}(?:\.(Content|LargeContent))?$/);
$field ||= ($string =~ /^{(.*?)}$/)[0] || $string;
my $cf;
%rest
);
}
- elsif ( $cf->Type eq 'Date' ) {
+ elsif ( $cfid and $cf->Type eq 'Date' ) {
$self->_DateFieldLimit(
'Content',
$op,
foreach my $uid ( $self->CurrentUser->Id, $RT::Nobody->Id ) {
if ( RT->Config->Get('DatabaseType') eq 'Oracle' ) {
my $f = ($row->{'ALIAS'} || 'main') .'.Owner';
- push @res, { %$row, ALIAS => '', FIELD => "CASE WHEN $f=$uid THEN 1 ELSE 0 END", ORDER => $order } ;
+ push @res, {
+ %$row,
+ FIELD => undef,
+ ALIAS => '',
+ FUNCTION => "CASE WHEN $f=$uid THEN 1 ELSE 0 END",
+ ORDER => $order
+ };
} else {
- push @res, { %$row, FIELD => "Owner=$uid", ORDER => $order } ;
+ push @res, {
+ %$row,
+ FIELD => undef,
+ FUNCTION => "Owner=$uid",
+ ORDER => $order
+ };
}
}
push @res, { %$row, FIELD => "Priority", ORDER => $order } ;
} elsif ( $field eq 'Customer' ) { #Freeside
- if ( $subkey eq 'Number' ) {
- my ($linkalias, $custnum_sql) = $self->JoinToCustLinks;
- push @res, { %$row,
- ALIAS => '',
- FIELD => $custnum_sql,
- };
+ # OrderBy(FIELD => expression) doesn't work, it has to be
+ # an actual field, so we have to do the join even if sorting
+ # by custnum
+ my $custalias = $self->JoinToCustomer;
+ my $cust_field = lc($subkey);
+ if ( !$cust_field or $cust_field eq 'number' ) {
+ $cust_field = 'custnum';
}
- else {
- my $custalias = $self->JoinToCustomer;
- my $field;
- if ( $subkey eq 'Name' ) {
- $field = "COALESCE( $custalias.company,
- $custalias.last || ', ' || $custalias.first
- )";
- }
- elsif ( $subkey eq 'Class' ) {
- $field = "$custalias.classnum";
- }
- elsif ( $subkey eq 'Agent' ) {
- $field = "$custalias.agentnum";
- }
- else {
- # no other cases exist yet, but for obviousness:
- $field = $subkey;
- }
- push @res, { %$row, ALIAS => '', FIELD => $field };
+ elsif ( $cust_field eq 'name' ) {
+ $cust_field = "COALESCE( $custalias.company,
+ $custalias.last || ', ' || $custalias.first
+ )";
}
+ else { # order by cust_main fields directly: 'Customer.agentnum'
+ $cust_field = $subkey;
+ }
+ push @res, { %$row, ALIAS => $custalias, FIELD => $cust_field };
+
+ } elsif ( $field eq 'Service' ) {
+
+ my $svcalias = $self->JoinToService;
+ my $svc_field = lc($subkey);
+ if ( !$svc_field or $svc_field eq 'number' ) {
+ $svc_field = 'svcnum';
+ }
+ push @res, { %$row, ALIAS => $svcalias, FIELD => $svc_field };
} #Freeside
# Return the linkalias for further join/limit action,
# and an sql expression to retrieve the custnum.
my $self = shift;
- my $linkalias = $self->Join(
- TYPE => 'LEFT',
- ALIAS1 => 'main',
- FIELD1 => 'id',
- TABLE2 => 'Links',
- FIELD2 => 'LocalBase',
- );
+ # only join once for each RT::Tickets object
+ my $linkalias = $self->{cust_main_linkalias};
+ if (!$linkalias) {
+ $linkalias = $self->Join(
+ TYPE => 'LEFT',
+ ALIAS1 => 'main',
+ FIELD1 => 'id',
+ TABLE2 => 'Links',
+ FIELD2 => 'LocalBase',
+ );
- $self->SUPER::Limit(
- LEFTJOIN => $linkalias,
- FIELD => 'Type',
- OPERATOR => '=',
- VALUE => 'MemberOf',
- );
- $self->SUPER::Limit(
- LEFTJOIN => $linkalias,
- FIELD => 'Target',
- OPERATOR => 'STARTSWITH',
- VALUE => 'freeside://freeside/cust_main/',
- );
+ $self->SUPER::Limit(
+ LEFTJOIN => $linkalias,
+ FIELD => 'Type',
+ OPERATOR => '=',
+ VALUE => 'MemberOf',
+ );
+ $self->SUPER::Limit(
+ LEFTJOIN => $linkalias,
+ FIELD => 'Target',
+ OPERATOR => 'STARTSWITH',
+ VALUE => 'freeside://freeside/cust_main/',
+ );
+ $self->{cust_main_linkalias} = $linkalias;
+ }
my $custnum_sql = "CAST(SUBSTR($linkalias.Target,31) AS ";
if ( RT->Config->Get('DatabaseType') eq 'mysql' ) {
$custnum_sql .= 'SIGNED INTEGER)';
sub JoinToCustomer {
my $self = shift;
my ($linkalias, $custnum_sql) = $self->JoinToCustLinks;
-
+ # don't reuse this join, though--negative queries need
+ # independent joins
my $custalias = $self->Join(
TYPE => 'LEFT',
EXPRESSION => $custnum_sql,
return $custalias;
}
+sub JoinToSvcLinks {
+ my $self = shift;
+ my $linkalias = $self->{cust_svc_linkalias};
+ if (!$linkalias) {
+ $linkalias = $self->Join(
+ TYPE => 'LEFT',
+ ALIAS1 => 'main',
+ FIELD1 => 'id',
+ TABLE2 => 'Links',
+ FIELD2 => 'LocalBase',
+ );
+
+ $self->SUPER::Limit(
+ LEFTJOIN => $linkalias,
+ FIELD => 'Type',
+ OPERATOR => '=',
+ VALUE => 'MemberOf',
+ );
+ $self->SUPER::Limit(
+ LEFTJOIN => $linkalias,
+ FIELD => 'Target',
+ OPERATOR => 'STARTSWITH',
+ VALUE => 'freeside://freeside/cust_svc/',
+ );
+ $self->{cust_svc_linkalias} = $linkalias;
+ }
+ my $svcnum_sql = "CAST(SUBSTR($linkalias.Target,30) AS ";
+ if ( RT->Config->Get('DatabaseType') eq 'mysql' ) {
+ $svcnum_sql .= 'SIGNED INTEGER)';
+ }
+ else {
+ $svcnum_sql .= 'INTEGER)';
+ }
+ return ($linkalias, $svcnum_sql);
+}
+
+sub JoinToService {
+ my $self = shift;
+ my ($linkalias, $svcnum_sql) = $self->JoinToSvcLinks;
+ $self->Join(
+ TYPE => 'LEFT',
+ EXPRESSION => $svcnum_sql,
+ TABLE2 => 'cust_svc',
+ FIELD2 => 'svcnum',
+ );
+}
+
+# This creates an alternate left join path to cust_main via cust_svc.
+# _FreesideFieldLimit needs to add this as a separate, independent join
+# and include all tickets that have a matching cust_main record via
+# either path.
+sub JoinToCustomerViaService {
+ my $self = shift;
+ my $svcalias = $self->JoinToService;
+ my $cust_pkg = $self->Join(
+ TYPE => 'LEFT',
+ ALIAS1 => $svcalias,
+ FIELD1 => 'pkgnum',
+ TABLE2 => 'cust_pkg',
+ FIELD2 => 'pkgnum',
+ );
+ my $cust_main = $self->Join(
+ TYPE => 'LEFT',
+ ALIAS1 => $cust_pkg,
+ FIELD1 => 'custnum',
+ TABLE2 => 'cust_main',
+ FIELD2 => 'custnum',
+ );
+ $cust_main;
+}
+
sub _FreesideFieldLimit {
my ( $self, $field, $op, $value, %rest ) = @_;
- my $alias = $self->JoinToCustomer;
my $is_negative = 0;
if ( $op eq '!=' || $op =~ /\bNOT\b/i ) {
# if the op is negative, do the join as though
$op = '=' if $op eq '!=';
$op =~ s/\bNOT\b//;
}
- my $meta = $FIELD_METADATA{$field};
- if ( $meta->[1] ) {
- $alias = $self->Join(
- TYPE => 'LEFT',
- ALIAS1 => $alias,
- FIELD1 => 'custnum',
- TABLE2 => $meta->[1],
- FIELD2 => 'custnum',
- );
- }
- $self->SUPER::Limit(
- LEFTJOIN => $alias,
- FIELD => lc($field),
- OPERATOR => $op,
- VALUE => $value,
- ENTRYAGGREGATOR => 'AND',
- );
- $self->_SQLLimit(
- %rest,
- ALIAS => $alias,
- FIELD => lc($field),
- OPERATOR => $is_negative ? 'IS' : 'IS NOT',
- VALUE => 'NULL',
- QUOTEVALUE => 0,
- );
+ my (@alias, $table2, $subfield, $pkey);
+ if ( $field eq 'Customer' ) {
+ push @alias, $self->JoinToCustomer;
+ push @alias, $self->JoinToCustomerViaService;
+ $pkey = 'custnum';
+ }
+ elsif ( $field eq 'Service' ) {
+ push @alias, $self->JoinToService;
+ $pkey = 'svcnum';
+ }
+ else {
+ die "malformed Freeside query: $field";
+ }
+
+ $subfield = $rest{SUBKEY} || $pkey;
+ my $table2;
+ # compound subkey: separate into table name and field in that table
+ # (must be linked by custnum)
+ $subfield = lc($subfield);
+ ($table2, $subfield) = ($1, $2) if $subfield =~ /^(\w+)?\.(\w+)$/;
+ $subfield = $pkey if $subfield eq 'number';
+
+ # if it's compound, create a join from cust_main or cust_svc to that
+ # table, using custnum or svcnum, and Limit on that table instead.
+ foreach my $a (@alias) {
+ if ( $table2 ) {
+ $a = $self->Join(
+ TYPE => 'LEFT',
+ ALIAS1 => $a,
+ FIELD1 => $pkey,
+ TABLE2 => $table2,
+ FIELD2 => $pkey,
+ );
+ }
+
+ # do the actual Limit
+ $self->SUPER::Limit(
+ LEFTJOIN => $a,
+ FIELD => $subfield,
+ OPERATOR => $op,
+ VALUE => $value,
+ ENTRYAGGREGATOR => 'AND',
+ # no SUBCLAUSE needed, limits on different aliases across left joins
+ # are inherently independent
+ );
+
+ # then, since it's a left join, exclude tickets for which there is now
+ # no matching record in the table we just limited on. (Or where there
+ # is a matching record, if $is_negative.)
+ # For a cust_main query (where there are two different aliases), this
+ # will produce a subclause: "cust_main_1.custnum IS NOT NULL OR
+ # cust_main_2.custnum IS NOT NULL" (or "IS NULL AND..." for a negative
+ # query).
+ $self->_SQLLimit(
+ %rest,
+ ALIAS => $a,
+ FIELD => $pkey,
+ OPERATOR => $is_negative ? 'IS' : 'IS NOT',
+ VALUE => 'NULL',
+ QUOTEVALUE => 0,
+ ENTRYAGGREGATOR => $is_negative ? 'AND' : 'OR',
+ SUBCLAUSE => 'fs_limit',
+ );
+ }
}
#Freeside
sub DescribeRestrictions {
my $self = shift;
- my ( $row, %listing );
+ my %listing;
- foreach $row ( keys %{ $self->{'TicketRestrictions'} } ) {
+ foreach my $row ( keys %{ $self->{'TicketRestrictions'} } ) {
$listing{$row} = $self->{'TicketRestrictions'}{$row}{'DESCRIPTION'};
}
return (%listing);
sub _RestrictionsToClauses {
my $self = shift;
- my $row;
my %clause;
- foreach $row ( keys %{ $self->{'TicketRestrictions'} } ) {
+ foreach my $row ( keys %{ $self->{'TicketRestrictions'} } ) {
my $restriction = $self->{'TicketRestrictions'}{$row};
# We need to reimplement the subclause aggregation that SearchBuilder does.