]> git.imager.perl.org - bse.git/blame - site/cgi-bin/modules/BSE/Message.pm
allow editing image tags on the big image tool page
[bse.git] / site / cgi-bin / modules / BSE / Message.pm
CommitLineData
589b789c
TC
1package BSE::Message;
2use strict;
3use Carp qw/confess/;
ebc63b18
TC
4use BSE::DB;
5use BSE::Cfg;
9aecfc15 6use BSE::Cache;
ebec7b06 7use DevHelp::HTML;
c0bf9781 8use Scalar::Util qw(reftype blessed);
07edb12d
TC
9use overload
10 "&{}" => sub { my $self = $_[0]; return sub { $self->_old_msg(@_) } },
11 "bool" => sub { 1 };
ebc63b18 12
e0ed81d7 13our $VERSION = "1.008";
cb7fd78d 14
9aecfc15
TC
15my $single;
16
ebc63b18
TC
17=head1 NAME
18
19BSE::Message - BSE message catalog access.
20
21=head1 SYNOPSIS
22
23 my $msgs = BSE::Message->new;
24 my $text = $msgs->text($lang, $msgid);
25 my $text = $msgs->text($lang, $msgid, [ parameters ]);
26 my $text = $msgs->text($lang, $msgid, [ parameters ], $def);
27 my $html = $msgs->html($lang, $msgid);
28 my $html = $msgs->html($lang, $msgid, [ parameters ]);
29 my $html = $msgs->html($lang, $msgid, [ parameters ], $def);
30
31=cut
589b789c 32
9aecfc15
TC
33sub _new {
34 my ($class, %opts) = @_;
35
36 return bless
37 {
38 cache_age => BSE::Cfg->single->entry("messages", "cache_age", 60),
39 mycache => {},
40 cache => scalar(BSE::Cache->load),
41 }, $class;
42}
43
589b789c
TC
44sub new {
45 my ($class, %opts) = @_;
46
9aecfc15
TC
47 $single ||= $class->_new;
48
49 if ($opts{section}) {
50 $single->{section} = $opts{section};
51 }
ebc63b18 52
9aecfc15 53 return $single;
ebc63b18
TC
54}
55
56sub text {
57 my ($self, $lang, $msgid, $parms, $def) = @_;
58
d8913cb9
TC
59 $msgid =~ s/^msg://;
60
9aecfc15
TC
61 ref $self or $self = $self->new;
62
ebc63b18
TC
63 my $msg = $self->_get_replaced($lang, $msgid, $parms);
64 if ($msg) {
65 if ($msg->{formatting} eq 'body') {
66 require BSE::Formatter;
67 my $formatter = BSE::Formatter->new;
68 return $formatter->remove_format($msg->{message});
69 }
70
71 return $msg->{message};
72 }
73 else {
74 $def and return $def;
75 return;
76 }
77}
78
79sub html {
80 my ($self, $lang, $msgid, $parms, $def) = @_;
81
d8913cb9
TC
82 $msgid =~ s/^msg://;
83
9aecfc15
TC
84 ref $self or $self = $self->new;
85
ebc63b18
TC
86 my $msg = $self->_get_replaced($lang, $msgid, $parms);
87 if ($msg) {
88 if ($msg->{formatting} eq 'body') {
9785c9c4 89 require BSE::Generate;
ebc63b18
TC
90 require BSE::Template;
91 my $gen = Generate->new(cfg => BSE::Cfg->single);
92 my $templater = BSE::Template->templater(BSE::Cfg->single);
93 return $gen->format_body(acts => {},
e0ed81d7 94 articles => "BSE::TB::Articles",
ebc63b18
TC
95 text => $msg->{message},
96 templater => $templater);
97 }
98
99 return escape_html($msg->{message});
100 }
101 else {
102 $def and return $def;
103 return;
104 }
105}
106
107=item $self->get_replaced($lang, $msgid, $parms)
108
109Retrieve the base message text + formatting info, and replace parameters.
110
111Currently just replaces parameters, should also:
112
113=over
114
115=item *
116
117have a mechanism to replace messages eg.
118
119 %msg{bse/bar}
120
121=item *
122
123have a zero/single/plural mechanism, eg.:
124
125 %c1{zero text;single text;multiple text}
126
127=back
128
129=cut
130
131sub _get_replaced {
132 my ($self, $lang, $msgid, $parms) = @_;
133
134 my $msg = $self->_get_base($lang, $msgid)
135 or return;
136
137 $parms ||= [];
495114d1 138 $msg->{message} =~ s/%(%|[0-9]+:(?:\{\w+\})?[-+ #0]?[0-9]*(?:\.[0-9]+)?[duoxXfFeEgGs])/
ebc63b18
TC
139 $1 eq "%" ? "%" : $self->_value($msg, $1, $parms)/ge;
140
141 return $msg;
142}
143
144sub _value {
145 my ($self, $msg, $code, $parms) = @_;
146
147 my ($index, $format) = $code =~ /^([0-9]+):(.*)$/;
148 $index >= 1 && $index <= @$parms
149 or return "(bad index $index in %$code)";
150
495114d1
TC
151 my $method = "describe";
152 if ($format =~ s/^\{(\w+)\}//) {
153 my $work = $1;
154 unless ($work =~ /^(remove|save|new)$/) {
155 $method = $work;
156 }
157 }
158
ebc63b18 159 my $value = $parms->[$index-1];
495114d1 160 if (ref $value) {
ebc63b18 161 local $@;
c0bf9781
TC
162 if (blessed $value) {
163 my $good = eval { $value = $value->$method; 1; };
164 unless ($good) {
165 return "(Bad parameter $index - blessed but no $method)";
166 }
167 }
168 elsif (reftype $value eq "HASH") {
169 defined $value->{$method}
170 or return "(Unknown key $method for $index)";
171 $value = $value->{$method};
172 }
173 else {
174 return "(Can't handle ".reftype($value)." values)";
ebc63b18
TC
175 }
176 }
177
178 return sprintf "%$format", $value;
179}
180
181=item $self->get_base($lang, $msgid)
182
183Retrieve the base message text + formatting info.
184
185=cut
186
187sub _get_base {
188 my ($self, $lang, $msgid) = @_;
189
190 defined $lang or $lang = BSE::Cfg->single->entry("basic", "language_code", "en");
191
192 if ($self->{cache_age} == 0) {
193 return $self->_get_base_low($lang, $msgid);
194 }
195
196 my $key = "$lang.$msgid";
197 my $entry = $self->{mycache}{$key};
198 if (!$entry && $self->{cache}) {
199 $entry = $self->{cache}->get("msg-$key");
200 }
201
202 my $now = time;
203 if ($entry) {
204 if ($entry->[0] < $now - $self->{cache_age}) {
205 undef $entry;
206 }
207 }
208
07edb12d
TC
209 if ($entry) {
210 # clone the entry so text replacement doesn't mess us up
63e7203f 211 $entry->[1] or return;
055e6576 212 my %entry = %{$entry->[1]};
07edb12d
TC
213 return \%entry;
214 }
ebc63b18
TC
215
216 my $msg = $self->_get_base_low($lang, $msgid);
217 $entry = [ $now, $msg ];
218 $self->{mycache}{$key} = $entry;
219 if ($self->{cache}) {
220 $self->{cache}->set("msg-$key", $entry);
221 }
222
3f9c8a96
TC
223 $msg or return;
224
055e6576
TC
225 # clone so the caller doesn't modify cached value
226 my %entry = %$msg;
227 return \%entry;
ebc63b18
TC
228}
229
230sub _get_base_low {
231 my ($self, $lang, $msgid) = @_;
232
233 # build a list of languages to search
234 my @langs = $lang;
defb9881
TC
235 if ($lang =~ /^([a-z]+(?:[a-z]+))\./) {
236 push @langs, $1;
237 }
238 if ($lang =~ /^([a-z]+)_/i) {
ebc63b18
TC
239 push @langs, $1;
240 }
241
242 my $msg;
243 for my $search_lang (@langs) {
244 ($msg) = BSE::DB->query(bseGetMsgManaged => $msgid, $search_lang)
245 and return $msg;
246 }
247
248 for my $search_lang (@langs) {
249 ($msg) = BSE::DB->query(bseGetMsgDefault => $msgid, $search_lang)
250 and return $msg;
251 }
252
253 for my $fallback ($self->fallback) {
254 ($msg) = BSE::DB->query(bseGetMsgManaged => $msgid, $fallback)
255 and return $msg;
256
257 ($msg) = BSE::DB->query(bseGetMsgDefault => $msgid, "")
258 and return $msg;
259 }
260
261 return;
262}
263
264sub _old_msg {
265 my ($self, $msgid, $def, @parms) = @_;
266
267 my $msg = BSE::Cfg->single->entry("messages", "$self->{section}/$msgid");
268 if ($msg) {
269 $msg =~ s/\$([\d\$])/$1 eq '$' ? '$' : $parms[$1-1]/eg;
270 return $msg;
271 }
272
ebc63b18
TC
273 $msgid = "bse/$self->{section}/$msgid";
274 my $text = $self->text(undef, $msgid, \@parms);
275 $text and return $text;
276
277 return $def;
278}
279
280sub languages {
281 my ($self) = @_;
282
283 my $cfg = BSE::Cfg->single;
284 my %langs = $cfg->entries("languages");
285 delete $langs{fallback};
286 $langs{en} ||= "English";
287 my @langs = map +{ id => $_, name => $langs{$_} }, sort keys %langs;
288
289 return @langs;
290}
291
292sub fallback {
293 my ($self) = @_;
294
295 my $cfg = BSE::Cfg->single;
296 my $fallback = $cfg->entry("languages", "fallback", "en");
297 return split /,/, $fallback;
298}
299
300sub uncache {
9aecfc15 301 my ($self, $id) = @_;
ebc63b18 302
9aecfc15
TC
303 ref $self or $self = $self->new;
304
305 my $cache = $self->{cache}
306 or return;
ebc63b18
TC
307
308 for my $lang ($self->languages) {
309 $cache->delete("msg-$lang->{id}.$id");
310 }
589b789c
TC
311}
312
3131;