Also look in the next-highest directory when detecting VCS; add SVN
[interchange.git] / lib / Vend / Options.pm
1 # Vend::Options - Interchange item options base module
2 #
3 # Copyright (C) 2002-2007 Interchange Development Group
4 # Copyright (C) 1996-2002 Red Hat, Inc.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public
17 # License along with this program; if not, write to the Free
18 # Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston,
19 # MA  02110-1301  USA.
20
21 package Vend::Options;
22 require Exporter;
23
24 $VERSION = '2.8';
25
26 @ISA = qw(Exporter);
27
28 @EXPORT = qw(
29                                 find_joiner
30                                 find_options_type
31                                 find_sort
32                                 inventory_function
33                                 option_cost
34                                 remap_option_record
35                 );
36
37 use Vend::Util;
38 use Vend::Data;
39 use Vend::Interpolate;
40 use strict;
41
42 sub remap_option_record {
43         my ($record, $map) = @_;
44
45         my %rec;
46         my @del;
47         my ($k, $v);
48         while (($k, $v) = each %$map) {
49                 next unless defined $record->{$v};
50                 $rec{$k} = $record->{$v};
51                 push @del, $v;
52         }
53         delete @{$record}{@del};
54         @{$record}{keys %rec} = (values %rec);
55         
56         return;
57 }
58
59 sub find_options_type {
60         my ($item, $opt) = @_;
61
62         my $attrib;
63         return $item->{$attrib}
64                 if      $attrib = $Vend::Cfg->{OptionsAttribute}
65                 and defined $item->{$attrib};
66
67         my $sku = $item->{mv_sku} || $item->{code};
68
69         return unless defined $sku && $sku ne '';
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__