GetModels-Filtered: Unterstützung für benutzerdefinierte Variablen
[kivitendo-erp.git] / SL / Controller / Helper / GetModels.pm
1 package SL::Controller::Helper::GetModels;
2
3 use strict;
4
5 use parent 'Rose::Object';
6 use SL::Controller::Helper::GetModels::Filtered;
7 use SL::Controller::Helper::GetModels::Sorted;
8 use SL::Controller::Helper::GetModels::Paginated;
9
10 use Scalar::Util qw(weaken);
11
12 use Rose::Object::MakeMethods::Generic (
13   scalar => [ qw(controller model query with_objects filtered sorted paginated finalized final_params) ],
14   'scalar --get_set_init' => [ qw(handlers source additional_url_params) ],
15   array => [ qw(plugins) ],
16 );
17
18 use constant PRIV => '__getmodelshelperpriv';
19
20
21 # official interface
22
23 sub get {
24   my ($self) = @_;
25   my %params = $self->finalize;
26
27   return $self->manager->get_all(%params);
28 }
29
30 sub count {
31   my ($self) = @_;
32   my %params = $self->finalize;
33
34   return $self->manager->get_all_count(%params);
35 }
36
37 sub disable_plugin {
38   my ($self, $plugin) = @_;
39   die 'cannot change internal state after finalize was called' if $self->finalized;
40   die 'unsupported plugin' unless $self->can($plugin) && $self->$plugin && $self->$plugin->isa('SL::Controller::Helper::GetModels::Base');
41
42   $self->$plugin->disabled(1);
43 }
44
45 sub enable_plugin {
46   my ($self, $plugin) = @_;
47   die 'cannot change internal state after finalize was called' if $self->finalized;
48   die 'unsupported plugin' unless $self->can($plugin) && $self->$plugin && $self->$plugin->isa('SL::Controller::Helper::GetModels::Base');
49   $self->$plugin->disabled(0);
50 }
51
52 sub is_enabled_plugin {
53   my ($self, $plugin) = @_;
54   die 'unsupported plugin' unless $self->can($plugin) && $self->$plugin && $self->$plugin->isa('SL::Controller::Helper::GetModels::Base');
55   $self->$plugin->is_enabled;
56 }
57
58 # TODO: get better delegation
59 sub set_report_generator_sort_options {
60   my ($self, %params) = @_;
61   $self->finalize;
62
63   $self->sorted->set_report_generator_sort_options(%params);
64 }
65
66 sub get_paginate_args {
67   my ($self) = @_;
68   my %params = $self->finalize;
69
70   $self->paginated->get_current_paginate_params(%params);
71 }
72
73 sub get_sort_spec {
74   my ($self) = @_;
75
76   $self->sorted->specs;
77 }
78
79 sub get_current_sort_params {
80   my ($self) = @_;
81
82   $self->sorted->read_params;
83 }
84
85 sub init {
86   my ($self, %params) = @_;
87
88   my $model = delete $params{model};
89   if (!$model && $params{controller} && ref $params{controller}) {
90     $model = ref $params{controller};
91     $model =~ s/.*:://;
92     die 'Need a valid model' unless $model;
93   }
94   $self->model($model);
95
96   my @plugins;
97   for my $plugin (qw(filtered sorted paginated)) {
98     next unless my $spec = delete $params{$plugin} // {};
99     my $plugin_class = "SL::Controller::Helper::GetModels::" . ucfirst $plugin;
100     push @plugins, $self->$plugin($plugin_class->new(%$spec, get_models => $self));
101   }
102   $self->plugins(@plugins);
103
104   $self->SUPER::init(%params);
105
106   $_->read_params for $self->plugins;
107
108   weaken $self->controller if $self->controller;
109 }
110
111 sub finalize {
112   my ($self, %params) = @_;
113
114   return %{ $self->final_params } if $self->finalized;
115
116   $self->register_handlers(callback => sub { shift; (@_, %{ $self->additional_url_params }) }) if %{ $self->additional_url_params };
117
118   push @{ $params{query}        ||= [] }, @{ $self->query || [] };
119   push @{ $params{with_objects} ||= [] }, @{ $self->with_objects || [] };
120
121   %params = $_->finalize(%params) for $self->plugins;
122
123   $self->finalized(1);
124   $self->final_params(\%params);
125
126   return %params;
127 }
128
129 sub register_handlers {
130   my ($self, %additional_handlers) = @_;
131
132   my $handlers    = $self->handlers;
133   map { push @{ $handlers->{$_} }, $additional_handlers{$_} if $additional_handlers{$_} } keys %$handlers;
134 }
135
136 sub add_additional_url_params {
137   my ($self, %params) = @_;
138
139   $self->additional_url_params({ %{ $self->additional_url_params }, %params });
140
141   return $self;
142 }
143
144 sub get_models_url_params {
145   my ($self, $sub_name_or_code) = @_;
146
147   my $code     = (ref($sub_name_or_code) || '') eq 'CODE' ? $sub_name_or_code : sub { shift->controller->$sub_name_or_code(@_) };
148   my $callback = sub {
149     my ($self, %params)   = @_;
150     my @additional_params = $code->($self);
151     return (
152       %params,
153       (scalar(@additional_params) == 1) && (ref($additional_params[0]) eq 'HASH') ? %{ $additional_params[0] } : @additional_params,
154     );
155   };
156
157   $self->register_handlers('callback' => $callback);
158 }
159
160 sub get_callback {
161   my ($self, %override_params) = @_;
162
163   my %default_params = $self->_run_handlers('callback', action => $self->controller->action_name);
164
165   return $self->controller->url_for(%default_params, %override_params);
166 }
167
168 sub manager {
169   die "No 'model' to work on" unless $_[0]->model;
170   "SL::DB::Manager::" . $_[0]->model;
171 }
172
173 #
174 # private/internal functions
175 #
176
177 sub _run_handlers {
178   my ($self, $handler_type, %params) = @_;
179
180   foreach my $sub (@{ $self->handlers->{$handler_type} }) {
181     if (ref $sub eq 'CODE') {
182       %params = $sub->($self, %params);
183     } elsif ($self->can($sub)) {
184       %params = $self->$sub(%params);
185     } else {
186       die "SL::Controller::Helper::GetModels::get_callback: Cannot call $sub on " . ref($self) . ")";
187     }
188   }
189
190   return %params;
191 }
192
193 sub init_handlers {
194   {
195     callback => [],
196   }
197 }
198
199 sub init_source {
200   $::form
201 }
202
203 sub init_additional_url_params { +{} }
204
205 1;
206 __END__
207
208 =pod
209
210 =encoding utf8
211
212 =head1 NAME
213
214 SL::Controller::Helper::GetModels - Base class for the GetModels system.
215
216 =head1 SYNOPSIS
217
218 In controller:
219
220   use SL::Controller::Helper::GetModels;
221
222   my $get_models = SL::Controller::Helper::GetModels->new(
223     controller   => $self,
224   );
225
226   my $models = $self->get_models->get;
227
228 =head1 OVERVIEW
229
230 Building a CRUD controller would be easy, were it not for those stupid
231 list actions. People unreasonably expect stuff like filtering, sorting,
232 paginating, exporting etc simply to work. Well, lets try to make it simply work
233 a little.
234
235 This class is a proxy between a controller and specialized
236 helper modules that handle these things (sorting, paginating etc) and gives you
237 the means to retrieve the information when needed to display sort headers or
238 paginating footers.
239
240 Information about the requested data query can be stored in the object up to
241 a certain point, from which on the object becomes locked and can only be
242 accessed for information. (See C<STATES>).
243
244 =head1 INTERFACE METHODS
245
246 =over 4
247
248 =item new PARAMS
249
250 Create a new GetModels object. Params must have at least an entry
251 C<controller>, other than that, see C<CONFIGURATION> for options.
252
253 =item get
254
255 Retrieve all models for the current configuration. Will finalize the object.
256
257 =item get_models_url_params SUB
258
259 Register a sub to be called whenever an URL has to be generated (e.g. for sort
260 and pagination links). This is a way for the controller to add additional
261 parameters to the URL (e.g. for filter parameters).
262
263 The parameter can be either a code reference or the name of
264 one of the controller's functions.
265
266 The value returned by C<SUB> must be either a single hash
267 reference or a hash of key/value pairs to add to the URL.
268
269 =item add_additional_url_params C<%params>
270
271 Sets additional parameters that will be added to each URL generated by
272 this model (e.g. for pagination/sorting). This is just sugar for a
273 proper call to L<get_models_url_params> with an anonymous sub adding
274 those parameters.
275
276 =item get_callback
277
278 Returns a URL suitable for use as a callback parameter. It maps to the
279 current controller and action. All registered handlers of type
280 'callback' (e.g. the ones by C<Sorted> and C<Paginated>) can inject
281 the parameters they need so that the same list view as is currently
282 visible can be re-rendered.
283
284 Optional C<%params> passed to this function may override any parameter
285 set by the registered handlers.
286
287 =item enable_plugin PLUGIN
288
289 =item disable_plugin PLUGIN
290
291 =item is_enabled_plugin PLUGIN
292
293 Enable or disable the specified plugin. Useful to disable paginating for
294 exports for example. C<is_enabled_plugin> can be used to check the current
295 state fo a plugin.
296
297 Must not be finalized to use this.
298
299 =item finalize
300
301 Forces finalized state. Can be used on finalized objects without error.
302
303 Note that most higher functions will call this themselves to force a finalized
304 state. If you do use it it must come before any other finalizing methods, and
305 will most likely function as a reminder for maintainers where your code
306 switches from configuration to finalized state.
307
308 =item source HASHREF
309
310 The source for user supplied information. Defaults to $::form. Changing it
311 after C<Base> phase has no effect.
312
313 =item controller CONTROLLER
314
315 A weakened link to the controller that created the GetModels object. Needed for
316 certain plugin methods.
317
318 =back
319
320 =head1 DELEGATION METHODS
321
322 All of these finalize.
323
324 Methods delegating to C<Sorted>:
325
326 =over 4
327
328 =item *
329
330 set_report_generator_sort_options
331
332 =item *
333
334 get_sort_spec
335
336 =item *
337
338 get_current_sort_params
339
340 =back
341
342 Methods delegating to C<Paginated>:
343
344 =over 4
345
346 =item *
347
348 get_paginate_args
349
350 =back
351
352 =head1 STATES
353
354 A GetModels object is in one of 3 states at any given time. Their purpose is to
355 make a class of bugs impossible that orginated from changing the configuration
356 of a GetModels object halfway during the request. This was a huge problem in
357 the old implementation.
358
359 =over 4
360
361 =item Base
362
363 This is the state after creating a new object.
364
365 =item Init
366
367 In this state all the information needed from the source ($::form) has been read
368 and subsequent changes to the source have no effect. In the current
369 implementation this will happen during creation, so that the return value of
370 C<new> is already in state C<Init>.
371
372 =item Finalized
373
374 In this state no new configuration will be accepted so that information gotten
375 through the various methods is consistent. Every information retrieval method
376 will trigger finalize.
377
378 =back
379
380
381 =head1 CONFIGURATION
382
383 Most of the configuration will be handed to GetModels on creation via C<new>.
384 This is a list of accepted params.
385
386 =over 4
387
388 =item controller SELF
389
390 The creating controller. Currently this is mandatory.
391
392 =item model MODEL
393
394 The name of the model for this GetModels instance. If none is given, the model
395 is inferred from the name of the controller class.
396
397 =item sorted PARAMS
398
399 =item paginated PARAMS
400
401 =item filtered PARAMS
402
403 Configuration for plugins. If the option for any plugin is omitted, it defaults
404 to enabled and is configured by default. Giving a falsish value as first argument
405 will disable the plugin.
406
407 If the value is a hashref, it will be passed to the plugin's C<init> method.
408
409 =item query
410
411 =item with_objects
412
413 Additional static parts for Rose to include into the final query.
414
415 =item source
416
417 Source for plugins to pull their data from. Defaults to $::form.
418
419 =back
420
421 =head1 BUGS AND CAVEATS
422
423 =over 4
424
425 =item *
426
427 Delegation is not as clean as it should be. Most of the methods rely on action
428 at a distance and should be moved out.
429
430 =back
431
432 =head1 AUTHORS
433
434 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
435
436 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>
437
438 =cut