Revert "Embed Safe 2.07 into Vend::Safe to avoid various problems with recent version...
[interchange.git] / lib / Vend / Options.pm
1 # Vend::Options - Interchange item options base module
2 #
3 # $Id: Options.pm,v 2.8 2007-08-09 13:40:53 pajamian Exp $
4 #
5 # Copyright (C) 2002-2007 Interchange Development Group
6 # Copyright (C) 1996-2002 Red Hat, Inc.
7 #
8 # This program is free software; you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 2 of the License, or
11 # (at your option) any later version.
12 #
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public
19 # License along with this program; if not, write to the Free
20 # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
21 # MA  02110-1301  USA.
22
23 package Vend::Options;
24 require Exporter;
25
26 $VERSION = substr(q$Revision: 2.8 $, 10);
27
28 @ISA = qw(Exporter);
29
30 @EXPORT = qw(
31                                 find_joiner
32                                 find_options_type
33                                 find_sort
34                                 inventory_function
35                                 option_cost
36                                 remap_option_record
37                 );
38
39 use Vend::Util;
40 use Vend::Data;
41 use Vend::Interpolate;
42 use strict;
43
44 sub remap_option_record {
45         my ($record, $map) = @_;
46
47         my %rec;
48         my @del;
49         my ($k, $v);
50         while (($k, $v) = each %$map) {
51                 next unless defined $record->{$v};
52                 $rec{$k} = $record->{$v};
53                 push @del, $v;
54         }
55         delete @{$record}{@del};
56         @{$record}{keys %rec} = (values %rec);
57         
58         return;
59 }
60
61 sub find_options_type {
62         my ($item, $opt) = @_;
63
64         my $attrib;
65         return $item->{$attrib}
66                 if      $attrib = $Vend::Cfg->{OptionsAttribute}
67                 and defined $item->{$attrib};
68
69         my $sku = $item->{mv_sku} || $item->{code};
70
71         $opt = get_option_hash($opt);
72
73         my $module;
74
75         if($Vend::Cfg->{OptionsEnable}) {
76                 my ($tab, $field) = split /:+/, $Vend::Cfg->{OptionsEnable};
77                 if(! $field) {
78                         $field = $tab;
79                         undef $tab;
80                 }
81                 elsif($tab =~ /=/) {
82                         my $att;
83                         ($att, $tab) = split /\s*=\s*/, $tab;
84                         $attrib ||= $att;
85                 }
86                 $attrib ||= $field;
87                 $Vend::Cfg->{OptionsAttribute} ||= $attrib;
88
89                 if(! defined $item->{$attrib}) {
90                         $tab = $item->{mv_ib} || product_code_exists_tag($sku)
91                                         or do {
92                                                 logOnce('error', "options: Unknown product %s.", $sku);
93                                                 return;
94                                         };
95                         $item->{$attrib} = tag_data($tab, $field, $sku);
96                 }
97                 $module = $item->{$attrib} || '';
98         }
99         else {
100                 ## Old style options
101                 my $loc = $Vend::Cfg->{Options_repository}{Old48} || {};
102                 my $table = $opt->{table}
103                                   ||= (
104                                                 $loc->{table} || $::Variable->{MV_OPTION_TABLE} || 'options'
105                                         );
106                 my $db = $Vend::Interpolate::Db{$table} || database_exists_ref($table)
107                                 or return;
108                 $db->record_exists($sku)
109                                 or return;
110                 my $record = $opt->{options_record} = $db->row_hash($sku)
111                                 or return;
112                 $opt->{options_db} = $db;
113                 remap_option_record($record, $loc->{map})
114                         if  $loc->{remap};
115
116                 return '' unless $record->{o_enable};
117
118                 $module = 'Old48';
119
120                 if($record->{o_matrix}) {
121                         $opt->{display_routine}
122                                 = 'Vend::Options::Old48::display_options_matrix';
123                 }
124                 elsif($record->{o_modular}) {
125                         $module = 'Modular';
126                 }
127                 else {
128                         $opt->{display_routine}
129                                 = 'Vend::Options::Old48::display_options_simple';
130                 }
131         }
132
133         return $module;
134 }
135
136 sub inventory_function {
137         my $opt = shift;
138         return unless $opt->{inventory};
139         my $inv_func;
140         my ($t, $c) = split /[.:]+/, $opt->{inventory};
141         my $idb;
142         if($idb = database_exists_ref($t)) {
143                 $inv_func = $idb->field_accessor($c);
144         }
145         return $inv_func;
146 }
147
148 sub find_joiner {
149         my $opt = shift;
150         if($opt->{report}) {
151                 $opt->{joiner}          ||= ', ';
152                 $opt->{separator}       ||= ': ';
153                 $opt->{type}            ||= 'display';
154         }
155         else {
156                 $opt->{joiner} ||= "<br$Vend::Xtrailer>";
157         }
158         return;
159 }
160
161 sub find_sort {
162         my $opt = shift;
163         my $db = shift;
164         my $loc = shift || $Vend::Cfg->{Options_repository}{$opt->{options_type}} || {};
165 #::logDebug("called find_sort from " . scalar(caller()) . ", opt=" . ::uneval($opt));
166         $opt->{sort} = defined $opt->{sort} ? $opt->{sort} : $loc->{sort};
167         return '' unless $opt->{sort};
168         return " ORDER BY $opt->{sort}" if $opt->{rawsort} || $loc->{rawsort};
169         my @fields = split /\s*,\s*/, $opt->{sort};
170         my $map = $loc->{map} ||= {};
171         for(@fields) {
172                 my $extra; 
173                 $extra = ' DESC' if s/\s+(r(?:ev(?:erse)?)?|desc(?:ending)?)//i;
174                 $_ = $map->{$_} || $_;
175                 unless (defined $db->test_column($_)) {
176                         logOnce(
177                                 "%s options sort field %s does not exist, returning unsorted",
178                                 'Matrix',
179                                 $_,
180                                 );
181                         return undef;
182                 }
183                 $_ .= $extra if $extra;
184         }
185
186         return " ORDER BY " . join(",", @fields);
187 }
188
189 sub tag_options {
190         my ($sku, $opt) = @_;
191         my $item;
192         if(ref $sku) {
193                 $item = $sku;
194                 $sku = $item->{mv_sku} || $item->{code};
195         }
196         $item ||= { code => $sku };
197         $opt = get_option_hash($opt);
198         find_joiner($opt);
199
200         my $module = find_options_type($item, $opt)
201                 or return '';
202         $opt->{options_type} = $module;
203 #::logDebug("tag_options module=$module");
204
205         my $loc = $Vend::Cfg->{Options_repository}{$module} || {};
206         no strict 'refs';
207         my $routine;
208         if($opt->{admin_page}) {
209                 $opt->{routine_description} ||= "admin page";
210                 $routine = $opt->{admin_page_routine}
211                         ||= "Vend::Options::${module}::admin_page";
212         }
213         else {
214                 $opt->{routine_description} ||= "display";
215                 $routine = $opt->{display_routine};
216                 $routine ||= $loc->{display_routine}
217                                 ||= "Vend::Options::${module}::display_options";
218 #::logDebug("tag_options display routine=$routine");
219         }
220         my $sub = \&{"$routine"};
221         if(! defined $sub) {
222                 ::logOnce(
223                         "Options type %s %s routine %s not found, aborting options for %s.",
224                         $module,
225                         $opt->{routine_description},
226                         $routine,
227                         $sku,
228                         );
229                 return undef;
230         }
231 #::logDebug("main tag_options item=" . ::uneval($item) . ", opt=" . ::uneval($opt));
232         return $sub->($item, $opt, $loc);
233 }
234
235 sub option_cost {
236         my ($item, $table, $final) = @_;
237
238         my $module = find_options_type($item)
239                 or return undef;
240 #::logDebug("price_options module=$module");
241         my $loc = $Vend::Cfg->{Options_repository}{$module} || {};
242         return undef if $loc->{no_pricing};
243         no strict 'refs';
244         my $routine = $loc->{price_routine};
245         $routine ||= "Vend::Options::${module}::price_options";
246         my $sub = \&{"$routine"};
247 #::logDebug("price_options sub=$sub");
248
249         if(! defined $sub) {
250                 ::logOnce(
251                         "Options type %s not found, aborting option_cost for %s.",
252                         $module,
253                         $item->{code},
254                         );
255                 return undef;
256         }
257         return $sub->($item, $table, $final, $loc);
258 }
259
260 1;
261 __END__