add dynallprods and dynallcats dynamic iterators
[bse.git] / site / cgi-bin / modules / BSE / Edit / Product.pm
CommitLineData
ca9aa2bf
TC
1package BSE::Edit::Product;
2use strict;
3use base 'BSE::Edit::Article';
4use Products;
5use HTML::Entities;
aefcabcb 6use BSE::Template;
0ec4ac8a 7use BSE::Util::Iterate;
ab3c22ff 8use DevHelp::HTML;
2076966c 9use BSE::CfgInfo 'product_options';
ca9aa2bf
TC
10
11my %money_fields =
12 (
13 retailPrice => "Retail price",
14 wholesalePrice => "Wholesale price",
15 gst => "GST",
16 );
17
18sub generator { 'Generate::Product' }
19
20sub base_template_dirs {
21 return ( "products" );
22}
23
24sub extra_templates {
25 my ($self, $article) = @_;
26
27 my @extras = $self->SUPER::extra_templates($article);
aefcabcb
TC
28 push @extras, 'shopitem.tmpl'
29 if grep -f "$_/shopitem.tmpl",
30 BSE::Template->template_dirs($self->{cfg});
ca9aa2bf 31
d64413ee
TC
32 my $extras = $self->{cfg}->entry('products', 'extra_templates');
33 push @extras, grep /\.(tmpl|html)$/i, split /,/, $extras
34 if $extras;
35
ca9aa2bf
TC
36 return @extras;
37}
38
39sub hash_tag {
40 my ($article, $arg) = @_;
41
42 my $value = $article->{$arg};
43 defined $value or $value = '';
7b81711b
TC
44 if ($value =~ /\cJ/ && $value =~ /\cM/) {
45 $value =~ tr/\cM//d;
46 }
ca9aa2bf
TC
47
48 return encode_entities($value);
49}
50
d7538448
TC
51sub iter_subs {
52 require BSE::TB::Subscriptions;
53 BSE::TB::Subscriptions->all;
54}
0ec4ac8a 55
ab3c22ff
TC
56sub tag_hash_mbcs {
57 my ($object, $args) = @_;
58
59 my $value = $object->{$args};
60 defined $value or $value = '';
61 if ($value =~ /\cJ/ && $value =~ /\cM/) {
62 $value =~ tr/\cM//d;
63 }
64 escape_html($value, '<>&"');
65}
66
ca9aa2bf
TC
67sub low_edit_tags {
68 my ($self, $acts, $req, $article, $articles, $msg, $errors) = @_;
ab3c22ff 69
2076966c
TC
70 my $product_opts = product_options($req->cfg);
71
ab3c22ff
TC
72 my $cfg = $req->cfg;
73 my $mbcs = $cfg->entry('html', 'mbcs', 0);
74 my $tag_hash = $mbcs ? \&tag_hash_mbcs : \&hash_tag;
0ec4ac8a 75 my $it = BSE::Util::Iterate->new;
ca9aa2bf
TC
76 return
77 (
ab3c22ff 78 product => [ $tag_hash, $article ],
ca9aa2bf
TC
79 $self->SUPER::low_edit_tags($acts, $req, $article, $articles, $msg,
80 $errors),
2076966c 81 alloptions => join(",", sort keys %$product_opts),
d7538448
TC
82 $it->make_iterator
83 ([ \&iter_subs, $req ], 'subscription', 'subscriptions'),
ca9aa2bf
TC
84 );
85}
86
87sub edit_template {
88 my ($self, $article, $cgi) = @_;
89
90 my $base = 'product';
91 my $t = $cgi->param('_t');
92 if ($t && $t =~ /^\w+$/) {
93 $base = $t;
94 }
95 return $self->{cfg}->entry('admin templates', $base,
96 "admin/edit_$base");
97}
98
99sub add_template {
100 my ($self, $article, $cgi) = @_;
101
102 return $self->{cfg}->entry('admin templates', 'add_product',
918735d1 103 'admin/edit_product');
ca9aa2bf
TC
104}
105
106sub validate_parent {
107 my ($self, $data, $articles, $parent, $rmsg) = @_;
108
109 my $shopid = $self->{cfg}->entryErr('articles', 'shop');
110 unless ($parent &&
111 $parent->{generator} eq 'Generate::Catalog') {
112 $$rmsg = "Products must be in a catalog (not $parent->{generator})";
113 return;
114 }
115
116 return $self->SUPER::validate_parent($data, $articles, $parent, $rmsg);
117}
118
119sub _validate_common {
120 my ($self, $data, $articles, $errors) = @_;
121
122 for my $col (keys %money_fields) {
123 my $value = $data->{$col};
0ec4ac8a 124 defined $value or next;
ca9aa2bf
TC
125 unless ($value =~ /^\d+(\.\d{1,2})?\s*/) {
126 $errors->{$col} = "$money_fields{$col} invalid";
127 }
128 }
2076966c 129
0ec4ac8a 130 if (defined $data->{options}) {
2076966c
TC
131 my $avail_options = product_options($self->{cfg});
132
133 my @bad_opts = grep !$avail_options->{$_},
0ec4ac8a
TC
134 split /,/, $data->{options};
135 if (@bad_opts) {
136 $errors->{options} = "Bad product options '". join(",", @bad_opts)."' entered";
137 }
138 }
ca9aa2bf 139
0ec4ac8a
TC
140 my @subs;
141 for my $sub_field (qw(subscription_id subscription_required)) {
142 my $value = $data->{$sub_field};
143 defined $value or next;
144 if ($value ne '-1') {
d7538448
TC
145 require BSE::TB::Subscriptions;
146 @subs = BSE::TB::Subscriptions->all unless @subs;
147 unless (grep $_->{subscription_id} == $value, @subs) {
0ec4ac8a 148 $errors->{$sub_field} = "Invalid $sub_field value";
d7538448 149 }
0ec4ac8a
TC
150 }
151 }
152 if (defined $data->{subscription_period}) {
ab2cd916
TC
153 my $sub = $data->{subscription_id};
154 if ($data->{subscription_period} !~ /^\d+$/) {
0ec4ac8a
TC
155 $errors->{subscription_period} = "Invalid subscription period, it must be the number of months to subscribe";
156 }
ab2cd916
TC
157 elsif ($sub != -1 && $data->{subscription_period} < 1) {
158 $errors->{subscription_period} = "Subscription period must be 1 or more when a subscription is selected";
159 }
0ec4ac8a
TC
160 }
161 if (defined $data->{subscription_usage}) {
162 unless ($data->{subscription_usage} =~ /^[123]$/) {
163 $errors->{subscription_usage} = "Invalid subscription usage";
164 }
ca9aa2bf
TC
165 }
166
167 return !keys %$errors;
168}
169
170sub validate {
918735d1 171 my ($self, $data, $articles, $errors) = @_;
ca9aa2bf 172
918735d1 173 my $ok = $self->SUPER::validate($data, $articles, $errors);
ca9aa2bf
TC
174 $self->_validate_common($data, $articles, $errors);
175
7fa9d326 176 for my $field (qw(title description body)) {
ca9aa2bf
TC
177 unless ($data->{$field} =~ /\S/) {
178 $errors->{$field} = "No $field entered";
179 }
180 }
181
182 return $ok && !keys %$errors;
183}
184
185sub validate_old {
918735d1 186 my ($self, $article, $data, $articles, $errors) = @_;
ca9aa2bf 187
918735d1 188 $self->SUPER::validate($data, $articles, $errors)
ca9aa2bf
TC
189 or return;
190
191 return !keys %$errors;
192}
193
194sub possible_parents {
195 my ($self, $article, $articles) = @_;
196
197 my %labels;
198 my @values;
199
200 my $shopid = $self->{cfg}->entryErr('articles', 'shop');
201 # the parents of a catalog can be other catalogs or the shop
202 my $shop = $articles->getByPkey($shopid);
203 my @work = [ $shopid, $shop->{title} ];
204 while (@work) {
205 my ($id, $title) = @{pop @work};
206 push(@values, $id);
207 $labels{$id} = $title;
208 push @work, map [ $_->{id}, $title.' / '.$_->{title} ],
209 sort { $b->{displayOrder} <=> $a->{displayOrder} }
210 grep $_->{generator} eq 'Generate::Catalog',
211 $articles->getBy(parentid=>$id);
212 }
a5e3fc4b
TC
213 unless ($shop->{generator} eq 'Generate::Catalog') {
214 shift @values;
215 delete $labels{$shopid};
216 }
ca9aa2bf
TC
217 return (\@values, \%labels);
218}
219
220sub table_object {
221 my ($self, $articles) = @_;
222
223 'Products';
224}
225
226sub get_article {
227 my ($self, $articles, $article) = @_;
228
229 return Products->getByPkey($article->{id});
230}
231
95989433
TC
232sub default_link_path {
233 my ($self, $article) = @_;
234
235 $self->{cfg}->entry('uri', 'shop', '/shop');
236}
237
ca9aa2bf
TC
238sub make_link {
239 my ($self, $article) = @_;
240
57d988af
TC
241# Modified by adrian
242 my $urlbase = '';
243 if ($self->{cfg}->entry('shop', 'secureurl_articles', 1)) {
244 $urlbase = $self->{cfg}->entryVar('site', 'secureurl');
245 }
246# end adrian
efcc5a30
TC
247
248 if ($article->is_dynamic) {
b873a8fa 249 return "$urlbase/cgi-bin/page.pl?page=$article->{id}&title=".escape_uri($article->{title});
efcc5a30
TC
250 }
251
252 my $shop_uri = $self->link_path($article);
ca9aa2bf
TC
253 return $urlbase.$shop_uri."/shop$article->{id}.html";
254}
255
256sub _fill_product_data {
257 my ($self, $req, $data, $src) = @_;
258
259 for my $money_col (qw(retailPrice wholesalePrice gst)) {
260 if (exists $src->{$money_col}) {
261 if ($src->{$money_col} =~ /^\d+(\.\d\d)?\s*/) {
262 $data->{$money_col} = 100 * $src->{$money_col};
263 }
264 else {
265 $data->{$money_col} = 0;
266 }
267 }
268 }
269 if (exists $src->{leadTime}) {
270 $src->{leadTime} =~ /^\d+\s*$/
271 or $src->{leadTime} = 0;
272 $data->{leadTime} = $src->{leadTime};
273 }
74b21f6d 274 if (exists $src->{description} && length $src->{description}) {
4010d92e 275 if ($data->{id}) {
74b21f6d
TC
276 if ($req->user_can('edit_field_edit_description', $data)) {
277 $data->{description} = $src->{description};
278 }
279 }
280 }
281 if (exists $src->{product_code} && length $src->{product_code}) {
282 if ($data->{id}) {
283 if ($req->user_can('edit_field_edit_product_code', $data)) {
284 $data->{product_code} = $src->{product_code};
4010d92e
TC
285 }
286 }
287 }
0ec4ac8a
TC
288 for my $field (qw(options subscription_id subscription_period
289 subscription_usage subscription_required)) {
290 if (exists $src->{$field}) {
291 $data->{$field} = $src->{$field};
292 }
293 elsif ($data == $src) {
294 # use the default
295 $data->{$field} = $self->default_value($req, $data, $field);
296 }
918735d1 297 }
ca9aa2bf
TC
298}
299
300sub fill_new_data {
301 my ($self, $req, $data, $articles) = @_;
302
303 $self->_fill_product_data($req, $data, $data);
304
305 return $self->SUPER::fill_new_data($req, $data, $articles);
306}
307
308sub fill_old_data {
309 my ($self, $req, $article, $src) = @_;
310
311 $self->_fill_product_data($req, $article, $src);
312
313 return $self->SUPER::fill_old_data($req, $article, $src);
314}
315
caa7299c
TC
316sub default_template {
317 my ($self, $article, $cfg, $templates) = @_;
318
d64413ee 319 my $template = $cfg->entry('products', 'template');
caa7299c
TC
320 return $template
321 if $template && grep $_ eq $template, @$templates;
322
323 return $self->SUPER::default_template($article, $cfg, $templates);
324}
325
6473c56f
TC
326sub can_remove {
327 my ($self, $req, $article, $articles, $rmsg) = @_;
328
4175638b
TC
329 require BSE::TB::OrderItems;
330 my @items = BSE::TB::OrderItems->getBy(productId=>$article->{id});
6473c56f
TC
331 if (@items) {
332 $$rmsg = "There are orders for this product. It cannot be deleted.";
333 return;
334 }
335
336 return $self->SUPER::can_remove($req, $article, $articles, $rmsg);
337}
338
918735d1
TC
339sub flag_sections {
340 my ($self) = @_;
341
342 return ( 'product flags', $self->SUPER::flag_sections );
343}
344
0ec4ac8a
TC
345my %defaults =
346 (
347 options => '',
348 subscription_id => -1,
349 subscription_required => -1,
350 subscription_period => 1,
351 subscription_usage => 3,
352 retailPrice => 0,
353 );
354
355sub default_value {
356 my ($self, $req, $article, $col) = @_;
357
358 my $value = $self->SUPER::default_value($req, $article, $col);
359 defined $value and return $value;
360
361 exists $defaults{$col} and return $defaults{$col};
362
363 return;
364}
365
deae2a52
TC
366sub type_default_value {
367 my ($self, $req, $col) = @_;
368
369 my $value = $req->cfg->entry('product defaults', $col);
370 defined $value and return $value;
371
372 return $self->SUPER::type_default_value($req, $col);
373}
374
ca9aa2bf 3751;