1 package SL::Controller::Helper::Filtered;
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);
10 use constant PRIV => '__filteredhelper_priv';
12 my %controller_filter_spec;
15 my ($class, %specs) = @_;
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};
22 $specs{ONLY} = [ $specs{ONLY} ] if !ref $specs{ONLY};
23 $specs{ONLY_MAP} = @{ $specs{ONLY} } ? { map { ($_ => 1) } @{ $specs{ONLY} } } : { '__ALL__' => 1 };
25 $controller_filter_spec{$class} = \%specs;
27 my %hook_params = @{ $specs{ONLY} } ? ( only => $specs{ONLY} ) : ();
28 $class->run_before('_save_current_filter_params', %hook_params);
30 SL::Controller::Helper::GetModels::register_get_models_handlers(
32 callback => '_callback_handler_for_filtered',
33 get_models => '_get_models_handler_for_filtered',
37 # $::lxdebug->dump(0, "CONSPEC", \%specs);
41 my ($class_or_self) = @_;
43 return $controller_filter_spec{ref($class_or_self) || $class_or_self};
46 sub get_current_filter_params {
49 return %{ _priv($self)->{filter_params} } if _priv($self)->{filter_params};
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.');
55 sub _make_current_filter_params {
56 my ($self, %params) = @_;
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},
66 if ($spec->{LAUNDER_TO} eq '__INPLACE__') {
68 } elsif ($spec->{LAUNDER_TO}) {
70 $parse_filter_args{launder_to} = $laundered;
72 $parse_filter_args{no_launder} = 1;
75 my %calculated_params = SL::Controller::Helper::ParseFilter::parse_filter($filter, %parse_filter_args);
77 $calculated_params{query} = [
78 @{ $calculated_params{query} || [] },
79 @{ $filter_args{ query} || [] },
80 @{ $params{ query} || [] },
83 $calculated_params{with_objects} = [
85 @{ $calculated_params{with_objects} || [] },
86 @{ $filter_args{ with_objects} || [] },
87 @{ $params{ with_objects} || [] },
91 if ($self->can($spec->{LAUNDER_TO})) {
92 $self->${\ $spec->{LAUNDER_TO} }($laundered);
94 $self->{$spec->{LAUNDER_TO}} = $laundered;
98 # $::lxdebug->dump(0, "get_current_filter_params: ", \%calculated_params);
100 _priv($self)->{filter_params} = \%calculated_params;
102 return %calculated_params;
105 sub disable_filtering {
107 _priv($self)->{disabled} = 1;
114 sub _get_filter_args {
115 my ($self, $spec) = @_;
117 $spec ||= $self->get_filter_spec;
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() } }
124 sub _save_current_filter_params {
127 return if !_is_enabled($self);
129 my $filter_spec = $self->get_filter_spec;
130 $self->{PRIV()}{filter} = $::form->{ $filter_spec->{FORM_PARAMS} };
132 # $::lxdebug->message(0, "saving current filter params to " . $self->{PRIV()}->{page} . ' / ' . $self->{PRIV()}->{per_page});
135 sub _callback_handler_for_filtered {
136 my ($self, %params) = @_;
137 my $priv = _priv($self);
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}, $filter_spec->{FORM_PARAMS});
142 %params = (%params, @$flattened);
145 # $::lxdebug->dump(0, "CB handler for filtered; params after flatten:", \%params);
150 sub _get_models_handler_for_filtered {
151 my ($self, %params) = @_;
152 my $spec = $self->get_filter_spec;
154 # $::lxdebug->dump(0, "params in get_models_for_filtered", \%params);
157 %filter_params = _make_current_filter_params($self, %params) if _is_enabled($self);
159 # $::lxdebug->dump(0, "GM handler for filtered; params nach modif (is_enabled? " . _is_enabled($self) . ")", \%params);
161 return (%params, %filter_params);
166 $self->{PRIV()} ||= {};
167 return $self->{PRIV()};
172 return !_priv($self)->{disabled} && ($self->get_filter_spec->{ONLY_MAP}->{$self->action_name} || $self->get_filter_spec->{ONLY_MAP}->{'__ALL__'});
185 SL::Controller::Helper::Filtered - A helper for semi-automatic handling
186 of filtered lists of database models in a controller
192 use SL::Controller::Helper::GetModels;
193 use SL::Controller::Helper::Filtered;
195 __PACKAGE__->make_filter(
197 ONLY => [ qw(list) ],
198 FORM_PARAMS => [ qw(filter) ],
204 my $filtered_models = $self->get_models(%addition_filters);
205 $self->render('controller/list', ENTRIES => $filtered_models);
211 This helper module enables use of the L<SL::Controller::Helper::ParseFilter>
212 methods in conjunction with the L<SL::Controller::Helper::GetModels> style of
213 plugins. Additional filters can be defined in the database models and filtering
214 can be reduced to a minimum of work.
216 This plugin can be combined with L<SL::Controller::Sorted> and
217 L<SL::Controller::Paginated> for filtered, sorted and paginated lists.
219 The controller has to provive information where to look for filter information
220 at compile time. This call is L<make_filtered>.
222 The underlying functionality that enables the use of more than just
223 the paginate helper is provided by the controller helper
224 C<GetModels>. See the documentation for L<SL::Controller::Sorted> for
225 more information on it.
227 =head1 PACKAGE FUNCTIONS
231 =item C<make_filtered %filter_spec>
233 This function must be called by a controller at compile time. It is
234 uesd to set the various parameters required for this helper to do its
237 Careful: If you want to use this in conjunction with
238 L<SL:Controller::Helper::Paginated>, you need to call C<make_filtered> first,
239 or the paginating will not get all the relevant information to estimate the
240 number of pages correctly. To ensure this does not happen, this module will
241 croak when it detects such a scenario.
243 The hash C<%filter_spec> can include the following parameters:
249 Optional. A string: the name of the Rose database model that is used
250 as a default in certain cases. If this parameter is missing then it is
251 derived from the controller's package (e.g. for the controller
252 C<SL::Controller::BackgroundJobHistory> the C<MODEL> would default to
253 C<BackgroundJobHistory>).
255 =item * C<FORM_PARAMS>
257 Optional. Indicates a key in C<$::form> to be used as filter.
259 Defaults to the values C<filter> if missing.
261 =item * C<LAUNDER_TO>
263 Option. Indicates a target for laundered filter arguments in the controller.
264 Can be set to C<undef> to disable laundering, and can be set to method named or
265 hash keys of the controller. In the latter case the laundered structure will be
268 Defaults to inplace laundering which is not normally settable.
272 Optional. An array reference containing a list of action names for
273 which the paginate parameters should be saved. If missing or empty then
274 all actions invoked on the controller are monitored.
280 =head1 INSTANCE FUNCTIONS
282 These functions are called on a controller instance.
286 =item C<get_current_filter_params>
288 Returns a hash to be used in manager C<get_all> calls or to be passed on to
289 GetModels. Will only work if the get_models chain has been called at least
290 once, because only then the full parameters can get parsed and stored. Will
293 =item C<disable_filtering>
295 Disable filtering for the duration of the current action. Can be used
296 when using the attribute C<ONLY> to L<make_filtered> does not
307 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>