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}, undef, $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__'});
186 SL::Controller::Helper::Filtered - A helper for semi-automatic handling
187 of filtered lists of database models in a controller
193 use SL::Controller::Helper::GetModels;
194 use SL::Controller::Helper::Filtered;
196 __PACKAGE__->make_filter(
198 ONLY => [ qw(list) ],
199 FORM_PARAMS => [ qw(filter) ],
205 my $filtered_models = $self->get_models(%addition_filters);
206 $self->render('controller/list', ENTRIES => $filtered_models);
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.
217 This plugin can be combined with L<SL::Controller::Sorted> and
218 L<SL::Controller::Paginated> for filtered, sorted and paginated lists.
220 The controller has to provive information where to look for filter information
221 at compile time. This call is L<make_filtered>.
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.
228 =head1 PACKAGE FUNCTIONS
232 =item C<make_filtered %filter_spec>
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
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.
244 The hash C<%filter_spec> can include the following parameters:
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>).
256 =item * C<FORM_PARAMS>
258 Optional. Indicates a key in E<$::form> to be used as filter.
260 Defaults to the values C<filter> if missing.
262 =item * C<LAUNDER_TO>
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
269 Defaults to inplace laundering which is not normally settable.
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.
281 =head1 INSTANCE FUNCTIONS
283 These functions are called on a controller instance.
287 =item C<get_current_filter_params>
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
294 =item C<disable_filtering>
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
308 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>