%#
%# 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 lib/RT/Assets.pm)
%# 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 &>
<& /Elements/Tabs, %TabArgs &>
<form method="post" action="Build.html" name="BuildQuery" id="BuildQuery">
<input type="hidden" class="hidden" name="SavedSearchId" value="<% $saved_search{'Id'} %>" />
<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'}, catalogs => $catalogs &>
</div>
<& /Elements/Submit, Label => loc('Add these terms'), SubmitId => 'AddClause', Name => 'AddClause'&>
<& /Elements/Submit, Label => loc('Add these terms and Search'), SubmitId => 'DoSearch', Name => 'DoSearch'&>
<div id="editquery">
<& /Search/Elements/EditQuery,
%ARGS,
actions => \@actions,
optionlist => $optionlist,
Description => $saved_search{'Description'},
&>
</div>
<div id="editsearches">
<& /Search/Elements/EditSearches, %saved_search, Type => 'Asset', CurrentSearch => \%query &>
</div>
<span id="display-options">
<& Elements/DisplayOptions,
%ARGS, %query,
AvailableColumns => $AvailableColumns,
CurrentFormat => $CurrentFormat,
&>
</span>
<& /Elements/Submit, Label => loc('Update format and Search'), Name => 'DoSearch', id=>"formatbuttons"&>
</form>
<%INIT>
use RT::Interface::Web::QueryBuilder;
use RT::Interface::Web::QueryBuilder::Tree;
my $title = loc("Asset Query Builder");
my %query = (Type => 'Asset');
for( qw(Query Format OrderBy Order RowsPerPage) ) {
$query{$_} = $ARGS{$_};
}
my %saved_search = (Type => 'Asset');
my @actions = $m->comp( '/Search/Elements/EditSearches:Init', %ARGS, Type => 'Asset', 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', Type => 'Asset', );
# ..then wipe the session out..
delete $session{'CurrentAssetSearchHash'};
# ..and the search results.
$session{'assets'}->CleanSlate if defined $session{'assets'};
}
{ # Attempt to load what we can from the session and preferences, set defaults
my $current = $session{'CurrentAssetSearchHash'};
my $default = { Query => '',
Format => '',
OrderBy => 'Name',
Order => 'ASC',
RowsPerPage => 50 };
for( qw(Query Format OrderBy Order RowsPerPage) ) {
$query{$_} = $current->{$_} 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->ParseAssetSQL( 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 ) {
push @actions, @parse_results;
return $m->comp(
"Edit.html",
Query => $query{'Query'},
Format => $query{'Format'},
SavedSearchId => $saved_search{'Id'},
actions => \@actions,
);
}
my @options = $tree->GetDisplayedNodes;
my @current_values = grep defined, @options[@clauses];
my @new_values = ();
my $cf_field_names =
join "|",
map quotemeta,
grep { $RT::Assets::FIELD_METADATA{$_}->[0] eq 'CUSTOMFIELD' }
sort keys %RT::Assets::FIELD_METADATA;
# Try to find if we're adding a clause
foreach my $arg ( keys %ARGS ) {
next unless $arg =~ m/^ValueOf(\w+|($cf_field_names).\{.*?\})$/
&& ( 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 '';
my $clause = {
Key => $keyword,
Op => $op,
Value => $value,
};
push @new_values, RT::Interface::Web::QueryBuilder::Tree->new($clause);
}
}
push @actions, $m->comp('/Search/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}>)
. (" " x (5 * $_->{DEPTH}))
. $m->interp->apply_escapes($_->{TEXT}, 'h') . qq(</option>) } @$optionlist_arrayref;
my $catalogs = $tree->GetReferencedCatalogs;
# Deal with format changes
my ( $AvailableColumns, $CurrentFormat );
( $query{'Format'}, $AvailableColumns, $CurrentFormat ) = $m->comp(
'Elements/BuildFormatString',
%ARGS,
catalogs => $catalogs,
Format => $query{'Format'},
);
# if we're asked to save the current search, save it
push @actions, $m->comp( '/Search/Elements/EditSearches:Save', %ARGS, Type => 'Asset', Query => \%query, SavedSearch => \%saved_search);
# Populate the "query" context with saved search data
if ($ARGS{SavedSearchSave}) {
$query{'SavedSearchId'} = $saved_search{'Id'};
}
# Push the updates into the session so we don't lose 'em
$session{'CurrentAssetSearchHash'} = {
%query,
SearchId => $saved_search{'Id'},
Object => $saved_search{'Object'},
Description => $saved_search{'Description'},
};
# Show the results, if we were asked.
if ( $ARGS{'DoSearch'} ) {
my $redir_query_string = $m->comp(
'/Elements/QueryString',
%query,
SavedSearchId => $saved_search{'Id'},
);
RT::Interface::Web::Redirect(RT->Config->Get('WebURL') . 'Asset/Search/Results.html?' . $redir_query_string);
$m->abort;
}
# Build a querystring for the tabs
my %TabArgs = ();
if ($NewQuery) {
$TabArgs{QueryString} = 'NewQuery=1';
}
elsif ( $query{'Query'} ) {
$TabArgs{QueryArgs} = \%query;
}
</%INIT>
<%ARGS>
$NewQuery => 0
@clauses => ()
</%ARGS>