392f441c24211bd63ebb9a320218df05319d27a3
[kivitendo-erp.git] / SL / Controller / Base.pm
1 package SL::Controller::Base;
2
3 use strict;
4
5 use parent qw(Rose::Object);
6
7 use Carp;
8 use IO::File;
9 use List::Util qw(first);
10
11 #
12 # public/helper functions
13 #
14
15 sub url_for {
16   my $self = shift;
17
18   return $_[0] if (scalar(@_) == 1) && !ref($_[0]);
19
20   my %params      = ref($_[0]) eq 'HASH' ? %{ $_[0] } : @_;
21   my $controller  = delete($params{controller}) || $self->_controller_name;
22   my $action      = delete($params{action})     || 'dispatch';
23   $params{action} = "${controller}/${action}";
24   my $query       = join('&', map { $::form->escape($_) . '=' . $::form->escape($params{$_}) } keys %params);
25
26   return "controller.pl?${query}";
27 }
28
29 sub redirect_to {
30   my $self = shift;
31   my $url  = $self->url_for(@_);
32
33   print $::cgi->redirect($url);
34 }
35
36 sub render {
37   my $self               = shift;
38   my $template           = shift;
39   my ($options, %locals) = (@_ && ref($_[0])) ? @_ : ({ }, @_);
40
41   $options->{type}       = lc($options->{type} || 'html');
42   $options->{no_layout}  = 1 if $options->{type} eq 'js';
43
44   my $source;
45   if ($options->{inline}) {
46     $source = \$template;
47
48   } else {
49     $source = "templates/webpages/${template}." . $options->{type};
50     croak "Template file ${source} not found" unless -f $source;
51   }
52
53   if (!$options->{partial} && !$options->{inline} && !$::form->{header}) {
54     if ($options->{no_layout}) {
55       $::form->{header} = 1;
56       my $content_type  = $options->{type} eq 'js' ? 'text/javascript' : 'text/html';
57
58       print $::form->create_http_response(content_type => $content_type,
59                                           charset      => $::lx_office_conf{system}->{dbcharset} || Common::DEFAULT_CHARSET());
60
61     } else {
62       $::form->{title} = $locals{title} if $locals{title};
63       $::form->header;
64     }
65   }
66
67   my %params = ( %locals,
68                  AUTH     => $::auth,
69                  FLASH    => $::form->{FLASH},
70                  FORM     => $::form,
71                  INSTANCE_CONF => $::instance_conf,
72                  LOCALE   => $::locale,
73                  LXCONFIG => \%::lx_office_conf,
74                  LXDEBUG  => $::lxdebug,
75                  MYCONFIG => \%::myconfig,
76                  SELF     => $self,
77                );
78
79   my $output;
80   my $parser = $self->_template_obj;
81   $parser->process($source, \%params, \$output) || croak $parser->error;
82
83   print $output unless $options->{inline} || $options->{no_output};
84
85   return $output;
86 }
87
88 sub send_file {
89   my ($self, $file_name, %params) = @_;
90
91   my $file            = IO::File->new($file_name, 'r') || croak("Cannot open file '${file_name}'");
92   my $content_type    =  $params{type} || 'application/octet_stream';
93   my $attachment_name =  $params{name} || $file_name;
94   $attachment_name    =~ s:.*//::g;
95
96   print $::form->create_http_response(content_type        => $content_type,
97                                       content_disposition => 'attachment; filename="' . $attachment_name . '"',
98                                       content_length      => -s $file);
99
100   $::locale->with_raw_io(\*STDOUT, sub { print while <$file> });
101   $file->close;
102 }
103
104 #
105 # Before/after run hooks
106 #
107
108 sub run_before {
109   _add_hook('before', @_);
110 }
111
112 sub run_after {
113   _add_hook('after', @_);
114 }
115
116 my %hooks;
117
118 sub _add_hook {
119   my ($when, $class, $sub, %params) = @_;
120
121   foreach my $key (qw(only except)) {
122     $params{$key} = { map { ( $_ => 1 ) } @{ $params{$key} } } if $params{$key};
123   }
124
125   my $idx = "${when}/${class}";
126   $hooks{$idx} ||= [ ];
127   push @{ $hooks{$idx} }, { %params, code => $sub };
128 }
129
130 sub _run_hooks {
131   my ($self, $when, $action) = @_;
132
133   my $idx = "${when}/" . ref($self);
134
135   foreach my $hook (@{ $hooks{$idx} || [] }) {
136     next if ($hook->{only  } && !$hook->{only  }->{$action})
137          || ($hook->{except} &&  $hook->{except}->{$action});
138
139     if (ref($hook->{code}) eq 'CODE') {
140       $hook->{code}->($self);
141     } else {
142       my $sub = $hook->{code};
143       $self->$sub;
144     }
145   }
146 }
147
148 #
149 # private functions -- for use in Base only
150 #
151
152 sub _run_action {
153   my $self   = shift;
154   my $action = shift;
155   my $sub    = "action_${action}";
156
157   return $self->_dispatch(@_) if $action eq 'dispatch';
158
159   $::form->error("Invalid action '${action}' for controller " . ref($self)) if !$self->can($sub);
160
161   $self->_run_hooks('before', $action);
162   $self->$sub(@_);
163   $self->_run_hooks('after', $action);
164 }
165
166 sub _controller_name {
167   return (split(/::/, ref($_[0])))[-1];
168 }
169
170 sub _dispatch {
171   my $self    = shift;
172
173   no strict 'refs';
174   my @actions = map { s/^action_//; $_ } grep { m/^action_/ } keys %{ ref($self) . "::" };
175   my $action  = first { $::form->{"action_${_}"} } @actions;
176   my $sub     = "action_${action}";
177
178   if ($self->can($sub)) {
179     $self->_run_hooks('before', $action);
180     $self->$sub(@_);
181     $self->_run_hooks('after', $action);
182   } else {
183     $::form->error($::locale->text('Oops. No valid action found to dispatch. Please report this case to the Lx-Office team.'));
184   }
185 }
186
187 sub _template_obj {
188   my ($self) = @_;
189
190   $self->{__basepriv_template_obj} ||=
191     Template->new({ INTERPOLATE  => 0,
192                     EVAL_PERL    => 0,
193                     ABSOLUTE     => 1,
194                     CACHE_SIZE   => 0,
195                     PLUGIN_BASE  => 'SL::Template::Plugin',
196                     INCLUDE_PATH => '.:templates/webpages',
197                     COMPILE_EXT  => '.tcc',
198                     COMPILE_DIR  => $::lx_office_conf{paths}->{userspath} . '/templates-cache',
199                   }) || croak;
200
201   return $self->{__basepriv_template_obj};
202 }
203
204 1;
205
206 __END__
207
208 =head1 NAME
209
210 SL::Controller::Base - base class for all action controllers
211
212 =head1 SYNOPSIS
213
214 =head2 OVERVIEW
215
216 This is a base class for all action controllers. Action controllers
217 provide subs that are callable by special URLs.
218
219 For each request made to the web server an instance of the controller
220 will be created. After the request has been served that instance will
221 handed over to garbage collection.
222
223 This base class is derived from L<Rose::Object>.
224
225 =head2 CONVENTIONS
226
227 The URLs have the following properties:
228
229 =over 2
230
231 =item *
232
233 The script part of the URL must be C<controller.pl>.
234
235 =item *
236
237 There must be a GET or POST parameter named C<action> containing the
238 name of the controller and the sub to call separated by C</>,
239 e.g. C<Message/list>.
240
241 =item *
242
243 The controller name is the package's name without the
244 C<SL::Controller::> prefix. At the moment only packages in the
245 C<SL::Controller> namespace are valid; sub-namespaces are not
246 allowed. The package name must start with an upper-case letter.
247
248 =item *
249
250 The sub part of the C<action> parameter is the name of the sub to
251 call. However, the sub's name is automatically prefixed with
252 C<action_>. Therefore for the example C<Message/list> the sub
253 C<SL::DB::Message::action_list> would be called. This in turn means
254 that subs whose name does not start with C<action_> cannot be invoked
255 directly via the URL.
256
257 =back
258
259 =head2 INDIRECT DISPATCHING
260
261 In the case that there are several submit buttons on a page it is
262 often impractical to have a single C<action> parameter match up
263 properly. For such a case a special dispatcher method is available. In
264 that case the C<action> parameter of the URL must be
265 C<Controller/dispatch>.
266
267 The C<SL::Controller::Base::_dispatch> method will iterate over all
268 subs in the controller package whose names start with C<action_>. The
269 first one for which there's a GET or POST parameter with the same name
270 and that's trueish is called.
271
272 Usage from a template usually looks like this:
273
274   <form method="POST" action="controller.pl">
275     ...
276     <input type="hidden" name="action" value="Message/dispatch">
277     <input type="submit" name="action_mark_as_read" value="Mark messages as read">
278     <input type="submit" name="action_delete" value="Delete messages">
279   </form>
280
281 The dispatching is handled by the function L</_dispatch>.
282
283 =head2 HOOKS
284
285 Hooks are functions that are called before or after the controller's
286 action is called. The controller package defines the hooks, and those
287 hooks themselves are run as instance methods.
288
289 Hooks are run in the order they're added.
290
291 The return value of the hooks is discarded.
292
293 Hooks can be defined to run for all actions, for only specific actions
294 or for all actions except a list of actions. Each entry is the action
295 name, not the sub's name. Therefore in order to run a hook before one
296 of the subs C<action_edit> or C<action_save> is called the following
297 code can be used:
298
299   __PACKAGE__->run_before('things_to_do_before_edit_and_save', only => [ 'edit', 'save' ]);
300
301 =head1 FUNCTIONS
302
303 =head2 PUBLIC HELPER FUNCTIONS
304
305 These functions are supposed to be called by sub-classed controllers.
306
307 =over 4
308
309 =item C<render $template, [ $options, ] %locals>
310
311 Renders the template C<$template>. Provides other variables than
312 C<Form::parse_html_template> does.
313
314 C<$options>, if present, must be a hash reference. All remaining
315 parameters are slurped into C<%locals>.
316
317 What is rendered and how C<$template> is interpreted is determined by
318 the options I<type>, I<inline>, I<partial> and I<no_layout>.
319
320 If C<< $options->{inline} >> is trueish then C<$template> is a string
321 containing the template code to interprete. Additionally the output
322 will not be sent to the browser. Instead it is only returned to the
323 caller.
324
325 If C<< $options->{inline} >> is falsish then C<$template> is
326 interpreted as the name of a template file. It is prefixed with
327 "templates/webpages/" and postfixed with a file extension based on
328 C<< $options->{type} >>. C<< $options->{type} >> can be either C<html>
329 or C<js> and defaults to C<html>. An exception will be thrown if that
330 file does not exist.
331
332 If C<< $options->{partial} >> or C<< $options->{inline} >> is trueish
333 then neither the HTTP response header nor the standard HTML header is
334 generated.
335
336 Otherwise at least the HTTP response header will be generated based on
337 the template type (C<< $options->{type} >>).
338
339 If the template type is C<html> then the standard HTML header will be
340 output via C<< $::form->header >> with C<< $::form->{title} >> set to
341 C<$locals{title}> (the latter only if C<$locals{title}> is
342 trueish). Setting C<< $options->{no_layout} >> to trueish will prevent
343 this.
344
345 The template itself has access to the following variables:
346
347 =over 2
348
349 =item * C<AUTH> -- C<$::auth>
350
351 =item * C<FORM> -- C<$::form>
352
353 =item * C<LOCALE> -- C<$::locale>
354
355 =item * C<LXCONFIG> -- all parameters from C<config/lx_office.conf>
356 with the same name they appear in the file (first level is the
357 section, second the actual variable, e.g. C<system.dbcharset>,
358 C<features.webdav> etc)
359
360 =item * C<LXDEBUG> -- C<$::lxdebug>
361
362 =item * C<MYCONFIG> -- C<%::myconfig>
363
364 =item * C<SELF> -- the controller instance
365
366 =item * All items from C<%locals>
367
368 =back
369
370 Unless C<< $options->{inline} >> is trueish the function will send the
371 output to the browser.
372
373 The function will always return the output.
374
375 Example: Render a HTML template with a certain title and a few locals
376
377   $self->render('todo/list',
378                 title      => 'List TODO items',
379                 TODO_ITEMS => SL::DB::Manager::Todo->get_all_sorted);
380
381 Example: Render a string and return its content for further processing
382 by the calling function. No header is generated due to C<inline>.
383
384   my $content = $self->render('[% USE JavaScript %][% JavaScript.replace_with("#someid", "js/something") %]',
385                               { type => 'js', inline => 1 });
386
387 Example: Render a JavaScript template and send it to the
388 browser. Typical use for actions called via AJAX:
389
390   $self->render('todo/single_item', { type => 'js' },
391                 item => $employee->most_important_todo_item);
392
393 =item C<send_file $file_name, [%params]>
394
395 Sends the file C<$file_name> to the browser including appropriate HTTP
396 headers for a download. C<%params> can include the following:
397
398 =over 2
399
400 =item * C<type> -- the file's content type; defaults to
401 'application/octet_stream'
402
403 =item * C<name> -- the name presented to the browser; defaults to
404 C<$file_name>
405
406 =back
407
408 =item C<url_for $url>
409
410 =item C<url_for $params>
411
412 =item C<url_for %params>
413
414 Creates an URL for the given parameters suitable for calling an action
415 controller. If there's only one scalar parameter then it is returned
416 verbatim.
417
418 Otherwise the parameters are given either as a single hash ref
419 parameter or as a normal hash.
420
421 The controller to call is given by C<$params{controller}>. It defaults
422 to the current controller as returned by
423 L</_controller_name>.
424
425 The action to call is given by C<$params{action}>. It defaults to
426 C<dispatch>.
427
428 All other key/value pairs in C<%params> are appended as GET parameters
429 to the URL.
430
431 Usage from a template might look like this:
432
433   <a href="[% SELF.url_for(controller => 'Message', action => 'new', recipient_id => 42) %]">create new message</a>
434
435 =item C<redirect_to %url_params>
436
437 Redirects the browser to a new URL by outputting a HTTP redirect
438 header. The URL is generated by calling L</url_for> with
439 C<%url_params>.
440
441 =item C<run_before $sub, %params>
442
443 =item C<run_after $sub, %params>
444
445 Adds a hook to run before or after certain actions are run for the
446 current package. The code to run is C<$sub> which is either the name
447 of an instance method or a code reference. If it's the latter then the
448 first parameter will be C<$self>.
449
450 C<%params> can contain two possible values that restrict the code to
451 be run only for certain actions:
452
453 =over 2
454
455 =item C<< only => \@list >>
456
457 Only run the code for actions given in C<@list>. The entries are the
458 action names, not the names of the sub (so it's C<list> instead of
459 C<action_list>).
460
461 =item C<< except => \@list >>
462
463 Run the code for all actions but for those given in C<@list>. The
464 entries are the action names, not the names of the sub (so it's
465 C<list> instead of C<action_list>).
466
467 =back
468
469 If neither restriction is used then the code will be run for any
470 action.
471
472 The hook's return values are discarded.
473
474 =back
475
476 =head2 PRIVATE FUNCTIONS
477
478 These functions are supposed to be used from this base class only.
479
480 =over 4
481
482 =item C<_controller_name>
483
484 Returns the name of the curernt controller package without the
485 C<SL::Controller::> prefix.
486
487 =item C<_dispatch>
488
489 Implements the method lookup for indirect dispatching mentioned in the
490 section L</INDIRECT DISPATCHING>.
491
492 =item C<_run_action $action>
493
494 Executes a sub based on the value of C<$action>. C<$action> is the sub
495 name part of the C<action> GET or POST parameter as described in
496 L</CONVENTIONS>.
497
498 If C<$action> equals C<dispatch> then the sub L</_dispatch> in this
499 base class is called for L</INDIRECT DISPATCHING>. Otherwise
500 C<$action> is prefixed with C<action_>, and that sub is called on the
501 current controller instance.
502
503 =back
504
505 =head1 AUTHOR
506
507 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>
508
509 =cut