Bugs bei formatierten Methoden behoben
[kivitendo-erp.git] / SL / Controller / Helper / ParseFilter.pm
index 96d3b32..3bf1b65 100644 (file)
@@ -20,11 +20,13 @@ my %filters = (
 );
 
 my %methods = (
-  lt     => sub { +{ lt    => $_[0] } },
-  gt     => sub { +{ gt    => $_[0] } },
-  ilike  => sub { +{ ilike => $_[0] } },
-  like   => sub { +{ like  => $_[0] } },
   enable => sub { ;;;; },
+  map {
+    # since $_ is an alias it can't be used in a closure. even "".$_ or "$_"
+    # does not work, we need a real copy.
+    my $_copy = "$_";
+    $_   => sub { +{ $_copy    => $_[0] } },
+  } qw(similar match imatch regex regexp like ilike rlike is is_not ne eq lt gt le ge),
 );
 
 sub parse_filter {
@@ -36,32 +38,36 @@ sub parse_filter {
 
   my $query = _parse_filter($flattened, %params);
 
-  _launder_keys($filter) unless $params{no_launder};
+  _launder_keys($filter, $params{launder_to}) unless $params{no_launder};
 
   return
-    query => $query,
-    @$objects ? ( with_objects => [ uniq @$objects ]) : ();
+    ($query   && @$query   ? (query => $query) : ()),
+    ($objects && @$objects ? ( with_objects => [ uniq @$objects ]) : ());
 }
 
 sub _launder_keys {
-  my ($filter) = @_;
+  my ($filter, $launder_to) = @_;
+  $launder_to ||= $filter;
   return unless ref $filter eq 'HASH';
-  my @keys = keys %$filter;
-  for my $key (@keys) {
+  for my $key (keys %$filter) {
     my $orig = $key;
     $key =~ s/:/_/g;
-    $filter->{$key} = $filter->{$orig};
-    _launder_keys($filter->{$key});
+    if ('' eq ref $filter->{$orig}) {
+      $launder_to->{$key} = $filter->{$orig};
+    } elsif ('ARRAY' eq ref $filter->{$orig}) {
+      $launder_to->{$key} = [ @{ $filter->{$orig} } ];
+    } else {
+      $launder_to->{$key} ||= { };
+      _launder_keys($filter->{$key}, $launder_to->{$key});
+    }
   };
-
-  return $filter;
 }
 
 sub _pre_parse {
   my ($filter, $with_objects, $prefix, %params) = @_;
 
-  return () unless 'HASH'  eq ref $filter;
-  $with_objects = [];
+  return (undef, $with_objects) unless 'HASH'  eq ref $filter;
+  $with_objects ||= [];
 
   my @result;
 
@@ -70,7 +76,7 @@ sub _pre_parse {
     if ('HASH' eq ref $value) {
       my ($query, $more_objects) = _pre_parse($value, $with_objects, _prefix($prefix, $key));
       push @result,        @$query if $query;
-      push @$with_objects, $key, ($more_objects ? @$more_objects : ());
+      push @$with_objects, _prefix($prefix, $key), ($more_objects ? @$more_objects : ());
     } else {
       push @result, _prefix($prefix, $key) => $value;
     }
@@ -146,9 +152,9 @@ search target. A search for sales orders may be filtered by the name of the
 customer. L<Rose::DB::Object> alloes you to search for these by filtering them prefixed with their table:
 
   query => [
-    customer.name => 'John Doe',
-    department.description => [ ilike => '%Sales%' ],
-    orddate => [ lt => DateTime->today ],
+    'customer.name'          => 'John Doe',
+    'department.description' => [ ilike => '%Sales%' ],
+    'orddate'                => [ lt    => DateTime->today ],
   ]
 
 Unfortunately, if you specify them in you form as these strings, the form
@@ -171,7 +177,7 @@ providing suffixes for common search patterns.
 
 =over 4
 
-=item parse_amount \%FILTER, [ %PARAMS ]
+=item C<parse_filter \%FILTER, [ %PARAMS ]>
 
 First argument is the filter from form. It is highly recommended that you put
 all filter attributes into a named container as to not confuse them with the
@@ -181,14 +187,14 @@ Nested structures will be parsed and interpreted as foreign references. For
 example if you search for L<Order>s, this input will search for those with a
 specific L<Salesman>:
 
-  [% L.select_tag('filter.salesman.id', ...
+  [% L.select_tag('filter.salesman.id', ...) %]
 
 Additionally you can add modifier to the name to set a certain method:
 
-  [% L.input_tag('filter.department.description:substr::ilike' ...
+  [% L.input_tag('filter.department.description:substr::ilike', ...) %]
 
-This will add the "% .. %" wildcards for substr matching in sql, and add an C<[
-ilike => $value ]> block around it to match case insensitively.
+This will add the "% .. %" wildcards for substr matching in SQL, and add an
+C<< ilike => $value >> block around it to match case insensitively.
 
 As a rule all value filters require a single colon and must be placed before
 match method suffixes, which are appended with 2 colons. See below for a full
@@ -207,17 +213,38 @@ and later
 The special empty method will be used to set the method for the previous
 method-less input.
 
-=item Laundering filter
+=back
+
+=head1 LAUNDERING
 
-Unfortunately Template cannot parse the postfixes if you want to rerender the
-filter. For this reason all colons filter keys are by default laundered into
-underscores. If you don't want this to happen pass C<no_launder => 1> as a
-parameter. A full select_tag then loks like this:
+Unfortunately Template cannot parse the postfixes if you want to
+rerender the filter. For this reason all colons filter keys are by
+default laundered into underscores, so you can use them like this:
 
   [% L.input_tag('filter.price:number::lt', filter.price_number__lt) %]
 
+All of your original entries will stay intactg. If you don't want this to
+happen pass C<< no_launder => 1 >> as a parameter.  Additionally you can pass a
+different target for the laundered values with the C<launder_to>  parameter. It
+takes an hashref and will deep copy all values in your filter to the target. So
+if you have a filter that looks liek this:
 
-=back
+  $filter = {
+    'price:number::lt' => '2,30',
+    'closed            => '1',
+  }
+
+and parse it with
+
+  parse_filter($filter, launder_to => $laundered_filter = { })
+
+the original filter will be unchanged, and C<$laundered_filter> will end up
+like this:
+
+  $filter = {
+    'price_number__lt' => '2,30',
+    'closed            => '1',
+  }
 
 =head1 FILTERS (leading with :)
 
@@ -227,15 +254,15 @@ The following filters are built in, and can be used.
 
 =item date
 
-Parses the input string with DateTime->from_lxoffice
+Parses the input string with C<< DateTime->from_lxoffice >>
 
 =item number
 
-Pasres the input string with Form->parse_amount
+Pasres the input string with C<< Form->parse_amount >>
 
 =item percent
 
-Parses the input string with Form->parse_amount / 100
+Parses the input string with C<< Form->parse_amount / 100 >>
 
 =item head
 
@@ -277,8 +304,8 @@ customer, or are linked to a L<SL::DB::Invoice> with this customer, the
 following will not work as you expect:
 
   # does not work!
-  L.input_tag('customer.name:substr::ilike', ...
-  L.input_tag('invoice.customer.name:substr::ilike', ...
+  L.input_tag('customer.name:substr::ilike', ...)
+  L.input_tag('invoice.customer.name:substr::ilike', ...)
 
 This will sarch for orders whoe invoice has the _same_ customer, which matches
 both inputs. This is because tables are aliased by their name and not by their