1 package SL::Controller::Base;
5 use parent qw(Rose::Object);
9 use List::Util qw(first);
10 use SL::Request qw(flatten);
11 use SL::MoreCommon qw(uri_encode);
14 # public/helper functions
20 return $_[0] if (scalar(@_) == 1) && !ref($_[0]);
22 my %params = ref($_[0]) eq 'HASH' ? %{ $_[0] } : @_;
23 my $controller = delete($params{controller}) || $self->_controller_name;
24 my $action = delete($params{action}) || 'dispatch';
25 $params{action} = "${controller}/${action}";
26 my $query = join '&', map { uri_encode($_->[0]) . '=' . uri_encode($_->[1]) } @{ flatten(\%params) };
28 return "controller.pl?${query}";
33 my $url = $self->url_for(@_);
35 if ($self->delay_flash_on_redirect) {
36 require SL::Helper::Flash;
37 SL::Helper::Flash::delay_flash();
40 print $::request->{cgi}->redirect($url);
46 my ($options, %locals) = (@_ && ref($_[0])) ? @_ : ({ }, @_);
48 $options->{type} = lc($options->{type} || 'html');
49 $options->{no_layout} = 1 if $options->{type} eq 'js';
52 if ($options->{inline}) {
55 } elsif($options->{raw}) {
59 $source = "templates/webpages/${template}." . $options->{type};
60 croak "Template file ${source} not found" unless -f $source;
63 if (!$options->{partial} && !$options->{inline} && !$::form->{header}) {
64 if ($options->{no_layout}) {
65 $::form->{header} = 1;
66 my $content_type = $options->{type} eq 'js' ? 'text/javascript' : 'text/html';
68 print $::form->create_http_response(content_type => $content_type,
69 charset => $::lx_office_conf{system}->{dbcharset} || Common::DEFAULT_CHARSET());
72 $::form->{title} = $locals{title} if $locals{title};
77 my %params = ( %locals,
79 FLASH => $::form->{FLASH},
81 INSTANCE_CONF => $::instance_conf,
83 LXCONFIG => \%::lx_office_conf,
84 LXDEBUG => $::lxdebug,
85 MYCONFIG => \%::myconfig,
90 if (!$options->{raw}) {
91 my $parser = $self->_template_obj;
92 $parser->process($source, \%params, \$output) || croak $parser->error;
97 print $output unless $options->{inline} || $options->{no_output};
103 my ($self, $file_name, %params) = @_;
105 my $file = IO::File->new($file_name, 'r') || croak("Cannot open file '${file_name}'");
106 my $content_type = $params{type} || 'application/octet_stream';
107 my $attachment_name = $params{name} || $file_name;
108 $attachment_name =~ s:.*//::g;
110 print $::form->create_http_response(content_type => $content_type,
111 content_disposition => 'attachment; filename="' . $attachment_name . '"',
112 content_length => -s $file);
114 $::locale->with_raw_io(\*STDOUT, sub { print while <$file> });
119 # Before/after run hooks
123 _add_hook('before', @_);
127 _add_hook('after', @_);
133 my ($when, $class, $sub, %params) = @_;
135 foreach my $key (qw(only except)) {
136 $params{$key} = { map { ( $_ => 1 ) } @{ $params{$key} } } if $params{$key};
139 my $idx = "${when}/${class}";
140 $hooks{$idx} ||= [ ];
141 push @{ $hooks{$idx} }, { %params, code => $sub };
145 my ($self, $when, $action) = @_;
147 my $idx = "${when}/" . ref($self);
149 foreach my $hook (@{ $hooks{$idx} || [] }) {
150 next if ($hook->{only } && !$hook->{only }->{$action})
151 || ($hook->{except} && $hook->{except}->{$action});
153 if (ref($hook->{code}) eq 'CODE') {
154 $hook->{code}->($self);
156 my $sub = $hook->{code};
163 # behaviour. override these
166 sub delay_flash_on_redirect {
171 # Ignore the 'action' parameter.
176 # private functions -- for use in Base only
182 my $sub = "action_${action}";
184 return $self->_dispatch(@_) if $action eq 'dispatch';
186 $::form->error("Invalid action '${action}' for controller " . ref($self)) if !$self->can($sub);
188 $self->_run_hooks('before', $action);
190 $self->_run_hooks('after', $action);
193 sub _controller_name {
194 return (split(/::/, ref($_[0])))[-1];
201 my @actions = map { s/^action_//; $_ } grep { m/^action_/ } keys %{ ref($self) . "::" };
202 my $action = first { $::form->{"action_${_}"} } @actions;
203 my $sub = "action_${action}";
205 if ($self->can($sub)) {
206 $self->_run_hooks('before', $action);
208 $self->_run_hooks('after', $action);
210 $::form->error($::locale->text('Oops. No valid action found to dispatch. Please report this case to the Lx-Office team.'));
217 $self->{__basepriv_template_obj} ||=
218 Template->new({ INTERPOLATE => 0,
222 PLUGIN_BASE => 'SL::Template::Plugin',
223 INCLUDE_PATH => '.:templates/webpages',
224 COMPILE_EXT => '.tcc',
225 COMPILE_DIR => $::lx_office_conf{paths}->{userspath} . '/templates-cache',
228 return $self->{__basepriv_template_obj};
237 SL::Controller::Base - base class for all action controllers
243 This is a base class for all action controllers. Action controllers
244 provide subs that are callable by special URLs.
246 For each request made to the web server an instance of the controller
247 will be created. After the request has been served that instance will
248 handed over to garbage collection.
250 This base class is derived from L<Rose::Object>.
254 The URLs have the following properties:
260 The script part of the URL must be C<controller.pl>.
264 There must be a GET or POST parameter named C<action> containing the
265 name of the controller and the sub to call separated by C</>,
266 e.g. C<Message/list>.
270 The controller name is the package's name without the
271 C<SL::Controller::> prefix. At the moment only packages in the
272 C<SL::Controller> namespace are valid; sub-namespaces are not
273 allowed. The package name must start with an upper-case letter.
277 The sub part of the C<action> parameter is the name of the sub to
278 call. However, the sub's name is automatically prefixed with
279 C<action_>. Therefore for the example C<Message/list> the sub
280 C<SL::DB::Message::action_list> would be called. This in turn means
281 that subs whose name does not start with C<action_> cannot be invoked
282 directly via the URL.
286 =head2 INDIRECT DISPATCHING
288 In the case that there are several submit buttons on a page it is
289 often impractical to have a single C<action> parameter match up
290 properly. For such a case a special dispatcher method is available. In
291 that case the C<action> parameter of the URL must be
292 C<Controller/dispatch>.
294 The C<SL::Controller::Base::_dispatch> method will iterate over all
295 subs in the controller package whose names start with C<action_>. The
296 first one for which there's a GET or POST parameter with the same name
297 and that's trueish is called.
299 Usage from a template usually looks like this:
301 <form method="POST" action="controller.pl">
303 <input type="hidden" name="action" value="Message/dispatch">
304 <input type="submit" name="action_mark_as_read" value="Mark messages as read">
305 <input type="submit" name="action_delete" value="Delete messages">
308 The dispatching is handled by the function L</_dispatch>.
312 Hooks are functions that are called before or after the controller's
313 action is called. The controller package defines the hooks, and those
314 hooks themselves are run as instance methods.
316 Hooks are run in the order they're added.
318 The return value of the hooks is discarded.
320 Hooks can be defined to run for all actions, for only specific actions
321 or for all actions except a list of actions. Each entry is the action
322 name, not the sub's name. Therefore in order to run a hook before one
323 of the subs C<action_edit> or C<action_save> is called the following
326 __PACKAGE__->run_before('things_to_do_before_edit_and_save', only => [ 'edit', 'save' ]);
330 =head2 PUBLIC HELPER FUNCTIONS
332 These functions are supposed to be called by sub-classed controllers.
336 =item C<render $template, [ $options, ] %locals>
338 Renders the template C<$template>. Provides other variables than
339 C<Form::parse_html_template> does.
341 C<$options>, if present, must be a hash reference. All remaining
342 parameters are slurped into C<%locals>.
344 What is rendered and how C<$template> is interpreted is determined by
345 the options I<type>, I<inline>, I<partial> and I<no_layout>.
347 If C<< $options->{inline} >> is trueish then C<$template> is a string
348 containing the template code to interprete. Additionally the output
349 will not be sent to the browser. Instead it is only returned to the
352 If C<< $options->{raw} >> is trueish, the function will treat the input as
353 already parsed, and will not filter the input through Template. Unlike
354 C<inline>, the input is taked as a reference.
356 If C<< $options->{inline} >> is falsish then C<$template> is
357 interpreted as the name of a template file. It is prefixed with
358 "templates/webpages/" and postfixed with a file extension based on
359 C<< $options->{type} >>. C<< $options->{type} >> can be either C<html>
360 or C<js> and defaults to C<html>. An exception will be thrown if that
363 If C<< $options->{partial} >> or C<< $options->{inline} >> is trueish
364 then neither the HTTP response header nor the standard HTML header is
367 Otherwise at least the HTTP response header will be generated based on
368 the template type (C<< $options->{type} >>).
370 If the template type is C<html> then the standard HTML header will be
371 output via C<< $::form->header >> with C<< $::form->{title} >> set to
372 C<$locals{title}> (the latter only if C<$locals{title}> is
373 trueish). Setting C<< $options->{no_layout} >> to trueish will prevent
376 The template itself has access to the following variables:
380 =item * C<AUTH> -- C<$::auth>
382 =item * C<FORM> -- C<$::form>
384 =item * C<LOCALE> -- C<$::locale>
386 =item * C<LXCONFIG> -- all parameters from C<config/lx_office.conf>
387 with the same name they appear in the file (first level is the
388 section, second the actual variable, e.g. C<system.dbcharset>,
389 C<features.webdav> etc)
391 =item * C<LXDEBUG> -- C<$::lxdebug>
393 =item * C<MYCONFIG> -- C<%::myconfig>
395 =item * C<SELF> -- the controller instance
397 =item * All items from C<%locals>
401 Unless C<< $options->{inline} >> is trueish the function will send the
402 output to the browser.
404 The function will always return the output.
406 Example: Render a HTML template with a certain title and a few locals
408 $self->render('todo/list',
409 title => 'List TODO items',
410 TODO_ITEMS => SL::DB::Manager::Todo->get_all_sorted);
412 Example: Render a string and return its content for further processing
413 by the calling function. No header is generated due to C<inline>.
415 my $content = $self->render('[% USE JavaScript %][% JavaScript.replace_with("#someid", "js/something") %]',
416 { type => 'js', inline => 1 });
418 Example: Render a JavaScript template and send it to the
419 browser. Typical use for actions called via AJAX:
421 $self->render('todo/single_item', { type => 'js' },
422 item => $employee->most_important_todo_item);
424 =item C<send_file $file_name, [%params]>
426 Sends the file C<$file_name> to the browser including appropriate HTTP
427 headers for a download. C<%params> can include the following:
431 =item * C<type> -- the file's content type; defaults to
432 'application/octet_stream'
434 =item * C<name> -- the name presented to the browser; defaults to
439 =item C<url_for $url>
441 =item C<url_for $params>
443 =item C<url_for %params>
445 Creates an URL for the given parameters suitable for calling an action
446 controller. If there's only one scalar parameter then it is returned
449 Otherwise the parameters are given either as a single hash ref
450 parameter or as a normal hash.
452 The controller to call is given by C<$params{controller}>. It defaults
453 to the current controller as returned by
454 L</_controller_name>.
456 The action to call is given by C<$params{action}>. It defaults to
459 All other key/value pairs in C<%params> are appended as GET parameters
462 Usage from a template might look like this:
464 <a href="[% SELF.url_for(controller => 'Message', action => 'new', recipient_id => 42) %]">create new message</a>
466 =item C<redirect_to %url_params>
468 Redirects the browser to a new URL by outputting a HTTP redirect
469 header. The URL is generated by calling L</url_for> with
472 =item C<run_before $sub, %params>
474 =item C<run_after $sub, %params>
476 Adds a hook to run before or after certain actions are run for the
477 current package. The code to run is C<$sub> which is either the name
478 of an instance method or a code reference. If it's the latter then the
479 first parameter will be C<$self>.
481 C<%params> can contain two possible values that restrict the code to
482 be run only for certain actions:
486 =item C<< only => \@list >>
488 Only run the code for actions given in C<@list>. The entries are the
489 action names, not the names of the sub (so it's C<list> instead of
492 =item C<< except => \@list >>
494 Run the code for all actions but for those given in C<@list>. The
495 entries are the action names, not the names of the sub (so it's
496 C<list> instead of C<action_list>).
500 If neither restriction is used then the code will be run for any
503 The hook's return values are discarded.
505 =item delay_flash_on_redirect
507 May be overridden by a controller. If this method returns true, redirect_to
508 will delay all flash messages for the current request. Defaults to false for
509 compatibility reasons.
511 =item C<get_auth_level $action>
513 May be overridden by a controller. Determines what kind of
514 authentication is required for a particular action. Must return either
515 C<admin> (which means that authentication as an admin is required),
516 C<user> (authentication as a normal user suffices) with a possible
517 future value C<none> (which would require no authentication but is not
522 =head2 PRIVATE FUNCTIONS
524 These functions are supposed to be used from this base class only.
528 =item C<_controller_name>
530 Returns the name of the curernt controller package without the
531 C<SL::Controller::> prefix.
535 Implements the method lookup for indirect dispatching mentioned in the
536 section L</INDIRECT DISPATCHING>.
538 =item C<_run_action $action>
540 Executes a sub based on the value of C<$action>. C<$action> is the sub
541 name part of the C<action> GET or POST parameter as described in
544 If C<$action> equals C<dispatch> then the sub L</_dispatch> in this
545 base class is called for L</INDIRECT DISPATCHING>. Otherwise
546 C<$action> is prefixed with C<action_>, and that sub is called on the
547 current controller instance.
553 Moritz Bunkus E<lt>m.bunkus@linet-services.deE<gt>