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