diff --git a/lib/WebGUI/Commerce/Item/Product.pm b/lib/WebGUI/Commerce/Item/Product.pm
new file mode 100755
index 000000000..2cd218b2c
--- /dev/null
+++ b/lib/WebGUI/Commerce/Item/Product.pm
@@ -0,0 +1,77 @@
+package WebGUI::Commerce::Item::Product;
+
+use strict;
+#use WebGUI::SQL;
+use WebGUI::Product;
+
+our @ISA = qw(WebGUI::Commerce::Item);
+
+#-------------------------------------------------------------------
+sub available {
+ return $_[0]->{_variant}->{available};
+}
+
+#-------------------------------------------------------------------
+sub description {
+ return $_[0]->{_product}->get('description');
+}
+
+#-------------------------------------------------------------------
+#sub duration {
+#
+
+#-------------------------------------------------------------------
+#sub handler {
+#}
+
+#-------------------------------------------------------------------
+sub id {
+ return $_[0]->{_variant}->{variantId};
+}
+
+#-------------------------------------------------------------------
+sub isRecurring {
+ return 0;
+}
+
+#-------------------------------------------------------------------
+sub name {
+ return $_[0]->{_product}->get('title').' ('.$_[0]->{_composition}.')';
+}
+
+#-------------------------------------------------------------------
+sub new {
+ my ($class, $sku, $product, $variantId);
+ $class = shift;
+ $variantId = shift;
+
+ $product = WebGUI::Product->getByVariantId($variantId);
+my $variant = $product->getVariant($variantId);
+my %parameters = map {split(/\./, $_)} split(/,/, $variant->{composition});
+my $composition = join(', ',map {$product->getParameter($_)->{name} .': '. $product->getOption($parameters{$_})->{value}} keys (%parameters));
+
+ bless {_product => $product, _composition => $composition, _variant => $variant}, $class;
+}
+
+#-------------------------------------------------------------------
+sub needsShipping {
+ return 1;
+}
+
+#-------------------------------------------------------------------
+sub price {
+ return $_[0]->{_variant}->{price};
+}
+
+#-------------------------------------------------------------------
+sub type {
+ return 'Product';
+}
+
+#-------------------------------------------------------------------
+sub weight {
+ return $_[0]->{_variant}->{weight};
+}
+
+1;
+
diff --git a/lib/WebGUI/Help/ProductManager.pm b/lib/WebGUI/Help/ProductManager.pm
new file mode 100644
index 000000000..d6051336a
--- /dev/null
+++ b/lib/WebGUI/Help/ProductManager.pm
@@ -0,0 +1,63 @@
+package WebGUI::Help::ProductManager;
+
+our $HELP = {
+ 'list products' => {
+ title => 'help list products title',
+ body => 'help list products body',
+ related => [
+ ]
+ },
+
+ 'manage product' => {
+ title => 'help manage product title',
+ body => 'help manage product body',
+ related => [
+ ]
+ },
+
+ 'edit product' => {
+ title => 'help edit product title',
+ body => 'help edit product body',
+ related => [
+ {
+ tag => 'template language',
+ namespace => 'Asset_Template'
+ },
+ ]
+ },
+
+ 'edit parameter' => {
+ title => 'help edit parameter title',
+ body => 'help edit parameter body',
+ related => [
+ ]
+ },
+ 'edit option' => {
+ title => 'help edit option title',
+ body => 'help edit option body',
+ related => [
+ ]
+ },
+ 'list variants' => {
+ title => 'help list variants title',
+ body => 'help list variants body',
+ related => [
+ ]
+ },
+ 'edit variant' => {
+ title => 'help edit variant title',
+ body => 'help edit variant body',
+ related => [
+ ]
+ },
+ 'edit sku template' => {
+ title => 'help edit sku template title',
+ body => 'help edit sku template body',
+ related => [
+ ]
+ },
+
+};
+
+1;
+
diff --git a/lib/WebGUI/Macro/Product.pm b/lib/WebGUI/Macro/Product.pm
new file mode 100644
index 000000000..d2766980b
--- /dev/null
+++ b/lib/WebGUI/Macro/Product.pm
@@ -0,0 +1,62 @@
+package WebGUI::Macro::Product;
+
+use strict;
+use WebGUI::Session;
+use WebGUI::Macro;
+use WebGUI::Product;
+use WebGUI::Asset::Template;
+use WebGUI::SQL;
+use WebGUI::International;
+
+sub process {
+ my (@param, $productId, $variantId, $product, $variant, $output, $templateId, @variantLoop, %var);
+
+ @param = WebGUI::Macro::getParams(@_);
+
+ return 'No SKU or productId passed' unless ($_[0]);
+
+ ($productId, $variantId) = WebGUI::SQL->quickArray("select productId, variantId from productVariants where sku=".quote($_[0]));
+ ($productId) = WebGUI::SQL->quickArray("select productId from products where sku=".quote($_[0])) unless ($productId);
+ ($productId) = WebGUI::SQL->quickArray("select productId from products where productId=".quote($_[0])) unless ($productId);
+
+ return 'Cannot find product' unless ($productId);
+
+ $product = WebGUI::Product->new($productId);
+
+ if ($variantId) {
+ $variant = [ $product->getVariant($variantId) ];
+ } else {
+ $variant = $product->getVariant;
+ };
+
+ foreach (@$variant) {
+ my @compositionLoop;
+ foreach (split(/,/,$_->{composition})) {
+ my ($parameterId, $optionId) = split(/\./, $_);
+ push(@compositionLoop, {
+ parameter => $product->getParameter($parameterId)->{name},
+ value => $product->getOption($optionId)->{value}
+ });
+ }
+
+ push (@variantLoop, {
+ 'variant.variantId' => $_->{variantId},
+ 'variant.price' => $_->{price},
+ 'variant.weight' => $_->{weight},
+ 'variant.sku' => $_->{sku},
+ 'variant.compositionLoop' => \@compositionLoop,
+ 'variant.addToCart.url' => WebGUI::URL::page('op=addToCart&itemType=Product&itemId='.$_->{variantId}),
+ 'variant.addToCart.label' => WebGUI::International::get('add to cart', 'Macro_Product'),
+ }) if ($_->{available});
+ }
+
+ %var = %{$product->get};
+ $var{variantLoop} = \@variantLoop;
+ $var{'variants.message'} = WebGUI::International::get('available product configurations', 'Macro_Product');
+ $templateId = $_[1] || $product->get('templateId');
+
+ return WebGUI::Asset::Template->new($templateId)->process(\%var);
+}
+
+1;
+
diff --git a/lib/WebGUI/Operation/ProductManager.pm b/lib/WebGUI/Operation/ProductManager.pm
new file mode 100755
index 000000000..2b6357ef5
--- /dev/null
+++ b/lib/WebGUI/Operation/ProductManager.pm
@@ -0,0 +1,571 @@
+package WebGUI::Operation::ProductManager;
+
+use strict;
+use WebGUI::Session;
+use WebGUI::SQL;
+use WebGUI::HTMLForm;
+use WebGUI::Form;
+use WebGUI::Id;
+use WebGUI::International;
+use WebGUI::AdminConsole;
+use Tie::IxHash;
+use WebGUI::Product;
+use WebGUI::Icon;
+use WebGUI::HTML;
+use WebGUI::Privilege;
+use WebGUI::Grouping;
+
+#-------------------------------------------------------------------
+sub _submenu {
+ my $i18n = WebGUI::International->new("ProductManager");
+
+ my $workarea = shift;
+ my $title = shift;
+ $title = $i18n->get($title) if ($title);
+ my $help = shift;
+ my $ac = WebGUI::AdminConsole->new("productManager");
+ if ($help) {
+ $ac->setHelp($help, 'ProductManager');
+ }
+
+ my $productId = $session{form}{productId} || WebGUI::Session::getScratch('managingProduct');
+ undef $productId if ($productId eq 'new');
+ $ac->addSubmenuItem(WebGUI::URL::page('op=editProduct&productId=new'), $i18n->get('add product'));
+ $ac->addSubmenuItem(WebGUI::URL::page('op=listProducts'), $i18n->get('list products'));
+ $ac->addSubmenuItem(WebGUI::URL::page('op=manageProduct&productId='.$productId), $i18n->get('manage product')) if ($productId);
+ $ac->addSubmenuItem(WebGUI::URL::page('op=listProductVariants&productId='.$productId), $i18n->get('list variants')) if ($productId);
+
+ return $ac->render($workarea, $title);
+}
+
+#-------------------------------------------------------------------
+sub www_deleteProductParameterOption {
+ my $optionId = $session{form}{optionId};
+
+ return WebGUI::Privilege::insufficient unless (WebGUI::Grouping::isInGroup(14));
+
+ WebGUI::Product->getByOptionId($optionId)->deleteOption($optionId);
+
+ return WebGUI::Operation::execute('manageProduct');
+}
+
+#-------------------------------------------------------------------
+sub www_deleteProductParameter {
+ my $parameterId = $session{form}{parameterId};
+
+ return WebGUI::Privilege::insufficient unless (WebGUI::Grouping::isInGroup(14));
+
+ WebGUI::Product->getByParameterId($parameterId)->deleteParameter($parameterId);
+
+ return WebGUI::Operation::execute('manageProduct');
+}
+
+#-------------------------------------------------------------------
+sub www_deleteProduct {
+ my $productId = $session{form}{productId};
+
+ return WebGUI::Privilege::insufficient unless (WebGUI::Grouping::isInGroup(14));
+
+ WebGUI::Product->new($productId)->delete;
+
+ return WebGUI::Operation::execute('listProducts');
+}
+
+#-------------------------------------------------------------------
+sub www_editProduct {
+ my ($productId, $product, $f, $i18n);
+
+ return WebGUI::Privilege::insufficient unless (WebGUI::Grouping::isInGroup(14));
+
+ $i18n = WebGUI::International->new('ProductManager');
+ $productId = $session{form}{productId};
+
+ unless ($productId eq 'new') {
+ $product = WebGUI::Product->new($productId)->get;
+ }
+
+ $f = WebGUI::HTMLForm->new;
+ $f->hidden('op', 'editProductSave');
+ $f->hidden('productId', $productId);
+ $f->text(
+ -name => 'title',
+ -label => $i18n->get('title'),
+ -value => $session{form}{title} || $product->{title},
+ -maxlength => 255,
+ );
+ $f->textarea(
+ -name => 'description',
+ -label => $i18n->get('description'),
+ -value => $session{form}{decsription} || $product->{description},
+ );
+ $f->float(
+ -name => 'price',
+ -label => $i18n->get('price'),
+ -value => $session{form}{price} || $product->{price},
+ -maxlength => 13,
+ );
+ $f->float(
+ -name => 'weight',
+ -label => $i18n->get('weight'),
+ -value => $session{form}{weight} || $product->{weight},
+ -maxlength => 9,
+ );
+ $f->text(
+ -name => 'sku',
+ -label => $i18n->get('sku'),
+ -value => $session{form}{sku} || $product->{SKU},
+ -maxlength => 64,
+ );
+ $f->template(
+ -name => 'templateId',
+ -label => $i18n->get('template'),
+ -value => $session{form}{templateId} || $product->{templateId},
+ -namespace => 'Commerce/Product',
+ );
+ $f->text(
+ -name => 'skuTemplate',
+ -label => $i18n->get('sku template'),
+ -value => $session{form}{skuTemplate} || $product->{skuTemplate},
+ -maxlength => 255,
+ );
+ $f->submit;
+
+ return _submenu($f->print, 'edit product', 'edit product');
+}
+
+#-------------------------------------------------------------------
+sub www_editProductSave {
+ my ($self, @error, $productId, $product, $i18n);
+
+ return WebGUI::Privilege::insufficient unless (WebGUI::Grouping::isInGroup(14));
+
+ $i18n = WebGUI::International->new('ProductManager');
+
+ push(@error, $i18n->get('edit product title error')) unless $session{form}{title};
+ push(@error, $i18n->get('edit product price error')) unless ($session{form}{price} && $session{form}{price} =~ /^\d+(\.\d+)?$/);
+ push(@error, $i18n->get('edit product weight error')) unless (defined $session{form}{weight} && $session{form}{price} =~ /^\d+(\.\d+)?$/);
+ push(@error, $i18n->get('edit product title error')) unless ($session{form}{sku});
+
+ return '
'.WebGUI::Operation::execute('editProduct') if (@error);
+
+ $productId = $session{form}{productId};
+ $product = WebGUI::Product->new($productId);
+ $product->set({
+ title => $session{form}{title},
+ description => $session{form}{description},
+ price => $session{form}{price},
+ weight => $session{form}{weight},
+ sku => $session{form}{sku},
+ templateId => $session{form}{templateId},
+ skuTemplate => $session{form}{skuTemplate},
+ });
+
+ $session{form}{productId} = $product->get('productId');
+ return WebGUI::Operation::execute('manageProduct');
+}
+
+#-------------------------------------------------------------------
+sub www_editProductParameter {
+ my ($parameterId, $product, $productId, $parameter, $f, $i18n);
+
+ return WebGUI::Privilege::insufficient unless (WebGUI::Grouping::isInGroup(14));
+
+ $i18n = WebGUI::International->new('ProductManager');
+
+ $parameterId = $session{form}{parameterId};
+ $productId = $session{form}{productId};
+
+ unless ($parameterId eq 'new') {
+ $product = WebGUI::Product->getByParameterId($parameterId);
+ $parameter = $product->getParameter($parameterId);
+ $productId = $product->get('productId');
+ }
+
+ $f = WebGUI::HTMLForm->new;
+ $f->hidden('op', 'editProductParameterSave');
+ $f->hidden('parameterId', $parameterId);
+ $f->hidden('productId', $productId);
+ $f->readOnly(
+ -label => 'parameterId',
+ -value => $parameterId,
+ );
+ $f->text(
+ -name => 'name',
+ -label => $i18n->get('edit parameter name'),
+ -value => $session{form}{name} || $parameter->{name},
+ -maxlength => 64,
+ );
+ $f->submit;
+
+ return _submenu($f->print, 'edit parameter', 'edit parameter');
+}
+
+#-------------------------------------------------------------------
+sub www_editProductParameterSave {
+ my (@error, $parameterId, $product, $i18n, $skuTemplate, $oldName, $newName);
+
+ return WebGUI::Privilege::insufficient unless (WebGUI::Grouping::isInGroup(14));
+
+ $i18n = WebGUI::International->new('ProductManager');
+
+ $parameterId = $session{form}{parameterId};
+
+ push (@error, $i18n->get('edit parameter error name')) unless $session{form}{name};
+ push (@error, $i18n->get('edit parameter productId error')) unless $session{form}{productId};
+
+ return "".WebGUI::Operation::execute('editProductParameter') if (@error);
+
+ $product = WebGUI::Product->new($session{form}{productId});
+ $skuTemplate = $product->get('skuTemplate');
+
+ if ($parameterId eq 'new') {
+ $parameterId = $product->addParameter;
+ } else {
+ ($oldName = $product->getParameter($parameterId)->{name}) =~ s/[ ><]/\./g;
+ ($newName = $session{form}{name}) =~ s/[ ><]/\./g;
+ $skuTemplate = $product->get('skuTemplate');
+ $skuTemplate =~ s/< *?tmpl_var *?param\.$oldName *?>//i;
+ $product->set({
+ skuTemplate => $skuTemplate
+ });
+ }
+
+ $product->setParameter($parameterId, {
+ name => $session{form}{name}
+ });
+
+ return WebGUI::Operation::execute('editSkuTemplate') if ($session{form}{parameterId} eq 'new');
+ return WebGUI::Operation::execute('manageProduct');
+}
+
+#-------------------------------------------------------------------
+sub www_editProductParameterOption {
+ my ($self, $optionId, $option, $f, $i18n);
+
+ return WebGUI::Privilege::insufficient unless (WebGUI::Grouping::isInGroup(14));
+
+ $i18n = WebGUI::International->new('ProductManager');
+
+ $optionId = $session{form}{optionId};
+ unless ($optionId eq 'new') {
+ $option = WebGUI::Product->getByOptionId($optionId)->getOption($optionId);
+ }
+
+ $f = WebGUI::HTMLForm->new;
+ $f->hidden('op', 'editProductParameterOptionSave');
+ $f->hidden('optionId', $optionId);
+ $f->hidden('parameterId', $session{form}{parameterId});
+ $f->readOnly(
+ -label => 'optionId',
+ -value => $optionId
+ );
+ $f->text(
+ -name => 'value',
+ -label => $i18n->get('edit option value'),
+ -value => $session{form}{value} || $option->{value},
+ -maxlength => 64,
+ );
+ $f->float(
+ -name => 'priceModifier',
+ -label => $i18n->get('edit option price modifier'),
+ -value => $session{form}{priceModifier} || $option->{priceModifier},
+ -maxlength => 11,
+ );
+ $f->float(
+ -name => 'weightModifier',
+ -label => $i18n->get('edit option weight modifier'),
+ -value => $session{form}{weightModifier} || $option->{weightModifier},
+ -maxlength => 7,
+ );
+ $f->text(
+ -name => 'skuModifier',
+ -label => $i18n->get('edit option sku modifier'),
+ -value => $session{form}{skuModifier} || $option->{skuModifier},
+ -maxlength => 64,
+ );
+ $f->submit;
+
+ return _submenu($f->print, 'edit option', 'edit option');
+}
+
+#-------------------------------------------------------------------
+sub www_editProductParameterOptionSave {
+ my ($self, @error, $optionId, $product, $i18n);
+
+ return WebGUI::Privilege::insufficient unless (WebGUI::Grouping::isInGroup(14));
+
+ $i18n = WebGUI::International->new('ProductManager');
+
+ push (@error, $i18n->get('edit option value error')) unless ($session{form}{value});
+ push (@error, $i18n->get('edit option parameterId error')) unless ($session{form}{parameterId});
+
+ return '
'.WebGUI::Operation::execute('editProduct') if (@error);
+
+ $product = WebGUI::Product->getByParameterId($session{form}{parameterId});
+ $optionId = $session{form}{optionId};
+ $optionId = $product->addOptionToParameter($session{form}{parameterId}) if ($optionId eq 'new');
+ $product->setOption($optionId, {
+ value => $session{form}{value},
+ priceModifier => $session{form}{priceModifier},
+ weightModifier => $session{form}{weightModifier},
+ skuModifier => $session{form}{skuModifier}
+ });
+
+ return WebGUI::Operation::execute('manageProduct');
+}
+
+#-------------------------------------------------------------------
+sub www_editProductVariant {
+ my ($variantId, $variant, $f, $i18n);
+
+ return WebGUI::Privilege::insufficient unless (WebGUI::Grouping::isInGroup(14));
+
+ $i18n = WebGUI::International->new("ProductManager");
+
+ $variantId = $session{form}{variantId};
+ $variant = WebGUI::Product->getByVariantId($variantId)->getVariant($variantId);
+
+ $f = WebGUI::HTMLForm->new;
+ $f->hidden('op', 'editProductVariantSave');
+ $f->hidden('variantId', $variantId);
+ $f->readOnly(
+ -label => 'varaiantId',
+ -value => $variant->{variantId}
+ );
+ $f->float(
+ -name => 'price',
+ -label => $i18n->get('price override'),
+ -value => $variant->{priceOverride} ? $variant->{price} : ''
+ );
+ $f->float(
+ -name => 'weight',
+ -label => $i18n->get('weight override'),
+ -value => $variant->{weightOverride} ? $variant->{weight} : ''
+ );
+ $f->text(
+ -name => 'sku',
+ -label => $i18n->get('sku override'),
+ -value => $variant->{skuOverride} ? $variant->{sku} : ''
+ );
+ $f->yesNo(
+ -name => 'available',
+ -label => $i18n->get('available'),
+ -value => $variant->{available}
+ );
+ $f->submit;
+
+ return _submenu($f->print, 'edit variant', 'edit variant');
+}
+
+#-------------------------------------------------------------------
+sub www_editProductVariantSave {
+my $variantId = $session{form}{variantId};
+
+ return WebGUI::Privilege::insufficient unless (WebGUI::Grouping::isInGroup(14));
+
+ WebGUI::Product->getByVariantId($variantId)->setVariant($variantId, $session{form});
+
+ return WebGUI::Operation::execute('listProductVariants');
+}
+
+#-------------------------------------------------------------------
+sub www_editSkuTemplate {
+ my ($product, $productId, $output, $f, $name, $i18n);
+
+ return WebGUI::Privilege::insufficient unless (WebGUI::Grouping::isInGroup(14));
+
+ $i18n = WebGUI::International->new("ProductManager");
+
+ $productId = $session{form}{productId};
+ $product = WebGUI::Product->new($productId);
+
+ $output .= "Available are:
\n";
+ $output .= "- base
\n";
+ foreach (@{$product->getParameter}) {
+ ($name = $_->{name}) =~ s/[ ><]/\./g;
+ $output .= "- param.".$name."
\n";
+ }
+ $output .= "
";
+
+ $f = WebGUI::HTMLForm->new;
+ $f->hidden('op', 'editSkuTemplateSave');
+ $f->hidden('productId', $productId);
+ $f->text(
+ -name => 'skuTemplate',
+ -value => $product->get('skuTemplate'),
+ -label => $i18n->get('sku template'),
+ );
+ $f->submit;
+ $output .= $f->print;
+
+ return _submenu($output, 'edit sku composition label', 'edit sku template');
+}
+
+#-------------------------------------------------------------------
+sub www_editSkuTemplateSave {
+ my ($productId) = $session{form}{productId};
+
+ return WebGUI::Privilege::insufficient unless (WebGUI::Grouping::isInGroup(14));
+
+ WebGUI::Product->new($productId)->set({
+ skuTemplate => $session{form}{skuTemplate},
+ });
+
+ return WebGUI::Operation::execute('manageProduct');
+}
+
+#-------------------------------------------------------------------
+sub www_listProducts {
+ my ($self, $sth, $output, $row, $i18n);
+
+ return WebGUI::Privilege::insufficient unless (WebGUI::Grouping::isInGroup(14));
+
+ $i18n = WebGUI::International->new('ProductManager');
+
+ WebGUI::Session::setScratch('managingProduct', '-delete-');
+
+ $sth = WebGUI::SQL->read('select * from products order by title');
+
+ $output .= '';
+ while ($row = $sth->hashRef) {
+ $output .= '';
+ $output .= '| ';
+ $output .= deleteIcon('op=deleteProduct&productId='.$row->{productId});
+ $output .= editIcon('op=manageProduct&productId='.$row->{productId});
+ $output .= ' | ';
+ $output .= ''.$row->{title}.' | ';
+ $output .= '
';
+ }
+ $output .= '
';
+
+ return _submenu($output, 'list products', 'list products');
+}
+
+#-------------------------------------------------------------------
+sub www_listProductVariants {
+ my ($productId, $product, @variants, %parameters, %options, $output, %composition, $i18n);
+
+ return WebGUI::Privilege::insufficient unless (WebGUI::Grouping::isInGroup(14));
+
+ $i18n = WebGUI::International->new("ProductManager");
+
+ $productId = $session{form}{productId} || WebGUI::Session::getScratch('managingProduct');
+
+ return WebGUI::Operation::execute('listProducts') if ($productId eq 'new' || !$productId);
+
+ $product = WebGUI::Product->new($productId);
+
+ @variants = sort {$a->{composition} cmp $b->{composition}} @{$product->getVariant};
+ tie %parameters, "Tie::IxHash";
+ %parameters = map {$_->{parameterId} => $_->{name}} sort {$a->{name} <=> $b->{name}} @{$product->getParameter};
+ %options = map {$_->{optionId} => $_->{value}} @{$product->getOption};
+
+ $output = WebGUI::Form::formHeader;
+ $output .= WebGUI::Form::hidden({
+ name => 'op',
+ value => 'listProductVariantsSave',
+ });
+ $output .= WebGUI::Form::hidden({
+ name => 'productId',
+ value => $productId,
+ });
+ $output .= '';
+ $output .= "| ".join(' | ', values(%parameters))." | " if (%parameters);
+ $output .= ''.$i18n->get('sku').' | '.
+ ''.$i18n->get('price').' | '.
+ ''.$i18n->get('weight').' | '.
+ ''.$i18n->get('available').' | ';
+ $output .= "
";
+ foreach (@variants) {
+ $output .= "";
+ %composition = map {split(/\./, $_)} split(/,/, $_->{composition});
+ foreach (keys(%parameters)) {
+ $output .= '| '.$options{$composition{$_}}.' | ';
+ }
+ $output .= ''.$_->{sku}." | ";
+ $output .= '*' if ($_->{skuOverride});
+ $output .= ' | '.$_->{price}." | ";
+ $output .= '*'if ($_->{priceOverride});
+ $output .= ' | '.$_->{weight}." | ";
+ $output .= '*' if ($_->{weightOverride});
+ $output .= " | ";
+ $output .= "".WebGUI::Form::checkbox({
+ name => 'available',
+ value => $_->{variantId},
+ checked => $_->{available},
+ }).editIcon('op=editProductVariant&variantId='.$_->{variantId})." | ";
+ $output .= "
";
+ }
+ $output .= "
";
+ $output .= WebGUI::Form::submit;
+ $output .= WebGUI::Form::formFooter;
+
+ return _submenu($output, 'list variants label', 'list variants');
+}
+
+#-------------------------------------------------------------------
+sub www_listProductVariantsSave {
+
+ return WebGUI::Privilege::insufficient unless (WebGUI::Grouping::isInGroup(14));
+
+ my %availableVariants = map {$_ => 1} $session{cgi}->param('available');
+
+ my $product = WebGUI::Product->new($session{form}{productId});
+ my @variants = @{$product->getVariant};
+
+ foreach (@variants) {
+ $product->setVariant($_->{variantId}, {
+ available => $availableVariants{$_->{variantId}} ? '1' : '0'});
+ }
+
+ return WebGUI::Operation::execute('listProductVariants');
+}
+
+#-------------------------------------------------------------------
+sub www_manageProduct {
+ my ($productId, $product, $output, $parameter, $option, $optionId, $i18n);
+
+ return WebGUI::Privilege::insufficient unless (WebGUI::Grouping::isInGroup(14));
+
+ $i18n = WebGUI::International->new("ProductManager");
+
+ $productId = $session{form}{productId} || WebGUI::Session::getScratch('managingProduct');
+ return WebGUI::Operation::execute('listProducts') if ($productId eq 'new' || !$productId);
+ WebGUI::Session::setScratch('managingProduct', $productId);
+
+ $product = WebGUI::Product->new($productId);
+
+ $output .= "".$product->get('title')."
";
+ $output .= "".$i18n->get('properties').editIcon('op=editProduct&productId='.$productId)."
";
+ $output .= "";
+ $output .= "| ".$i18n->get('price')." | ".$product->get('price')." |
";
+ $output .= "| ".$i18n->get('weight')." | ".$product->get('weight')." |
";
+ $output .= "| ".$i18n->get('sku')." | ".$product->get('sku')." |
";
+ $output .= "| ".$i18n->get('description')." | ".$product->get('description')." |
";
+ $output .= "| ".$i18n->get('sku template')." | ".WebGUI::HTML::format($product->get('skuTemplate'), 'text')." |
";
+ $output .= "
";
+
+ $output .= "Parameters
";
+ $output .= ''.
+ $i18n->get('add parameter').'
';
+ foreach $parameter (@{$product->getParameter}) {
+ $output .= deleteIcon('op=deleteProductParameter¶meterId='.$parameter->{parameterId}).
+ editIcon('op=editProductParameter¶meterId='.$parameter->{parameterId});
+ $output .= ''.$parameter->{name}.'
';
+ $output .= ''.
+ $i18n->get('add option').'
';
+ foreach $optionId (@{$parameter->{options}}) {
+ $option = $product->getOption($optionId);
+ $output .= ''.
+ deleteIcon('op=deleteProductParameterOption&optionId='.$option->{optionId}).
+ editIcon('op=editProductParameterOption¶meterId='.$parameter->{parameterId}.'&optionId='.$option->{optionId}).$option->{value}.'
';
+ }
+ $output .= '
';
+ }
+
+ return _submenu($output, 'manage product', 'manage product');
+}
+
+1;
+
diff --git a/lib/WebGUI/Product.pm b/lib/WebGUI/Product.pm
new file mode 100755
index 000000000..9c75f8a6a
--- /dev/null
+++ b/lib/WebGUI/Product.pm
@@ -0,0 +1,420 @@
+package WebGUI::Product;
+
+use strict;
+use WebGUI::SQL;
+use WebGUI::Id;
+use WebGUI::Asset::Template;
+
+#-------------------------------------------------------------------
+sub _permute {
+ my ($currentSet, @permutations, $permutation, $value, @result);
+ $currentSet = shift;
+
+ @permutations = (@_) ? _permute(@_) : [];
+ foreach $permutation (@permutations) {
+ foreach $value (@$currentSet) {
+ push(@result, [$value, @{$permutation}]);
+ }
+ }
+
+ return @result;
+}
+
+#-------------------------------------------------------------------
+sub addOptionToParameter {
+ my ($self, $parameterId, $properties, $optionId);
+ $self = shift;
+ $parameterId = shift;
+ $properties = shift || {};
+
+ $optionId = WebGUI::Id::generate;
+
+ WebGUI::SQL->write("insert into productParameterOptions ".
+ "(optionId, parameterId) values ".
+ "(".quote($optionId).", ".quote($parameterId).")");
+
+ $self->{_options}->{$optionId} = {
+ %$properties,
+ parameterId => $parameterId,
+ optionId => $optionId,
+ };
+ push(@{$self->{_parameters}->{$parameterId}->{options}}, $optionId);
+
+ $self->updateVariants;
+
+ return $optionId;
+}
+
+#-------------------------------------------------------------------
+sub addParameter {
+ my ($self, $properties, $parameterId);
+
+ $self = shift;
+ $properties = shift;
+
+ $parameterId = WebGUI::Id::generate;
+
+ WebGUI::SQL->write("insert into productParameters (parameterId, productId) values ".
+ "(".quote($parameterId).", ".quote($self->get('productId')).")");
+
+ $self->{_parameters}->{$parameterId}->{parameterId} = $parameterId;
+ $self->{_parameters}->{$parameterId}->{options} = [];
+
+ return $parameterId;
+}
+
+#-------------------------------------------------------------------
+sub delete {
+ my ($self) = shift;
+
+ foreach (@{$self->getParameter}) {
+ WebGUI::SQL->write("delete from productParameterOptions where parameterId=".quote($_->{parameterId}));
+ }
+
+ WebGUI::SQL->write("delete from productParameters where productId=".quote($self->get('productId')));
+ WebGUI::SQL->write("delete from productVariants where productId=".quote($self->get('productId')));
+ WebGUI::SQL->write("delete from products where productId=".quote($self->get('productId')));
+
+ return undef;
+}
+
+#-------------------------------------------------------------------
+sub deleteParameter {
+ my ($self, $parameterId);
+ $self = shift;
+ $parameterId = shift;
+
+ WebGUI::SQL->write("delete from productParameterOptions where parameterId=".quote($parameterId));
+ WebGUI::SQL->write("delete from productParameters where parameterId=".quote($parameterId));
+
+ $self->updateVariants;
+
+ return undef;
+}
+
+#-------------------------------------------------------------------
+sub deleteOption {
+ my ($self, $optionId, @options, $parameterId);
+ $self = shift;
+ $optionId = shift;
+
+ WebGUI::SQL->write("delete from productParameterOptions where optionId=".quote($optionId));
+
+ $parameterId = $self->{_options}->{$optionId}->{parameterId};
+
+ delete($self->{_options}->{$optionId});
+
+ foreach (@{$self->{_parameters}->{$parameterId}->{options}}) {
+ push(@options, $_) unless ($_ eq $optionId);
+ }
+
+ $self->{_parameters}->{$parameterId}->{options} = \@options;
+
+ $self->updateVariants;
+
+ return undef;
+}
+
+#-------------------------------------------------------------------
+sub get {
+ my ($self, $property);
+ $self = shift;
+ $property = shift;
+
+ return $self->{_properties}->{$property} if ($property);
+
+ return $self->{_properties};
+}
+
+#-------------------------------------------------------------------
+sub getByOptionId {
+ my ($class, $optionId, $productId);
+
+ $class = shift;
+ $optionId = shift;
+
+
+ ($productId) = WebGUI::SQL->quickArray("select productId from productParameters as t1, productParameterOptions as t2 ".
+ "where t1.parameterId=t2.parameterId and t2.optionId=".quote($optionId));
+
+ return undef unless ($productId);
+
+ return WebGUI::Product->new($productId);
+}
+
+#-------------------------------------------------------------------
+sub getByParameterId {
+ my ($class, $parameterId, $productId);
+ $class = shift;
+ $parameterId = shift;
+
+ ($productId) = WebGUI::SQL->quickArray("select productId from productParameters where parameterId=".quote($parameterId));
+
+ return WebGUI::Product->new($productId);
+}
+
+#-------------------------------------------------------------------
+sub getByVariantId {
+ my ($class, $productId, $variantId);
+ $class = shift;
+ $variantId = shift;
+
+ ($productId) = WebGUI::SQL->quickArray("select productId from productVariants where variantId=".quote($variantId));
+
+ return WebGUI::Product->new($productId);
+}
+
+#-------------------------------------------------------------------
+sub getOption {
+ my ($self, $optionId);
+ $self = shift;
+ $optionId = shift;
+
+ return $self->{_options}->{$optionId} if ($optionId);
+
+ return [ values %{$self->{_options}} ];
+}
+
+#-------------------------------------------------------------------
+sub getParameter {
+ my ($self, $parameterId);
+ $self = shift;
+ $parameterId = shift;
+
+ return $self->{_parameters}->{$parameterId} if ($parameterId);
+
+ return [ values %{$self->{_parameters}} ];
+}
+
+#-------------------------------------------------------------------
+sub getVariant {
+ my ($self, $variantId);
+ $self = shift;
+ $variantId = shift;
+
+ return $self->{_variants}->{$variantId} if ($variantId);
+
+ return [ values %{$self->{_variants}} ];
+}
+
+#-------------------------------------------------------------------
+sub new {
+ my ($class, $productId, $properties, $parameters, $variants, $options, $sth, %row, $option, $new);
+ $class = shift;
+ $productId = shift;
+
+ WebGUI::ErrorHandler::fatal('no productId') unless ($productId);
+
+ $parameters = {};
+ $variants = {};
+ $options = {};
+
+ if ($productId eq 'new') {
+ $productId = WebGUI::Id::generate;
+ $properties = {productId => $productId};
+ WebGUI::SQL->write("insert into products (productId) values (".quote($productId).")");
+ } else {
+ $properties = WebGUI::SQL->quickHashRef("select * from products where productId=".quote($productId));
+
+ # fetch parameters and options
+ $sth = WebGUI::SQL->read("select opt.*, param.* from productParameters as param left join productParameterOptions as opt ".
+ "on param.parameterId=opt.parameterId where param.productId=".quote($productId));
+ while (%row = $sth->hash) {
+ $parameters->{$row{parameterId}} = {
+ name => $row{name},
+ parameterId => $row{parameterId},
+ options => [],
+ } unless (defined $parameters->{$row{parameterId}});
+ if ($row{value}) {
+ $option = {
+ value => $row{value},
+ optionId => $row{optionId},
+ parameterId => $row{parameterId},
+ priceModifier => $row{priceModifier},
+ weightModifier => $row{weightModifier},
+ skuModifier => $row{skuModifier}
+ };
+ push(@{$parameters->{$row{parameterId}}->{options}}, $row{optionId});
+ $options->{$row{optionId}} = $option;
+ }
+ }
+
+ # fetch variants
+ $sth = WebGUI::SQL->read("select * from productVariants where productId=".quote($productId));
+ while (%row = $sth->hash) {
+ $variants->{$row{variantId}} = {%row};
+ }
+
+ $new = 0;
+ }
+
+ bless {_properties => $properties, _parameters => $parameters, _options => $options, _variants => $variants, _new => $new}, $class;
+}
+
+#-------------------------------------------------------------------
+sub set {
+ my ($self, $properties);
+ $self = shift;
+ $properties = shift;
+
+ WebGUI::SQL->write("update products set ".join(', ', map {$_."=".quote($properties->{$_})} keys(%$properties)).
+ " where productId=".quote($self->get('productId')));
+
+ foreach (keys(%$properties)) {
+ $self->{_properties}->{$_} = $properties->{$_};
+ }
+
+ $self->updateVariants;
+}
+
+#-------------------------------------------------------------------
+sub setParameter {
+ my ($self, $parameterId, $properties);
+ $self = shift;
+ $parameterId = shift;
+ $properties = shift;
+
+ WebGUI::SQL->write("update productParameters set ".join(', ', map {$_."=".quote($properties->{$_})} keys(%$properties)).
+ " where parameterId=".quote($parameterId));
+
+ map {$self->{_parameter}->{$parameterId}->{$_} = $properties->{$_}} keys %$properties;
+}
+
+#-------------------------------------------------------------------
+sub setOption {
+ my ($self, $optionId, $properties);
+ $self = shift;
+ $optionId = shift;
+ $properties = shift;
+
+ WebGUI::SQL->write("update productParameterOptions set ".join(', ', map {$_."=".quote($properties->{$_})} keys(%$properties)).
+ " where optionId=".quote($optionId));
+
+ foreach (keys(%$properties)) {
+ $self->{_options}->{$optionId}->{$_} = $properties->{$_};
+ }
+
+ $self->updateVariants;
+}
+
+#-------------------------------------------------------------------
+sub setVariant {
+ my ($self, $variantId, $properties, @pairs, $original, %sku, $parameterName);
+ $self = shift;
+ $variantId = shift;
+ $properties = shift;
+
+my %pairs = map {split(/\./, $_)} split(/,/, $self->getVariant($variantId)->{composition});
+
+ $original->{price} = $self->get('price');
+ $original->{weight} = $self->get('weight');
+ $sku{base} = $self->get('sku');
+
+ foreach (values(%pairs)) {
+my $currentOption = $self->getOption($_);
+ $original->{price} += $currentOption->{priceModifier};
+ $original->{weight} += $currentOption->{weightModifier};
+ ($parameterName = $self->{_parameters}->{$currentOption->{parameterId}}->{name}) =~ s/ //g;
+ $sku{'param.'.$parameterName} = $currentOption->{skuModifier};
+ }
+ $original->{sku} = WebGUI::Asset::Template->processRaw($self->get('skuTemplate'), \%sku );
+
+ if (defined $properties->{price}) {
+ if ($properties->{price} ne '') {
+ push(@pairs, 'price='.quote($properties->{price}).', priceOverride=1');
+ } else {
+ push(@pairs, 'price='.quote($original->{price}).', priceOverride=0');
+ }
+ }
+ if (defined $properties->{weight}) {
+ if ($properties->{weight} ne '') {
+ push(@pairs, 'weight='.quote($properties->{weight}).', weightOverride=1');
+ } else {
+ push(@pairs, 'weight='.quote($original->{weight}).', weightOverride=0');
+ }
+ }
+ if (defined $properties->{sku}) {
+ if ($properties->{sku} ne '') {
+ push(@pairs, 'sku='.quote($properties->{sku}).', skuOverride=1');
+ } else {
+ push(@pairs, 'sku='.quote($original->{sku}).', skuOverride=0');
+ }
+ }
+
+ push(@pairs, 'available='.quote($properties->{available})) if (defined $properties->{available});
+
+ WebGUI::SQL->write("update productVariants set ".join(', ', @pairs)." where variantId=".quote($variantId)) if (@pairs);
+
+ $self->{_variants}->{$variantId} = {%{$self->{_variants}->{$variantId}}, %$properties};
+}
+
+#-------------------------------------------------------------------
+sub updateVariants {
+ my ($self, %variants, @optionSets, @variants, $variant, %var, @composition, $option, @newVariants, $parameterName);
+ $self = shift;
+
+ foreach (@{$self->getVariant}) {
+ $variants{$_->{composition}} = $_;
+ }
+
+ # group options per parameter so they can be permuted
+ foreach my $parameter (@{$self->getParameter}) {
+ push (@optionSets, [ map {$self->{_options}->{$_}} @{$parameter->{options}} ] ) if (@{$parameter->{options}});
+ }
+
+ @variants = _permute(@optionSets);
+
+ @variants = ([]) unless (@variants);
+ my %newVariants;
+ foreach $variant (@variants) {
+ my %sku;
+
+ $var{productId} = $self->get('productId');
+ $var{price} = $self->get('price');
+ $var{weight} = $self->get('weight');
+ $var{sku} = $self->get('sku');
+ $sku{base} = $self->get('sku');
+ @composition = ();
+
+ foreach $option (@{$variant}) {
+ $var{price} += $option->{priceModifier};
+ $var{weight} += $option->{weightModifier};
+ $var{sku} .= $option->{skuModifier};
+ ($parameterName = $self->{_parameters}->{$option->{parameterId}}->{name}) =~ s/ //g;
+ $sku{'param.'.$parameterName} = $option->{skuModifier};
+ $var{description} .= $option->{value};
+ push (@composition, $option->{parameterId}.".".$option->{optionId});
+ }
+
+ $var{composition} = join(',', sort @composition);
+ $var{available} = 1;
+ $var{sku} = WebGUI::Asset::Template->processRaw($self->get('skuTemplate'), \%sku ) || $self->get('sku');
+
+ if (defined $variants{$var{composition}}) {
+ $var{price} = $variants{$var{composition}}{price} if ($variants{$var{composition}}{priceOverride});
+ $var{weight} = $variants{$var{composition}}{weight} if ($variants{$var{composition}}{weightOverride});
+ $var{sku} = $variants{$var{composition}}{sku} if ($variants{$var{composition}}{skuOverride});
+ $var{available} = 0 unless ($variants{$var{composition}}{available});
+ }
+
+ if (exists $variants{$var{composition}}) {
+ $var{variantId} = $variants{$var{composition}}{variantId},
+ } else {
+ $var{variantId} = WebGUI::Id::generate;
+ }
+
+ push (@newVariants, {%var});
+ $newVariants{$var{variantId}} = {%var};
+ }
+
+ WebGUI::SQL->write("delete from productVariants where productId=".quote($self->get('productId')));
+ foreach (values %newVariants) {
+ WebGUI::SQL->write("insert into productVariants (variantId, productId, composition, price, weight, sku, available) values ".
+ "(".quote($_->{variantId}).", ".quote($_->{productId}).", ".quote($_->{composition}).", ".quote($_->{price}).
+ ", ".quote($_->{weight}).", ".quote($_->{sku}).", ".quote($_->{available}).")");
+ }
+
+ $self->{_variants} = \%newVariants;
+}
+
+1;