don't confuse the user with the lastModified checks
[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;
3f9c8a96 8use BSE::Util::HTML;
2076966c 9use BSE::CfgInfo 'product_options';
58baa27b
TC
10use BSE::Util::Tags qw(tag_hash);
11
75e51df4 12our $VERSION = "1.001";
cb7fd78d 13
58baa27b
TC
14=head1 NAME
15
16BSE::Edit::Product - tags and actions for editing BSE products
17
18=head1 SYNOPSIS
19
20 http://www.example.com/cgi-bin/admin/add.pl ...
21
22=head1 DESCRIPTION
23
24Article editor subclass for editing Products.
25
26=cut
ca9aa2bf
TC
27
28my %money_fields =
29 (
30 retailPrice => "Retail price",
31 wholesalePrice => "Wholesale price",
32 gst => "GST",
33 );
34
35sub generator { 'Generate::Product' }
36
37sub base_template_dirs {
38 return ( "products" );
39}
40
41sub extra_templates {
42 my ($self, $article) = @_;
43
44 my @extras = $self->SUPER::extra_templates($article);
aefcabcb
TC
45 push @extras, 'shopitem.tmpl'
46 if grep -f "$_/shopitem.tmpl",
47 BSE::Template->template_dirs($self->{cfg});
ca9aa2bf 48
d64413ee
TC
49 my $extras = $self->{cfg}->entry('products', 'extra_templates');
50 push @extras, grep /\.(tmpl|html)$/i, split /,/, $extras
51 if $extras;
52
ca9aa2bf
TC
53 return @extras;
54}
55
56sub hash_tag {
57 my ($article, $arg) = @_;
58
59 my $value = $article->{$arg};
60 defined $value or $value = '';
7b81711b
TC
61 if ($value =~ /\cJ/ && $value =~ /\cM/) {
62 $value =~ tr/\cM//d;
63 }
ca9aa2bf
TC
64
65 return encode_entities($value);
66}
67
d7538448
TC
68sub iter_subs {
69 require BSE::TB::Subscriptions;
70 BSE::TB::Subscriptions->all;
71}
0ec4ac8a 72
58baa27b
TC
73sub iter_option_values {
74 my ($self, $rcurrent_option, $args) = @_;
75
76 $$rcurrent_option
77 or return;
78
79 return $$rcurrent_option->values;
80}
81
ab3c22ff
TC
82sub tag_hash_mbcs {
83 my ($object, $args) = @_;
84
85 my $value = $object->{$args};
86 defined $value or $value = '';
87 if ($value =~ /\cJ/ && $value =~ /\cM/) {
88 $value =~ tr/\cM//d;
89 }
90 escape_html($value, '<>&"');
91}
92
58baa27b
TC
93sub tag_dboptionvalue_move {
94 my ($self, $req, $article, $rvalues, $rindex, $args) = @_;
95
96 $$rindex >= 0 && $$rindex < @$rvalues
97 or return "** dboptionvalue_move only in dboption_values iterator **";
98
99 my $my_id = $rvalues->[$$rindex]{id};
100 my $base_url = "$ENV{SCRIPT_NAME}?id=$article->{id}&value_id=$my_id&_csrfp=".$req->get_csrf_token("admin_move_option_value") . "&";
101
102 my $t = $req->cgi->param('_t');
103 $t && $t =~ /^\w+$/
104 and $base_url .= "_t=$t&";
105
106 my $up_url = '';
107 if ($$rindex > 0) {
108 $up_url = $base_url . "a_option_value_moveup=1";
109 }
110 my $down_url = '';
111 if ($$rindex < $#$rvalues) {
112 $down_url = $base_url . "a_option_value_movedown=1";
113 }
114
115 my $refresh = $self->refresh_url($article, $req->cgi);
116
117 require BSE::Arrows;
118 return BSE::Arrows::make_arrows($req->cfg, $down_url, $up_url, $refresh, $args, id => $my_id, id_prefix => "prodoptvaluemove");
119}
120
121sub tag_dboption_move {
122 my ($self, $req, $article, $roptions, $rindex, $args) = @_;
123
124 $$rindex >= 0 && $$rindex < @$roptions
125 or return "** dboption_move only in dboptions iterator **";
126
127 my $my_id = $roptions->[$$rindex]{id};
128 my $base_url = "$ENV{SCRIPT_NAME}?id=$article->{id}&option_id=$my_id&_csrfp=".$req->get_csrf_token("admin_move_option") . "&";
129
130 my $t = $req->cgi->param('_t');
131 $t && $t =~ /^\w+$/
132 and $base_url .= "_t=$t&";
133
134 my $up_url = '';
135 if ($$rindex > 0) {
136 $up_url = $base_url . "a_option_moveup=1";
137 }
138 my $down_url = '';
139 if ($$rindex < $#$roptions) {
140 $down_url = $base_url . "a_option_movedown=1";
141 }
142
143 my $refresh = $self->refresh_url($article, $req->cgi);
144
145 require BSE::Arrows;
146 return BSE::Arrows::make_arrows($req->cfg, $down_url, $up_url, $refresh, $args, id => $my_id, id_prefix => "prodoptmove");
147}
148
149=head1 Edit tags
150
151These a tags available on admin/edit_* pages specific to products.
152
153=over
154
155=item *
156
157product I<field> - display the given field from the product being edited.
158
159=item *
160
161iterator begin dboptions ... dboption I<field> ... iterator end dboptions
162
163- iterate over the existing database stored options for the product
164
165=item *
166
167dboption_move - display arrows to move the current dboption. The span
168for the arrows is given an id of "prodoptmoveI<option-id>" by default.
169
170=item *
171
172iterator begin dboptionvalues ... dboptionvalue I<field> ... iterator end dboptionvalues
173
174- iterate over the values for the current dboption
175
176=item *
177
178dboptionvalue_move - display arrows to move the current dboption. The
179span for the arrows is given an id of "prodoptvaluemoveI<value-id>"
180by default.
181
182=item *
183
184dboptionsjson - returns the product options as JSON.
185
186=back
187
188=cut
189
ca9aa2bf
TC
190sub low_edit_tags {
191 my ($self, $acts, $req, $article, $articles, $msg, $errors) = @_;
ab3c22ff 192
2076966c
TC
193 my $product_opts = product_options($req->cfg);
194
ab3c22ff
TC
195 my $cfg = $req->cfg;
196 my $mbcs = $cfg->entry('html', 'mbcs', 0);
197 my $tag_hash = $mbcs ? \&tag_hash_mbcs : \&hash_tag;
58baa27b
TC
198 my $current_option;
199 my @dboptions;
200 my $dboption_index;
201 my @dboption_values;
202 my $dboption_value_index;
203 my $current_option_value;
0ec4ac8a 204 my $it = BSE::Util::Iterate->new;
ca9aa2bf
TC
205 return
206 (
ab3c22ff 207 product => [ $tag_hash, $article ],
ca9aa2bf
TC
208 $self->SUPER::low_edit_tags($acts, $req, $article, $articles, $msg,
209 $errors),
2076966c 210 alloptions => join(",", sort keys %$product_opts),
d7538448
TC
211 $it->make_iterator
212 ([ \&iter_subs, $req ], 'subscription', 'subscriptions'),
58baa27b
TC
213 $it->make
214 (
215 single => "dboption",
216 plural => "dboptions",
217 store => \$current_option,
218 data => \@dboptions,
219 index => \$dboption_index,
220 code => [ db_options => $article ],
221 ),
222 dboption_move =>
223 [
224 tag_dboption_move =>
225 $self, $req, $article, \@dboptions, \$dboption_index
226 ],
227 $it->make
228 (
229 single => "dboptionvalue",
230 plural => "dboptionvalues",
231 data => \@dboption_values,
232 index => \$dboption_value_index,
233 store => \$current_option_value,
234 code => [ iter_option_values => $self, \$current_option ],
235 nocache => 1,
236 ),
237 dboptionsjson => [ tag_dboptionsjson => $self, $article ],
238 dboptionvalue_move =>
239 [
240 tag_dboptionvalue_move =>
241 $self, $req, $article, \@dboption_values, \$dboption_value_index
242 ],
ca9aa2bf
TC
243 );
244}
245
246sub edit_template {
247 my ($self, $article, $cgi) = @_;
248
249 my $base = 'product';
250 my $t = $cgi->param('_t');
251 if ($t && $t =~ /^\w+$/) {
252 $base = $t;
253 }
254 return $self->{cfg}->entry('admin templates', $base,
255 "admin/edit_$base");
256}
257
258sub add_template {
259 my ($self, $article, $cgi) = @_;
260
261 return $self->{cfg}->entry('admin templates', 'add_product',
918735d1 262 'admin/edit_product');
ca9aa2bf
TC
263}
264
265sub validate_parent {
266 my ($self, $data, $articles, $parent, $rmsg) = @_;
267
268 my $shopid = $self->{cfg}->entryErr('articles', 'shop');
269 unless ($parent &&
270 $parent->{generator} eq 'Generate::Catalog') {
271 $$rmsg = "Products must be in a catalog (not $parent->{generator})";
272 return;
273 }
274
275 return $self->SUPER::validate_parent($data, $articles, $parent, $rmsg);
276}
277
278sub _validate_common {
279 my ($self, $data, $articles, $errors) = @_;
280
281 for my $col (keys %money_fields) {
282 my $value = $data->{$col};
0ec4ac8a 283 defined $value or next;
ca9aa2bf
TC
284 unless ($value =~ /^\d+(\.\d{1,2})?\s*/) {
285 $errors->{$col} = "$money_fields{$col} invalid";
286 }
287 }
2076966c 288
0ec4ac8a 289 if (defined $data->{options}) {
2076966c
TC
290 my $avail_options = product_options($self->{cfg});
291
292 my @bad_opts = grep !$avail_options->{$_},
0ec4ac8a
TC
293 split /,/, $data->{options};
294 if (@bad_opts) {
295 $errors->{options} = "Bad product options '". join(",", @bad_opts)."' entered";
296 }
297 }
ca9aa2bf 298
0ec4ac8a
TC
299 my @subs;
300 for my $sub_field (qw(subscription_id subscription_required)) {
301 my $value = $data->{$sub_field};
302 defined $value or next;
303 if ($value ne '-1') {
d7538448
TC
304 require BSE::TB::Subscriptions;
305 @subs = BSE::TB::Subscriptions->all unless @subs;
306 unless (grep $_->{subscription_id} == $value, @subs) {
0ec4ac8a 307 $errors->{$sub_field} = "Invalid $sub_field value";
d7538448 308 }
0ec4ac8a
TC
309 }
310 }
311 if (defined $data->{subscription_period}) {
ab2cd916
TC
312 my $sub = $data->{subscription_id};
313 if ($data->{subscription_period} !~ /^\d+$/) {
0ec4ac8a
TC
314 $errors->{subscription_period} = "Invalid subscription period, it must be the number of months to subscribe";
315 }
ab2cd916
TC
316 elsif ($sub != -1 && $data->{subscription_period} < 1) {
317 $errors->{subscription_period} = "Subscription period must be 1 or more when a subscription is selected";
318 }
0ec4ac8a
TC
319 }
320 if (defined $data->{subscription_usage}) {
321 unless ($data->{subscription_usage} =~ /^[123]$/) {
322 $errors->{subscription_usage} = "Invalid subscription usage";
323 }
ca9aa2bf
TC
324 }
325
326 return !keys %$errors;
327}
328
329sub validate {
918735d1 330 my ($self, $data, $articles, $errors) = @_;
ca9aa2bf 331
918735d1 332 my $ok = $self->SUPER::validate($data, $articles, $errors);
ca9aa2bf
TC
333 $self->_validate_common($data, $articles, $errors);
334
7fa9d326 335 for my $field (qw(title description body)) {
ca9aa2bf
TC
336 unless ($data->{$field} =~ /\S/) {
337 $errors->{$field} = "No $field entered";
338 }
339 }
340
341 return $ok && !keys %$errors;
342}
343
344sub validate_old {
918735d1 345 my ($self, $article, $data, $articles, $errors) = @_;
ca9aa2bf 346
0d7dc36d 347 $self->SUPER::validate_old($article, $data, $articles, $errors)
ca9aa2bf
TC
348 or return;
349
350 return !keys %$errors;
351}
352
353sub possible_parents {
354 my ($self, $article, $articles) = @_;
355
356 my %labels;
357 my @values;
358
359 my $shopid = $self->{cfg}->entryErr('articles', 'shop');
360 # the parents of a catalog can be other catalogs or the shop
361 my $shop = $articles->getByPkey($shopid);
362 my @work = [ $shopid, $shop->{title} ];
363 while (@work) {
364 my ($id, $title) = @{pop @work};
365 push(@values, $id);
366 $labels{$id} = $title;
367 push @work, map [ $_->{id}, $title.' / '.$_->{title} ],
368 sort { $b->{displayOrder} <=> $a->{displayOrder} }
369 grep $_->{generator} eq 'Generate::Catalog',
370 $articles->getBy(parentid=>$id);
371 }
a5e3fc4b
TC
372 unless ($shop->{generator} eq 'Generate::Catalog') {
373 shift @values;
374 delete $labels{$shopid};
375 }
ca9aa2bf
TC
376 return (\@values, \%labels);
377}
378
379sub table_object {
380 my ($self, $articles) = @_;
381
382 'Products';
383}
384
385sub get_article {
386 my ($self, $articles, $article) = @_;
387
388 return Products->getByPkey($article->{id});
389}
390
95989433
TC
391sub default_link_path {
392 my ($self, $article) = @_;
393
394 $self->{cfg}->entry('uri', 'shop', '/shop');
395}
396
ca9aa2bf
TC
397sub make_link {
398 my ($self, $article) = @_;
399
75e51df4
TC
400 $article->is_linked
401 or return "";
402
57d988af
TC
403# Modified by adrian
404 my $urlbase = '';
405 if ($self->{cfg}->entry('shop', 'secureurl_articles', 1)) {
406 $urlbase = $self->{cfg}->entryVar('site', 'secureurl');
407 }
408# end adrian
efcc5a30
TC
409
410 if ($article->is_dynamic) {
b873a8fa 411 return "$urlbase/cgi-bin/page.pl?page=$article->{id}&title=".escape_uri($article->{title});
efcc5a30
TC
412 }
413
414 my $shop_uri = $self->link_path($article);
ca9aa2bf
TC
415 return $urlbase.$shop_uri."/shop$article->{id}.html";
416}
417
418sub _fill_product_data {
419 my ($self, $req, $data, $src) = @_;
420
421 for my $money_col (qw(retailPrice wholesalePrice gst)) {
422 if (exists $src->{$money_col}) {
423 if ($src->{$money_col} =~ /^\d+(\.\d\d)?\s*/) {
424 $data->{$money_col} = 100 * $src->{$money_col};
425 }
426 else {
427 $data->{$money_col} = 0;
428 }
429 }
430 }
431 if (exists $src->{leadTime}) {
432 $src->{leadTime} =~ /^\d+\s*$/
433 or $src->{leadTime} = 0;
434 $data->{leadTime} = $src->{leadTime};
435 }
74b21f6d 436 if (exists $src->{description} && length $src->{description}) {
4010d92e 437 if ($data->{id}) {
74b21f6d
TC
438 if ($req->user_can('edit_field_edit_description', $data)) {
439 $data->{description} = $src->{description};
440 }
441 }
442 }
443 if (exists $src->{product_code} && length $src->{product_code}) {
444 if ($data->{id}) {
445 if ($req->user_can('edit_field_edit_product_code', $data)) {
446 $data->{product_code} = $src->{product_code};
4010d92e
TC
447 }
448 }
449 }
0ec4ac8a 450 for my $field (qw(options subscription_id subscription_period
306eb97a
AMS
451 subscription_usage subscription_required
452 weight length width height)) {
0ec4ac8a
TC
453 if (exists $src->{$field}) {
454 $data->{$field} = $src->{$field};
455 }
456 elsif ($data == $src) {
457 # use the default
458 $data->{$field} = $self->default_value($req, $data, $field);
459 }
918735d1 460 }
ca9aa2bf
TC
461}
462
463sub fill_new_data {
464 my ($self, $req, $data, $articles) = @_;
465
466 $self->_fill_product_data($req, $data, $data);
467
468 return $self->SUPER::fill_new_data($req, $data, $articles);
469}
470
471sub fill_old_data {
472 my ($self, $req, $article, $src) = @_;
473
474 $self->_fill_product_data($req, $article, $src);
475
476 return $self->SUPER::fill_old_data($req, $article, $src);
477}
478
caa7299c
TC
479sub default_template {
480 my ($self, $article, $cfg, $templates) = @_;
481
d64413ee 482 my $template = $cfg->entry('products', 'template');
caa7299c
TC
483 return $template
484 if $template && grep $_ eq $template, @$templates;
485
486 return $self->SUPER::default_template($article, $cfg, $templates);
487}
488
918735d1
TC
489sub flag_sections {
490 my ($self) = @_;
491
492 return ( 'product flags', $self->SUPER::flag_sections );
493}
494
6d91d9dc
TC
495sub shop_article { 1 }
496
0ec4ac8a
TC
497my %defaults =
498 (
499 options => '',
500 subscription_id => -1,
501 subscription_required => -1,
502 subscription_period => 1,
503 subscription_usage => 3,
504 retailPrice => 0,
4ad55bda
TC
505 product_code => '',
506 weight => 0,
507 length => 0,
508 height => 0,
509 width => 0,
0ec4ac8a
TC
510 );
511
512sub default_value {
513 my ($self, $req, $article, $col) = @_;
514
515 my $value = $self->SUPER::default_value($req, $article, $col);
516 defined $value and return $value;
517
518 exists $defaults{$col} and return $defaults{$col};
519
520 return;
521}
522
deae2a52
TC
523sub type_default_value {
524 my ($self, $req, $col) = @_;
525
526 my $value = $req->cfg->entry('product defaults', $col);
527 defined $value and return $value;
528
529 return $self->SUPER::type_default_value($req, $col);
530}
531
58baa27b
TC
532my %option_fields =
533 (
534 name =>
535 {
536 description => "Option name",
537 required => 1,
538 rules => "dh_one_line",
085b34a0 539 maxlength => 255,
58baa27b
TC
540 },
541 value1 =>
542 {
543 description => "Value 1",
544 rules => "dh_one_line",
085b34a0 545 maxlength => 255,
58baa27b
TC
546 },
547 value2 =>
548 {
549 description => "Value 2",
550 rules => "dh_one_line",
085b34a0 551 maxlength => 255,
58baa27b
TC
552 },
553 value3 =>
554 {
555 description => "Value 3",
556 rules => "dh_one_line",
085b34a0 557 maxlength => 255,
58baa27b
TC
558 },
559 value4 =>
560 {
561 description => "Value 4",
562 rules => "dh_one_line",
085b34a0 563 maxlength => 255,
58baa27b
TC
564 },
565 value5 =>
566 {
567 description => "Value 5",
568 rules => "dh_one_line",
085b34a0 569 maxlength => 255,
58baa27b
TC
570 },
571 );
572
573=head1 Targets
574
575Actions you can request from add.pl for products.
576
577=over
578
579=item a_add_option
580
581Add a new product option.
582
583On failure perform a service error.
584
585Requires _csrfp for admin_add_option
586
587For Ajax requests (or with a _ parameter) returns JSON like:
588
589 {
590 success: 1,
591 option: { <option data> },
592 values: [ { value data }, { value data }, ... ]
593 }
594
595Parameters:
596
597=over
598
599=item *
600
601id - Article id
602
603=item *
604
605name - Name of the option (required)
606
607=item *
608
609value1 .. value5 - if any of these are non-blank they are added to the
610option as values.
611
612=back
613
9b3a5df0
TC
614Permission required: bse_edit_prodopt_add
615
58baa27b
TC
616=cut
617
618sub req_add_option {
619 my ($self, $req, $article, $articles, $msg, $errors) = @_;
620
621 $req->check_csrf('admin_add_option')
622 or return $self->csrf_error($req, $article, "admin_add_option", "Add Product Option");
623
9b3a5df0
TC
624 $req->user_can(bse_edit_prodopt_add => $article)
625 or return $self->_service_error($req, $article, $articles, "Insufficient product access to add options");
626
58baa27b
TC
627 my %errors;
628 $req->validate(fields => \%option_fields,
629 errors => \%errors);
630 keys %errors
631 and return $self->_service_error($req, $article, $articles, undef,
632 \%errors);
633
634 my $cgi = $req->cgi;
635 require BSE::TB::ProductOptions;
636 require BSE::TB::ProductOptionValues;
637 my $option = BSE::TB::ProductOptions->make
638 (
639 product_id => $article->{id},
640 name => scalar($cgi->param('name')),
641 display_order => time,
642 );
643
644 my $order = time;
645 my @values;
646 for my $value_key (sort grep /^value/, keys %option_fields) {
647 print STDERR "fetching $value_key\n";
648 my ($value) = $cgi->param($value_key);
649 if (defined $value && $value =~ /\S/) {
650 my $entry = BSE::TB::ProductOptionValues->make
651 (
652 product_option_id => $option->{id},
653 value => $value,
654 display_order => $order,
655 );
656 push @values, $entry;
657 ++$order;
658 }
659 }
660
661 $req->is_ajax
662 and return $req->json_content
663 (
664 success => 1,
665 option => $option->data_only,
666 values => [ map $_->data_only, @values ]
667 );
668
669 return $self->refresh($article, $cgi, undef, "Option added");
670}
671
672my %option_id =
673 (
674 option_id =>
675 {
676 rules => "required;positiveint",
677 },
678 );
679
680sub _get_option {
681 my ($self, $req, $article, $errors) = @_;
682
683 my $option;
684 my $cgi = $req->cgi;
685 $req->validate(fields => \%option_id,
686 errors => $errors);
4de47893
TC
687 my @option_ids = $cgi->param("option_id");
688 unless ($errors->{option_id}) {
689 @option_ids == 1
690 or $errors->{option_id} = "This request accepts only one option_id";
691 }
58baa27b
TC
692 unless ($errors->{option_id}) {
693 require BSE::TB::ProductOptions;
694 $option = BSE::TB::ProductOptions->getByPkey($cgi->param("option_id"));
695 $option
696 or $errors->{option_id} = "Unknown option id";
697 }
698 unless ($errors->{option_id}) {
699 $option->{product_id} = $article->{id}
700 or $errors->{option_id} = "Option doesn't belong to this product";
701 }
702 $errors->{option_id}
703 and return;
704
705 return $option;
706}
707
708sub _common_option {
709 my ($self, $template, $req, $article, $articles, $msg, $errors) = @_;
710
711 my %errors;
712 my $option = $self->_get_option($req, $article, \%errors);
713 keys %errors
714 and return $self->_service_error($req, $article, $articles, undef, \%errors);
715
716 my $it = BSE::Util::Iterate->new;
717 my %acts;
718 %acts =
719 (
720 $self->low_edit_tags(\%acts, $req, $article, $articles, $msg, $errors),
721 option => [ \&tag_hash, $option ],
722 $it->make
723 (
724 single => "dboptionvalue",
725 plural => "dboptionvalues",
726 code => [ iter_option_values => $self, \$option ],
727 ),
728 );
729
730 return $req->dyn_response($template, \%acts);
731}
732
733=item a_edit_option
734
735Produce a form to edit the given option.
736
737Parameters:
738
739=over
740
741=item *
742
743id - article id
744
745=item *
746
747option_id - option id. This must belong to the product identified by
748id.
749
750=back
751
752Template: admin/prodopt_edit
753
9b3a5df0
TC
754Permission required: bse_edit_prodopt_edit
755
58baa27b
TC
756=cut
757
758sub req_edit_option {
759 my ($self, $req, $article, $articles, $msg, $errors) = @_;
760
9b3a5df0
TC
761 $req->user_can(bse_edit_prodopt_edit => $article)
762 or return $self->_service_error($req, $article, $articles, "Insufficient product access to edit options");
763
58baa27b
TC
764 return $self->_common_option('admin/prodopt_edit', $req, $article,
765 $articles, $msg, $errors);
766}
767
768my %option_name =
769 (
770 name =>
771 {
772 description => "Option name",
9eebac67 773 rules => "required;dh_one_line",
085b34a0 774 maxlength => 255,
58baa27b
TC
775 },
776 default_value =>
777 {
778 description => "Default Value",
9b3a5df0 779 rules => "positiveint"
58baa27b
TC
780 }
781 );
782
783my %option_value =
784 (
785 description => "Value",
9eebac67 786 rules => "required;dh_one_line",
085b34a0 787 maxlength => 255,
58baa27b
TC
788 );
789
790=item a_save_option
791
792Saves changes to an option.
793
794On failure perform a service error.
795
796Requires _csrfp for admin_save_option
797
798For Ajax requests (or with a _ parameter), returns JSON like:
799
800 {
801 success: 1,
802 option: { <option data> },
803 values: [ { value data, value data, ... } ]
804 }
805
806Parameters:
807
808=over
809
810=item *
811
812id - article id
813
814=item *
815
816option_id - id of the option to save, must belong to the product
817identified by id.
818
819=item *
820
821name - new value for the name field
822
823=item *
824
825default_value - id of the default value
826
827=item *
828
829save_enabled - if supplied and true, set enabled from the enabled
830parameter.
831
832=item *
833
834enabled - If supplied and true, enable the option, otherwise disable
835it. Ignored unless save_enabled is true.
836
837=item *
838
839valueI<value-id> - set the displayed value for the value record
5708b3ac
TC
840identified by I<value-id>. If these aren't supplied the values aren't
841changed.
58baa27b
TC
842
843=back
844
9b3a5df0
TC
845Permission required: bse_edit_prodopt_save
846
58baa27b
TC
847=cut
848
849sub req_save_option {
850 my ($self, $req, $article, $articles) = @_;
851
852 my $cgi = $req->cgi;
853
854 $req->check_csrf("admin_save_option")
855 or return $self->csrf_error($req, $article, "admin_save_option", "Save Product Option");
856
9b3a5df0
TC
857 $req->user_can(bse_edit_prodopt_edit => $article)
858 or return $self->_service_error($req, $article, $articles, "Insufficient product access to edit options");
859
58baa27b
TC
860 my %errors;
861 my $option = $self->_get_option($req, $article, \%errors);
862 keys %errors
863 and return $self->_service_error($req, $article, $articles, undef, \%errors);
9b3a5df0 864 $req->validate(fields => \%option_name,
58baa27b
TC
865 errors => \%errors);
866 my @values = $option->values;
867 my %fields = map {; "value$_->{id}" => \%option_value } @values;
9b3a5df0
TC
868 $req->validate(fields => \%fields,
869 errors => \%errors,
870 optional => 1);
58baa27b
TC
871 my $default_value = $cgi->param('default_value');
872 if (!$errors{default_value} && $default_value) {
873 grep $_->{id} == $default_value, @values
874 or $errors{default_value} = "Unknown value selected as default";
875 }
876 keys %errors
9b3a5df0 877 and return $self->_service_error($req, $article, $articles, undef, \%errors);
58baa27b
TC
878
879 my $name = $cgi->param("name");
880 defined $name
881 and $option->set_name($name);
882 defined $default_value
883 and $option->set_default_value($default_value);
884 if ($cgi->param("save_enabled")) {
885 my $enabled = $cgi->param("enabled") ? 1 : 0;
886 $option->set_enabled($enabled);
887 }
888 $option->save;
889 for my $value (@values) {
890 my $new_value = $cgi->param("value$value->{id}");
5708b3ac 891 if (defined $new_value && $new_value ne $value->value) {
58baa27b
TC
892 $value->set_value($new_value);
893 $value->save;
894 }
895 }
896
897 $req->is_ajax
898 and return $req->json_content
899 (
900 success => 1,
901 option => $option->data_only,
902 values => [ map $_->data_only, @values ],
903 );
904
905 return $self->refresh($article, $req->cgi, undef, "Option saved");
906}
907
908=item a_delconf_option
909
910Produce a form to confirm deletion of the given option.
911
912Parameters:
913
914=over
915
916=item *
917
918id - article id
919
920=item *
921
922option_id - option id. This must belong to the product identified by
923id.
924
925=back
926
927Template: admin/prodopt_delete
928
929=cut
930
931sub req_delconf_option {
932 my ($self, $req, $article, $articles, $msg, $errors) = @_;
933
9b3a5df0
TC
934 $req->user_can(bse_edit_prodopt_delete => $article)
935 or return $self->_service_error($req, $article, $articles, "Insufficient product access to delete options");
936
58baa27b
TC
937 return $self->_common_option('admin/prodopt_delete', $req, $article,
938 $articles, $msg, $errors);
939}
940
941=item a_delete_option
942
943Delete the given option.
944
945On failure perform a service error.
946
947Requires _csrfp for admin_delete_option
948
949For Ajax requests (or with a _ parameter), returns JSON like:
950
951 {
952 success: 1,
953 }
954
9b3a5df0
TC
955Permission required: bse_edit_prodopt_delete
956
58baa27b
TC
957=cut
958
959sub req_delete_option {
960 my ($self, $req, $article, $articles) = @_;
961
962 $req->check_csrf("admin_delete_option")
963 or return $self->csrf_error($req, $article, "admin_delete_option", "Delete Product Option");
964
9b3a5df0
TC
965 $req->user_can(bse_edit_prodopt_delete => $article)
966 or return $self->_service_error($req, $article, $articles, "Insufficient product access to delete options");
967
58baa27b
TC
968 my %errors;
969 my $option = $self->_get_option($req, $article, \%errors);
970 keys %errors
971 and return $self->_service_error($req, $article, $articles, undef, \%errors);
972 my @values = $option->values;
973
974 for my $value (@values) {
975 $value->remove;
976 }
977 $option->remove;
978
979 $req->is_ajax
980 and return $req->json_content
981 (
982 success => 1
983 );
984
985 return $self->refresh($article, $req->cgi, undef, "Option deleted");
986}
987
988
989my %add_option_value_fields =
990 (
991 option_id =>
992 {
993 description => "Option id",
994 rules => "required;positiveint",
995 },
996 value =>
997 {
998 description => "Value",
085b34a0
TC
999 rules => "required;dh_one_line",
1000 maxlength => 255,
58baa27b
TC
1001 },
1002 );
1003
1004=item a_add_option_value
1005
1006Add a value to a product option.
1007
1008On failure perform a service error, see BSE::Edit::Article::_service_error.
1009
1010Requires _csrfp for admin_add_option_value
1011
1012For Ajax requests returns JSON like
1013
1014 { success: 1, value: (valueobject) }
1015
1016Standard redirect on success otherwise.
1017
1018Parameters:
1019
1020=over
1021
1022=item *
1023
1024id - article id
1025
1026=item *
1027
1028option_id - id of the option to add the value to
1029
1030=item *
1031
1032value - text of the value to add.
1033
1034=back
1035
9b3a5df0
TC
1036Permission required: bse_edit_prodopt_edit
1037
58baa27b
TC
1038=cut
1039
1040sub req_add_option_value {
1041 my ($self, $req, $article, $articles, $msg, $errors) = @_;
1042
1043 $req->check_csrf("admin_add_option_value")
1044 or return $self->csrf_error($req, $article, "admin_add_option_value", "Add Product Option Value");
1045
9b3a5df0
TC
1046 $req->user_can(bse_edit_prodopt_edit => $article)
1047 or return $self->_service_error($req, $article, $articles, "Insufficient product access to edit options");
1048
58baa27b
TC
1049 my %errors;
1050 $req->validate(fields => \%add_option_value_fields,
1051 errors => \%errors);
1052 my $option;
1053 my $cgi = $req->cgi;
1054 unless ($errors{option_id}) {
1055 require BSE::TB::ProductOptions;
1056 $option = BSE::TB::ProductOptions->getByPkey($cgi->param("option_id"));
1057 defined $option && $option->{product_id}
1058 or $errors{option_id} = "Bad option id - either unknown or for a different product";
1059 }
1060 keys %errors
1061 and return $self->_service_error($req, $article, $articles, undef, \%errors);
1062
1063 my $value = $cgi->param("value");
1064 require BSE::TB::ProductOptionValues;
1065 my $entry = BSE::TB::ProductOptionValues->make
1066 (
1067 product_option_id => $option->{id},
1068 value => $value,
1069 display_order => time,
1070 );
1071
1072 $req->is_ajax
1660ce30 1073 and return $req->json_content
58baa27b
TC
1074 (
1075 success => 1,
1076 value => $entry->data_only
1077 );
1078
1079 return $self->refresh($article, $cgi, undef, "Value added");
1080}
1081
1082
1083my %option_value_id =
1084 (
1085 value_id =>
1086 {
1087 rules => "required;positiveint",
1088 },
1089 );
1090
1091sub _get_option_value {
1092 my ($self, $req, $article, $errors) = @_;
1093
1094 my $option_value;
1095 my $cgi = $req->cgi;
1096 $req->validate(fields => \%option_value_id,
1097 errors => $errors);
1098 unless ($errors->{value_id}) {
1099 require BSE::TB::ProductOptionValues;
1100 $option_value = BSE::TB::ProductOptionValues->getByPkey($cgi->param("value_id"));
1101 $option_value
1102 or $errors->{value_id} = "Unknown option value id";
1103 }
1104 my $option;
1105 unless ($errors->{value_id}) {
1106 $option = $option_value->option;
1107 defined $option && $option->{product_id} == $article->{id}
1108 or $errors->{value_id} = "Value has no option or doesn't belong to the product";
1109 }
1110
1111 $errors->{value_id}
1112 and return;
1113
1114 return wantarray ? ( $option_value, $option ) : $option_value ;
1115}
1116
1117sub _common_option_value {
1118 my ($self, $template, $req, $article, $articles, $msg, $errors) = @_;
1119
1120 my %errors;
1121 my ($option_value, $option) = $self->_get_option_value($req, $article, \%errors);
1122 keys %errors
1123 and return $self->_service_error($req, $article, $articles, undef, \%errors);
1124
1125 my %acts;
1126 %acts =
1127 (
1128 $self->low_edit_tags(\%acts, $req, $article, $articles, $msg, $errors),
1129 option_value => [ \&tag_hash, $option_value ],
1130 option => [ \&tag_hash, $option ],
1131 );
1132
1133 return $req->dyn_response($template, \%acts);
1134}
1135
1136=item a_edit_option_value
1137
1138Displays a form to edit the value for a given option.
1139
1140Parameters:
1141
1142=over
1143
1144=item *
1145
1146id - id of the product
1147
1148=item *
1149
1150value_id - id of he product option value to edit, must belong to the
1151given product.
1152
1153=back
1154
1155Template: admin/prodopt_value_edit
1156
9b3a5df0
TC
1157Permission required: bse_edit_prodopt_edit
1158
58baa27b
TC
1159=cut
1160
1161sub req_edit_option_value {
1162 my ($self, $req, $article, $articles, $msg, $errors) = @_;
1163
9b3a5df0
TC
1164 $req->user_can(bse_edit_prodopt_edit => $article)
1165 or return $self->_service_error($req, $article, $articles, "Insufficient product access to edit options");
1166
58baa27b
TC
1167 return $self->_common_option_value('admin/prodopt_value_edit', $req,
1168 $article, $articles, $msg, $errors);
1169}
1170
1171my %save_option_value_fields =
1172 (
1173 value =>
1174 {
1175 rules => "required;dh_one_line",
085b34a0 1176 maxlength => 255,
58baa27b
TC
1177 },
1178 );
1179
1180=item a_save_option_value
1181
1182Saves changes to an option.
1183
1184On failure perform a service error.
1185
1186Requires _csrfp for admin_save_option_value
1187
1188For Ajax requests (or with a _ parameter), returns JSON like:
1189
1190 {
1191 success: 1,
1192 value: { value data }
1193 }
1194
1195Parameters:
1196
1197=over
1198
1199=item *
1200
1201id - article id
1202
1203=item *
1204
1205value_id - id of the value to save, must belong to the product
1206identified by id.
1207
1208=item *
1209
1210value - new displayed value for the option value.
1211
1212=back
1213
9b3a5df0
TC
1214Permission required: bse_edit_prodopt_edit
1215
58baa27b
TC
1216=cut
1217
1218sub req_save_option_value {
1219 my ($self, $req, $article, $articles, $msg, $errors) = @_;
1220
1221 $req->check_csrf("admin_save_option_value")
1222 or return $self->csrf_error($req, $article, "admin_save_option_value", "Save Product Option Value");
1223
9b3a5df0
TC
1224 $req->user_can(bse_edit_prodopt_edit => $article)
1225 or return $self->_service_error($req, $article, $articles, "Insufficient product access to edit options");
1226
58baa27b
TC
1227 my %errors;
1228 $req->validate(fields => \%save_option_value_fields,
1229 errors => \%errors);
1230 my $option_value = $self->_get_option_value($req, $article, \%errors);
1231 keys %errors
1232 and return $self->_service_error($req, $article, $articles, undef, \%errors);
1233
1234 my $cgi = $req->cgi;
1235 $option_value->{value} = $cgi->param("value");
1236 $option_value->save;
1237
1238 $req->is_ajax
1660ce30 1239 and return $req->json_content
58baa27b
TC
1240 (
1241 success => 1,
1242 value => $option_value->data_only
1243 );
1244
1245 return $self->refresh($article, $cgi, undef, "Value saved");
1246}
1247
1248=item a_confdel_option_value
1249
1250Displays a page confirming deletion of a product option value.
1251
1252Parameters:
1253
1254=over
1255
1256=item *
1257
1258id - article id
1259
1260=item *
1261
1262value_id - option value id
1263
1264=back
1265
1266Template: admin/prodopt_value_delete
1267
9b3a5df0
TC
1268Permission required: bse_edit_prodopt_edit
1269
58baa27b
TC
1270=cut
1271
1272sub req_confdel_option_value {
1273 my ($self, $req, $article, $articles, $msg, $errors) = @_;
1274
9b3a5df0
TC
1275 $req->user_can(bse_edit_prodopt_edit => $article)
1276 or return $self->_service_error($req, $article, $articles, "Insufficient product access to edit options");
1277
58baa27b
TC
1278 return $self->_common_option_value('admin/prodopt_value_delete', $req,
1279 $article, $articles, $msg, $errors);
1280}
1281
1282=item a_delete_option_value
1283
1284Deletes a product option.
1285
1286On failure perform a service error.
1287
1288Requires _csrfp for admin_delete_option_value
1289
1290For Ajax requests (or with a _ parameter), returns JSON like:
1291
1292 {
1293 success: 1,
1294 }
1295
1296Parameters:
1297
1298=over
1299
1300=item *
1301
1302id - article id
1303
1304=item *
1305
1306value_id - id of the value to delete, must belong to the product
1307identified by id.
1308
1309=back
1310
9b3a5df0
TC
1311Permission required: bse_edit_prodopt_edit
1312
58baa27b
TC
1313=cut
1314
1315sub req_delete_option_value {
1316 my ($self, $req, $article, $articles, $msg, $errors) = @_;
1317
1318 $req->check_csrf("admin_delete_option_value")
1319 or return $self->csrf_error($req, $article, "admin_delete_option_value", "Delete Product Option Value");
1320
9b3a5df0
TC
1321 $req->user_can(bse_edit_prodopt_edit => $article)
1322 or return $self->_service_error($req, $article, $articles, "Insufficient product access to edit options");
1323
58baa27b
TC
1324 my %errors;
1325 my $option_value = $self->_get_option_value($req, $article, \%errors);
1326 keys %errors
1327 and return $self->_service_error($req, $article, $articles, undef, \%errors);
1328
1329 $option_value->remove;
1330
1331 $req->is_ajax
1660ce30 1332 and return $req->json_content
58baa27b
TC
1333 (
1334 success => 1
1335 );
1336
1337 return $self->refresh($article, $req->cgi, undef, "Value removed");
1338}
1339
1340sub tag_dboptionsjson {
1341 my ($self, $article) = @_;
1342
1343 my @result;
1344 my @options = $article->db_options;
1345 my @opt_cols = BSE::TB::ProductOption->columns;
1346 for my $option (@options) {
1347 my $entry = $option->data_only;
1348 $entry->{values} = [ map $_->data_only, $option->values ];
1349 push @result, $entry;
1350 }
1351
1352 require JSON;
1353 my $json = JSON->new;
1354 return $json->encode(\@result);
1355}
1356
1357sub _option_move {
1358 my ($self, $req, $article, $articles, $direction) = @_;
1359
1360 $req->check_csrf("admin_move_option")
1361 or return $self->csrf_error($req, $article, "admin_move_option", "Move Product Option");
1362
9b3a5df0
TC
1363 $req->user_can(bse_edit_prodopt_move => $article)
1364 or return $self->_service_error($req, $article, $articles, "Insufficient product access to move options");
1365
58baa27b
TC
1366 my %errors;
1367 my $option = $self->_get_option($req, $article, \%errors);
1368 keys %errors
1369 and return $self->_service_error($req, $article, $articles, undef, \%errors);
1370 my @options = $article->db_options;
1371 my ($index) = grep $options[$_]{id} == $option->{id}, 0 .. $#options
1372 or return $self->_service_error($req, $article, $articles, "Unknown option id");
1373
1374 $options[$index] = $option;
1375
1376 my $other_index = $index + $direction;
1377 $other_index >= 0 && $other_index < @options
1378 or return $self->_service_error($req, $article, $articles, "Can't move option beyond end");
1379
1380 my $other = $options[$other_index];
1381
1382 ($option->{display_order}, $other->{display_order}) =
1383 ($other->{display_order}, $option->{display_order});
1384 $option->save;
1385 $other->save;
1386
4de47893
TC
1387 if ($req->is_ajax) {
1388 @options = sort { $a->{display_order} <=> $b->{display_order} } @options;
9b3a5df0 1389 return return $req->json_content
58baa27b
TC
1390 (
1391 success => 1,
1392 order => [ map $_->{id}, @options ]
1393 );
4de47893 1394 }
58baa27b
TC
1395
1396 return $self->refresh($article, $req->cgi, undef, "Option moved");
1397}
1398
4de47893 1399=item a_option_moveup
58baa27b 1400
4de47893 1401=item a_option_movedown
58baa27b
TC
1402
1403Move a product option up/down through the options for a product.
1404
1405On failure perform a service error.
1406
1407Requires _csrfp for admin_move_option
1408
1409For Ajax requests (or with a _ parameter), returns JSON like:
1410
1411 {
1412 success: 1,
1413 order: [ list of option ids ]
1414 }
1415
1416Parameters:
1417
1418=over
1419
1420=item *
1421
1422id - article id
1423
1424=item *
1425
1426option_id - option id. This must belong to the product identified by
1427id.
1428
1429=back
1430
9b3a5df0
TC
1431Permission required: bse_edit_prodopt_move
1432
58baa27b
TC
1433=cut
1434
1435sub req_option_moveup {
1436 my ($self, $req, $article, $articles) = @_;
1437
1438 return $self->_option_move($req, $article, $articles, -1);
1439}
1440
1441sub req_option_movedown {
1442 my ($self, $req, $article, $articles) = @_;
1443
1444 return $self->_option_move($req, $article, $articles, 1);
1445}
1446
1447=item a_option_reorder
1448
1449Move a product option up/down through the options for a product.
1450
1451On failure perform a service error.
1452
1453Requires _csrfp for admin_move_option
1454
1455For Ajax requests (or with a _ parameter), returns JSON like:
1456
1457 {
1458 success: 1,
1459 order: [ list of option ids ]
1460 }
1461
1462Parameters:
1463
1464=over
1465
1466=item *
1467
1468id - article id
1469
1470=item *
1471
1472option_ids - option ids separated by commas. These must belong to the
1473product identified by id.
1474
1475=back
1476
9b3a5df0
TC
1477Permission required: bse_edit_prodopt_move
1478
58baa27b
TC
1479=cut
1480
1481sub req_option_reorder {
1482 my ($self, $req, $article, $articles) = @_;
1483
1484 $req->check_csrf("admin_move_option")
1485 or return $self->csrf_error($req, $article, "admin_move_option", "Move Product Option");
1486
9b3a5df0
TC
1487 $req->user_can(bse_edit_prodopt_move => $article)
1488 or return $self->_service_error($req, $article, $articles, "Insufficient product access to move options");
1489
58baa27b
TC
1490 my @options = $article->db_options;
1491 my @order = map { split ',' } $req->cgi->param('option_ids');
1492 my %options = map { $_->{id} => $_ } @options;
1493 my @new_options;
1494 for my $id (@order) {
1495 my $option = delete $options{$id}
1496 or next;
1497 push @new_options, $option;
1498 }
1499 push @new_options, sort { $a->{display_order} <=> $b->{display_order} } values %options;
1500 my @display_order = map $_->{display_order}, @options;
1501 for my $index (0 .. $#new_options) {
1502 $new_options[$index]{display_order} = $display_order[$index];
1503 $new_options[$index]->save;
1504 }
1505
1506 $req->is_ajax
1507 and return $req->json_content
1508 (
1509 success => 1,
1510 order => [ map $_->{id}, @new_options ]
1511 );
1512
1513 return $self->refresh($article, $req->cgi, undef, "Options reordered");
1514}
1515
1516sub _option_value_move {
1517 my ($self, $req, $article, $articles, $direction) = @_;
1518
1519 $req->check_csrf("admin_move_option_value")
1520 or return $self->csrf_error($req, $article, "admin_move_option_value", "Move Product Option Value");
1521
9b3a5df0
TC
1522 $req->user_can(bse_edit_prodopt_edit => $article)
1523 or return $self->_service_error($req, $article, $articles, "Insufficient product access to edit options");
1524
58baa27b
TC
1525 my %errors;
1526 my ($option_value, $option) = $self->_get_option_value($req, $article, \%errors);
1527 keys %errors
1528 and return $self->_service_error($req, $article, $articles, undef, \%errors);
1529 my @values = $option->values;
1530 my ($index) = grep $values[$_]{id} == $option_value->{id}, 0 .. $#values
1531 or return $self->_service_error($req, $article, $articles, "Unknown option value id");
1532
1533 $values[$index] = $option_value;
1534
1535 my $other_index = $index + $direction;
1536 $other_index >= 0 && $other_index < @values
1537 or return $self->_service_error($req, $article, $articles, "Can't move option value beyond end");
1538
1539 my $other = $values[$other_index];
1540
1541 ($option_value->{display_order}, $other->{display_order}) =
1542 ($other->{display_order}, $option_value->{display_order});
1543 $option_value->save;
1544 $other->save;
1545
9b3a5df0
TC
1546 # make sure the json gets the new order
1547 @values[$index, $other_index] = @values[$other_index, $index];
1548
58baa27b 1549 $req->is_ajax
9b3a5df0 1550 and return $req->json_content
58baa27b
TC
1551 (
1552 success => 1,
1553 order => [ map $_->{id}, @values ]
1554 );
1555
1556 return $self->refresh($article, $req->cgi, undef, "Value moved");
1557}
1558
1559=item a_option_value_moveup
1560
1561=item a_option_value_movedown
1562
1563Move a product option value up/down through the values for a product
1564option.
1565
1566On failure perform a service error.
1567
1568Requires _csrfp for admin_move_option_value
1569
1570For Ajax requests (or with a _ parameter), returns JSON like:
1571
1572 {
1573 success: 1,
1574 order: [ list of value ids ]
1575 }
1576
1577Parameters:
1578
1579=over
1580
1581=item *
1582
1583id - article id
1584
1585=item *
1586
1587value_id - option id. This must belong to the product identified by
1588id.
1589
1590=back
1591
9b3a5df0
TC
1592Permission required: bse_edit_prodopt_edit
1593
58baa27b
TC
1594=cut
1595
1596sub req_option_value_moveup {
1597 my ($self, $req, $article, $articles) = @_;
1598
1599 return $self->_option_value_move($req, $article, $articles, -1);
1600}
1601
1602sub req_option_value_movedown {
1603 my ($self, $req, $article, $articles) = @_;
1604
1605 return $self->_option_value_move($req, $article, $articles, 1);
1606}
1607
1608=item a_option_value_reorder
1609
1610Specify a new order for the values belonging to a product option.
1611
1612On failure perform a service error.
1613
1614Requires _csrfp for admin_move_option_value
1615
1616For Ajax requests (or with a _ parameter), returns JSON like:
1617
1618 {
1619 success: 1,
1620 order: [ list of value ids ]
1621 }
1622
1623Parameters:
1624
1625=over
1626
1627=item *
1628
1629id - article id
1630
1631=item *
1632
4de47893
TC
1633option_id - the option to reorder values for
1634
1635=item *
1636
58baa27b
TC
1637value_ids - new order for values specified as value ids separated by
1638commas.
1639
1640=back
1641
9b3a5df0
TC
1642Permission required: bse_edit_prodopt_edit
1643
58baa27b
TC
1644=cut
1645
1646sub req_option_value_reorder {
1647 my ($self, $req, $article, $articles) = @_;
1648
1649 $req->check_csrf("admin_move_option_value")
1650 or return $self->csrf_error($req, $article, "admin_move_option_value", "Move Product Option Value");
1651
9b3a5df0
TC
1652 $req->user_can(bse_edit_prodopt_edit => $article)
1653 or return $self->_service_error($req, $article, $articles, "Insufficient product access to edit options");
1654
58baa27b
TC
1655 my %errors;
1656 my $option = $self->_get_option($req, $article, \%errors);
1657 keys %errors
1658 and return $self->_service_error($req, $article, $articles, undef, \%errors);
1659 my @order = map { split ',' } $req->cgi->param('value_ids');
1660 my @values = $option->values;
1661 my %values = map { $_->{id} => $_ } @values;
1662 my @new_values;
1663 for my $id (@order) {
1664 my $value = delete $values{$id}
1665 or next;
1666 push @new_values, $value;
1667 }
1668 push @new_values, sort { $a->{display_order} <=> $b->{display_order} } values %values;
1669 my @display_order = map $_->{display_order}, @values;
1670 for my $index (0 .. $#new_values) {
1671 $new_values[$index]{display_order} = $display_order[$index];
1672 $new_values[$index]->save;
1673 }
1674
1675 $req->is_ajax
1676 and return $req->json_content
1677 (
1678 success => 1,
1679 option => $option->data_only,
1680 order => [ map $_->{id}, @new_values ]
1681 );
1682
1683 return $self->refresh($article, $req->cgi, undef, "Values reordered");
1684}
1685
1686sub article_actions {
1687 my $self = shift;
1688
1689 return
1690 (
1691 $self->SUPER::article_actions,
1692 a_add_option => 'req_add_option',
1693 a_confdel_option => 'req_confdel_option',
1694 a_del_option => 'req_del_option',
1695 a_edit_option => 'req_edit_option',
1696 a_save_option => 'req_save_option',
1697 a_delconf_option => 'req_delconf_option',
1698 a_delete_option => 'req_delete_option',
1699 a_get_option => 'req_get_option',
1700 a_edit_option_value => 'req_edit_option_value',
1701 a_save_option_value => 'req_save_option_value',
1702 a_confdel_option_value => 'req_confdel_option_value',
1703 a_delete_option_value => 'req_delete_option_value',
1704 a_add_option_value => 'req_add_option_value',
1705 a_option_value_moveup => 'req_option_value_moveup',
1706 a_option_value_movedown => 'req_option_value_movedown',
1707 a_option_value_reorder => 'req_option_value_reorder',
1708 a_option_moveup => 'req_option_moveup',
1709 a_option_movedown => 'req_option_movedown',
1710 a_option_reorder => 'req_option_reorder',
1711 );
1712}
1713
ca9aa2bf 17141;