X-Git-Url: http://wagnertech.de/git?a=blobdiff_plain;f=SL%2FController%2FHelper%2FParseFilter.pm;h=0f7314179fcdddd9c31e84f5f6d738993f0cbe00;hb=c46d944c0f688128ee33b2989260f3d9390c61d5;hp=c38974e40c3680b56226d31ef0bfc7c4aa37d93c;hpb=36b7bf7bb11f3875b818387aeda1fad363eb6c96;p=kivitendo-erp.git diff --git a/SL/Controller/Helper/ParseFilter.pm b/SL/Controller/Helper/ParseFilter.pm index c38974e40..0f7314179 100644 --- a/SL/Controller/Helper/ParseFilter.pm +++ b/SL/Controller/Helper/ParseFilter.pm @@ -8,17 +8,27 @@ our @EXPORT = qw(parse_filter); use DateTime; use SL::Helper::DateTime; use List::MoreUtils qw(uniq); +use SL::Util qw(trim); use SL::MoreCommon qw(listify); use Data::Dumper; use Text::ParseWords; +sub _lazy_bool_eq { + my ($key, $value) = @_; + + return () if ($value // '') eq ''; + return (or => [ $key => undef, $key => 0 ]) if !$value; + return ($key => 1); +} + my %filters = ( date => sub { DateTime->from_lxoffice($_[0]) }, number => sub { $::form->parse_amount(\%::myconfig, $_[0]) }, percent => sub { $::form->parse_amount(\%::myconfig, $_[0]) / 100 }, - head => sub { $_[0] . '%' }, - tail => sub { '%' . $_[0] }, - substr => sub { '%' . $_[0] . '%' }, + head => sub { trim($_[0]) . '%' }, + tail => sub { '%' . trim($_[0]) }, + substr => sub { '%' . trim($_[0]) . '%' }, + trim => sub { trim($_[0]) }, ); my %methods = ( @@ -32,6 +42,10 @@ my %methods = ( } qw(similar match imatch regex regexp like ilike rlike is is_not ne eq lt gt le ge), ); +my %complex_methods = ( + lazy_bool_eq => \&_lazy_bool_eq, +); + sub parse_filter { my ($filter, %params) = @_; @@ -99,31 +113,41 @@ sub _parse_filter { $flattened = _collapse_indirect_filters($flattened); + my $all_filters = { %filters, %{ $params{filters} || {} } }; + my $all_methods = { %methods, %{ $params{methods} || {} } }; + my $all_complex = { %complex_methods, %{ $params{complex_methods} || {} } }; + my @result; for (my $i = 0; $i < scalar @$flattened; $i += 2) { + my (@args, @filters, $method); + my ($key, $value) = ($flattened->[$i], $flattened->[$i+1]); my ($type, $op) = $key =~ m{:(.+)::(.+)}; - if ($key =~ s/:multi//) { - my @multi; - my $orig_key = $key; - for my $value (parse_line('\s+', 0, $value)) { - ($key, $value) = _apply_all($key, $value, qr/\b:(\w+)/, { %filters, %{ $params{filters} || {} } }); - ($key, $value) = _apply_all($key, $value, qr/\b::(\w+)/, { %methods, %{ $params{methods} || {} } }); - ($key, $value) = _dispatch_custom_filters($params{class}, $with_objects, $key, $value) if $params{class}; - ($key, $value) = _apply_value_filters($key, $value, $type, $op); - push @multi, $key, $value; - $key = $orig_key; - } - ($key, $value) = (and => \@multi); - } else { - ($key, $value) = _apply_all($key, $value, qr/\b:(\w+)/, { %filters, %{ $params{filters} || {} } }); - ($key, $value) = _apply_all($key, $value, qr/\b::(\w+)/, { %methods, %{ $params{methods} || {} } }); - ($key, $value) = _dispatch_custom_filters($params{class}, $with_objects, $key, $value) if $params{class}; - ($key, $value) = _apply_value_filters($key, $value, $type, $op); + my $is_multi = $key =~ s/:multi//; + my $is_any = $key =~ s/:any//; + my @value_tokens = $is_multi || $is_any ? parse_line('\s+', 0, $value) : ($value); + + ($key, $method) = split m{::}, $key, 2; + ($key, @filters) = split m{:}, $key; + + my $orig_key = $key; + + for my $value_token (@value_tokens) { + $key = $orig_key; + + $value_token = _apply($value_token, $_, $all_filters) for @filters; + $value_token = _apply($value_token, $method, $all_methods) if $method && exists $all_methods->{$method}; + ($key, $value_token) = _apply_complex($key, $value_token, $method, $all_complex) if $method && exists $all_complex->{$method}; + ($key, $value_token) = _dispatch_custom_filters($params{class}, $with_objects, $key, $value_token) if $params{class}; + ($key, $value_token) = _apply_value_filters($key, $value_token, $type, $op); + + push @args, $key, $value_token; } - push @result, $key, $value if defined $key; + next unless defined $key; + + push @result, $is_multi ? (and => [ @args ]) : $is_any ? (or => [ @args ]) : @args; } return \@result; } @@ -151,11 +175,22 @@ sub _dispatch_custom_filters { my @tokens = split /\./, $key; my $curr_class = $class->object_class; - # find first token which is not a relationship + # our key will consist of dot-delimited tokens + # like this: order.part.unit.name + # each of these tokens except the last one is one of: + # - a relationship in the parent object + # - a custom filter + # + # the last token must be + # - a custom filter + # - a column in the parent object + # + # find first token which is not a relationship, + # so we can pass the rest on my $i = 0; while ($i < $#tokens) { eval { - $curr_class = $curr_class->meta->relationship($tokens[$_])->class; + $curr_class = $curr_class->meta->relationship($tokens[$i])->class; ++$i; } or do { last; @@ -228,20 +263,20 @@ sub _apply { return $filters->{$name}->($value); } -sub _apply_all { - my ($key, $value, $re, $subs) = @_; - - while ($key =~ s/$re//) { - $value = _apply($value, $1, $subs); - } - - return $key, $value; +sub _apply_complex { + my ($key, $value, $name, $filters) = @_; + return $key, $value unless $name && $filters->{$name}; + return $filters->{$name}->($key, $value); } 1; __END__ +=pod + +=encoding utf8 + =head1 NAME SL::Controller::Helper::ParseFilter - Convert a form filter spec into a RDBO get_all filter @@ -249,10 +284,10 @@ SL::Controller::Helper::ParseFilter - Convert a form filter spec into a RDBO get =head1 SYNOPSIS use SL::Controller::Helper::ParseFilter; - SL::DB::Object->get_all(parse_filter($::form->{filter})); + SL::DB::Manager::Object->get_all(parse_filter($::form->{filter})); # or more complex - SL::DB::Object->get_all(parse_filter($::form->{filter}, + SL::DB::Manager::Object->get_all(parse_filter($::form->{filter}, with_objects => [ qw(part customer) ])); =head1 DESCRIPTION @@ -263,8 +298,8 @@ customer. L allows you to search for these by filtering them p query => [ 'customer.name' => 'John Doe', - 'department.description' => [ ilike => '%Sales%' ], - 'orddate' => [ lt => DateTime->today ], + 'department.description' => { ilike => '%Sales%' }, + 'orddate' => { lt => DateTime->today }, ] Unfortunately, if you specify them in your form as these strings, the form @@ -404,22 +439,22 @@ Pasres the input string with C<< Form->parse_amount >> Parses the input string with C<< Form->parse_amount / 100 >> +=item trim + +Removes whitespace characters (to be precice, characters with the \p{WSpace} +property from beginning and end of the value. + =item head -Adds "%" at the end of the string. +Adds "%" at the end of the string and applies C. =item tail -Adds "%" at the end of the string. +Adds "%" at the end of the string and applies C. =item substr -Adds "% .. %" around the search string. - -=item eq_ignore_empty - -Ignores this item if it's empty. Otherwise compares it with the -standard SQL C<=> operator. +Adds "% .. %" around the search string and applies C. =back @@ -437,6 +472,18 @@ standard SQL C<=> operator. All these are recognized like the L methods. +=item lazy_bool_eq + +If the value is undefined or an empty string then this parameter will +be completely removed from the query. Otherwise a falsish filter value +will match for C and C; trueish values will only match +C. + +=item eq_ignore_empty + +Ignores this item if it's empty. Otherwise compares it with the +standard SQL C<=> operator. + =back =head1 BUGS AND CAVEATS