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