cb73ab35435f49ebdac2dba489e8429e6e129ab2
[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 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->controller->action_name);
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_additional_url_params { +{} }
212
213 1;
214 __END__
215
216 =pod
217
218 =encoding utf8
219
220 =head1 NAME
221
222 SL::Controller::Helper::GetModels - Base class for the GetModels system.
223
224 =head1 SYNOPSIS
225
226 In controller:
227
228   use SL::Controller::Helper::GetModels;
229
230   my $get_models = SL::Controller::Helper::GetModels->new(
231     controller   => $self,
232   );
233
234   my $models = $self->get_models->get;
235
236 =head1 OVERVIEW
237
238 Building a CRUD controller would be easy, were it not for those stupid
239 list actions. People unreasonably expect stuff like filtering, sorting,
240 paginating, exporting etc simply to work. Well, lets try to make it simply work
241 a little.
242
243 This class is a proxy between a controller and specialized
244 helper modules that handle these things (sorting, paginating etc) and gives you
245 the means to retrieve the information when needed to display sort headers or
246 paginating footers.
247
248 Information about the requested data query can be stored in the object up to
249 a certain point, from which on the object becomes locked and can only be
250 accessed for information. (See C<STATES>).
251
252 =head1 INTERFACE METHODS
253
254 =over 4
255
256 =item new PARAMS
257
258 Create a new GetModels object. Params must have at least an entry
259 C<controller>, other than that, see C<CONFIGURATION> for options.
260
261 =item get
262
263 Retrieve all models for the current configuration. Will finalize the object.
264
265 =item get_models_url_params SUB
266
267 Register a sub to be called whenever an URL has to be generated (e.g. for sort
268 and pagination links). This is a way for the controller to add additional
269 parameters to the URL (e.g. for filter parameters).
270
271 The parameter can be either a code reference or the name of
272 one of the controller's functions.
273
274 The value returned by C<SUB> must be either a single hash
275 reference or a hash of key/value pairs to add to the URL.
276
277 =item add_additional_url_params C<%params>
278
279 Sets additional parameters that will be added to each URL generated by
280 this model (e.g. for pagination/sorting). This is just sugar for a
281 proper call to L<get_models_url_params> with an anonymous sub adding
282 those parameters.
283
284 =item get_callback
285
286 Returns a URL suitable for use as a callback parameter. It maps to the
287 current controller and action. All registered handlers of type
288 'callback' (e.g. the ones by C<Sorted> and C<Paginated>) can inject
289 the parameters they need so that the same list view as is currently
290 visible can be re-rendered.
291
292 Optional C<%params> passed to this function may override any parameter
293 set by the registered handlers.
294
295 =item enable_plugin PLUGIN
296
297 =item disable_plugin PLUGIN
298
299 =item is_enabled_plugin PLUGIN
300
301 Enable or disable the specified plugin. Useful to disable paginating for
302 exports for example. C<is_enabled_plugin> can be used to check the current
303 state fo a plugin.
304
305 Must not be finalized to use this.
306
307 =item finalize
308
309 Forces finalized state. Can be used on finalized objects without error.
310
311 Note that most higher functions will call this themselves to force a finalized
312 state. If you do use it it must come before any other finalizing methods, and
313 will most likely function as a reminder for maintainers where your code
314 switches from configuration to finalized state.
315
316 =item source HASHREF
317
318 The source for user supplied information. Defaults to $::form. Changing it
319 after C<Base> phase has no effect.
320
321 =item controller CONTROLLER
322
323 A weakened link to the controller that created the GetModels object. Needed for
324 certain plugin methods.
325
326 =back
327
328 =head1 DELEGATION METHODS
329
330 All of these finalize.
331
332 Methods delegating to C<Sorted>:
333
334 =over 4
335
336 =item *
337
338 set_report_generator_sort_options
339
340 =item *
341
342 get_sort_spec
343
344 =item *
345
346 get_current_sort_params
347
348 =back
349
350 Methods delegating to C<Paginated>:
351
352 =over 4
353
354 =item *
355
356 get_paginate_args
357
358 =back
359
360 =head1 STATES
361
362 A GetModels object is in one of 3 states at any given time. Their purpose is to
363 make a class of bugs impossible that orginated from changing the configuration
364 of a GetModels object halfway during the request. This was a huge problem in
365 the old implementation.
366
367 =over 4
368
369 =item Base
370
371 This is the state after creating a new object.
372
373 =item Init
374
375 In this state all the information needed from the source ($::form) has been read
376 and subsequent changes to the source have no effect. In the current
377 implementation this will happen during creation, so that the return value of
378 C<new> is already in state C<Init>.
379
380 =item Finalized
381
382 In this state no new configuration will be accepted so that information gotten
383 through the various methods is consistent. Every information retrieval method
384 will trigger finalize.
385
386 =back
387
388
389 =head1 CONFIGURATION
390
391 Most of the configuration will be handed to GetModels on creation via C<new>.
392 This is a list of accepted params.
393
394 =over 4
395
396 =item controller SELF
397
398 The creating controller. Currently this is mandatory.
399
400 =item model MODEL
401
402 The name of the model for this GetModels instance. If none is given, the model
403 is inferred from the name of the controller class.
404
405 =item sorted PARAMS
406
407 =item paginated PARAMS
408
409 =item filtered PARAMS
410
411 Configuration for plugins. If the option for any plugin is omitted, it defaults
412 to enabled and is configured by default. Giving a falsish value as first argument
413 will disable the plugin.
414
415 If the value is a hashref, it will be passed to the plugin's C<init> method.
416
417 =item query
418
419 =item with_objects
420
421 Additional static parts for Rose to include into the final query.
422
423 =item source
424
425 Source for plugins to pull their data from. Defaults to $::form.
426
427 =back
428
429 =head1 BUGS AND CAVEATS
430
431 =over 4
432
433 =item *
434
435 Delegation is not as clean as it should be. Most of the methods rely on action
436 at a distance and should be moved out.
437
438 =back
439
440 =head1 AUTHORS
441
442 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
443
444 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>
445
446 =cut