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