diff options
Diffstat (limited to 'rt/lib/RT/Tickets_Overlay.pm')
-rw-r--r-- | rt/lib/RT/Tickets_Overlay.pm | 2140 |
1 files changed, 0 insertions, 2140 deletions
diff --git a/rt/lib/RT/Tickets_Overlay.pm b/rt/lib/RT/Tickets_Overlay.pm deleted file mode 100644 index 55777b0fb..000000000 --- a/rt/lib/RT/Tickets_Overlay.pm +++ /dev/null @@ -1,2140 +0,0 @@ -# BEGIN LICENSE BLOCK -# -# Copyright (c) 1996-2003 Jesse Vincent <jesse@bestpractical.com> -# -# (Except where explictly superceded by other copyright notices) -# -# 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. -# -# Unless otherwise specified, all modifications, corrections or -# extensions to this work which alter its source code become the -# property of Best Practical Solutions, LLC when submitted for -# inclusion in the work. -# -# -# END LICENSE BLOCK -# Major Changes: - -# - Decimated ProcessRestrictions and broke it into multiple -# functions joined by a LUT -# - Semi-Generic SQL stuff moved to another file - -# Known Issues: FIXME! - -# - ClearRestrictions and Reinitialization is messy and unclear. The -# only good way to do it is to create a new RT::Tickets object. - -=head1 NAME - - RT::Tickets - A collection of Ticket objects - - -=head1 SYNOPSIS - - use RT::Tickets; - my $tickets = new RT::Tickets($CurrentUser); - -=head1 DESCRIPTION - - A collection of RT::Tickets. - -=head1 METHODS - -=begin testing - -ok (require RT::Tickets); - -=end testing - -=cut -use strict; -no warnings qw(redefine); -use vars qw(@SORTFIELDS); - - -# Configuration Tables: - -# FIELDS is a mapping of searchable Field name, to Type, and other -# metadata. - -my %FIELDS = - ( Status => ['ENUM'], - Queue => ['ENUM' => 'Queue',], - Type => ['ENUM',], - Creator => ['ENUM' => 'User',], - LastUpdatedBy => ['ENUM' => 'User',], - Owner => ['ENUM' => 'User',], - EffectiveId => ['INT',], - id => ['INT',], - InitialPriority => ['INT',], - FinalPriority => ['INT',], - Priority => ['INT',], - TimeLeft => ['INT',], - TimeWorked => ['INT',], - MemberOf => ['LINK' => To => 'MemberOf', ], - DependsOn => ['LINK' => To => 'DependsOn',], - RefersTo => ['LINK' => To => 'RefersTo',], - HasMember => ['LINK' => From => 'MemberOf',], - DependentOn => ['LINK' => From => 'DependsOn',], - ReferredToBy => ['LINK' => From => 'RefersTo',], -# HasDepender => ['LINK',], -# RelatedTo => ['LINK',], - Told => ['DATE' => 'Told',], - Starts => ['DATE' => 'Starts',], - Started => ['DATE' => 'Started',], - Due => ['DATE' => 'Due',], - Resolved => ['DATE' => 'Resolved',], - LastUpdated => ['DATE' => 'LastUpdated',], - Created => ['DATE' => 'Created',], - Subject => ['STRING',], - Type => ['STRING',], - Content => ['TRANSFIELD',], - ContentType => ['TRANSFIELD',], - Filename => ['TRANSFIELD',], - TransactionDate => ['TRANSDATE',], - Requestor => ['WATCHERFIELD' => 'Requestor',], - CC => ['WATCHERFIELD' => 'Cc',], - AdminCC => ['WATCHERFIELD' => 'AdminCC',], - Watcher => ['WATCHERFIELD'], - LinkedTo => ['LINKFIELD',], - CustomFieldValue =>['CUSTOMFIELD',], - CF => ['CUSTOMFIELD',], - ); - -# Mapping of Field Type to Function -my %dispatch = - ( ENUM => \&_EnumLimit, - INT => \&_IntLimit, - LINK => \&_LinkLimit, - DATE => \&_DateLimit, - STRING => \&_StringLimit, - TRANSFIELD => \&_TransLimit, - TRANSDATE => \&_TransDateLimit, - WATCHERFIELD => \&_WatcherLimit, - LINKFIELD => \&_LinkFieldLimit, - CUSTOMFIELD => \&_CustomFieldLimit, - ); -my %can_bundle = - ( WATCHERFIELD => "yeps", - ); - -# Default EntryAggregator per type -# if you specify OP, you must specify all valid OPs -my %DefaultEA = ( - INT => 'AND', - ENUM => { '=' => 'OR', - '!='=> 'AND' - }, - DATE => { '=' => 'OR', - '>='=> 'AND', - '<='=> 'AND', - '>' => 'AND', - '<' => 'AND' - }, - STRING => { '=' => 'OR', - '!='=> 'AND', - 'LIKE'=> 'AND', - 'NOT LIKE' => 'AND' - }, - TRANSFIELD => 'AND', - TRANSDATE => 'AND', - LINK => 'OR', - LINKFIELD => 'AND', - TARGET => 'AND', - BASE => 'AND', - WATCHERFIELD => { '=' => 'OR', - '!='=> 'AND', - 'LIKE'=> 'OR', - 'NOT LIKE' => 'AND' - }, - - CUSTOMFIELD => 'OR', - ); - - -# Helper functions for passing the above lexically scoped tables above -# into Tickets_Overlay_SQL. -sub FIELDS { return \%FIELDS } -sub dispatch { return \%dispatch } -sub can_bundle { return \%can_bundle } - -# Bring in the clowns. -require RT::Tickets_Overlay_SQL; - -# {{{ sub SortFields - -@SORTFIELDS = qw(id Status - Queue Subject - Owner Created Due Starts Started - Told - Resolved LastUpdated Priority TimeWorked TimeLeft); - -=head2 SortFields - -Returns the list of fields that lists of tickets can easily be sorted by - -=cut - -sub SortFields { - my $self = shift; - return(@SORTFIELDS); -} - - -# }}} - - -# BEGIN SQL STUFF ********************************* - -=head1 Limit Helper Routines - -These routines are the targets of a dispatch table depending on the -type of field. They all share the same signature: - - my ($self,$field,$op,$value,@rest) = @_; - -The values in @rest should be suitable for passing directly to -DBIx::SearchBuilder::Limit. - -Essentially they are an expanded/broken out (and much simplified) -version of what ProcessRestrictions used to do. They're also much -more clearly delineated by the TYPE of field being processed. - -=head2 _EnumLimit - -Handle Fields which are limited to certain values, and potentially -need to be looked up from another class. - -This subroutine actually handles two different kinds of fields. For -some the user is responsible for limiting the values. (i.e. Status, -Type). - -For others, the value specified by the user will be looked by via -specified class. - -Meta Data: - name of class to lookup in (Optional) - -=cut - -sub _EnumLimit { - my ($sb,$field,$op,$value,@rest) = @_; - - # SQL::Statement changes != to <>. (Can we remove this now?) - $op = "!=" if $op eq "<>"; - - die "Invalid Operation: $op for $field" - unless $op eq "=" or $op eq "!="; - - my $meta = $FIELDS{$field}; - if (defined $meta->[1]) { - my $class = "RT::" . $meta->[1]; - my $o = $class->new($sb->CurrentUser); - $o->Load( $value ); - $value = $o->Id; - } - $sb->_SQLLimit( FIELD => $field,, - VALUE => $value, - OPERATOR => $op, - @rest, - ); -} - -=head2 _IntLimit - -Handle fields where the values are limited to integers. (For example, -Priority, TimeWorked.) - -Meta Data: - None - -=cut - -sub _IntLimit { - my ($sb,$field,$op,$value,@rest) = @_; - - die "Invalid Operator $op for $field" - unless $op =~ /^(=|!=|>|<|>=|<=)$/; - - $sb->_SQLLimit( - FIELD => $field, - VALUE => $value, - OPERATOR => $op, - @rest, - ); -} - - -=head2 _LinkLimit - -Handle fields which deal with links between tickets. (MemberOf, DependsOn) - -Meta Data: - 1: Direction (From,To) - 2: Relationship Type (MemberOf, DependsOn,RefersTo) - -=cut - -sub _LinkLimit { - my ($sb,$field,$op,$value,@rest) = @_; - - die "Op must be =" - unless $op eq "="; - - my $meta = $FIELDS{$field}; - die "Incorrect Meta Data for $field" - unless (defined $meta->[1] and defined $meta->[2]); - - my $LinkAlias = $sb->NewAlias ('Links'); - - $sb->_OpenParen(); - - $sb->_SQLLimit( - ALIAS => $LinkAlias, - FIELD => 'Type', - OPERATOR => '=', - VALUE => $meta->[2], - @rest, - ); - - if ($meta->[1] eq "To") { - my $matchfield = ( $value =~ /^(\d+)$/ ? "LocalTarget" : "Target" ); - - $sb->_SQLLimit( - ALIAS => $LinkAlias, - ENTRYAGGREGATOR => 'AND', - FIELD => $matchfield, - OPERATOR => '=', - VALUE => $value , - ); - - #If we're searching on target, join the base to ticket.id - $sb->Join( ALIAS1 => 'main', FIELD1 => $sb->{'primary_key'}, - ALIAS2 => $LinkAlias, FIELD2 => 'LocalBase'); - - } elsif ( $meta->[1] eq "From" ) { - my $matchfield = ( $value =~ /^(\d+)$/ ? "LocalBase" : "Base" ); - - $sb->_SQLLimit( - ALIAS => $LinkAlias, - ENTRYAGGREGATOR => 'AND', - FIELD => $matchfield, - OPERATOR => '=', - VALUE => $value , - ); - - #If we're searching on base, join the target to ticket.id - $sb->Join( ALIAS1 => 'main', FIELD1 => $sb->{'primary_key'}, - ALIAS2 => $LinkAlias, FIELD2 => 'LocalTarget'); - - } else { - die "Invalid link direction '$meta->[1]' for $field\n"; - } - - $sb->_CloseParen(); - -} - -=head2 _DateLimit - -Handle date fields. (Created, LastTold..) - -Meta Data: - 1: type of relationship. (Probably not necessary.) - -=cut - -sub _DateLimit { - my ($sb,$field,$op,$value,@rest) = @_; - - die "Invalid Date Op: $op" - unless $op =~ /^(=|>|<|>=|<=)$/; - - my $meta = $FIELDS{$field}; - die "Incorrect Meta Data for $field" - unless (defined $meta->[1]); - - require Time::ParseDate; - use POSIX 'strftime'; - - # FIXME: Replace me with RT::Date( Type => 'unknown' ...) - my $time = Time::ParseDate::parsedate( $value, - UK => $RT::DateDayBeforeMonth, - PREFER_PAST => $RT::AmbiguousDayInPast, - PREFER_FUTURE => !($RT::AmbiguousDayInPast), - FUZZY => 1 - ); - - if ($op eq "=") { - # if we're specifying =, that means we want everything on a - # particular single day. in the database, we need to check for > - # and < the edges of that day. - - my $daystart = strftime("%Y-%m-%d %H:%M", - gmtime($time - ( $time % 86400 ))); - my $dayend = strftime("%Y-%m-%d %H:%M", - gmtime($time + ( 86399 - $time % 86400 ))); - - $sb-> _OpenParen; - - $sb->_SQLLimit( - FIELD => $meta->[1], - OPERATOR => ">=", - VALUE => $daystart, - @rest, - ); - - $sb->_SQLLimit( - FIELD => $meta->[1], - OPERATOR => "<=", - VALUE => $dayend, - @rest, - ENTRYAGGREGATOR => 'AND', - ); - - $sb-> _CloseParen; - - } else { - $value = strftime("%Y-%m-%d %H:%M", gmtime($time)); - $sb->_SQLLimit( - FIELD => $meta->[1], - OPERATOR => $op, - VALUE => $value, - @rest, - ); - } -} - -=head2 _StringLimit - -Handle simple fields which are just strings. (Subject,Type) - -Meta Data: - None - -=cut - -sub _StringLimit { - my ($sb,$field,$op,$value,@rest) = @_; - - # FIXME: - # Valid Operators: - # =, !=, LIKE, NOT LIKE - - $sb->_SQLLimit( - FIELD => $field, - OPERATOR => $op, - VALUE => $value, - CASESENSITIVE => 0, - @rest, - ); -} - -=head2 _TransDateLimit - -Handle fields limiting based on Transaction Date. - -The inpupt value must be in a format parseable by Time::ParseDate - -Meta Data: - None - -=cut - -sub _TransDateLimit { - my ($sb,$field,$op,$value,@rest) = @_; - - # See the comments for TransLimit, they apply here too - - $sb->{_sql_transalias} = $sb->NewAlias ('Transactions') - unless defined $sb->{_sql_transalias}; - $sb->{_sql_trattachalias} = $sb->NewAlias ('Attachments') - unless defined $sb->{_sql_trattachalias}; - - $sb->_OpenParen; - - # Join Transactions To Attachments - $sb->Join( ALIAS1 => $sb->{_sql_trattachalias}, FIELD1 => 'TransactionId', - ALIAS2 => $sb->{_sql_transalias}, FIELD2 => 'id'); - - # Join Transactions to Tickets - $sb->Join( ALIAS1 => 'main', FIELD1 => $sb->{'primary_key'}, # UGH! - ALIAS2 => $sb->{_sql_transalias}, FIELD2 => 'Ticket'); - - my $d = new RT::Date( $sb->CurrentUser ); - $d->Set( Format => 'ISO', Value => $value); - $value = $d->ISO; - - #Search for the right field - $sb->_SQLLimit(ALIAS => $sb->{_sql_trattachalias}, - FIELD => 'Created', - OPERATOR => $op, - VALUE => $value, - CASESENSITIVE => 0, - @rest - ); - - $sb->_CloseParen; -} - -=head2 _TransLimit - -Limit based on the Content of a transaction or the ContentType. - -Meta Data: - none - -=cut - -sub _TransLimit { - # Content, ContentType, Filename - - # If only this was this simple. We've got to do something - # complicated here: - - #Basically, we want to make sure that the limits apply to - #the same attachment, rather than just another attachment - #for the same ticket, no matter how many clauses we lump - #on. We put them in TicketAliases so that they get nuked - #when we redo the join. - - # In the SQL, we might have - # (( Content = foo ) or ( Content = bar AND Content = baz )) - # The AND group should share the same Alias. - - # Actually, maybe it doesn't matter. We use the same alias and it - # works itself out? (er.. different.) - - # Steal more from _ProcessRestrictions - - # FIXME: Maybe look at the previous FooLimit call, and if it was a - # TransLimit and EntryAggregator == AND, reuse the Aliases? - - # Or better - store the aliases on a per subclause basis - since - # those are going to be the things we want to relate to each other, - # anyway. - - # maybe we should not allow certain kinds of aggregation of these - # clauses and do a psuedo regex instead? - the problem is getting - # them all into the same subclause when you have (A op B op C) - the - # way they get parsed in the tree they're in different subclauses. - - my ($sb,$field,$op,$value,@rest) = @_; - - $sb->{_sql_transalias} = $sb->NewAlias ('Transactions') - unless defined $sb->{_sql_transalias}; - $sb->{_sql_trattachalias} = $sb->NewAlias ('Attachments') - unless defined $sb->{_sql_trattachalias}; - - $sb->_OpenParen; - - # Join Transactions To Attachments - $sb->Join( ALIAS1 => $sb->{_sql_trattachalias}, FIELD1 => 'TransactionId', - ALIAS2 => $sb->{_sql_transalias}, FIELD2 => 'id'); - - # Join Transactions to Tickets - $sb->Join( ALIAS1 => 'main', FIELD1 => $sb->{'primary_key'}, # UGH! - ALIAS2 => $sb->{_sql_transalias}, FIELD2 => 'Ticket'); - - #Search for the right field - $sb->_SQLLimit(ALIAS => $sb->{_sql_trattachalias}, - FIELD => $field, - OPERATOR => $op, - VALUE => $value, - CASESENSITIVE => 0, - @rest - ); - - $sb->_CloseParen; - -} - -=head2 _WatcherLimit - -Handle watcher limits. (Requestor, CC, etc..) - -Meta Data: - 1: Field to query on - -=cut - -sub _WatcherLimit { - my ($self,$field,$op,$value,@rest) = @_; - my %rest = @rest; - - $self->_OpenParen; - - my $groups = $self->NewAlias('Groups'); - my $groupmembers = $self->NewAlias('CachedGroupMembers'); - my $users = $self->NewAlias('Users'); - - - #Find user watchers -# my $subclause = undef; -# my $aggregator = 'OR'; -# if ($restriction->{'OPERATOR'} =~ /!|NOT/i ){ -# $subclause = 'AndEmailIsNot'; -# $aggregator = 'AND'; -# } - - if (ref $field) { # gross hack - my @bundle = @$field; - $self->_OpenParen; - for my $chunk (@bundle) { - ($field,$op,$value,@rest) = @$chunk; - $self->_SQLLimit(ALIAS => $users, - FIELD => $rest{SUBKEY} || 'EmailAddress', - VALUE => $value, - OPERATOR => $op, - CASESENSITIVE => 0, - @rest, - ); - } - $self->_CloseParen; - } else { - $self->_SQLLimit(ALIAS => $users, - FIELD => $rest{SUBKEY} || 'EmailAddress', - VALUE => $value, - OPERATOR => $op, - CASESENSITIVE => 0, - @rest, - ); - } - - # {{{ Tie to groups for tickets we care about - $self->_SQLLimit(ALIAS => $groups, - FIELD => 'Domain', - VALUE => 'RT::Ticket-Role', - ENTRYAGGREGATOR => 'AND'); - - $self->Join(ALIAS1 => $groups, FIELD1 => 'Instance', - ALIAS2 => 'main', FIELD2 => 'id'); - # }}} - - # If we care about which sort of watcher - my $meta = $FIELDS{$field}; - my $type = ( defined $meta->[1] ? $meta->[1] : undef ); - - if ( $type ) { - $self->_SQLLimit(ALIAS => $groups, - FIELD => 'Type', - VALUE => $type, - ENTRYAGGREGATOR => 'AND'); - } - - $self->Join (ALIAS1 => $groups, FIELD1 => 'id', - ALIAS2 => $groupmembers, FIELD2 => 'GroupId'); - - $self->Join( ALIAS1 => $groupmembers, FIELD1 => 'MemberId', - ALIAS2 => $users, FIELD2 => 'id'); - - $self->_CloseParen; - -} - -sub _LinkFieldLimit { - my $restriction; - my $self; - my $LinkAlias; - my %args; - if ($restriction->{'TYPE'}) { - $self->SUPER::Limit(ALIAS => $LinkAlias, - ENTRYAGGREGATOR => 'AND', - FIELD => 'Type', - OPERATOR => '=', - VALUE => $restriction->{'TYPE'} ); - } - - #If we're trying to limit it to things that are target of - if ($restriction->{'TARGET'}) { - # If the TARGET is an integer that means that we want to look at - # the LocalTarget field. otherwise, we want to look at the - # "Target" field - my ($matchfield); - if ($restriction->{'TARGET'} =~/^(\d+)$/) { - $matchfield = "LocalTarget"; - } else { - $matchfield = "Target"; - } - $self->SUPER::Limit(ALIAS => $LinkAlias, - ENTRYAGGREGATOR => 'AND', - FIELD => $matchfield, - OPERATOR => '=', - VALUE => $restriction->{'TARGET'} ); - #If we're searching on target, join the base to ticket.id - $self->Join( ALIAS1 => 'main', FIELD1 => $self->{'primary_key'}, - ALIAS2 => $LinkAlias, - FIELD2 => 'LocalBase'); - } - #If we're trying to limit it to things that are base of - elsif ($restriction->{'BASE'}) { - # If we're trying to match a numeric link, we want to look at - # LocalBase, otherwise we want to look at "Base" - my ($matchfield); - if ($restriction->{'BASE'} =~/^(\d+)$/) { - $matchfield = "LocalBase"; - } else { - $matchfield = "Base"; - } - - $self->SUPER::Limit(ALIAS => $LinkAlias, - ENTRYAGGREGATOR => 'AND', - FIELD => $matchfield, - OPERATOR => '=', - VALUE => $restriction->{'BASE'} ); - #If we're searching on base, join the target to ticket.id - $self->Join( ALIAS1 => 'main', FIELD1 => $self->{'primary_key'}, - ALIAS2 => $LinkAlias, - FIELD2 => 'LocalTarget') - } -} - - -=head2 KeywordLimit - -Limit based on Keywords - -Meta Data: - none - -=cut - -sub _CustomFieldLimit { - my ($self,$_field,$op,$value,@rest) = @_; - - my %rest = @rest; - my $field = $rest{SUBKEY} || die "No field specified"; - - # For our sanity, we can only limit on one queue at a time - my $queue = undef; - # Ugh. This will not do well for things with underscores in them - - use RT::CustomFields; - my $CF = RT::CustomFields->new( $self->CurrentUser ); - #$CF->Load( $cfid} ); - - my $q; - if ($field =~ /^(.+?)\.{(.+)}$/) { - my $q = RT::Queue->new($self->CurrentUser); - $q->Load($1); - $field = $2; - $CF->LimitToQueue( $q->Id ); - $queue = $q->Id; - } else { - $field = $1 if $field =~ /^{(.+)}$/; # trim { } - $CF->LimitToGlobal; - } - $CF->FindAllRows; - - my $cfid = 0; - - while ( my $CustomField = $CF->Next ) { - if ($CustomField->Name eq $field) { - $cfid = $CustomField->Id; - last; - } - } - die "No custom field named $field found\n" - unless $cfid; - -# use RT::CustomFields; -# my $CF = RT::CustomField->new( $self->CurrentUser ); -# $CF->Load( $cfid ); - - - my $null_columns_ok; - - my $TicketCFs; - # Perform one Join per CustomField - if ($self->{_sql_keywordalias}{$cfid}) { - $TicketCFs = $self->{_sql_keywordalias}{$cfid}; - } else { - $TicketCFs = $self->{_sql_keywordalias}{$cfid} = - $self->Join( TYPE => 'left', - ALIAS1 => 'main', - FIELD1 => 'id', - TABLE2 => 'TicketCustomFieldValues', - FIELD2 => 'Ticket' ); - } - - $self->_OpenParen; - - $self->_SQLLimit( ALIAS => $TicketCFs, - FIELD => 'Content', - OPERATOR => $op, - VALUE => $value, - QUOTEVALUE => 1, - @rest ); - - if ( $op =~ /^IS$/i - or ( $op eq '!=' ) ) { - $null_columns_ok = 1; - } - - #If we're trying to find tickets where the keyword isn't somethng, - #also check ones where it _IS_ null - - if ( $op eq '!=' ) { - $self->_SQLLimit( ALIAS => $TicketCFs, - FIELD => 'Content', - OPERATOR => 'IS', - VALUE => 'NULL', - QUOTEVALUE => 0, - ENTRYAGGREGATOR => 'OR', ); - } - - $self->_SQLLimit( LEFTJOIN => $TicketCFs, - FIELD => 'CustomField', - VALUE => $cfid, - ENTRYAGGREGATOR => 'OR' ); - - - - $self->_CloseParen; - -} - - -# End Helper Functions - -# End of SQL Stuff ------------------------------------------------- - -# {{{ Limit the result set based on content - -# {{{ sub Limit - -=head2 Limit - -Takes a paramhash with the fields FIELD, OPERATOR, VALUE and DESCRIPTION -Generally best called from LimitFoo methods - -=cut -sub Limit { - my $self = shift; - my %args = ( FIELD => undef, - OPERATOR => '=', - VALUE => undef, - DESCRIPTION => undef, - @_ - ); - $args{'DESCRIPTION'} = $self->loc( - "[_1] [_2] [_3]", $args{'FIELD'}, $args{'OPERATOR'}, $args{'VALUE'} - ) if (!defined $args{'DESCRIPTION'}) ; - - my $index = $self->_NextIndex; - - #make the TicketRestrictions hash the equivalent of whatever we just passed in; - - %{$self->{'TicketRestrictions'}{$index}} = %args; - - $self->{'RecalcTicketLimits'} = 1; - - # If we're looking at the effective id, we don't want to append the other clause - # which limits us to tickets where id = effective id - if ($args{'FIELD'} eq 'EffectiveId') { - $self->{'looking_at_effective_id'} = 1; - } - - if ($args{'FIELD'} eq 'Type') { - $self->{'looking_at_type'} = 1; - } - - return ($index); -} - -# }}} - - - - -=head2 FreezeLimits - -Returns a frozen string suitable for handing back to ThawLimits. - -=cut -# {{{ sub FreezeLimits - -sub FreezeLimits { - my $self = shift; - require FreezeThaw; - return (FreezeThaw::freeze($self->{'TicketRestrictions'}, - $self->{'restriction_index'} - )); -} - -# }}} - -=head2 ThawLimits - -Take a frozen Limits string generated by FreezeLimits and make this tickets -object have that set of limits. - -=cut -# {{{ sub ThawLimits - -sub ThawLimits { - my $self = shift; - my $in = shift; - - #if we don't have $in, get outta here. - return undef unless ($in); - - $self->{'RecalcTicketLimits'} = 1; - - require FreezeThaw; - - #We don't need to die if the thaw fails. - - eval { - ($self->{'TicketRestrictions'}, - $self->{'restriction_index'} - ) = FreezeThaw::thaw($in); - } - -} - -# }}} - -# {{{ Limit by enum or foreign key - -# {{{ sub LimitQueue - -=head2 LimitQueue - -LimitQueue takes a paramhash with the fields OPERATOR and VALUE. -OPERATOR is one of = or !=. (It defaults to =). -VALUE is a queue id or Name. - - -=cut - -sub LimitQueue { - my $self = shift; - my %args = (VALUE => undef, - OPERATOR => '=', - @_); - - #TODO VALUE should also take queue names and queue objects - #TODO FIXME why are we canonicalizing to name, not id, robrt? - if ($args{VALUE} =~ /^\d+$/) { - my $queue = new RT::Queue($self->CurrentUser); - $queue->Load($args{'VALUE'}); - $args{VALUE} = $queue->Name; - } - - # What if they pass in an Id? Check for isNum() and convert to - # string. - - #TODO check for a valid queue here - - $self->Limit (FIELD => 'Queue', - VALUE => $args{VALUE}, - OPERATOR => $args{'OPERATOR'}, - DESCRIPTION => join( - ' ', $self->loc('Queue'), $args{'OPERATOR'}, $args{VALUE}, - ), - ); - -} -# }}} - -# {{{ sub LimitStatus - -=head2 LimitStatus - -Takes a paramhash with the fields OPERATOR and VALUE. -OPERATOR is one of = or !=. -VALUE is a status. - -=cut - -sub LimitStatus { - my $self = shift; - my %args = ( OPERATOR => '=', - @_); - $self->Limit (FIELD => 'Status', - VALUE => $args{'VALUE'}, - OPERATOR => $args{'OPERATOR'}, - DESCRIPTION => join( - ' ', $self->loc('Status'), $args{'OPERATOR'}, $self->loc($args{'VALUE'}) - ), - ); -} - -# }}} - -# {{{ sub IgnoreType - -=head2 IgnoreType - -If called, this search will not automatically limit the set of results found -to tickets of type "Ticket". Tickets of other types, such as "project" and -"approval" will be found. - -=cut - -sub IgnoreType { - my $self = shift; - - # Instead of faking a Limit that later gets ignored, fake up the - # fact that we're already looking at type, so that the check in - # Tickets_Overlay_SQL/FromSQL goes down the right branch - - # $self->LimitType(VALUE => '__any'); - $self->{looking_at_type} = 1; -} - -# }}} - -# {{{ sub LimitType - -=head2 LimitType - -Takes a paramhash with the fields OPERATOR and VALUE. -OPERATOR is one of = or !=, it defaults to "=". -VALUE is a string to search for in the type of the ticket. - - - -=cut - -sub LimitType { - my $self = shift; - my %args = (OPERATOR => '=', - VALUE => undef, - @_); - $self->Limit (FIELD => 'Type', - VALUE => $args{'VALUE'}, - OPERATOR => $args{'OPERATOR'}, - DESCRIPTION => join( - ' ', $self->loc('Type'), $args{'OPERATOR'}, $args{'Limit'}, - ), - ); -} - -# }}} - -# }}} - -# {{{ Limit by string field - -# {{{ sub LimitSubject - -=head2 LimitSubject - -Takes a paramhash with the fields OPERATOR and VALUE. -OPERATOR is one of = or !=. -VALUE is a string to search for in the subject of the ticket. - -=cut - -sub LimitSubject { - my $self = shift; - my %args = (@_); - $self->Limit (FIELD => 'Subject', - VALUE => $args{'VALUE'}, - OPERATOR => $args{'OPERATOR'}, - DESCRIPTION => join( - ' ', $self->loc('Subject'), $args{'OPERATOR'}, $args{'VALUE'}, - ), - ); -} - -# }}} - -# }}} - -# {{{ Limit based on ticket numerical attributes -# Things that can be > < = != - -# {{{ sub LimitId - -=head2 LimitId - -Takes a paramhash with the fields OPERATOR and VALUE. -OPERATOR is one of =, >, < or !=. -VALUE is a ticket Id to search for - -=cut - -sub LimitId { - my $self = shift; - my %args = (OPERATOR => '=', - @_); - - $self->Limit (FIELD => 'id', - VALUE => $args{'VALUE'}, - OPERATOR => $args{'OPERATOR'}, - DESCRIPTION => join( - ' ', $self->loc('Id'), $args{'OPERATOR'}, $args{'VALUE'}, - ), - ); -} - -# }}} - -# {{{ sub LimitPriority - -=head2 LimitPriority - -Takes a paramhash with the fields OPERATOR and VALUE. -OPERATOR is one of =, >, < or !=. -VALUE is a value to match the ticket\'s priority against - -=cut - -sub LimitPriority { - my $self = shift; - my %args = (@_); - $self->Limit (FIELD => 'Priority', - VALUE => $args{'VALUE'}, - OPERATOR => $args{'OPERATOR'}, - DESCRIPTION => join( - ' ', $self->loc('Priority'), $args{'OPERATOR'}, $args{'VALUE'}, - ), - ); -} - -# }}} - -# {{{ sub LimitInitialPriority - -=head2 LimitInitialPriority - -Takes a paramhash with the fields OPERATOR and VALUE. -OPERATOR is one of =, >, < or !=. -VALUE is a value to match the ticket\'s initial priority against - - -=cut - -sub LimitInitialPriority { - my $self = shift; - my %args = (@_); - $self->Limit (FIELD => 'InitialPriority', - VALUE => $args{'VALUE'}, - OPERATOR => $args{'OPERATOR'}, - DESCRIPTION => join( - ' ', $self->loc('Initial Priority'), $args{'OPERATOR'}, $args{'VALUE'}, - ), - ); -} - -# }}} - -# {{{ sub LimitFinalPriority - -=head2 LimitFinalPriority - -Takes a paramhash with the fields OPERATOR and VALUE. -OPERATOR is one of =, >, < or !=. -VALUE is a value to match the ticket\'s final priority against - -=cut - -sub LimitFinalPriority { - my $self = shift; - my %args = (@_); - $self->Limit (FIELD => 'FinalPriority', - VALUE => $args{'VALUE'}, - OPERATOR => $args{'OPERATOR'}, - DESCRIPTION => join( - ' ', $self->loc('Final Priority'), $args{'OPERATOR'}, $args{'VALUE'}, - ), - ); -} - -# }}} - -# {{{ sub LimitTimeWorked - -=head2 LimitTimeWorked - -Takes a paramhash with the fields OPERATOR and VALUE. -OPERATOR is one of =, >, < or !=. -VALUE is a value to match the ticket's TimeWorked attribute - -=cut - -sub LimitTimeWorked { - my $self = shift; - my %args = (@_); - $self->Limit (FIELD => 'TimeWorked', - VALUE => $args{'VALUE'}, - OPERATOR => $args{'OPERATOR'}, - DESCRIPTION => join( - ' ', $self->loc('Time worked'), $args{'OPERATOR'}, $args{'VALUE'}, - ), - ); -} - -# }}} - -# {{{ sub LimitTimeLeft - -=head2 LimitTimeLeft - -Takes a paramhash with the fields OPERATOR and VALUE. -OPERATOR is one of =, >, < or !=. -VALUE is a value to match the ticket's TimeLeft attribute - -=cut - -sub LimitTimeLeft { - my $self = shift; - my %args = (@_); - $self->Limit (FIELD => 'TimeLeft', - VALUE => $args{'VALUE'}, - OPERATOR => $args{'OPERATOR'}, - DESCRIPTION => join( - ' ', $self->loc('Time left'), $args{'OPERATOR'}, $args{'VALUE'}, - ), - ); -} - -# }}} - -# }}} - -# {{{ Limiting based on attachment attributes - -# {{{ sub LimitContent - -=head2 LimitContent - -Takes a paramhash with the fields OPERATOR and VALUE. -OPERATOR is one of =, LIKE, NOT LIKE or !=. -VALUE is a string to search for in the body of the ticket - -=cut -sub LimitContent { - my $self = shift; - my %args = (@_); - $self->Limit (FIELD => 'Content', - VALUE => $args{'VALUE'}, - OPERATOR => $args{'OPERATOR'}, - DESCRIPTION => join( - ' ', $self->loc('Ticket content'), $args{'OPERATOR'}, $args{'VALUE'}, - ), - ); -} - -# }}} - -# {{{ sub LimitFilename - -=head2 LimitFilename - -Takes a paramhash with the fields OPERATOR and VALUE. -OPERATOR is one of =, LIKE, NOT LIKE or !=. -VALUE is a string to search for in the body of the ticket - -=cut -sub LimitFilename { - my $self = shift; - my %args = (@_); - $self->Limit (FIELD => 'Filename', - VALUE => $args{'VALUE'}, - OPERATOR => $args{'OPERATOR'}, - DESCRIPTION => join( - ' ', $self->loc('Attachment filename'), $args{'OPERATOR'}, $args{'VALUE'}, - ), - ); -} - -# }}} -# {{{ sub LimitContentType - -=head2 LimitContentType - -Takes a paramhash with the fields OPERATOR and VALUE. -OPERATOR is one of =, LIKE, NOT LIKE or !=. -VALUE is a content type to search ticket attachments for - -=cut - -sub LimitContentType { - my $self = shift; - my %args = (@_); - $self->Limit (FIELD => 'ContentType', - VALUE => $args{'VALUE'}, - OPERATOR => $args{'OPERATOR'}, - DESCRIPTION => join( - ' ', $self->loc('Ticket content type'), $args{'OPERATOR'}, $args{'VALUE'}, - ), - ); -} -# }}} - -# }}} - -# {{{ Limiting based on people - -# {{{ sub LimitOwner - -=head2 LimitOwner - -Takes a paramhash with the fields OPERATOR and VALUE. -OPERATOR is one of = or !=. -VALUE is a user id. - -=cut - -sub LimitOwner { - my $self = shift; - my %args = ( OPERATOR => '=', - @_); - - my $owner = new RT::User($self->CurrentUser); - $owner->Load($args{'VALUE'}); - # FIXME: check for a valid $owner - $self->Limit (FIELD => 'Owner', - VALUE => $args{'VALUE'}, - OPERATOR => $args{'OPERATOR'}, - DESCRIPTION => join( - ' ', $self->loc('Owner'), $args{'OPERATOR'}, $owner->Name(), - ), - ); - -} - -# }}} - -# {{{ Limiting watchers - -# {{{ sub LimitWatcher - - -=head2 LimitWatcher - - Takes a paramhash with the fields OPERATOR, TYPE and VALUE. - OPERATOR is one of =, LIKE, NOT LIKE or !=. - VALUE is a value to match the ticket\'s watcher email addresses against - TYPE is the sort of watchers you want to match against. Leave it undef if you want to search all of them - -=begin testing - -my $t1 = RT::Ticket->new($RT::SystemUser); -$t1->Create(Queue => 'general', Subject => "LimitWatchers test", Requestors => \['requestor1@example.com']); - -=end testing - -=cut - -sub LimitWatcher { - my $self = shift; - my %args = ( OPERATOR => '=', - VALUE => undef, - TYPE => undef, - @_); - - - #build us up a description - my ($watcher_type, $desc); - if ($args{'TYPE'}) { - $watcher_type = $args{'TYPE'}; - } - else { - $watcher_type = "Watcher"; - } - - $self->Limit (FIELD => $watcher_type, - VALUE => $args{'VALUE'}, - OPERATOR => $args{'OPERATOR'}, - TYPE => $args{'TYPE'}, - DESCRIPTION => join( - ' ', $self->loc($watcher_type), $args{'OPERATOR'}, $args{'VALUE'}, - ), - ); -} - - -sub LimitRequestor { - my $self = shift; - my %args = (@_); - my ($package, $filename, $line) = caller; - $RT::Logger->error("Tickets->LimitRequestor is deprecated. please rewrite call at $package - $filename: $line"); - $self->LimitWatcher(TYPE => 'Requestor', @_); - -} - -# }}} - - -# }}} - -# }}} - -# {{{ Limiting based on links - -# {{{ LimitLinkedTo - -=head2 LimitLinkedTo - -LimitLinkedTo takes a paramhash with two fields: TYPE and TARGET -TYPE limits the sort of relationship we want to search on - -TYPE = { RefersTo, MemberOf, DependsOn } - -TARGET is the id or URI of the TARGET of the link -(TARGET used to be 'TICKET'. 'TICKET' is deprecated, but will be treated as TARGET - -=cut - -sub LimitLinkedTo { - my $self = shift; - my %args = ( - TICKET => undef, - TARGET => undef, - TYPE => undef, - @_); - - $self->Limit( - FIELD => 'LinkedTo', - BASE => undef, - TARGET => ($args{'TARGET'} || $args{'TICKET'}), - TYPE => $args{'TYPE'}, - DESCRIPTION => $self->loc( - "Tickets [_1] by [_2]", $self->loc($args{'TYPE'}), ($args{'TARGET'} || $args{'TICKET'}) - ), - ); -} - - -# }}} - -# {{{ LimitLinkedFrom - -=head2 LimitLinkedFrom - -LimitLinkedFrom takes a paramhash with two fields: TYPE and BASE -TYPE limits the sort of relationship we want to search on - - -BASE is the id or URI of the BASE of the link -(BASE used to be 'TICKET'. 'TICKET' is deprecated, but will be treated as BASE - - -=cut - -sub LimitLinkedFrom { - my $self = shift; - my %args = ( BASE => undef, - TICKET => undef, - TYPE => undef, - @_); - - # translate RT2 From/To naming to RT3 TicketSQL naming - my %fromToMap = qw(DependsOn DependentOn - MemberOf HasMember - RefersTo ReferredToBy); - - my $type = $args{'TYPE'}; - $type = $fromToMap{$type} if exists($fromToMap{$type}); - - $self->Limit( FIELD => 'LinkedTo', - TARGET => undef, - BASE => ($args{'BASE'} || $args{'TICKET'}), - TYPE => $type, - DESCRIPTION => $self->loc( - "Tickets [_1] [_2]", $self->loc($args{'TYPE'}), ($args{'BASE'} || $args{'TICKET'}) - ), - ); -} - - -# }}} - -# {{{ LimitMemberOf -sub LimitMemberOf { - my $self = shift; - my $ticket_id = shift; - $self->LimitLinkedTo ( TARGET=> "$ticket_id", - TYPE => 'MemberOf', - ); - -} -# }}} - -# {{{ LimitHasMember -sub LimitHasMember { - my $self = shift; - my $ticket_id =shift; - $self->LimitLinkedFrom ( BASE => "$ticket_id", - TYPE => 'HasMember', - ); - -} -# }}} - -# {{{ LimitDependsOn - -sub LimitDependsOn { - my $self = shift; - my $ticket_id = shift; - $self->LimitLinkedTo ( TARGET => "$ticket_id", - TYPE => 'DependsOn', - ); - -} - -# }}} - -# {{{ LimitDependedOnBy - -sub LimitDependedOnBy { - my $self = shift; - my $ticket_id = shift; - $self->LimitLinkedFrom ( BASE => "$ticket_id", - TYPE => 'DependentOn', - ); - -} - -# }}} - - -# {{{ LimitRefersTo - -sub LimitRefersTo { - my $self = shift; - my $ticket_id = shift; - $self->LimitLinkedTo ( TARGET => "$ticket_id", - TYPE => 'RefersTo', - ); - -} - -# }}} - -# {{{ LimitReferredToBy - -sub LimitReferredToBy { - my $self = shift; - my $ticket_id = shift; - $self->LimitLinkedFrom ( BASE=> "$ticket_id", - TYPE => 'ReferredTo', - ); - -} - -# }}} - -# }}} - -# {{{ limit based on ticket date attribtes - -# {{{ sub LimitDate - -=head2 LimitDate (FIELD => 'DateField', OPERATOR => $oper, VALUE => $ISODate) - -Takes a paramhash with the fields FIELD OPERATOR and VALUE. - -OPERATOR is one of > or < -VALUE is a date and time in ISO format in GMT -FIELD is one of Starts, Started, Told, Created, Resolved, LastUpdated - -There are also helper functions of the form LimitFIELD that eliminate -the need to pass in a FIELD argument. - -=cut - -sub LimitDate { - my $self = shift; - my %args = ( - FIELD => undef, - VALUE => undef, - OPERATOR => undef, - - @_); - - #Set the description if we didn't get handed it above - unless ($args{'DESCRIPTION'} ) { - $args{'DESCRIPTION'} = $args{'FIELD'} . " " .$args{'OPERATOR'}. " ". $args{'VALUE'} . " GMT" - } - - $self->Limit (%args); - -} - -# }}} - - - - -sub LimitCreated { - my $self = shift; - $self->LimitDate( FIELD => 'Created', @_); -} -sub LimitDue { - my $self = shift; - $self->LimitDate( FIELD => 'Due', @_); - -} -sub LimitStarts { - my $self = shift; - $self->LimitDate( FIELD => 'Starts', @_); - -} -sub LimitStarted { - my $self = shift; - $self->LimitDate( FIELD => 'Started', @_); -} -sub LimitResolved { - my $self = shift; - $self->LimitDate( FIELD => 'Resolved', @_); -} -sub LimitTold { - my $self = shift; - $self->LimitDate( FIELD => 'Told', @_); -} -sub LimitLastUpdated { - my $self = shift; - $self->LimitDate( FIELD => 'LastUpdated', @_); -} -# -# {{{ sub LimitTransactionDate - -=head2 LimitTransactionDate (OPERATOR => $oper, VALUE => $ISODate) - -Takes a paramhash with the fields FIELD OPERATOR and VALUE. - -OPERATOR is one of > or < -VALUE is a date and time in ISO format in GMT - - -=cut - -sub LimitTransactionDate { - my $self = shift; - my %args = ( - FIELD => 'TransactionDate', - VALUE => undef, - OPERATOR => undef, - - @_); - - # <20021217042756.GK28744@pallas.fsck.com> - # "Kill It" - Jesse. - - #Set the description if we didn't get handed it above - unless ($args{'DESCRIPTION'} ) { - $args{'DESCRIPTION'} = $args{'FIELD'} . " " .$args{'OPERATOR'}. " ". $args{'VALUE'} . " GMT" - } - - $self->Limit (%args); - -} - -# }}} - -# }}} - -# {{{ Limit based on custom fields -# {{{ sub LimitCustomField - -=head2 LimitCustomField - -Takes a paramhash of key/value pairs with the following keys: - -=over 4 - -=item CUSTOMFIELD - CustomField name or id. If a name is passed, an additional -parameter QUEUE may also be passed to distinguish the custom field. - -=item OPERATOR - The usual Limit operators - -=item VALUE - The value to compare against - -=back - -=cut - -sub LimitCustomField { - my $self = shift; - my %args = ( VALUE => undef, - CUSTOMFIELD => undef, - OPERATOR => '=', - DESCRIPTION => undef, - FIELD => 'CustomFieldValue', - QUOTEVALUE => 1, - @_ ); - - use RT::CustomFields; - my $CF = RT::CustomField->new( $self->CurrentUser ); - if ( $args{CUSTOMFIELD} =~ /^\d+$/) { - $CF->Load( $args{CUSTOMFIELD} ); - } - else { - $CF->LoadByNameAndQueue( Name => $args{CUSTOMFIELD}, Queue => $args{QUEUE} ); - $args{CUSTOMFIELD} = $CF->Id; - } - - #If we are looking to compare with a null value. - if ( $args{'OPERATOR'} =~ /^is$/i ) { - $args{'DESCRIPTION'} ||= $self->loc("Custom field [_1] has no value.", $CF->Name); - } - elsif ( $args{'OPERATOR'} =~ /^is not$/i ) { - $args{'DESCRIPTION'} ||= $self->loc("Custom field [_1] has a value.", $CF->Name); - } - - # if we're not looking to compare with a null value - else { - $args{'DESCRIPTION'} ||= $self->loc("Custom field [_1] [_2] [_3]", $CF->Name , $args{OPERATOR} , $args{VALUE}); - } - - my $q = ""; - if ($CF->Queue) { - my $qo = new RT::Queue( $self->CurrentUser ); - $qo->load( $CF->Queue ); - $q = $qo->Name; - } - - my @rest; - @rest = ( ENTRYAGGREGATOR => 'AND' ) - if ($CF->Type eq 'SelectMultiple'); - - $self->Limit( VALUE => $args{VALUE}, - FIELD => "CF.".( $q - ? $q . ".{" . $CF->Name . "}" - : $CF->Name - ), - OPERATOR => $args{OPERATOR}, - CUSTOMFIELD => 1, - @rest, - ); - - - $self->{'RecalcTicketLimits'} = 1; -} - -# }}} -# }}} - - -# {{{ sub _NextIndex - -=head2 _NextIndex - -Keep track of the counter for the array of restrictions - -=cut - -sub _NextIndex { - my $self = shift; - return ($self->{'restriction_index'}++); -} -# }}} - -# }}} - -# {{{ Core bits to make this a DBIx::SearchBuilder object - -# {{{ sub _Init -sub _Init { - my $self = shift; - $self->{'table'} = "Tickets"; - $self->{'RecalcTicketLimits'} = 1; - $self->{'looking_at_effective_id'} = 0; - $self->{'looking_at_type'} = 0; - $self->{'restriction_index'} =1; - $self->{'primary_key'} = "id"; - delete $self->{'items_array'}; - delete $self->{'item_map'}; - $self->SUPER::_Init(@_); - - $self->_InitSQL; - -} -# }}} - -# {{{ sub Count -sub Count { - my $self = shift; - $self->_ProcessRestrictions() if ($self->{'RecalcTicketLimits'} == 1 ); - return($self->SUPER::Count()); -} -# }}} - -# {{{ sub CountAll -sub CountAll { - my $self = shift; - $self->_ProcessRestrictions() if ($self->{'RecalcTicketLimits'} == 1 ); - return($self->SUPER::CountAll()); -} -# }}} - - -# {{{ sub ItemsArrayRef - -=head2 ItemsArrayRef - -Returns a reference to the set of all items found in this search - -=cut - -sub ItemsArrayRef { - my $self = shift; - my @items; - - unless ( $self->{'items_array'} ) { - - my $placeholder = $self->_ItemsCounter; - $self->GotoFirstItem(); - while ( my $item = $self->Next ) { - push ( @{ $self->{'items_array'} }, $item ); - } - $self->GotoItem($placeholder); - } - return ( $self->{'items_array'} ); -} -# }}} - -# {{{ sub Next -sub Next { - my $self = shift; - - $self->_ProcessRestrictions() if ($self->{'RecalcTicketLimits'} == 1 ); - - my $Ticket = $self->SUPER::Next(); - if ((defined($Ticket)) and (ref($Ticket))) { - - #Make sure we _never_ show deleted tickets - #TODO we should be doing this in the where clause. - #but you can't do multiple clauses on the same field just yet :/ - - if ($Ticket->__Value('Status') eq 'deleted') { - return($self->Next()); - } - # Since Ticket could be granted with more rights instead - # of being revoked, it's ok if queue rights allow - # ShowTicket. It seems need another query, but we have - # rights cache in Principal::HasRight. - elsif ($Ticket->QueueObj->CurrentUserHasRight('ShowTicket') || - $Ticket->CurrentUserHasRight('ShowTicket')) { - return($Ticket); - } - - #If the user doesn't have the right to show this ticket - else { - return($self->Next()); - } - } - #if there never was any ticket - else { - return(undef); - } - -} -# }}} - -# }}} - -# {{{ Deal with storing and restoring restrictions - -# {{{ sub LoadRestrictions - -=head2 LoadRestrictions - -LoadRestrictions takes a string which can fully populate the TicketRestrictons hash. -TODO It is not yet implemented - -=cut - -# }}} - -# {{{ sub DescribeRestrictions - -=head2 DescribeRestrictions - -takes nothing. -Returns a hash keyed by restriction id. -Each element of the hash is currently a one element hash that contains DESCRIPTION which -is a description of the purpose of that TicketRestriction - -=cut - -sub DescribeRestrictions { - my $self = shift; - - my ($row, %listing); - - foreach $row (keys %{$self->{'TicketRestrictions'}}) { - $listing{$row} = $self->{'TicketRestrictions'}{$row}{'DESCRIPTION'}; - } - return (%listing); -} -# }}} - -# {{{ sub RestrictionValues - -=head2 RestrictionValues FIELD - -Takes a restriction field and returns a list of values this field is restricted -to. - -=cut - -sub RestrictionValues { - my $self = shift; - my $field = shift; - map $self->{'TicketRestrictions'}{$_}{'VALUE'}, - grep { - $self->{'TicketRestrictions'}{$_}{'FIELD'} eq $field - && $self->{'TicketRestrictions'}{$_}{'OPERATOR'} eq "=" - } - keys %{$self->{'TicketRestrictions'}}; -} - -# }}} - -# {{{ sub ClearRestrictions - -=head2 ClearRestrictions - -Removes all restrictions irretrievably - -=cut - -sub ClearRestrictions { - my $self = shift; - delete $self->{'TicketRestrictions'}; - $self->{'looking_at_effective_id'} = 0; - $self->{'looking_at_type'} = 0; - $self->{'RecalcTicketLimits'} =1; -} - -# }}} - -# {{{ sub DeleteRestriction - -=head2 DeleteRestriction - -Takes the row Id of a restriction (From DescribeRestrictions' output, for example. -Removes that restriction from the session's limits. - -=cut - - -sub DeleteRestriction { - my $self = shift; - my $row = shift; - delete $self->{'TicketRestrictions'}{$row}; - - $self->{'RecalcTicketLimits'} = 1; - #make the underlying easysearch object forget all its preconceptions -} - -# }}} - -# {{{ sub _RestrictionsToClauses - -# Convert a set of oldstyle SB Restrictions to Clauses for RQL - -sub _RestrictionsToClauses { - my $self = shift; - - my $row; - my %clause; - foreach $row (keys %{$self->{'TicketRestrictions'}}) { - my $restriction = $self->{'TicketRestrictions'}{$row}; - #use Data::Dumper; - #print Dumper($restriction),"\n"; - - # We need to reimplement the subclause aggregation that SearchBuilder does. - # Default Subclause is ALIAS.FIELD, and default ALIAS is 'main', - # Then SB AND's the different Subclauses together. - - # So, we want to group things into Subclauses, convert them to - # SQL, and then join them with the appropriate DefaultEA. - # Then join each subclause group with AND. - - my $field = $restriction->{'FIELD'}; - my $realfield = $field; # CustomFields fake up a fieldname, so - # we need to figure that out - - # One special case - # Rewrite LinkedTo meta field to the real field - if ($field =~ /LinkedTo/) { - $realfield = $field = $restriction->{'TYPE'}; - } - - # Two special case - # CustomFields have a different real field - if ($field =~ /^CF\./) { - $realfield = "CF" - } - - die "I don't know about $field yet" - unless (exists $FIELDS{$realfield} or $restriction->{CUSTOMFIELD}); - - my $type = $FIELDS{$realfield}->[0]; - my $op = $restriction->{'OPERATOR'}; - - my $value = ( grep { defined } - map { $restriction->{$_} } qw(VALUE TICKET BASE TARGET))[0]; - - # this performs the moral equivalent of defined or/dor/C<//>, - # without the short circuiting.You need to use a 'defined or' - # type thing instead of just checking for truth values, because - # VALUE could be 0.(i.e. "false") - - # You could also use this, but I find it less aesthetic: - # (although it does short circuit) - #( defined $restriction->{'VALUE'}? $restriction->{VALUE} : - # defined $restriction->{'TICKET'} ? - # $restriction->{TICKET} : - # defined $restriction->{'BASE'} ? - # $restriction->{BASE} : - # defined $restriction->{'TARGET'} ? - # $restriction->{TARGET} ) - - my $ea = $restriction->{ENTRYAGGREGATOR} || $DefaultEA{$type} || "AND"; - if ( ref $ea ) { - die "Invalid operator $op for $field ($type)" - unless exists $ea->{$op}; - $ea = $ea->{$op}; - } - - # Each CustomField should be put into a different Clause so they - # are ANDed together. - if ($restriction->{CUSTOMFIELD}) { - $realfield = $field; - } - - exists $clause{$realfield} or $clause{$realfield} = []; - # Escape Quotes - $field =~ s!(['"])!\\$1!g; - $value =~ s!(['"])!\\$1!g; - my $data = [ $ea, $type, $field, $op, $value ]; - - # here is where we store extra data, say if it's a keyword or - # something. (I.e. "TYPE SPECIFIC STUFF") - - #print Dumper($data); - push @{$clause{$realfield}}, $data; - } - return \%clause; -} - -# }}} - -# {{{ sub _ProcessRestrictions - -=head2 _ProcessRestrictions PARAMHASH - -# The new _ProcessRestrictions is somewhat dependent on the SQL stuff, -# but isn't quite generic enough to move into Tickets_Overlay_SQL. - -=cut - -sub _ProcessRestrictions { - my $self = shift; - - #Blow away ticket aliases since we'll need to regenerate them for - #a new search - delete $self->{'TicketAliases'}; - delete $self->{'items_array'}; - delete $self->{'item_map'}; - delete $self->{'raw_rows'}; - delete $self->{'rows'}; - delete $self->{'count_all'}; - - my $sql = $self->{_sql_query}; # Violating the _SQL namespace - if (!$sql||$self->{'RecalcTicketLimits'}) { - # "Restrictions to Clauses Branch\n"; - my $clauseRef = eval { $self->_RestrictionsToClauses; }; - if ($@) { - $RT::Logger->error( "RestrictionsToClauses: " . $@ ); - $self->FromSQL(""); - } else { - $sql = $self->ClausesToSQL($clauseRef); - $self->FromSQL($sql); - } - } - - - $self->{'RecalcTicketLimits'} = 0; - -} - -=head2 _BuildItemMap - - # Build up a map of first/last/next/prev items, so that we can display search nav quickly - -=cut - -sub _BuildItemMap { - my $self = shift; - - my $items = $self->ItemsArrayRef; - my $prev = 0 ; - - delete $self->{'item_map'}; - if ($items->[0]) { - $self->{'item_map'}->{'first'} = $items->[0]->EffectiveId; - while (my $item = shift @$items ) { - my $id = $item->EffectiveId; - $self->{'item_map'}->{$id}->{'defined'} = 1; - $self->{'item_map'}->{$id}->{prev} = $prev; - $self->{'item_map'}->{$id}->{next} = $items->[0]->EffectiveId if ($items->[0]); - $prev = $id; - } - $self->{'item_map'}->{'last'} = $prev; - } -} - - -=head2 ItemMap - -Returns an a map of all items found by this search. The map is of the form - -$ItemMap->{'first'} = first ticketid found -$ItemMap->{'last'} = last ticketid found -$ItemMap->{$id}->{prev} = the tikcet id found before $id -$ItemMap->{$id}->{next} = the tikcet id found after $id - -=cut - -sub ItemMap { - my $self = shift; - $self->_BuildItemMap() unless ($self->{'item_map'}); - return ($self->{'item_map'}); -} - - - - -=cut - -} - - - -# }}} - -# }}} - -=head2 PrepForSerialization - -You don't want to serialize a big tickets object, as the {items} hash will be instantly invalid _and_ eat lots of space - -=cut - - -sub PrepForSerialization { - my $self = shift; - delete $self->{'items'}; - $self->RedoSearch(); -} - -1; - |