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