1# Vend::Options::Old48 - Interchange 4.8 compatible product options
2#
3# $Id: Old48.pm,v 1.14 2007-08-09 13:40:55 pajamian Exp $
4#
5# Copyright (C) 2002-2007 Interchange Development Group <interchange@icdevgroup.org>
6# Copyright (C) 2002-2003 Mike Heins <mikeh@perusion.net>
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
24package Vend::Options::Old48;
25
26$VERSION = substr(q$Revision: 1.14 $, 10);
27
28=head1 NAME
29
30Vend::Options::Old48 - Interchange Compatibility Options Support
31
32=head1 SYNOPSIS
33
34    [item-options]
35
36        or
37
38    [price code=SKU]
39
40=head1 PREREQUISITES
41
42Vend::Options
43
44=head1 DESCRIPTION
45
46The Vend::Options::Old48 module implements simple and matrix product
47options for Interchange. It is compatible with Interchange 4.8.x
48matrix options. Newer versions use Simple and Matrix options instead.
49
50If the Interchange Variable MV_OPTION_TABLE is not set, it defaults
51to "options", which combines options for Simple, Matrix, and
52Modular into that one table. This goes along with foundation and
53construct demos up until Interchange 4.9.8.
54
55The "options" table remains the default for matrix options.
56
57=head1 AUTHORS
58
59Mike Heins <mikeh@perusion.net>
60
61=head1 CREDITS
62
63    Jon Jensen <jon@swelter.net>
64
65=cut
66
67use Vend::Util;
68use Vend::Data;
69use Vend::Interpolate;
70use Vend::Options;
71
72sub option_cost {
73	my ($item, $opt) = @_;
74
75}
76
77sub display_options_matrix {
78	my ($item, $opt, $loc) = @_;
79
80	$loc ||= $Vend::Cfg->{Options_repository}{Old48} || \%Default;
81#::logDebug("Matrix options by module, old");
82	my $sku = $item->{mv_sku} || $item->{code};
83	my $db;
84	my $tab;
85
86	if(not $db = $opt->{options_db}) {
87		$tab = $opt->{table} || $::Variable->{MV_OPTION_TABLE} || 'options';
88		$db = database_exists_ref($tab)
89			or do {
90				logOnce(
91						"Matrix options: unable to find table %s for item %s",
92						$tab,
93						$sku,
94					);
95				return undef;
96			};
97	}
98
99	my $record;
100	if(not $record = $opt->{options_record}) {
101		$db->record_exists($sku)
102			or do {
103				logOnce(
104					"Matrix options: unable to find record in table %s for item %s",
105					$tab,
106					$sku,
107				);
108				return;
109			};
110		$record = $db->row_hash($sku) || {};
111	}
112
113	my $tname = $db->name();
114
115	if(not $opt->{display_type} ||= $record->{display_type}) {
116		$opt->{display_type} = $record->{o_matrix} == 2 ? 'separate' : 'single';
117	}
118
119	$opt->{display_type} = lc $opt->{display_type};
120
121	my $map;
122	if(not $map = $opt->{options_map}) {
123		$map = $opt->{options_map} = {};
124		if(my $remap = $opt->{remap} || $::Variable->{MV_OPTION_TABLE_MAP}) {
125			remap_option_record($record, $map, $remap);
126		}
127	}
128
129	my @rf;
130	my @out;
131	my $out;
132
133    my $inv_func;
134    if($opt->{inventory}) {
135        my ($tab, $col) = split /:+/, $opt->{inventory};
136        MAKEFUNC: {
137            my $idb = dbref($tab)
138                or do {
139                    logError("Bad table %s for inventory function.", $tab);
140                    last MAKEFUNC;
141                };
142            $idb->test_column($col)
143                or do {
144                    logError(
145                        "Bad column %s in table %s for inventory function.",
146                        $col,
147                        $tab,
148                    );
149                    last MAKEFUNC;
150                };
151            $inv_func = sub {
152                my $key = shift;
153                return $idb->field($key, $col);
154            };
155        }
156    }
157
158	my $rsort = find_sort($opt, $db, $loc);
159
160	if($opt->{display_type} eq 'separate') {
161		for(qw/code o_enable o_group o_value o_label o_widget price/) {
162			push @rf, ($map->{$_} || $_);
163		}
164		my @def;
165		if($item and $item->{code}) {
166			@def = split /-/, $item->{code};
167		}
168		my $fsel = $map->{sku} || 'sku';
169		my $rsel = $db->quote($sku, $fsel);
170
171		my $q = "SELECT " .
172				join (",", @rf) .
173				" FROM $tname where $fsel = $rsel $rsort";
174#::logDebug("tag_options matrix query: $q");
175		my $ary = $db->query($q);
176#::logDebug("tag_options matrix ary: " . ::uneval($ary));
177		my $ref;
178		my $i = 0;
179		my $phony = { %{$item || { }} };
180		foreach $ref (@$ary) {
181
182			next unless $ref->[3];
183
184			# skip based on inventory if enabled
185			if($inv_func) {
186				my $oh = $inv_func->($ref->[0]);
187				next if $oh <= 0;
188			}
189
190			$i++;
191
192			# skip unless o_value
193			$phony->{mv_sku} = $def[$i];
194
195			if ($opt->{label}) {
196				$ref->[4] = "<b>$ref->[4]</b>" if $opt->{bold};
197				push @out, $ref->[4];
198			}
199			push @out, Vend::Interpolate::tag_accessories(
200							$sku,
201							'',
202							{
203								passed => $ref->[3],
204								type => $opt->{type} || $ref->[5] || 'select',
205								attribute => 'mv_sku',
206								price_data => $ref->[6],
207								price => $opt->{price},
208								extra => $opt->{extra},
209								js => $opt->{js},
210								item => $phony,
211							},
212							$phony || undef,
213						);
214		}
215
216		$phony->{mv_sku} = $sku;
217		my $begin = Vend::Interpolate::tag_accessories(
218							$sku,
219							'',
220							{
221								type => 'hidden',
222								attribute => 'mv_sku',
223								item => $phony,
224								default => $sku,
225							},
226							$phony,
227						);
228		if($opt->{td}) {
229			for(@out) {
230				$out .= "<td>$begin$_</td>";
231				$begin = '';
232			}
233		}
234		else {
235			$opt->{joiner} = "<br$Vend::Xtrailer>" if ! $opt->{joiner};
236			$out .= $begin;
237			$out .= join $opt->{joiner}, @out;
238		}
239	}
240	else {
241		for(qw/code o_enable o_group description price weight volume differential o_widget/) {
242			push @rf, ($map->{$_} || $_);
243		}
244		my $ccol = $map->{code} || 'code';
245		my $lcol = $map->{sku} || 'sku';
246		my $lval = $db->quote($sku, $lcol);
247
248		my $q = "SELECT " . join(",", @rf);
249		$q .= " FROM $tname where $lcol = $lval AND $ccol <> $lval $rsort";
250#::logDebug("tag_options matrix query: $q");
251		my $ary = $db->query($q);
252#::logDebug("tag_options matrix ary: " . ::uneval($ary));
253		my $ref;
254		my $price = {};
255		foreach $ref (@$ary) {
256			# skip unless description
257			next unless $ref->[3];
258
259			# skip based on inventory if enabled
260			if($inv_func) {
261				my $oh = $inv_func->($ref->[0]);
262				next if $oh <= 0;
263			}
264
265			$ref->[3] =~ s/,/&#44;/g;
266			$ref->[3] =~ s/=/&#61;/g;
267			$price->{$ref->[0]} = $ref->[4];
268			push @out, "$ref->[0]=$ref->[3]";
269		}
270		$out .= "<td>" if $opt->{td};
271		$out .= Vend::Interpolate::tag_accessories(
272							$sku,
273							'',
274							{
275								attribute => 'code',
276								default => undef,
277								extra => $opt->{extra},
278								item => $item,
279								js => $opt->{js},
280								name => 'mv_sku',
281								passed => join(",", @out),
282								price => $opt->{price},
283								price_data => $price,
284								type => $opt->{type} || $ref->[8] || 'select',
285							},
286							$item || undef,
287						);
288		$out .= "</td>" if $opt->{td};
289#::logDebug("matrix option returning $out");
290	}
291
292	return $out;
293}
294
295sub price_options {
296	my ($item, $table, $final, $loc) = @_;
297
298#::logDebug("option_cost table=$table");
299	$loc ||= $Vend::Cfg->{Options_repository}{Old48} || {};
300
301	my $sku = $item->{mv_sku} || $item->{code};
302	my $db = database_exists_ref($table)
303		or return undef;
304#::logDebug("option_cost db=$db");
305
306	my $map = $loc->{map} || {};
307	my $fsel = $map->{sku} || 'sku';
308	my $rsel = $db->quote($sku, $fsel);
309	my @rf;
310	for(qw/o_group price/) {
311		push @rf, ($map->{$_} || $_);
312	}
313
314	my $q = "SELECT " . join (",", @rf) . " FROM $table WHERE $fsel = $rsel";
315#::logDebug("option_cost query=$q");
316	my $ary = $db->query($q);
317	return if ! $ary->[0];
318	my $ref;
319	my $price = 0;
320	my $f;
321
322	foreach $ref (@$ary) {
323#::logDebug("checking option " . uneval_it($ref));
324		next unless defined $item->{$ref->[0]};
325		$ref->[1] =~ s/^\s+//;
326		$ref->[1] =~ s/\s+$//;
327		$ref->[1] =~ s/==/=:/g;
328		my %info = split /\s*[=,]\s*/, $ref->[1];
329		if(defined $info{ $item->{$ref->[0]} } ) {
330			my $atom = $info{ $item->{$ref->[0]} };
331			if($atom =~ s/^://) {
332				$f = $atom;
333				next;
334			}
335			elsif ($atom =~ s/\%$//) {
336				$f = $final if ! defined $f;
337				$f += ($atom * $final / 100);
338			}
339			else {
340				$price += $atom;
341			}
342		}
343	}
344#::logDebug("option_cost returning price=$price f=$f");
345	return ($price, $f);
346}
347
348sub display_options_simple {
349	my ($item, $opt) = @_;
350#::logDebug("Simple options, item=" . ::uneval($item) . "\nopt=" . ::uneval($opt));
351	my $map = $opt->{options_map} ||= {};
352#::logDebug("Simple options by module, old");
353
354	my $sku = $item->{code};
355	my $db;
356	my $tab;
357	if(not $db = $opt->{options_db}) {
358		$tab = $opt->{table} ||= $::Variable->{MV_OPTION_TABLE_SIMPLE}
359							 ||= $::Variable->{MV_OPTION_TABLE}
360							 ||= 'options';
361		$db = database_exists_ref($tab)
362			or do {
363				logOnce(
364						"Simple options: unable to find table %s for item %s",
365						$tab,
366						$sku,
367					);
368				return undef;
369			};
370	}
371
372	my $tname = $db->name();
373
374	my @rf;
375	my @out;
376	my $out;
377
378	my $ishash = defined $item->{mv_ip} ? 1 : 0;
379
380	for(qw/code o_enable o_group o_value o_label o_widget price o_height o_width/) {
381		push @rf, ($map->{$_} || $_);
382	}
383
384	my $fsel = $map->{sku} || 'sku';
385	my $rsel = $db->quote($sku, $fsel);
386
387	my $q = "SELECT " . join (",", @rf) . " FROM $tname where $fsel = $rsel";
388
389	if(my $rsort = find_sort($opt, $db, $loc)) {
390		$q .= " $rsort";
391	}
392#::logDebug("tag_options simple query: $q");
393
394	my $ary = $db->query($q)
395		or return;
396
397	my $ref;
398	foreach $ref (@$ary) {
399		# skip unless o_value
400		next unless $ref->[3];
401		if ($opt->{label}) {
402			$ref->[4] = "<b>$ref->[4]</b>" if $opt->{bold};
403			push @out, $ref->[4];
404		}
405		my $precursor = $opt->{report}
406					  ? "$ref->[2]$opt->{separator}"
407					  : qq{<input type="hidden" name="mv_item_option" value="$ref->[2]">};
408		push @out, $precursor . Vend::Interpolate::tag_accessories(
409						$sku,
410						'',
411						{
412							attribute => $ref->[2],
413							default => undef,
414							extra => $opt->{extra},
415							item => $item,
416							name => $ishash ? undef : "mv_order_$ref->[2]",
417							js => $opt->{js},
418							passed => $ref->[3],
419							price => $opt->{price},
420							price_data => $ref->[6],
421							height => $opt->{height} || $ref->[7],
422							width  => $opt->{width} || $ref->[8],
423							type => $opt->{type} || $ref->[5] || 'select',
424						},
425						$item || undef,
426					);
427	}
428	if($opt->{td}) {
429		for(@out) {
430			$out .= "<td>$_</td>";
431		}
432	}
433	else {
434		$opt->{joiner} = "<br$Vend::Xtrailer>" if ! $opt->{joiner};
435		$out .= join $opt->{joiner}, @out;
436	}
437	return $out;
438}
439
440*display_options = \&display_options_simple;
441
4421;
443