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