Filtered Plugin für GetModels
[kivitendo-erp.git] / SL / Controller / Helper / Filtered.pm
1 package SL::Controller::Helper::Filtered;
2
3 use strict;
4
5 use Exporter qw(import);
6 use SL::Controller::Helper::ParseFilter ();
7 use List::MoreUtils qw(uniq);
8 our @EXPORT = qw(make_filtered get_filter_spec get_current_filter_params disable_filtering _save_current_filter_params _callback_handler_for_filtered _get_models_handler_for_filtered);
9
10 use constant PRIV => '__filteredhelper_priv';
11
12 my %controller_filter_spec;
13
14 sub make_filtered {
15   my ($class, %specs)       = @_;
16
17   $specs{MODEL}           //=  $class->controller_name;
18   $specs{MODEL}             =~ s{ ^ SL::DB:: (?: .* :: )? }{}x;
19   $specs{FORM_PARAMS}     //= 'filter';
20   $specs{LAUNDER_TO}        = '__INPLACE__' unless exists $specs{LAUNDER_TO};
21   $specs{ONLY}            //= [];
22   $specs{ONLY}              = [ $specs{ONLY} ] if !ref $specs{ONLY};
23   $specs{ONLY_MAP}          = @{ $specs{ONLY} } ? { map { ($_ => 1) } @{ $specs{ONLY} } } : { '__ALL__' => 1 };
24
25   $controller_filter_spec{$class} = \%specs;
26
27   my %hook_params           = @{ $specs{ONLY} } ? ( only => $specs{ONLY} ) : ();
28   $class->run_before('_save_current_filter_params', %hook_params);
29
30   SL::Controller::Helper::GetModels::register_get_models_handlers(
31     $class,
32     callback   => '_callback_handler_for_filtered',
33     get_models => '_get_models_handler_for_filtered',
34     ONLY       => $specs{ONLY},
35   );
36
37   # $::lxdebug->dump(0, "CONSPEC", \%specs);
38 }
39
40 sub get_filter_spec {
41   my ($class_or_self) = @_;
42
43   return $controller_filter_spec{ref($class_or_self) || $class_or_self};
44 }
45
46 sub get_current_filter_params {
47   my ($self)   = @_;
48
49   return %{ _priv($self)->{filter_params} } if _priv($self)->{filter_params};
50
51   require Carp;
52   Carp::confess('It seems a GetModels plugin tries to access filter params before they got calculated. Make sure your make_filtered call comes first.');
53 }
54
55 sub _make_current_filter_params {
56   my ($self, %params)   = @_;
57
58   my $spec              = $self->get_filter_spec;
59   my $filter            = $params{filter} // _priv($self)->{filter} // {},
60   my %filter_args       = _get_filter_args($self, $spec);
61   my %parse_filter_args = (
62     class        => "SL::DB::Manager::$spec->{MODEL}",
63     with_objects => $params{with_objects},
64   );
65   my $laundered;
66   if ($spec->{LAUNDER_TO} eq '__INPLACE__') {
67
68   } elsif ($spec->{LAUNDER_TO}) {
69     $laundered = {};
70     $parse_filter_args{launder_to} = $laundered;
71   } else {
72     $parse_filter_args{no_launder} = 1;
73   }
74
75   my %calculated_params = SL::Controller::Helper::ParseFilter::parse_filter($filter, %parse_filter_args);
76
77   $calculated_params{query} = [
78     @{ $calculated_params{query} || [] },
79     @{ $filter_args{query} || [] },
80     @{ $params{query} || [] },
81   ];
82
83   $calculated_params{with_objects} = [
84     uniq
85     @{ $calculated_params{with_objects} || [] },
86     @{ $filter_args{with_objects} || [] },
87     @{ $params{with_objects} || [] },
88   ];
89
90   if ($laundered) {
91     if ($self->can($spec->{LAUNDER_TO})) {
92       $self->${\ $spec->{LAUNDER_TO} }($laundered);
93     } else {
94       $self->{$spec->{LAUNDER_TO}} = $laundered;
95     }
96   }
97
98   # $::lxdebug->dump(0, "get_current_filter_params: ", \%calculated_params);
99
100   _priv($self)->{filter_params} = \%calculated_params;
101
102   return %calculated_params;
103 }
104
105 sub disable_filtering {
106   my ($self)               = @_;
107   _priv($self)->{disabled} = 1;
108 }
109
110 #
111 # private functions
112 #
113
114 sub _get_filter_args {
115   my ($self, $spec) = @_;
116
117   $spec ||= $self->get_filter_spec;
118
119   my %filter_args       = ref($spec->{FILTER_ARGS}) eq 'CODE' ? %{ $spec->{FILTER_ARGS}->($self) }
120                         :     $spec->{FILTER_ARGS}            ? do { my $sub = $spec->{FILTER_ARGS}; %{ $self->$sub() } }
121                         :                                       ();
122 }
123
124 sub _save_current_filter_params {
125   my ($self)        = @_;
126
127   return if !_is_enabled($self);
128
129   my $filter_spec = $self->get_filter_spec;
130   $self->{PRIV()}{filter} = $::form->{ $filter_spec->{FORM_PARAMS} };
131
132   # $::lxdebug->message(0, "saving current filter params to " . $self->{PRIV()}->{page} . ' / ' . $self->{PRIV()}->{per_page});
133 }
134
135 sub _callback_handler_for_filtered {
136   my ($self, %params) = @_;
137   my $priv            = _priv($self);
138
139   if (_is_enabled($self) && $priv->{filter}) {
140     my $filter_spec                             = $self->get_filter_spec;
141     my ($flattened) = SL::Controller::Helper::ParseFilter::flatten($priv->{filter}, undef, $filter_spec->{FORM_PARAMS});
142     %params = (%params, @$flattened);
143   }
144
145   # $::lxdebug->dump(0, "CB handler for filtered; params after flatten:", \%params);
146
147   return %params;
148 }
149
150 sub _get_models_handler_for_filtered {
151   my ($self, %params)    = @_;
152   my $spec               = $self->get_filter_spec;
153
154   # $::lxdebug->dump(0,  "params in get_models_for_filtered", \%params);
155
156   my %filter_params;
157   %filter_params = _make_current_filter_params($self, %params)  if _is_enabled($self);
158
159   # $::lxdebug->dump(0, "GM handler for filtered; params nach modif (is_enabled? " . _is_enabled($self) . ")", \%params);
160
161   return (%params, %filter_params);
162 }
163
164 sub _priv {
165   my ($self)        = @_;
166   $self->{PRIV()} ||= {};
167   return $self->{PRIV()};
168 }
169
170 sub _is_enabled {
171   my ($self) = @_;
172   return !_priv($self)->{disabled} && ($self->get_filter_spec->{ONLY_MAP}->{$self->action_name} || $self->get_filter_spec->{ONLY_MAP}->{'__ALL__'});
173 }
174
175
176 1;
177
178 __END__
179
180 =pod
181
182 =encoding utf8
183
184 =head1 NAME
185
186 SL::Controller::Helper::Filtered - A helper for semi-automatic handling
187 of filtered lists of database models in a controller
188
189 =head1 SYNOPSIS
190
191 In a controller:
192
193   use SL::Controller::Helper::GetModels;
194   use SL::Controller::Helper::Filtered;
195
196   __PACKAGE__->make_filter(
197     MODEL       => 'Part',
198     ONLY        => [ qw(list) ],
199     FORM_PARAMS => [ qw(filter) ],
200   );
201
202   sub action_list {
203     my ($self) = @_;
204
205     my $filtered_models = $self->get_models(%addition_filters);
206     $self->render('controller/list', ENTRIES => $filtered_models);
207   }
208
209
210 =head1 OVERVIEW
211
212 This helper module enables use of the L<SL::Controller::Helper::ParseFilter>
213 methods in conjunction with the L<SL::Controller::Helper::GetModels> style of
214 plugins. Additional filters can be defined in the database models and filtering
215 can be reduced to a minimum of work.
216
217 This plugin can be combined with L<SL::Controller::Sorted> and
218 L<SL::Controller::Paginated> for filtered, sorted and paginated lists.
219
220 The controller has to provive information where to look for filter information
221 at compile time. This call is L<make_filtered>.
222
223 The underlying functionality that enables the use of more than just
224 the paginate helper is provided by the controller helper
225 C<GetModels>. See the documentation for L<SL::Controller::Sorted> for
226 more information on it.
227
228 =head1 PACKAGE FUNCTIONS
229
230 =over 4
231
232 =item C<make_filtered %filter_spec>
233
234 This function must be called by a controller at compile time. It is
235 uesd to set the various parameters required for this helper to do its
236 magic.
237
238 Careful: If you want to use this in conjunction with
239 L<SL:Controller::Helper::Paginated>, you need to call C<make_filtered> first,
240 or the paginating will not get all the relevant information to estimate the
241 number of pages correctly. To ensure this does not happen, this module will
242 croak when it detects such a scenario.
243
244 The hash C<%filter_spec> can include the following parameters:
245
246 =over 4
247
248 =item * C<MODEL>
249
250 Optional. A string: the name of the Rose database model that is used
251 as a default in certain cases. If this parameter is missing then it is
252 derived from the controller's package (e.g. for the controller
253 C<SL::Controller::BackgroundJobHistory> the C<MODEL> would default to
254 C<BackgroundJobHistory>).
255
256 =item * C<FORM_PARAMS>
257
258 Optional. Indicates a key in E<$::form> to be used as filter.
259
260 Defaults to the values C<filter> if missing.
261
262 =item * C<LAUNDER_TO>
263
264 Option. Indicates a target for laundered filter arguments in the controller.
265 Can be set to C<undef> to disable laundering, and can be set to method named or
266 hash keys of the controller. In the latter case the laundered structure will be
267 put there.
268
269 Defaults to inplace laundering which is not normally settable.
270
271 =item * C<ONLY>
272
273 Optional. An array reference containing a list of action names for
274 which the paginate parameters should be saved. If missing or empty then
275 all actions invoked on the controller are monitored.
276
277 =back
278
279 =back
280
281 =head1 INSTANCE FUNCTIONS
282
283 These functions are called on a controller instance.
284
285 =over 4
286
287 =item C<get_current_filter_params>
288
289 Returns a hash to be used in manager C<get_all> calls or to be passed on to
290 GetModels. Will only work if the get_models chain has been called at least
291 once, because only then the full parameters can get parsed and stored. Will
292 croak otherwise.
293
294 =item C<disable_filtering>
295
296 Disable filtering for the duration of the current action. Can be used
297 when using the attribute C<ONLY> to L<make_filtered> does not
298 cover all cases.
299
300 =back
301
302 =head1 BUGS
303
304 Nothing here yet.
305
306 =head1 AUTHOR
307
308 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>
309
310 =cut