]> wagnertech.de Git - mfinanz.git/blob - SL/Layout/Base.pm
restart apache2 in postinst
[mfinanz.git] / SL / Layout / Base.pm
1 package SL::Layout::Base;
2
3 use strict;
4 use parent qw(Rose::Object);
5
6 use SL::Version;
7
8 use File::Slurp qw(read_file);
9 use List::MoreUtils qw(uniq);
10 use Time::HiRes qw();
11
12 use Rose::Object::MakeMethods::Generic (
13   'scalar --get_set_init' => [ qw(menu auto_reload_resources_param sub_layouts_by_name) ],
14   'scalar'                => qw(focus),
15   'array'                 => [
16     'add_stylesheets_inline' => { interface => 'add', hash_key => 'stylesheets_inline' },
17     'add_javascripts_inline' => { interface => 'add', hash_key => 'javascripts_inline' },
18     'sub_layouts',           => { interface => 'get_set_init' },
19     'add_sub_layouts'        => { interface => 'add', hash_key => 'sub_layouts' },
20   ],
21 );
22
23 use SL::Menu;
24 use SL::Presenter;
25 use SL::System::Process;
26
27 my %menu_cache;
28
29 sub new {
30   my ($class, @slurp) = @_;
31
32   my $self = $class->SUPER::new(@slurp);
33 }
34
35 sub init_menu {
36   SL::Menu->new('user');
37 }
38
39 sub init_sublayouts_by_name {
40   {}
41 }
42
43 sub webpages_path {
44   "templates/webpages";
45 }
46
47 sub webpages_fallback_path {
48 }
49
50 sub html_dialect {
51   'transitional'
52 }
53
54 sub allow_stylesheet_fallback {
55   1
56 }
57
58 sub get {
59   $_[0]->sub_layouts;
60   return grep { $_ } ($_[0]->sub_layouts_by_name->{$_[1]});
61 }
62
63 sub _current_git_ref {
64   my $git_dir = SL::System::Process::exe_dir() . '/.git';
65
66   return unless -d $git_dir;
67
68   my $content = eval { scalar(read_file($git_dir . '/HEAD')) };
69
70   return unless ($content // '') =~ m{\Aref: ([^\r\n]+)};
71
72   $content = eval { scalar(read_file($git_dir . '/' . $1)) };
73
74   return unless ($content // '') =~ m{\A([0-9a-fA-F]+)};
75
76   return $1;
77 }
78
79 sub init_auto_reload_resources_param {
80   my $value;
81
82   $value   = sprintf('%d-%d-%d', Time::HiRes::gettimeofday(), int(rand 1000000000000)) if $::lx_office_conf{debug}->{auto_reload_resources};
83   $value ||= _current_git_ref();
84   $value ||= SL::Version->get_version;
85
86   return $value ? "?rand=${value}" : '';
87 }
88
89 ##########################################
90 #  inheritable/overridable
91 ##########################################
92
93 sub pre_content {
94   join '', map { $_->pre_content } $_[0]->sub_layouts;
95 }
96
97 sub start_content {
98   join '', map { $_->start_content } $_[0]->sub_layouts;
99 }
100
101 sub end_content {
102   join '', map { $_->end_content } $_[0]->sub_layouts;
103 }
104
105 sub post_content {
106   join '', map { $_->post_content } $_[0]->sub_layouts;
107 }
108
109 sub stylesheets_inline {
110   uniq ( map { $_->stylesheets_inline } $_[0]->sub_layouts ),
111   @{ $_[0]->{stylesheets_inline} || [] };
112 }
113
114 sub javascripts_inline {
115   uniq ( map { $_->javascripts_inline } $_[0]->sub_layouts ),
116   @{ $_[0]->{javascripts_inline} || [] };
117 }
118
119 sub init_sub_layouts { [] }
120
121 sub init_sub_layouts_by_name { +{} }
122
123
124 #########################################
125 # Stylesheets
126 ########################################
127
128 # override in sub layouts
129 sub static_stylesheets {}
130
131 sub add_stylesheets {
132   &use_stylesheet;
133 }
134
135 sub use_stylesheet {
136   my $self = shift;
137   push @{ $self->{stylesheets} ||= [] }, @_ if @_;
138     (map { $_->use_stylesheet } $self->sub_layouts), $self->static_stylesheets, @{ $self->{stylesheets} ||= [] };
139 }
140
141 sub stylesheets {
142   my ($self) = @_;
143   my $css_path = $self->get_stylesheet_for_user;
144
145   return uniq grep { $_ } map { $self->_find_stylesheet($_, $css_path)  }
146     $self->use_stylesheet;
147 }
148
149 sub _find_stylesheet {
150   my ($self, $stylesheet, $css_path) = @_;
151
152   return "$css_path/$stylesheet" if -f "$css_path/$stylesheet";
153   return "css/$stylesheet"       if -f "css/$stylesheet" && $self->allow_stylesheet_fallback;
154   return $stylesheet             if -f $stylesheet;
155   return $stylesheet             if $stylesheet =~ /^http/; # external
156 }
157
158 sub get_stylesheet_for_user {
159   my $css_path = 'css';
160   if (my $user_style = $::myconfig{stylesheet}) {
161     $user_style =~ s/\.css$//; # nuke trailing .css, this is a remnant of pre 2.7.0 stylesheet handling
162     if (-d "$css_path/$user_style" &&
163         -f "$css_path/$user_style/main.css") {
164       $css_path = "$css_path/$user_style";
165     } else {
166       $css_path = "$css_path/kivitendo";
167     }
168   } else {
169     $css_path = "$css_path/kivitendo";
170   }
171
172   return $css_path;
173 }
174
175 #########################################
176 # Javascripts
177 ########################################
178
179 # override in sub layouts
180 sub static_javascripts {}
181
182 sub add_javascripts {
183   &use_javascript
184 }
185
186 sub use_javascript {
187   my $self = shift;
188   push @{ $self->{javascripts} ||= [] }, @_ if @_;
189   map({ $_->use_javascript } $self->sub_layouts), $self->static_javascripts, @{ $self->{javascripts} ||= [] };
190 }
191
192 sub javascripts {
193   my ($self) = @_;
194
195   return uniq grep { $_ } map { $self->_find_javascript($_)  }
196      $self->use_javascript;
197 }
198
199 sub _find_javascript {
200   my ($self, $javascript) = @_;
201
202   return "js/$javascript"        if -f "js/$javascript";
203   return $javascript             if -f $javascript;
204   return $javascript             if $javascript =~ /^http/;
205 }
206
207
208 ############################################
209 # track state of form header
210 ############################################
211
212 sub header_done {
213   $_[0]{_header_done} = 1;
214 }
215
216 sub need_footer {
217   $_[0]{_header_done};
218 }
219
220 sub presenter {
221   SL::Presenter->get;
222 }
223
224 1;
225
226 __END__
227
228 =encoding utf-8
229
230 =head1 NAME
231
232 SL::Layout::Base - Base class for layouts
233
234 =head1 SYNOPSIS
235
236   package SL::Layout::MyLayout;
237
238   use parent qw(SL::Layout::Base);
239
240 =head1 DESCRIPTION
241
242 For a description of the external interface of layouts in general see
243 L<SL::Layout::Dispatcher>.
244
245 This is a base class for layouts in general. It provides the basic interface
246 and some capabilities to extend and cascade layouts.
247
248
249 =head1 IMPLEMENTING LAYOUT CALLBACKS
250
251 There are eight callbacks (C<pre_content>, C<post_content>, C<start_content>,
252 C<end_content>, C<stylesheets>, C<stylesheets_inline>, C<javascripts>,
253 C<javascripts_inline>) which are documented in L<SL::Layout::Dispatcher>. If
254 you are writing a new simple layout, you can just override some of them like
255 this:
256
257   package SL::Layout::MyEvilLayout;
258
259   sub pre_content {
260     '<h1>This is MY page now</h1>'
261   }
262
263   sub post_content {
264     '<p align="right"><small><em>Brought to you by my own layout class</em></small></p>'
265   }
266
267
268 To preserve the sanitizing effects of C<stylesheets> and C<javascripts> you should instead do the following:
269
270   sub stylesheets {
271     $_[0]->add_stylesheets(qw(mystyle1.css mystyle2.css);
272     $_[0]->SUPER::stylesheets;
273   }
274
275 If you want to add something to a different layout, you should write a sub
276 layout and add it to the other layouts.
277
278
279 =head1 SUB LAYOUTS
280
281 Layouts can be aggregated, so that common elements can be used in different
282 layouts. Currently this is used for the L<None|SL::Layout::None> sub layout,
283 which contains a lot of the stylesheets and javascripts necessary. Another
284 example is the L<Top|SL::Layout::Top> layout, which is used to generate a
285 common top bar for all menu types.
286
287 To add a sub layout to your layout just overwrite the sub_layout method:
288
289   package SL::Layout::MyFinalLayout;
290
291   sub init_sub_layout {
292     [
293       SL::Layout::None->new,
294       SL::Layout::MyEvilLayout->new,
295     ]
296   }
297
298 You can also add a sublayout at runtime:
299
300   $layout->add_sub_layout(SL::Layout::SideBar->new);
301
302 The standard implementation for the callbacks will see to it that the contents
303 of all sub layouts will get rendered.
304
305
306 =head1 COMBINING SUB LAYOUTS AND OWN BEHAVIOUR
307
308 This is still somewhat rough, and improvements are welcome.
309
310 For the C<*_content> callbacks this works if you just remember to dispatch to the base method:
311
312   sub post_content {
313     return $_[0]->render_status_bar .
314     $_[0]->SUPER::post_content
315   }
316
317
318 Stylesheets and Javascripts can be added to every layout and sub-layout at
319 runtime with L<SL::Layout::Dispatcher/add_stylesheets> and
320 L<SL::Layout::Dispatcher/add_javascripts> (C<use_stylesheets> and
321 C<use_javascripts> are aliases for backwards compatibility):
322
323   $layout->add_stylesheets("custom.css");
324   $layout->add_javascripts("app.js", "widget.js");
325
326 Or they can be overwritten in sub layouts with the calls
327 L<SL::Layout::Displatcher/static_stylesheets> and
328 L<SL::Layout::Dispatcher/static_javascripts>:
329
330   sub static_stylesheets {
331     "custom.css"
332   }
333
334   sub static_javascripts {
335     qw(app.css widget.js)
336   }
337
338 Note how these are relative to the base dirs of the currently selected
339 stylesheets. Javascripts are resolved relative to the C<js/> basedir.
340
341 Setting directly with C<stylesheets> and C<javascripts> is eprecated.
342
343
344 =head1 BEHAVIOUR SWITCHES FOR SUB LAYOUTS
345
346 Certain methods have been added to adjust behaviour in sub layouts. Most of these are single case uses.
347
348 =over 4
349
350 =item * sublayouts_by_name
351
352 Contains a map that holds named sublayouts. If a sublayout needs to targeted
353 directly, the compositing layout needs to add it here. Initially introduced for
354 the ActionPlan.
355
356 =item * webpages_path
357
358 Overrides the default webpages path "templates/webpages". Used for mobile and design40 styles.
359
360 Note that this does not have fallback behaviour by default. It is intended for
361 stylesheets where the templates are so incompatible that a complete fork of the
362 templates dir is sensible.
363
364 =item * webpages_fallback_path
365
366 Allows partial template sets to fallback to other paths in case a template
367 wasn't found. Intended to be used in conjunction with L</webpages_path>.
368
369 Note: in case a template can't be found at all, generic/error.html will be
370 rendered, and the fallback doesn't work in this case.
371
372
373 =item * allow_stylesheet_fallback
374
375 Defaults to true. The default behaviour is that stylesheets not found in the
376 stylesheet path of the user will fallback to files found in css/. This is
377 usually desirable for shared stuff like common.css, which contains behaviour
378 styling. If a stylesheet comes from a separate generator, this can be used to
379 turn falllback off. Files can still be included with the complete path though.
380 A request for "common.css" would not find "css/common.css", but a request for
381 "css/common.css" would be found.
382
383 Also see the next section L</GORY DETAILS ABOUT JAVASCRIPT AND STYLESHEET OVERLOADING>
384
385 =item * html_dialect
386
387 Default 'transitional'. Controls the html dialect that the header will
388 generate. Used in combination with template overriding for html5.
389
390 See also L<SL::Form/header>
391
392 =back
393
394
395
396 =head1 GORY DETAILS ABOUT JAVASCRIPT AND STYLESHEET OVERLOADING
397
398 The original code used to store one stylesheet in C<< $form->{stylesheet} >> and
399 allowed/expected authors of potential C<bin/mozilla/> controllers to change
400 that into their own modified stylesheet.
401
402 This was at some point cleaned up into a method C<use stylesheet> which took a
403 string of space separated stylesheets and processed them into the response.
404
405 A lot of controllers are still using this method so the layout interface
406 supports it to change as little controller code as possible, while providing the
407 more intuitive C<add_stylesheets> method.
408
409 At the same time the following things need to be possible:
410
411 =over 4
412
413 =item 1.
414
415 Runtime additions.
416
417   $layout->add_stylesheets(...)
418
419 Since add_stylesheets adds to C<< $self->{stylesheets} >> there must be a way to read
420 from it. Currently this is the deprecated C<use_stylesheet>.
421
422 =item 2.
423
424 Overriding Callbacks
425
426 A leaf layout should be able to override a callback to return a list.
427
428 =item 3.
429
430 Sanitizing
431
432 C<stylesheets> needs to retain its sanitizing behaviour.
433
434 =item 4.
435
436 Aggregation
437
438 The standard implementation should be able to collect from sub layouts.
439
440 =item 5.
441
442 Preserving Inclusion Order
443
444 Since there is currently no standard way of mixing own content and including
445 sub layouts, this has to be done manually. Certain things like jquery get added
446 in L<SL::Layout::None> so that they get rendered first.
447
448 =back
449
450 The current implementation provides no good candidate for overriding in sub
451 classes, which should be changed. The other points work pretty well.
452
453 =head1 BUGS
454
455 * stylesheet/javascript interface is a horrible mess.
456
457 * It's currently not possible to do compositor layouts without assupmtions
458 about the position of the content. That's because the content will return
459 control to the actual controller, so the layouts need to know where to split
460 pre- and post-content.
461
462 =head1 AUTHOR
463
464 Sven Schöling E<lt>s.schoeling@linet-services.deE<gt>
465
466 =cut