commit 8e405005059d7957a627be4bd145b68fd945ac1d
Author: Moritz Bunkus <m.bunkus@linet.de>
Date:   Tue Feb 17 10:48:45 2015 +0100

    GetModels-Filtered: Unterstützung für benutzerdefinierte Variablen

diff --git a/SL/Controller/Helper/ParseFilter.pm b/SL/Controller/Helper/ParseFilter.pm
index 491eb63b5..759e146c5 100644
--- a/SL/Controller/Helper/ParseFilter.pm
+++ b/SL/Controller/Helper/ParseFilter.pm
@@ -149,25 +149,27 @@ sub _dispatch_custom_filters {
   die 'unrecognized filters' if $key =~ /:/;
 
   my @tokens     = split /\./, $key;
-  my $last_token = pop @tokens;
   my $curr_class = $class->object_class;
 
-  for my $token (@tokens) {
+  # find first token which is not a relationship
+  my $i = 0;
+   while ($i < $#tokens) {
     eval {
-      $curr_class = $curr_class->meta->relationship($token)->class;
-      1;
+      $curr_class = $curr_class->meta->relationship($tokens[$_])->class;
+      ++$i;
     } or do {
-      require Carp;
-      Carp::croak("Could not resolve the relationship '$token' in '$key' while building the filter request");
+      last;
     }
   }
 
   my $manager    = $curr_class->meta->convention_manager->auto_manager_class_name;
-  my $obj_path   = join '.', @tokens;
-  my $obj_prefix = join '.', @tokens, '';
+  my $obj_path   = join '.', @tokens[0..$i-1];
+  my $obj_prefix = join '.', @tokens[0..$i-1], '';
+  my $key_token  = $tokens[$i];
+  my @additional_tokens = @tokens[$i+1..$#tokens];
 
   if ($manager->can('filter')) {
-    ($key, $value, my $obj) = $manager->filter($last_token, $value, $obj_prefix, $obj_path);
+    ($key, $value, my $obj) = $manager->filter($key_token, $value, $obj_prefix, $obj_path, @additional_tokens);
     _add_uniq($with_objects, $obj) if $obj;
   } else {
     _add_uniq($with_objects, $obj_path) if $obj_path;
diff --git a/SL/DB/CustomVariableConfig.pm b/SL/DB/CustomVariableConfig.pm
index 5404ebff8..973e3a836 100644
--- a/SL/DB/CustomVariableConfig.pm
+++ b/SL/DB/CustomVariableConfig.pm
@@ -103,4 +103,24 @@ sub type_dependent_default_value {
   return (any { $_ eq $self->default_value } @{ $self->processed_options }) ? $self->default_value : $self->processed_options->[0];
 }
 
+sub value_col {
+  my ($self) = @_;
+
+  my $type = $self->type;
+
+  return {
+    bool      => 'bool_value',
+    timestamp => 'timestamp_value',
+    number    => 'number_value',
+    integer   => 'number_value',
+    customer  => 'number_value',
+    vendor    => 'number_value',
+    part      => 'number_value',
+    text      => 'text_value',
+    textfield => 'text_value',
+    date      => 'text_value',
+    select    => 'text_value'
+  }->{$type};
+}
+
 1;
diff --git a/SL/DB/Helper/CustomVariables.pm b/SL/DB/Helper/CustomVariables.pm
index 713685519..4b3f39965 100644
--- a/SL/DB/Helper/CustomVariables.pm
+++ b/SL/DB/Helper/CustomVariables.pm
@@ -28,6 +28,7 @@ sub import {
   make_cvar_by_name($caller_package, %params);
   make_cvar_as_hashref($caller_package, %params);
   make_cvar_value_parser($caller_package, %params);
+  make_cvar_custom_filter($caller_package, %params);
 }
 
 sub save_meta_info {
@@ -223,6 +224,99 @@ sub _get_primary_key_column {
   return $column_name;
 }
 
+sub make_cvar_custom_filter {
+  my ($caller_package, %params) = @_;
+
+  my $manager    = $caller_package->meta->convention_manager->auto_manager_class_name;
+
+  return unless $manager->can('filter');
+
+  $manager->add_filter_specs(
+    cvar => sub {
+      my ($key, $value, $prefix, $config_id) = @_;
+      my $config = SL::DB::Manager::CustomVariableConfig->find_by(id => $config_id);
+
+      if (!$config) {
+        die "invalid config_id in $caller_package\::cvar custom filter: $config_id";
+      }
+
+      if ($config->module != $params{module}) {
+        die "invalid config_id in $caller_package\::cvar custom filter: expected module $params{module} - got @{[ $config->module ]}";
+      }
+
+      my (%query, %bind_vals);
+      ($query{customized}, $bind_vals{customized}) = Rose::DB::Object::QueryBuilder::build_select(
+        dbh                  => $config->dbh,
+        select               => 'trans_id',
+        tables               => [ 'custom_variables' ],
+        columns              => { custom_variables => [ qw(trans_id config_id text_value number_value bool_value timestamp_value sub_module) ] },
+        query                => [
+          config_id          => $config_id,
+          sub_module         => $params{sub_module},
+          $config->value_col => $value,
+        ],
+        query_is_sql         => 1,
+      );
+
+      my $conversion  = $config->type =~ m{^(?:date|timestamp)$}       ? $config->type
+                      : $config->type =~ m{^(?:customer|vendor|part)$} ? 'integer'
+                      : $config->type eq 'bool'                        ? 'boolean'
+                      : $config->type eq 'number'                      ? 'numeric'
+                      :                                                  '';
+
+      ($query{config}, $bind_vals{config}) = Rose::DB::Object::QueryBuilder::build_select(
+        dbh             => $config->dbh,
+        select          => 'id',
+        tables          => [ 'custom_variable_configs' ],
+        columns         => { custom_variable_configs => [ qw(id default_value) ] },
+        query           => [
+          id            => $config->id,
+          default_value => $value,
+        ],
+        query_is_sql    => 1,
+      );
+
+      $query{config} =~ s{\bdefault_value\b}{default_value::${conversion}} if $conversion;
+
+      ($query{not_customized}, $bind_vals{not_customized}) = Rose::DB::Object::QueryBuilder::build_select(
+        dbh          => $config->dbh,
+        select       => 'trans_id',
+        tables       => [ 'custom_variables' ],
+        columns      => { custom_variables => [ qw(trans_id config_id sub_module) ] },
+        query        => [
+          config_id  => $config_id,
+          sub_module => $params{sub_module},
+        ],
+        query_is_sql => 1,
+      );
+
+      foreach my $key (keys %query) {
+        # remove rose aliases. query builder sadly is not reentrant, and will reuse the same aliases. :(
+        $query{$key} =~ s{\bt\d+(?:\.)?\b}{}g;
+
+        # manually inline the values. again, rose doen't know how to handly bind params in subqueries :(
+        $query{$key} =~ s{\?}{ $config->dbh->quote($_) }xe for @{ $bind_vals{$key} };
+
+        $query{$key} =~ s{\n}{ }g;
+      }
+
+      my $qry_config = "EXISTS (" . $query{config} . ")";
+
+      my @result = (
+        'or' => [
+          $prefix . 'id'   => [ \$query{customized} ],
+          and              => [
+            "!${prefix}id" => [ \$query{not_customized}  ],
+            \$qry_config,
+          ]
+        ],
+      );
+
+      return @result;
+    }
+  );
+}
+
 1;
 
 __END__
@@ -371,6 +465,18 @@ some way then you have to call this function manually. For example:
 
 =back
 
+=head1 INSTALLED MANAGER METHODS
+
+=over 4
+
+=item Custom filter for GetModels
+
+If the Manager for the calling C<SL::DB::Object> has included the helper L<SL::DB::Helper::Filtered>, a custom filter for cvars will be added to the specs, with the following syntax:
+
+  filter.cvar.$config_id
+
+=back
+
 =head1 AUTHOR
 
 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>,
diff --git a/SL/DB/Helper/Filtered.pm b/SL/DB/Helper/Filtered.pm
index 35329494b..5d18d8e89 100644
--- a/SL/DB/Helper/Filtered.pm
+++ b/SL/DB/Helper/Filtered.pm
@@ -10,13 +10,13 @@ our @EXPORT = qw (filter add_filter_specs);
 my %filter_spec;
 
 sub filter {
-  my ($class, $key, $value, $prefix, $path) = @_;
+  my ($class, $key, $value, $prefix, $path, @additional_tokens) = @_;
 
   my $filters = _get_filters($class);
 
   return ($prefix . $key, $value, $path) unless $filters->{$key};
 
-  return $filters->{$key}->($key, $value, $prefix);
+  return $filters->{$key}->($key, $value, $prefix, @additional_tokens);
 }
 
 sub _get_filters {
