import rt 3.8.7
[freeside.git] / rt / share / html / Search / Build.html
diff --git a/rt/share/html/Search/Build.html b/rt/share/html/Search/Build.html
new file mode 100644 (file)
index 0000000..0fd7974
--- /dev/null
@@ -0,0 +1,323 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%# 
+%# COPYRIGHT:
+%# 
+%# This software is Copyright (c) 1996-2009 Best Practical Solutions, LLC
+%#                                          <jesse@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 }}}
+%#
+%# Data flow here:
+%#   The page receives a Query from the previous page, and maybe arguments
+%#   corresponding to actions.  (If it doesn't get a Query argument, it pulls
+%#   one out of the session hash.  Also, it could be getting just a raw query from
+%#   Build/Edit.html (Advanced).)
+%#
+%#   After doing some stuff with default arguments and saved searches, the ParseQuery
+%#   function (which is similar to, but not the same as, _parser in RT/Tickets_Overlay_SQL)
+%#   converts the Query into a RT::Interface::Web::QueryBuilder::Tree.  This mason file
+%#   then adds stuff to or modifies the tree based on the actions that had been requested
+%#   by clicking buttons.  It then calls GetQueryAndOptionList on the tree to generate
+%#   the SQL query (which is saved as a hidden input) and the option list for the Clauses
+%#   box in the top right corner.
+%#
+%#   Worthwhile refactoring: the tree manipulation code for the actions could use some cleaning
+%#   up.  The node-adding code is different in the "add" actions from in ParseQuery, which leads
+%#   to things like ParseQuery correctly not quoting numbers in numerical fields, while the "add"
+%#   action does quote it (this breaks SQLite).
+%#
+<& /Elements/Header, Title => $title &>
+<& /Ticket/Elements/Tabs, 
+    current_tab => "Search/Build.html?".$QueryString, 
+    Title => $title,
+    %query,
+    SavedSearchId => $saved_search{'Id'},
+    SavedChartSearchId => $ARGS{SavedChartSearchId},
+&>
+
+<form method="post" action="Build.html" name="BuildQuery">
+<input type="hidden" class="hidden" name="SavedSearchId" value="<% $saved_search{'Id'} %>" />
+<input type="hidden" class="hidden" name="SavedChartSearchId" value="<% $ARGS{'SavedChartSearchId'} %>" />
+<input type="hidden" class="hidden" name="Query" value="<% $query{'Query'} %>" />
+<input type="hidden" class="hidden" name="Format" value="<% $query{'Format'} %>" />
+
+
+
+
+<div id="pick-criteria">
+    <& Elements/PickCriteria, query => $query{'Query'}, cfqueues => $queues &>
+</div>
+<& /Elements/Submit,  Label => loc('Add these terms'), Name => 'AddClause'&>
+<& /Elements/Submit, Label => loc('Add these terms and Search'), Name => 'DoSearch'&>
+
+
+<div id="editquery">
+<& Elements/EditQuery,
+    %ARGS,
+    actions => \@actions,
+    optionlist => $optionlist,
+    Description => $saved_search{'Description'},
+    &>
+</div>
+<div id="editsearches">
+    <& Elements/EditSearches, %saved_search, CurrentSearch => \%query &>
+</div>
+
+<span id="display-options">
+<& Elements/DisplayOptions,
+    %ARGS, %query,
+    AvailableColumns => $AvailableColumns,
+    CurrentFormat    => $CurrentFormat,
+&>
+<& /Elements/Submit, Label => loc('Update format and Search'), Name => 'DoSearch', id=>"formatbuttons"&>
+</span>
+</form>
+
+<%INIT>
+use RT::Interface::Web::QueryBuilder;
+use RT::Interface::Web::QueryBuilder::Tree;
+
+$ARGS{SavedChartSearchId} ||= 'new';
+
+my $title = loc("Query Builder");
+
+my %query;
+for( qw(Query Format OrderBy Order RowsPerPage) ) {
+    $query{$_} = $ARGS{$_};
+}
+
+my %saved_search;
+my @actions = $m->comp( 'Elements/EditSearches:Init', %ARGS, Query => \%query, SavedSearch => \%saved_search);
+
+if ( $NewQuery ) {
+
+    # Wipe all data-carrying variables clear if we want a new
+    # search, or we're deleting an old one..
+    %query = ();
+    %saved_search = ( Id => 'new' );
+
+    # ..then wipe the session out..
+    delete $session{'CurrentSearchHash'};
+
+    # ..and the search results.
+    $session{'tickets'}->CleanSlate if defined $session{'tickets'};
+}
+
+{ # Attempt to load what we can from the session and preferences, set defaults
+
+    my $current = $session{'CurrentSearchHash'};
+    my $prefs = $session{'CurrentUser'}->UserObj->Preferences("SearchDisplay") || {};
+    my $default = { Query => '', Format => '', OrderBy => 'id', Order => 'ASC', RowsPerPage => 50 };
+
+    for( qw(Query Format OrderBy Order RowsPerPage) ) {
+        $query{$_} = $current->{$_} unless defined $query{$_};
+        $query{$_} = $prefs->{$_} unless defined $query{$_};
+        $query{$_} = $default->{$_} unless defined $query{$_};
+    }
+
+    for( qw(Order OrderBy) ) {
+        if (ref $query{$_} eq "ARRAY") {
+            $query{$_} = join( '|', @{ $query{$_} } );
+        }
+    }
+    if ( $query{'Format'} ) {
+        # Clean unwanted junk from the format
+        $query{'Format'} = $m->comp( '/Elements/ScrubHTML', Content => $query{'Format'} );
+    }
+}
+
+my $ParseQuery = sub {
+    my ($string, $results) = @_;
+
+    my $tree = RT::Interface::Web::QueryBuilder::Tree->new('AND');
+    @$results = $tree->ParseSQL( Query => $string, CurrentUser => $session{'CurrentUser'} );
+
+    return $tree;
+};
+
+my @parse_results;
+my $tree = $ParseQuery->( $query{'Query'}, \@parse_results );
+
+# if parsing went poorly, send them to the edit page to fix it
+if ( @parse_results ) {
+    return $m->comp( "Edit.html", Query => $query{'Query'}, actions => \@actions );
+}
+
+my @options = $tree->GetDisplayedNodes;
+my @current_values = grep defined, @options[@clauses];
+my @new_values = ();
+
+# {{{ Try to find if we're adding a clause
+foreach my $arg ( keys %ARGS ) {
+    next unless $arg =~ m/^ValueOf(\w+|'CF.{.*?}')$/
+                && ( ref $ARGS{$arg} eq "ARRAY"
+                     ? grep $_ ne '', @{ $ARGS{$arg} }
+                     : $ARGS{$arg} ne '' );
+
+    # We're adding a $1 clause
+    my $field = $1;
+
+    my ($op, $value);
+
+    #figure out if it's a grouping
+    my $keyword = $ARGS{ $field . "Field" } || $field;
+
+    my ( @ops, @values );
+    if ( ref $ARGS{ 'ValueOf' . $field } eq "ARRAY" ) {
+        # we have many keys/values to iterate over, because there is
+        # more than one CF with the same name.
+        @ops    = @{ $ARGS{ $field . 'Op' } };
+        @values = @{ $ARGS{ 'ValueOf' . $field } };
+    }
+    else {
+        @ops    = ( $ARGS{ $field . 'Op' } );
+        @values = ( $ARGS{ 'ValueOf' . $field } );
+    }
+    $RT::Logger->error("Bad Parameters passed into Query Builder")
+        unless @ops == @values;
+
+    for ( my $i = 0; $i < @ops; $i++ ) {
+        my ( $op, $value ) = ( $ops[$i], $values[$i] );
+        next if !defined $value || $value eq '';
+
+        if ( $value eq 'NULL' && $op =~ /=/ ) {
+            if ( $op eq '=' ) {
+                $op = "IS";
+            }
+            elsif ( $op eq '!=' ) {
+                $op = "IS NOT";
+            }
+
+            # This isn't "right", but...
+            # It has to be this way until #5182 is fixed
+            $value = "'NULL'";
+        }
+        else {
+            $value =~ s/'/\\'/g;
+            $value = "'$value'" unless $value =~ /^\d+$/;
+        }
+
+        my $clause = {
+            Key   => $keyword,
+            Op    => $op,
+            Value => $value
+        };
+
+        push @new_values, RT::Interface::Web::QueryBuilder::Tree->new($clause);
+    }
+}
+
+# }}}
+
+push @actions, $m->comp('Elements/EditQuery:Process',
+    %ARGS,
+    Tree     => $tree,
+    Selected => \@current_values,
+    New      => \@new_values,
+);
+
+# {{{ Rebuild $Query based on the additions / movements
+
+my $optionlist_arrayref;
+($query{'Query'}, $optionlist_arrayref) = $tree->GetQueryAndOptionList(\@current_values);
+
+my $optionlist = join "\n", map { qq(<option value="$_->{INDEX}" $_->{SELECTED}>) 
+                                  . ("&nbsp;" x (5 * $_->{DEPTH}))
+                                  . $m->interp->apply_escapes($_->{TEXT}, 'h') . qq(</option>) } @$optionlist_arrayref;
+
+# }}}
+
+my $queues = $tree->GetReferencedQueues;
+
+# {{{ Deal with format changes
+my ( $AvailableColumns, $CurrentFormat );
+( $query{'Format'}, $AvailableColumns, $CurrentFormat ) = $m->comp(
+    'Elements/BuildFormatString',
+    %ARGS,
+    cfqueues => $queues,
+    Format => $query{'Format'},
+);
+
+# }}}
+
+# if we're asked to save the current search, save it
+push @actions, $m->comp( 'Elements/EditSearches:Save', %ARGS, Query => \%query, SavedSearch => \%saved_search);
+
+# {{{ Push the updates into the session so we don't loose 'em
+
+$session{'CurrentSearchHash'} = {
+    %query,
+    SearchId    => $saved_search{'Id'},
+    Object      => $saved_search{'Object'},
+    Description => $saved_search{'Description'},
+};
+
+# }}}
+
+# {{{ Show the results, if we were asked.
+
+if ( $ARGS{'DoSearch'} ) {
+    $m->comp( 'Results.html', %query, SavedChartSearchId => $ARGS{'SavedChartSearchId'}, );
+    $m->comp( '/Elements/Footer' );
+    $m->abort;
+}
+
+# }}}
+
+# {{{ Build a querystring for the tabs
+
+my $QueryString = '';
+if ($NewQuery) {
+    $QueryString = 'NewQuery=1';
+}
+elsif ( $query{'Query'} ) {
+    $QueryString = $m->comp('/Elements/QueryString', %query );
+}
+
+# }}}
+
+</%INIT>
+
+<%ARGS>
+$NewQuery => 0
+@clauses => ()
+</%ARGS>