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