From df2251e44e7584899e20793cdf3f0b1b3e38310b Mon Sep 17 00:00:00 2001 From: Rene Krenn Date: Tue, 24 Oct 2023 13:31:32 +0200 Subject: [PATCH] MT#58346 caller/callee filters for conversations the conversation list now supports ?caller= and ?callee= url query parameters. since this involves SQL queries against potentially large database tables, special care is taken with wildcard search to prevent slow queries: - the ?wildcard=true query parameter has to be specified to accept search patterns that contain wildcard symbols, so wilddcards are not accepted by default. WARNING: a search string with a leading wildcard will always force a *slow* full db table scan! - the * symbol is used as a wildcard symbol - \ (backslash) is used as escape character to search for a literal '*' Change-Id: I792d2ea9c649c69c4b5cc98076097cb96467d4bc (cherry picked from commit ae612eb4017995335d96f1007385d0b7b097c451) --- .../Panel/Controller/API/Conversations.pm | 8 + lib/NGCP/Panel/Role/API/Conversations.pm | 186 +++++++++++++++++- lib/NGCP/Panel/Utils/Datatables.pm | 13 +- 3 files changed, 199 insertions(+), 8 deletions(-) diff --git a/lib/NGCP/Panel/Controller/API/Conversations.pm b/lib/NGCP/Panel/Controller/API/Conversations.pm index e0def497b9..00a7ffd8f7 100644 --- a/lib/NGCP/Panel/Controller/API/Conversations.pm +++ b/lib/NGCP/Panel/Controller/API/Conversations.pm @@ -66,6 +66,14 @@ sub query_params { param => 'fax_number_rewrite_mode', description => "Force the fax numbers normalization logic (available: 'default', 'extended').", }, + { + param => 'caller', + description => 'Filter for conversation events by caller (supports patterns with * wildcard).', + }, + { + param => 'callee', + description => 'Filter for conversation events by callee (supports patterns with * wildcard).', + }, ]; } diff --git a/lib/NGCP/Panel/Role/API/Conversations.pm b/lib/NGCP/Panel/Role/API/Conversations.pm index f93ed40f3c..35a8a6ce4a 100644 --- a/lib/NGCP/Panel/Role/API/Conversations.pm +++ b/lib/NGCP/Panel/Role/API/Conversations.pm @@ -15,6 +15,8 @@ use HTTP::Status qw(:constants); use NGCP::Panel::Form; use Data::Dumper; +use NGCP::Panel::Utils::Datatables qw(); + use Tie::IxHash; #use Class::Hash; @@ -330,6 +332,115 @@ sub _apply_timestamp_from_to { return $rs; } +sub _apply_caller { + my $self = shift; + my %params = @_; + my ($rs,$params,$col) = @params{qw/rs params col/}; + + if (exists $params->{caller}) { + $rs = $rs->search_rs({ + _wildcard_search( + search_string => $params->{caller}, + search => 1, + exact_search => _check_wildcard_search($params), + int_search => 0, + col_name => $col, + comparison_op => undef, + convert_code => undef, + ) + }); + } + + return $rs; +} + +sub _apply_callee { + my $self = shift; + my %params = @_; + my ($rs,$params,$col) = @params{qw/rs params col/}; + + if (exists $params->{callee}) { + $rs = $rs->search_rs({ + _wildcard_search( + search_string => $params->{callee}, + search => 1, + exact_search => _check_wildcard_search($params), + int_search => 0, + col_name => $col, + comparison_op => undef, + convert_code => undef, + ) + }); + } + + return $rs; +} + +sub _wildcard_search { + my %params = @_; + my ($search_string, + $search, + $exact_search, + $int_search, + $col_name, + $comparison_op, + $convert_code) = @params{qw/ + search_string + search + exact_search + int_search + col_name + comparison_op + convert_code + /}; + + if ($search or $exact_search or $int_search) { + my $is_pattern = 0; + my ($search_value,$op); + (my $search_string_escaped, $is_pattern) = NGCP::Panel::Utils::Datatables::escape_search_string_pattern( + $search_string,( $exact_search || $int_search )); + if ($is_pattern) { + $op = 'like'; + $search_value = $search_string_escaped; + } elsif ($exact_search) { + $op = '='; + $search_string_escaped = $search_string; + $search_string_escaped =~ s/\\\*/*/g; + $search_string_escaped =~ s/\\\\/\\/g; + $search_value = $search_string_escaped; + } elsif ($int_search) { + $op = '='; + $search_value = $search_string; + } else { + $op = 'like'; + $search_value = '%' . $search_string_escaped . '%'; + } + $op = $comparison_op if (defined $comparison_op); + $search_value = $convert_code->($search_string) if (ref $convert_code eq 'CODE'); + my $stmt; + if (defined $search_value) { + if (not $int_search or $search_string =~ /^\d{1,10}$/) { + return ( $col_name => { $op => $search_value } ); + } + } + } + return (); +} + +sub _check_wildcard_search { + + my $params = shift; + my $exact = 1; + if (exists $params->{wildcards} and defined $params->{wildcards}) { + if ('1' eq $params->{wildcards} + or'true' eq lc($params->{wildcards})) { + $exact = 0; + } + } + return $exact; + +} + sub _apply_direction { my $self = shift; my %params = @_; @@ -360,7 +471,21 @@ sub _get_call_rs { my $rs = $c->model('DB')->resultset('cdr'); - $rs = $self->_apply_timestamp_from_to(rs => $rs,params => $params,col => 'me.start_time'); + $rs = $self->_apply_timestamp_from_to( + rs => $rs, + params => $params, + col => 'me.start_time' + ); + $rs = $self->_apply_caller( + rs => $rs, + params => $params, + col => 'me.source_cli' + ); + $rs = $self->_apply_callee( + rs => $rs, + params => $params, + col => 'me.destination_user_in' + ); if ($provider_id) { $self->_apply_direction(params => $params, @@ -473,7 +598,16 @@ sub _get_voicemail_rs { duration => { '!=' => '' }, }); - $rs = $self->_apply_timestamp_from_to(rs => $rs,params => $params,col => 'me.origtime'); + $rs = $self->_apply_timestamp_from_to( + s => $rs, + params => $params, + col => 'me.origtime' + ); + $rs = $self->_apply_caller( + rs => $rs, + params => $params, + col => 'me.callerid' + ); if ($reseller_id) { $rs = $rs->search({ @@ -528,7 +662,21 @@ sub _get_sms_rs { my $rs = $c->model('DB')->resultset('sms_journal'); - $rs = $self->_apply_timestamp_from_to(rs => $rs,params => $params,col => 'me.time'); + $rs = $self->_apply_timestamp_from_to( + rs => $rs, + params => $params, + col => 'me.time' + ); + $rs = $self->_apply_caller( + rs => $rs, + params => $params, + col => 'me.caller' + ); + $rs = $self->_apply_callee( + rs => $rs, + params => $params, + col => 'me.callee' + ); $self->_apply_direction(params => $params, in => sub { @@ -584,7 +732,21 @@ sub _get_fax_rs { my $rs = $c->model('DB')->resultset('voip_fax_journal'); - $rs = $self->_apply_timestamp_from_to(rs => $rs,params => $params,col => 'me.time'); + $rs = $self->_apply_timestamp_from_to( + rs => $rs, + params => $params, + col => 'me.time' + ); + $rs = $self->_apply_caller( + rs => $rs, + params => $params, + col => 'me.caller' + ); + $rs = $self->_apply_callee( + rs => $rs, + params => $params, + col => 'me.callee' + ); $self->_apply_direction(params => $params, in => sub { @@ -649,7 +811,21 @@ sub _get_xmpp_rs { join => 'domain', }); - $rs = $self->_apply_timestamp_from_to(rs => $rs,params => $params,col => 'epoch'); + $rs = $self->_apply_timestamp_from_to( + rs => $rs, + params => $params, + col => 'epoch' + ); + $rs = $self->_apply_caller( + rs => $rs, + params => $params, + col => 'user' + ); + $rs = $self->_apply_callee( + rs => $rs, + params => $params, + col => 'with' + ); if ($reseller_id) { $rs = $rs->search_rs({ diff --git a/lib/NGCP/Panel/Utils/Datatables.pm b/lib/NGCP/Panel/Utils/Datatables.pm index 7c171d70ac..b858ffe451 100644 --- a/lib/NGCP/Panel/Utils/Datatables.pm +++ b/lib/NGCP/Panel/Utils/Datatables.pm @@ -239,9 +239,16 @@ sub _resolve_joins { } sub get_search_string_pattern { - + my ($c,$no_pattern) = @_; - my $searchString = $c->request->params->{sSearch} // ""; + return get_search_string_pattern($c->request->params->{sSearch},$no_pattern); + +} + +sub escape_search_string_pattern { + + my ($searchString,$no_pattern) = @_; + $searchString //= ""; my $is_pattern = 0; return ($searchString,$is_pattern) if $no_pattern; my $searchString_escaped = join('',map { @@ -276,7 +283,7 @@ sub _apply_search_filters { #for search string from one search input we need to check all columns which contain the 'search' spec (now: qw/search search_lower_column search_upper_column/). so, for example user entered into search input ip address - we don't know that it is ip address, so we check that name like search OR id like search OR search is between network_lower_value and network upper value foreach my $col(@{ $cols }) { next unless $col->{name}; - my ($name,$search_value,$op,$convert); + my ($name,$search_value,$op); # avoid amigious column names if we have the same column in different joined tables if($col->{search} or $col->{strict_search} or $col->{int_search}){ my $is_pattern = 0;