X-Git-Url: http://git.freeside.biz/gitweb/?p=freeside.git;a=blobdiff_plain;f=rt%2Flib%2FRT%2FInterface%2FWeb%2FQueryBuilder%2FTree.pm;h=efcc43f158d91d9e54f85a8b6f3bbb7d90dd5db7;hp=467627313ead8dfdc2e034304b765397bb6f60ab;hb=7322f2afedcc2f427e997d1535a503613a83f088;hpb=9509e5bfb7f9331303153cac24d7bfecbe2ea9f1 diff --git a/rt/lib/RT/Interface/Web/QueryBuilder/Tree.pm b/rt/lib/RT/Interface/Web/QueryBuilder/Tree.pm index 467627313..efcc43f15 100755 --- a/rt/lib/RT/Interface/Web/QueryBuilder/Tree.pm +++ b/rt/lib/RT/Interface/Web/QueryBuilder/Tree.pm @@ -1,40 +1,40 @@ # BEGIN BPS TAGGED BLOCK {{{ -# +# # COPYRIGHT: -# -# This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC -# -# +# +# This software is Copyright (c) 1996-2016 Best Practical Solutions, LLC +# +# # (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/copyleft/gpl.html. -# -# +# 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 @@ -43,13 +43,15 @@ # 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 }}} + package RT::Interface::Web::QueryBuilder::Tree; use strict; use warnings; +use Tree::Simple qw/use_weak_refs/; use base qw/Tree::Simple/; =head1 NAME @@ -77,19 +79,21 @@ on the root node passed to it.) sub TraversePrePost { my ($self, $prefunc, $postfunc) = @_; - $prefunc->($self); - + # XXX: if pre or post action changes siblings (delete or adds) + # we could have problems + $prefunc->($self) if $prefunc; + foreach my $child ($self->getAllChildren()) { $child->TraversePrePost($prefunc, $postfunc); } - $postfunc->($self); + $postfunc->($self) if $postfunc; } =head2 GetReferencedQueues -Returns a hash reference with keys each queue name referenced in a clause in -the key (even if it's "Queue != 'Foo'"), and values all 1. +Returns a hash reference; each queue referenced with an '=' operation +will appear as a key whose value is 1. =cut @@ -103,12 +107,13 @@ sub GetReferencedQueues { my $node = shift; return if $node->isRoot; + return unless $node->isLeaf; my $clause = $node->getNodeValue(); - - if ( ref($clause) and $clause->{Key} eq 'Queue' ) { - $queues->{ $clause->{Value} } = 1; - }; + return unless $clause->{Key} eq 'Queue'; + return unless $clause->{Op} eq '='; + + $queues->{ $clause->{RawValue} } = 1; } ); @@ -133,55 +138,13 @@ sub GetQueryAndOptionList { my $self = shift; my $selected_nodes = shift; - my $optionlist = []; - - my $i = 0; - - $self->TraversePrePost( - sub { # This is called before recursing to the node's children. - my $node = shift; - - return if $node->isRoot or $node->getParent->isRoot; - - my $clause = $node->getNodeValue(); - my $str = ' '; - my $aggregator_context = $node->getParent()->getNodeValue(); - $str = $aggregator_context . " " if $node->getIndex() > 0; - - if ( ref($clause) ) { # ie, it's a leaf - $str .= - $clause->{Key} . " " . $clause->{Op} . " " . $clause->{Value}; - } - - unless ($node->getParent->getParent->isRoot) { - # used to check !ref( $parent->getNodeValue() ) ) - if ( $node->getIndex() == 0 ) { - $str = '( ' . $str; - } - } - - push @$optionlist, { - TEXT => $str, - INDEX => $i, - SELECTED => (grep { $_ == $node } @$selected_nodes) ? 'SELECTED' : '', - DEPTH => $node->getDepth() - 1, - }; - - $i++; - }, sub { - # This is called after recursing to the node's children. - my $node = shift; - - return if $node->isRoot or $node->getParent->isRoot or $node->getParent->getParent->isRoot; + my $list = $self->__LinearizeTree; + foreach my $e( @$list ) { + $e->{'DEPTH'} = $e->{'NODE'}->getDepth; + $e->{'SELECTED'} = (grep $_ == $e->{'NODE'}, @$selected_nodes)? qq[ selected="selected"] : ''; + } - # Only do this for the rightmost child. - return unless $node->getIndex == $node->getParent->getChildCount - 1; - - $optionlist->[-1]{TEXT} .= ' )'; - } - ); - - return (join ' ', map { $_->{TEXT} } @$optionlist), $optionlist; + return (join ' ', map $_->{'TEXT'}, @$list), $list; } =head2 PruneChildLessAggregators @@ -195,23 +158,18 @@ sub PruneChildlessAggregators { my $self = shift; $self->TraversePrePost( - sub { - }, + undef, sub { my $node = shift; + return unless $node->isLeaf; - return if $node->isRoot or $node->getParent->isRoot; - # We're only looking for aggregators (AND/OR) return if ref $node->getNodeValue; - - return if $node->getChildCount != 0; - + + return if $node->isRoot; + # OK, this is a childless aggregator. Remove self. - $node->getParent->removeChild($node); - - # Deal with circular refs $node->DESTROY; } ); @@ -226,22 +184,112 @@ In fact, it's all of them but the root and its child. =cut sub GetDisplayedNodes { + return map $_->{NODE}, @{ (shift)->__LinearizeTree }; +} + + +sub __LinearizeTree { my $self = shift; - my @lines; - $self->traverse(sub { + my ($list, $i) = ([], 0); + + $self->TraversePrePost( sub { my $node = shift; + return if $node->isRoot; - push @lines, $node unless $node->isRoot or $node->getParent->isRoot; + my $str = ''; + if( $node->getIndex > 0 ) { + $str .= " ". $node->getParent->getNodeValue ." "; + } + + unless( $node->isLeaf ) { + $str .= '( '; + } else { + + my $clause = $node->getNodeValue; + $str .= $clause->{Key}; + $str .= " ". $clause->{Op}; + $str .= " ". $clause->{Value}; + + } + $str =~ s/^\s+|\s+$//; + + push @$list, { + NODE => $node, + TEXT => $str, + INDEX => $i, + }; + + $i++; + }, sub { + my $node = shift; + return if $node->isRoot; + return if $node->isLeaf; + $list->[-1]->{'TEXT'} .= ' )'; }); - return @lines; + return $list; } +sub ParseSQL { + my $self = shift; + my %args = ( + Query => '', + CurrentUser => '', #XXX: Hack + @_ + ); + my $string = $args{'Query'}; + + my @results; + + my %field = %{ RT::Tickets->new( $args{'CurrentUser'} )->FIELDS }; + my %lcfield = map { ( lc($_) => $_ ) } keys %field; + + my $node = $self; + + my %callback; + $callback{'OpenParen'} = sub { + $node = __PACKAGE__->new( 'AND', $node ); + }; + $callback{'CloseParen'} = sub { $node = $node->getParent }; + $callback{'EntryAggregator'} = sub { $node->setNodeValue( $_[0] ) }; + $callback{'Condition'} = sub { + my ($key, $op, $value) = @_; + my $rawvalue = $value; + + my ($main_key) = split /[.]/, $key; + + my $class; + if ( exists $lcfield{ lc $main_key } ) { + $key =~ s/^[^.]+/ $lcfield{ lc $main_key } /e; + ($main_key) = split /[.]/, $key; # make the case right + $class = $field{ $main_key }->[0]; + } + unless( $class ) { + push @results, [ $args{'CurrentUser'}->loc("Unknown field: [_1]", $key), -1 ] + } + + if ( lc $op eq 'is' || lc $op eq 'is not' ) { + $value = 'NULL'; # just fix possible mistakes here + } elsif ( $value !~ /^[+-]?[0-9]+$/ ) { + $value =~ s/(['\\])/\\$1/g; + $value = "'$value'"; + } + + if ($key =~ s/(['\\])/\\$1/g or $key =~ /[^{}\w\.]/) { + $key = "'$key'"; + } + + my $clause = { Key => $key, Op => $op, Value => $value, RawValue => $rawvalue }; + $node->addChild( __PACKAGE__->new( $clause ) ); + }; + $callback{'Error'} = sub { push @results, @_ }; + + require RT::SQL; + RT::SQL::Parse($string, \%callback); + return @results; +} -eval "require RT::Interface::Web::QueryBuilder::Tree_Vendor"; -die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/Web/QueryBuilder/Tree_Vendor.pm}); -eval "require RT::Interface::Web::QueryBuilder::Tree_Local"; -die $@ if ($@ && $@ !~ qr{^Can't locate RT/Interface/Web/QueryBuilder/Tree_Local.pm}); +RT::Base->_ImportOverlays(); 1;