diff --git a/docs/changelog/7.x.x.txt b/docs/changelog/7.x.x.txt index 8a90ecfca..b2ab52f95 100644 --- a/docs/changelog/7.x.x.txt +++ b/docs/changelog/7.x.x.txt @@ -21,6 +21,7 @@ - Survey now shows warnings if bad goto, bad gotoExpressions, no question text, survey looping, or no question answers are found. - fixed: Gateway problem with VendorPayout. + - fixed #9976: carts not cleaned up 7.7.1 - the AdSku project: create a Sku that allows buyers to purchase advertising in select AdSpaces at selected priorities diff --git a/docs/gotcha.txt b/docs/gotcha.txt index 33f521e3a..7a85c5f8a 100644 --- a/docs/gotcha.txt +++ b/docs/gotcha.txt @@ -10,6 +10,8 @@ save you many hours of grief. 7.7.2 -------------------------------------------------------------------- * WebGUI now requires Clone version 0.31 or greater. + * You must upgrade to WebGUI 7.7.2 before going on to higher versions of WebGUI due + to changes in the database table for the Cart. 7.7.1 -------------------------------------------------------------------- diff --git a/docs/upgrades/upgrade_7.7.1-7.7.2.pl b/docs/upgrades/upgrade_7.7.1-7.7.2.pl index d1769d77a..c0a227de5 100644 --- a/docs/upgrades/upgrade_7.7.1-7.7.2.pl +++ b/docs/upgrades/upgrade_7.7.1-7.7.2.pl @@ -22,7 +22,8 @@ use Getopt::Long; use WebGUI::Session; use WebGUI::Storage; use WebGUI::Asset; - +use WebGUI::Workflow; +use WebGUI::Utility; my $toVersion = '7.7.2'; my $quiet; # this line required @@ -37,6 +38,9 @@ addRssFeedAspect($session); addRssFeedAspectToAssets($session); addRssFeedAspectToCollaboration($session); removeRssCapableAsset($session); +addCreationTimeToCart($session); +addCartKillerActivityToConfig($session); +addCartKillerActivityToWorkflow($session); finish($session); # this line required @@ -128,6 +132,38 @@ sub removeRssCapableAsset { print "Done.\n" unless $quiet; } +#---------------------------------------------------------------------------- +sub addCreationTimeToCart { + my $session = shift; + print "\tAdding creation time to cart..." unless $quiet; + $session->db->write("alter table cart add column creationDate int(20)"); + $session->db->write('update cart set creationDate=NOW()'); + print "Done.\n" unless $quiet; +} + +#---------------------------------------------------------------------------- +sub addCartKillerActivityToConfig { + my $session = shift; + print "\tAdding Remove Old Carts workflow activity to config files..." unless $quiet; + my $activities = $session->config->get('workflowActivities'); + my $none = $activities->{'None'}; + if (!isIn('WebGUI::Workflow::Activity::RemoveOldCarts', @{ $none })) { + push @{ $none }, 'WebGUI::Workflow::Activity::RemoveOldCarts'; + } + $session->config->set('workflowActivities', $activities); + print "Done.\n" unless $quiet; +} + +#---------------------------------------------------------------------------- +sub addCartKillerActivityToWorkflow { + my $session = shift; + print "\tAdding Remove Old Carts workflow activity to Daily Workflow..." unless $quiet; + my $workflow = WebGUI::Workflow->new($session, 'pbworkflow000000000001'); + my $removeCarts = $workflow->addActivity('WebGUI::Workflow::Activity::RemoveOldCarts'); + $removeCarts->set('title', 'Remove old carts'); + print "Done.\n" unless $quiet; +} + #---------------------------------------------------------------------------- # Describe what our function does diff --git a/etc/WebGUI.conf.original b/etc/WebGUI.conf.original index 847c82855..35b5d1b5b 100644 --- a/etc/WebGUI.conf.original +++ b/etc/WebGUI.conf.original @@ -814,6 +814,7 @@ "WebGUI::Workflow::Activity::NotifyAdminsWithOpenVersionTags", "WebGUI::Workflow::Activity::PurgeOldAssetRevisions", "WebGUI::Workflow::Activity::PurgeOldTrash", + "WebGUI::Workflow::Activity::RemoveOldCarts", "WebGUI::Workflow::Activity::SendQueuedMailMessages", "WebGUI::Workflow::Activity::SummarizePassiveProfileLog", "WebGUI::Workflow::Activity::SyncProfilesToLdap", diff --git a/lib/WebGUI/Shop/Cart.pm b/lib/WebGUI/Shop/Cart.pm index 62fc97c82..e4d803f58 100644 --- a/lib/WebGUI/Shop/Cart.pm +++ b/lib/WebGUI/Shop/Cart.pm @@ -175,7 +175,7 @@ sub create { WebGUI::Error::InvalidObject->throw(expected=>"WebGUI::Session", got=>(ref $session), error=>"Need a session."); } my $cartId = $session->id->generate; - $session->db->write('insert into cart (cartId, sessionId) values (?,?)', [$cartId, $session->getId]); + $session->db->write('insert into cart (cartId, sessionId, creationDate) values (?,?,UNIX_TIMESTAMP())', [$cartId, $session->getId]); return $class->new($session, $cartId); } @@ -561,6 +561,10 @@ The unique id of the configured shipping driver that will be used to ship these The ID of a user being checked out, if they're being checked out by a cashier. +=head4 creationDate + +The date the cart was created. + =cut sub update { @@ -569,7 +573,7 @@ sub update { WebGUI::Error::InvalidParam->throw(error=>"Need a properties hash ref."); } my $id = id $self; - foreach my $field (qw(shippingAddressId posUserId shipperId)) { + foreach my $field (qw(shippingAddressId posUserId shipperId creationDate)) { $properties{$id}{$field} = (exists $newProperties->{$field}) ? $newProperties->{$field} : $properties{$id}{$field}; } $self->session->db->setRow("cart","cartId",$properties{$id}); diff --git a/lib/WebGUI/Workflow/Activity/RemoveOldCarts.pm b/lib/WebGUI/Workflow/Activity/RemoveOldCarts.pm new file mode 100644 index 000000000..7697105c9 --- /dev/null +++ b/lib/WebGUI/Workflow/Activity/RemoveOldCarts.pm @@ -0,0 +1,110 @@ +package WebGUI::Workflow::Activity::RemoveOldCarts; + + +=head1 LEGAL + + ------------------------------------------------------------------- + WebGUI is Copyright 2001-2009 Plain Black Corporation. + ------------------------------------------------------------------- + Please read the legal notices (docs/legal.txt) and the license + (docs/license.txt) that came with this distribution before using + this software. + ------------------------------------------------------------------- + http://www.plainblack.com info@plainblack.com + ------------------------------------------------------------------- + +=cut + +use strict; +use base 'WebGUI::Workflow::Activity'; +use WebGUI::International; +use WebGUI::Asset::Sku::Product; +use WebGUI::Inbox; + +=head1 NAME + +Package WebGUI::Workflow::Activity::RemoveOldCarts + +=head1 DESCRIPTION + +Remove carts that are older than a configurable threshold. + +=head1 SYNOPSIS + +See WebGUI::Workflow::Activity for details on how to use any activity. + +=head1 METHODS + +These methods are available from this class: + +=cut + +#------------------------------------------------------------------- + +=head2 definition ( session, definition ) + +See WebGUI::Workflow::Activity::defintion() for details. + +=cut + +sub definition { + my $class = shift; + my $session = shift; + my $definition = shift; + my $i18n = WebGUI::International->new($session, 'Workflow_Activity_RemoveOldCarts'); + push(@{$definition}, { + name=>$i18n->get('activityName'), + properties=> { + cartTimeout => { + fieldType=>'interval', + label=>$i18n->get('cart timeout'), + defaultValue=>48*3600, + hoverHelp=>$i18n->get('cart timeout help'), + }, + } + }); + return $class->SUPER::definition($session,$definition); +} + + +#------------------------------------------------------------------- + +=head2 execute ( [ object ] ) + +See WebGUI::Workflow::Activity::execute() for details. + +=cut + +sub execute { + my ($self) = @_; + my $session = $self->session; + my $now = time(); + my $finishTime = $now + $self->getTTL; + my $expired = 0; + my $cartIds = []; + my $limit = $now - $self->get('cartTimeout'); + $session->log->warn("limit: $limit"); + my $expiredCarts = $session->db->read('select cartId from cart where creationDate < '.$limit); + $expiredCarts->execute(); + CART: while( my ($cartId) = $expiredCarts->array() ) { + my $cart = eval { + WebGUI::Shop::Cart->new($session, $cartId); + }; + next CART if WebGUI::Error->caught; + $session->log->warn("cartId: $cartId"); + $cart->delete; ##Delete will empty, then delete. + ##Time check and set flag + if (time() > $finishTime) { + $expired = 1; + last CART; + } + } + ##If timer expired, then store message and limit and release + if ($expired) { + return $self->WAITING(1); + } + + return $self->COMPLETE; +} + +1; diff --git a/lib/WebGUI/i18n/English/Workflow_Activity_RemoveOldCarts.pm b/lib/WebGUI/i18n/English/Workflow_Activity_RemoveOldCarts.pm new file mode 100644 index 000000000..84ef410f0 --- /dev/null +++ b/lib/WebGUI/i18n/English/Workflow_Activity_RemoveOldCarts.pm @@ -0,0 +1,25 @@ +package WebGUI::i18n::English::Workflow_Activity_RemoveOldCarts; +use strict; + +our $I18N = { + 'cart timeout help' => { + message => q|How old should carts be before we delete them?|, + context => q|the hover help for the storage timeout field|, + lastUpdated => 0, + }, + + 'cart timeout' => { + message => q|Cart Timeout|, + context => q|a label indicating how old carts should be before we delete them|, + lastUpdated => 0, + }, + + 'activityName' => { + message => q|Remove Old Carts|, + context => q|The name of this workflow activity.|, + lastUpdated => 0, + }, + +}; + +1; diff --git a/t/Shop/Cart.t b/t/Shop/Cart.t index 47f3ab1c9..148085918 100644 --- a/t/Shop/Cart.t +++ b/t/Shop/Cart.t @@ -33,7 +33,7 @@ my $i18n = WebGUI::International->new($session, "Shop"); #---------------------------------------------------------------------------- # Tests -plan tests => 27; # Increment this number for each test you create +plan tests => 29; # Increment this number for each test you create #---------------------------------------------------------------------------- # put your tests here @@ -52,6 +52,7 @@ my $cart = WebGUI::Shop::Cart->newBySession($session); isa_ok($cart, "WebGUI::Shop::Cart"); isa_ok($cart->session, "WebGUI::Session"); +ok($cart->get('creationDate'), 'creationDate set on cart creation'); my $message = $i18n->get('empty cart') . "\n"; like($cart->www_view, qr/There are no items currently in your cart./, 'Display empty cart message'); @@ -73,9 +74,13 @@ is($item->get("quantity"), 3, "Should have 3 of these in the cart."); is(scalar(@{$cart->getItems}), 1, "Should have 1 item type in cart regardless of quanity."); $item->update({shippingAddressId => "XXXX"}); -is($item->get("shippingAddressId"), "XXXX", "Can set values to the cart item properties."); +is($item->get("shippingAddressId"), "XXXX", "Can set shippingAddressId in the cart item properties."); $item->update({shippingAddressId => undef}); +my $now = time(); +$cart->update({creationDate => $now}); +is($cart->get('creationDate'), $now, 'update: set creationDate'); + like($cart->getId, qr/[A-Za-z0-9\_\-]{22}/, "Id looks like a guid."); is(ref($cart->get), "HASH", "Cart properties are a hash reference."); diff --git a/t/Workflow/Activity/RemoveOldCarts.t b/t/Workflow/Activity/RemoveOldCarts.t new file mode 100644 index 000000000..427ab8f3c --- /dev/null +++ b/t/Workflow/Activity/RemoveOldCarts.t @@ -0,0 +1,110 @@ +#------------------------------------------------------------------- +# WebGUI is Copyright 2001-2009 Plain Black Corporation. +#------------------------------------------------------------------- +# Please read the legal notices (docs/legal.txt) and the license +# (docs/license.txt) that came with this distribution before using +# this software. +#------------------------------------------------------------------- +# http://www.plainblack.com info@plainblack.com +#------------------------------------------------------------------- + +use FindBin; +use strict; +use lib "$FindBin::Bin/../../lib"; + +use WebGUI::Test; +use WebGUI::Session; +use WebGUI::Utility; +use WebGUI::Workflow::Activity::RemoveOldCarts; +use WebGUI::Shop::Cart; + +use Test::More; +use Test::Deep; + +plan tests => 6; # increment this value for each test you create + +my $session = WebGUI::Test->session; + +my $root = WebGUI::Asset->getRoot($session); +my $donation = $root->addChild({ + className => 'WebGUI::Asset::Sku::Donation', + title => 'test donation', +}); +my $tag = WebGUI::VersionTag->getWorking($session); +$tag->commit; + + +my $cart1 = WebGUI::Shop::Cart->create($session); + +my $session2 = WebGUI::Session->open(WebGUI::Test->root, WebGUI::Test->file); +my $cart2 = WebGUI::Shop::Cart->create($session2); +$cart2->update({creationDate => time()-10000}); + +my @cartIds = $session->db->buildArray('select cartId from cart'); +cmp_bag( + \@cartIds, + [ $cart1->getId, $cart2->getId ], + 'Made two carts for testing' +); + +$donation->applyOptions({ price => 1111}); +my $item1 = $cart1->addItem($donation); + +$donation->applyOptions({ price => 2222}); +my $item2 = $cart2->addItem($donation); + +my @itemIds = $session->db->buildArray('select itemId from cartItem'); +cmp_bag( + \@itemIds, + [ $item1->getId, $item2->getId ], + 'Made two items for testing' +); + +my $workflow = WebGUI::Workflow->create($session, + { + enabled => 1, + objectType => 'None', + mode => 'realtime', + }, +); +my $cartNuker = $workflow->addActivity('WebGUI::Workflow::Activity::RemoveOldCarts'); +$cartNuker->set('cartTimeout', 3600); + +my $instance1 = WebGUI::Workflow::Instance->create($session, + { + workflowId => $workflow->getId, + skipSpectreNotification => 1, + } +); + +my $retVal; + +$retVal = $instance1->run(); +is($retVal, 'complete', 'cleanup: activity complete'); +$retVal = $instance1->run(); +is($retVal, 'done', 'cleanup: activity is done'); +$instance1->delete; + +@cartIds = $session->db->buildArray('select cartId from cart'); +cmp_bag( + \@cartIds, + [ $cart1->getId, ], + 'Deleted 1 cart, the correct one' +); + +@itemIds = $session->db->buildArray('select itemId from cartItem'); +cmp_bag( + \@itemIds, + [ $item1->getId, ], + 'Deleted 1 item, the correct one' +); + +END { + $instance1->delete('skipNotify'); + $workflow->delete; + $cart1->delete; + $cart2->delete; + $session2->close; + $donation->purge; + $tag->rollback; +}